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.
125 lines
3.6 KiB
JavaScript
125 lines
3.6 KiB
JavaScript
(() => {
|
|
// src/index.ts
|
|
var Machine = function(...states) {
|
|
return { states };
|
|
};
|
|
var State = function(name, ...eventReactionCouplings) {
|
|
return { name, eventReactionCouplings };
|
|
};
|
|
var On = function(eventName, ...reactions) {
|
|
return { eventName, reactions };
|
|
};
|
|
var SideEffect = function(fn) {
|
|
return { type: "SideEffect", fn };
|
|
};
|
|
var Goto = function(targetStateName) {
|
|
return { type: "Goto", targetStateName };
|
|
};
|
|
function interpret(machine2, options) {
|
|
let { state, context } = options;
|
|
if (typeof state === "undefined") {
|
|
state = machine2.states[0].name;
|
|
}
|
|
const interpreter = { machine: machine2, state, context, eventQueue: [], isTransitioning: false };
|
|
send(interpreter, ["entry", null]);
|
|
return interpreter;
|
|
}
|
|
function getState(interpreter) {
|
|
return interpreter.machine.states.find((state) => state.name === interpreter.state);
|
|
}
|
|
function getMatchingEventReactionCouplings(state, event) {
|
|
return state.eventReactionCouplings.filter((eventReactionCoupling) => eventReactionCoupling.eventName === event[0]);
|
|
}
|
|
function send(interpreter, event) {
|
|
interpreter.eventQueue.push(event);
|
|
if (interpreter.isTransitioning === false) {
|
|
interpreter.isTransitioning = true;
|
|
while (interpreter.eventQueue.length > 0) {
|
|
processNextEvent(interpreter);
|
|
}
|
|
interpreter.isTransitioning = false;
|
|
}
|
|
}
|
|
function processNextEvent(interpreter) {
|
|
const nextEvent = interpreter.eventQueue.shift();
|
|
if (typeof nextEvent !== "undefined") {
|
|
const state = getState(interpreter);
|
|
const eventReactionCouplings = getMatchingEventReactionCouplings(state, nextEvent);
|
|
const reactions = eventReactionCouplings.map((eventReactionCoupling) => eventReactionCoupling.reactions).flat();
|
|
const { sideEffects, contextMutations, goto_ } = categorizeReactions(reactions);
|
|
sideEffects.forEach((sideEffect) => {
|
|
sideEffect.fn(interpreter.context, nextEvent, interpreter);
|
|
});
|
|
contextMutations.forEach((contextMutation) => {
|
|
interpreter.context = contextMutation.fn(interpreter.context, nextEvent, interpreter);
|
|
});
|
|
if (goto_ !== null) {
|
|
interpreter.state = goto_.targetStateName;
|
|
send(interpreter, ["entry", null]);
|
|
}
|
|
}
|
|
}
|
|
function categorizeReactions(reactions) {
|
|
let sideEffects = [], contextMutations = [], goto_ = null;
|
|
reactions.forEach((reaction) => {
|
|
if (reaction.type === "SideEffect") {
|
|
sideEffects.push(reaction);
|
|
} else if (reaction.type === "ContextMutation") {
|
|
contextMutations.push(reaction);
|
|
} else if (reaction.type === "Goto") {
|
|
goto_ = reaction;
|
|
}
|
|
});
|
|
return { sideEffects, contextMutations, goto_ };
|
|
}
|
|
|
|
// src/tests/00-basic.ts
|
|
var beginTimer = (ctx, e, self) => {
|
|
setTimeout(() => {
|
|
send(self, ["timer-finished", null]);
|
|
}, 800);
|
|
};
|
|
var log = (ctx, e, self) => {
|
|
console.log(self.state);
|
|
};
|
|
var machine = Machine(
|
|
State(
|
|
"green",
|
|
On(
|
|
"entry",
|
|
SideEffect(beginTimer),
|
|
SideEffect(log)
|
|
),
|
|
On(
|
|
"timer-finished",
|
|
Goto("yellow")
|
|
)
|
|
),
|
|
State(
|
|
"yellow",
|
|
On(
|
|
"entry",
|
|
SideEffect(beginTimer),
|
|
SideEffect(log)
|
|
),
|
|
On(
|
|
"timer-finished",
|
|
Goto("red")
|
|
)
|
|
),
|
|
State(
|
|
"red",
|
|
On(
|
|
"entry",
|
|
SideEffect(beginTimer),
|
|
SideEffect(log)
|
|
),
|
|
On(
|
|
"timer-finished",
|
|
Goto("green")
|
|
)
|
|
)
|
|
);
|
|
var actor = interpret(machine, { context: {} });
|
|
})();
|