You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
3.8 KiB
JavaScript
153 lines
3.8 KiB
JavaScript
(() => {
|
|
// src/index.ts
|
|
var Machine = function(...states) {
|
|
return { states };
|
|
};
|
|
var State = function(name, ...ons) {
|
|
return { name, ons };
|
|
};
|
|
var On = function(eventName, ...reactions) {
|
|
return { eventName, reactions };
|
|
};
|
|
var Do = function(fn) {
|
|
return { type: "Do", fn };
|
|
};
|
|
var Goto = function(targetStateName) {
|
|
return { type: "Goto", targetStateName };
|
|
};
|
|
var Tick = function(doFunctions) {
|
|
return { doFunctions };
|
|
};
|
|
function interpret(machine, options) {
|
|
let { state, context } = options;
|
|
if (typeof state === "undefined") {
|
|
state = machine.states[0].name;
|
|
}
|
|
const interpreter = { machine, state, context, tickQueues: [] };
|
|
console.log(interpreter);
|
|
send(interpreter, ["entry", null]);
|
|
return interpreter;
|
|
}
|
|
function getState(interpreter) {
|
|
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
|
}
|
|
function getOns(state, event) {
|
|
return state.ons.filter((on) => on.eventName === event[0]);
|
|
}
|
|
function noop() {
|
|
}
|
|
function send(interpreter, event) {
|
|
const state = getState(interpreter);
|
|
const onsTree = getOns(state, event);
|
|
const reactions = onsTree.map((on) => on.reactions).flat();
|
|
const indexOfFirstGoto = reactions.findIndex((reaction) => reaction.type === "Goto");
|
|
const indexOfFinalReaction = indexOfFirstGoto === -1 ? reactions.length - 1 : indexOfFirstGoto;
|
|
const reactionsUntilFirstGoto = reactions.slice(0, indexOfFinalReaction + 1);
|
|
const functionsToRunInTick = reactionsUntilFirstGoto.map((reaction) => {
|
|
if (reaction.type === "Do") {
|
|
return reaction.fn;
|
|
} else if (reaction.type === "Goto") {
|
|
return (ctx, e, self) => {
|
|
self.state = reaction.targetStateName;
|
|
send(self, ["entry", e]);
|
|
};
|
|
} else {
|
|
return noop;
|
|
}
|
|
});
|
|
const tick = Tick(functionsToRunInTick);
|
|
tick.doFunctions.forEach((fn) => {
|
|
fn(interpreter.context, event, interpreter);
|
|
});
|
|
}
|
|
|
|
// src/tests/01-ping-pong.ts
|
|
var wait = (ms) => new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve(1);
|
|
}, ms);
|
|
});
|
|
var makeRequest = (ctx, e, self) => {
|
|
send(ctx.serverActor, ["received-request", self]);
|
|
};
|
|
var sendResponse = (ctx, e, self) => {
|
|
send(ctx.clientActor, ["received-response", self]);
|
|
};
|
|
var startTimer = async (ctx, e, self) => {
|
|
await wait(500);
|
|
send(self, ["timer-finished", null]);
|
|
};
|
|
var log = (ctx, e, self) => {
|
|
console.log(self.state);
|
|
};
|
|
var client = Machine(
|
|
State(
|
|
"idle",
|
|
On(
|
|
"entry",
|
|
Do(log)
|
|
),
|
|
On(
|
|
"server-created",
|
|
Do((_ctx, [_eventName, serverActor2], self) => {
|
|
self.context.serverActor = serverActor2;
|
|
}),
|
|
Goto("making-request")
|
|
)
|
|
),
|
|
State(
|
|
"making-request",
|
|
On(
|
|
"entry",
|
|
Do(log),
|
|
Do(makeRequest),
|
|
Goto("awaiting-response")
|
|
)
|
|
),
|
|
State(
|
|
"awaiting-response",
|
|
On(
|
|
"entry",
|
|
Do(log)
|
|
),
|
|
On(
|
|
"received-response",
|
|
Do(log),
|
|
Goto("making-request")
|
|
)
|
|
)
|
|
);
|
|
var server = Machine(
|
|
State(
|
|
"awaiting-request",
|
|
On(
|
|
"entry",
|
|
Do(log)
|
|
),
|
|
On(
|
|
"received-request",
|
|
Do((_ctx, [_eventName, clientActor2], self) => {
|
|
self.context.clientActor = clientActor2;
|
|
}),
|
|
Goto("sending-response")
|
|
)
|
|
),
|
|
State(
|
|
"sending-response",
|
|
On(
|
|
"entry",
|
|
Do(log),
|
|
Do(startTimer)
|
|
),
|
|
On(
|
|
"timer-finished",
|
|
Do(sendResponse),
|
|
Goto("awaiting-request")
|
|
)
|
|
)
|
|
);
|
|
var clientActor = interpret(client, { context: {} });
|
|
var serverActor = interpret(server, { context: {} });
|
|
send(clientActor, ["server-created", serverActor]);
|
|
})();
|