history seems to be working
This commit is contained in:
parent
b55cba198e
commit
c07489080a
7 changed files with 292 additions and 222 deletions
|
|
@ -1,170 +1,26 @@
|
|||
import { computeArena2, computePath, ConcreteState, getDescendants, isOverlapping, OrState, StableState, Statechart, stateDescription, Transition } from "./abstract_syntax";
|
||||
import { computeArena2, computePath, ConcreteState, getDescendants, HistoryState, isOverlapping, OrState, StableState, Statechart, stateDescription, Transition, transitionDescription } from "./abstract_syntax";
|
||||
import { evalExpr } from "./actionlang_interpreter";
|
||||
import { Action, EventTrigger, TransitionLabel } from "./label_ast";
|
||||
import { BigStepOutput, Environment, initialRaised, Mode, RaisedEvents, RT_Event, RT_Statechart, TimerElapseEvent, Timers } from "./runtime_types";
|
||||
import { BigStepOutput, Environment, initialRaised, Mode, RaisedEvents, RT_Event, RT_History, RT_Statechart, TimerElapseEvent, Timers } from "./runtime_types";
|
||||
|
||||
export function initialize(ast: Statechart): BigStepOutput {
|
||||
let {enteredStates, environment, ...raised} = enterDefault(0, ast.root, {
|
||||
let history = new Map();
|
||||
let enteredStates, environment, raised;
|
||||
({enteredStates, environment, history, ...raised} = enterDefault(0, ast.root, {
|
||||
environment: new Environment([new Map([["_timers", []]])]),
|
||||
history,
|
||||
...initialRaised,
|
||||
});
|
||||
return handleInternalEvents(0, ast, {mode: enteredStates, environment, ...raised});
|
||||
}));
|
||||
return handleInternalEvents(0, ast, {mode: enteredStates, environment, history, ...raised});
|
||||
}
|
||||
|
||||
type ActionScope = {
|
||||
environment: Environment,
|
||||
history: RT_History,
|
||||
} & RaisedEvents;
|
||||
|
||||
type EnteredScope = { enteredStates: Mode } & ActionScope;
|
||||
|
||||
export function entryActions(simtime: number, state: ConcreteState, actionScope: ActionScope): ActionScope {
|
||||
// console.log('enter', stateDescription(state), '...');
|
||||
let {environment, ...rest} = actionScope;
|
||||
// environment = environment.pushScope();
|
||||
for (const action of state.entryActions) {
|
||||
({environment, ...rest} = execAction(action, {environment, ...rest}));
|
||||
}
|
||||
// schedule timers
|
||||
// we store timers in the environment (dirty!)
|
||||
environment = environment.transform<Timers>("_timers", oldTimers => {
|
||||
const newTimers = [
|
||||
...oldTimers,
|
||||
...state.timers.map(timeOffset => {
|
||||
const futureSimTime = simtime + timeOffset;
|
||||
return [futureSimTime, {kind: "timer", state: state.uid, timeDurMs: timeOffset}] as [number, TimerElapseEvent];
|
||||
}),
|
||||
];
|
||||
newTimers.sort((a,b) => a[0] - b[0]);
|
||||
return newTimers;
|
||||
}, []);
|
||||
// new nested scope
|
||||
return {environment, ...rest};
|
||||
}
|
||||
|
||||
export function exitActions(simtime: number, state: ConcreteState, actionScope: ActionScope): ActionScope {
|
||||
// console.log('exit', stateDescription(state), '...');
|
||||
for (const action of state.exitActions) {
|
||||
(actionScope = execAction(action, actionScope));
|
||||
}
|
||||
let environment = actionScope.environment;
|
||||
// cancel timers
|
||||
environment = environment.transform<Timers>("_timers", oldTimers => {
|
||||
// remove all timers of 'state':
|
||||
return oldTimers.filter(([_, {state: s}]) => s !== state.uid);
|
||||
}, []);
|
||||
// environment = environment.popScope();
|
||||
return {...actionScope, environment};
|
||||
}
|
||||
|
||||
export function enterDefault(simtime: number, state: ConcreteState, rt: ActionScope): EnteredScope {
|
||||
let actionScope = rt;
|
||||
|
||||
// execute entry actions
|
||||
actionScope = entryActions(simtime, state, actionScope);
|
||||
|
||||
// enter children...
|
||||
let enteredStates = new Set([state.uid]);
|
||||
if (state.kind === "and") {
|
||||
// enter every child
|
||||
for (const child of state.children) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, child, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
}
|
||||
else if (state.kind === "or") {
|
||||
// same as AND-state, but we only enter the initial state(s)
|
||||
if (state.initial.length > 0) {
|
||||
if (state.initial.length > 1) {
|
||||
console.warn(state.uid + ': multiple initial states, only entering one of them');
|
||||
}
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, state.initial[0][1], actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
console.warn(state.uid + ': no initial state');
|
||||
}
|
||||
|
||||
return {enteredStates, ...actionScope};
|
||||
}
|
||||
|
||||
export function enterPath(simtime: number, path: ConcreteState[], rt: ActionScope): EnteredScope {
|
||||
let actionScope = rt;
|
||||
|
||||
const [state, ...rest] = path;
|
||||
|
||||
// execute entry actions
|
||||
actionScope = entryActions(simtime, state, actionScope);
|
||||
|
||||
// enter children...
|
||||
let enteredStates = new Set([state.uid]);
|
||||
if (state.kind === "and") {
|
||||
// enter every child
|
||||
for (const child of state.children) {
|
||||
let enteredChildren;
|
||||
if (rest.length > 0 && child.uid === rest[0].uid) {
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterPath(simtime, rest, actionScope));
|
||||
}
|
||||
else {
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, child, actionScope));
|
||||
}
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
}
|
||||
else if (state.kind === "or") {
|
||||
if (rest.length > 0) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterPath(simtime, rest, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
else {
|
||||
// same as AND-state, but we only enter the initial state(s)
|
||||
for (const [_, child] of state.initial) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, child, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { enteredStates, ...actionScope };
|
||||
}
|
||||
|
||||
// exit the given state and all its active descendants
|
||||
export function exitCurrent(simtime: number, state: ConcreteState, rt: EnteredScope): ActionScope {
|
||||
let {enteredStates, ...actionScope} = rt;
|
||||
|
||||
if (enteredStates.has(state.uid)) {
|
||||
// exit all active children...
|
||||
for (const child of state.children) {
|
||||
actionScope = exitCurrent(simtime, child, {enteredStates, ...actionScope});
|
||||
}
|
||||
|
||||
// execute exit actions
|
||||
actionScope = exitActions(simtime, state, actionScope);
|
||||
}
|
||||
|
||||
return actionScope;
|
||||
}
|
||||
|
||||
export function exitPath(simtime: number, path: ConcreteState[], rt: EnteredScope): ActionScope {
|
||||
let {enteredStates, ...actionScope} = rt;
|
||||
|
||||
const toExit = enteredStates.difference(new Set(path.map(s=>s.uid)));
|
||||
|
||||
const [state, ...rest] = path;
|
||||
|
||||
// exit state and all its children, *except* states along the rest of the path
|
||||
actionScope = exitCurrent(simtime, state, {enteredStates: toExit, ...actionScope});
|
||||
if (rest.length > 0) {
|
||||
actionScope = exitPath(simtime, rest, {enteredStates, ...actionScope});
|
||||
}
|
||||
|
||||
// execute exit actions
|
||||
actionScope = exitActions(simtime, state, actionScope);
|
||||
return actionScope;
|
||||
}
|
||||
|
||||
export function execAction(action: Action, rt: ActionScope): ActionScope {
|
||||
if (action.kind === "assignment") {
|
||||
const rhs = evalExpr(action.rhs, rt.environment);
|
||||
|
|
@ -197,7 +53,156 @@ export function execAction(action: Action, rt: ActionScope): ActionScope {
|
|||
throw new Error("should never reach here");
|
||||
}
|
||||
|
||||
export function handleEvent(simtime: number, event: RT_Event, statechart: Statechart, activeParent: StableState, {environment, mode, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
export function entryActions(simtime: number, state: ConcreteState, actionScope: ActionScope): ActionScope {
|
||||
// console.log('enter', stateDescription(state), '...');
|
||||
let {environment, ...rest} = actionScope;
|
||||
// environment = environment.pushScope();
|
||||
for (const action of state.entryActions) {
|
||||
({environment, ...rest} = execAction(action, {environment, ...rest}));
|
||||
}
|
||||
// schedule timers
|
||||
// we store timers in the environment (dirty!)
|
||||
environment = environment.transform<Timers>("_timers", oldTimers => {
|
||||
const newTimers = [
|
||||
...oldTimers,
|
||||
...state.timers.map(timeOffset => {
|
||||
const futureSimTime = simtime + timeOffset;
|
||||
return [futureSimTime, {kind: "timer", state: state.uid, timeDurMs: timeOffset}] as [number, TimerElapseEvent];
|
||||
}),
|
||||
];
|
||||
newTimers.sort((a,b) => a[0] - b[0]);
|
||||
return newTimers;
|
||||
}, []);
|
||||
// new nested scope
|
||||
return {environment, ...rest};
|
||||
}
|
||||
|
||||
export function exitActions(simtime: number, state: ConcreteState, {enteredStates, ...actionScope}: EnteredScope): ActionScope {
|
||||
// console.log('exit', stateDescription(state), '...');
|
||||
for (const action of state.exitActions) {
|
||||
(actionScope = execAction(action, actionScope));
|
||||
}
|
||||
let environment = actionScope.environment;
|
||||
// cancel timers
|
||||
environment = environment.transform<Timers>("_timers", oldTimers => {
|
||||
// remove all timers of 'state':
|
||||
return oldTimers.filter(([_, {state: s}]) => s !== state.uid);
|
||||
}, []);
|
||||
// environment = environment.popScope();
|
||||
return {...actionScope, environment};
|
||||
}
|
||||
|
||||
// recursively enter the given state's default state
|
||||
export function enterDefault(simtime: number, state: ConcreteState, rt: ActionScope): EnteredScope {
|
||||
let actionScope = rt;
|
||||
|
||||
// execute entry actions
|
||||
actionScope = entryActions(simtime, state, actionScope);
|
||||
|
||||
// enter children...
|
||||
let enteredStates = new Set([state.uid]);
|
||||
if (state.kind === "and") {
|
||||
// enter every child
|
||||
for (const child of state.children) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, child, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
}
|
||||
else if (state.kind === "or") {
|
||||
// same as AND-state, but we only enter the initial state(s)
|
||||
if (state.initial.length > 0) {
|
||||
if (state.initial.length > 1) {
|
||||
console.warn(state.uid + ': multiple initial states, only entering one of them');
|
||||
}
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, state.initial[0][1], actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
console.warn(state.uid + ': no initial state');
|
||||
}
|
||||
|
||||
return {enteredStates, ...actionScope};
|
||||
}
|
||||
|
||||
// recursively enter the given state and, if children need to be entered, preferrably those occurring in 'toEnter' will be entered. If no child occurs in 'toEnter', the default child will be entered.
|
||||
export function enterStates(simtime: number, state: ConcreteState, toEnter: Set<string>, actionScope: ActionScope): EnteredScope {
|
||||
|
||||
// execute entry actions
|
||||
actionScope = entryActions(simtime, state, actionScope);
|
||||
|
||||
// enter children...
|
||||
let enteredStates = new Set([state.uid]);
|
||||
|
||||
if (state.kind === "and") {
|
||||
// every child must be entered
|
||||
for (const child of state.children) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterStates(simtime, child, toEnter, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
}
|
||||
else if (state.kind === "or") {
|
||||
// only one child can be entered
|
||||
const childToEnter = state.children.filter(child => toEnter.has(child.uid));
|
||||
if (childToEnter.length === 1) {
|
||||
// good
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterStates(simtime, childToEnter[0], toEnter, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
}
|
||||
else if (childToEnter.length === 0) {
|
||||
// also good, enter default child
|
||||
for (const [_, defaultChild] of state.initial) {
|
||||
let enteredChildren;
|
||||
({enteredStates: enteredChildren, ...actionScope} = enterDefault(simtime, defaultChild, actionScope));
|
||||
enteredStates = enteredStates.union(enteredChildren);
|
||||
break; // one is enough
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("can only enter one child of an OR-state, stupid!");
|
||||
}
|
||||
}
|
||||
|
||||
return { enteredStates, ...actionScope };
|
||||
}
|
||||
|
||||
// exit the given state and all its active descendants
|
||||
export function exitCurrent(simtime: number, state: ConcreteState, rt: EnteredScope): ActionScope {
|
||||
let {enteredStates, history, ...actionScope} = rt;
|
||||
|
||||
if (enteredStates.has(state.uid)) {
|
||||
// exit all active children...
|
||||
for (const child of state.children) {
|
||||
({history, ...actionScope} = exitCurrent(simtime, child, {enteredStates, history, ...actionScope}));
|
||||
}
|
||||
|
||||
// execute exit actions
|
||||
({history, ...actionScope} = exitActions(simtime, state, {enteredStates, history, ...actionScope}));
|
||||
|
||||
// record history
|
||||
for (const h of state.history) {
|
||||
if (h.kind === "shallow") {
|
||||
history.set(h.uid, new Set(state.children
|
||||
.filter(child => enteredStates.has(child.uid))
|
||||
.map(child => child.uid)));
|
||||
}
|
||||
else if (h.kind === "deep") {
|
||||
// horribly inefficient (i don't care)
|
||||
history.set(h.uid,
|
||||
getDescendants(state)
|
||||
.difference(new Set([state.uid]))
|
||||
.intersection(enteredStates)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {history, ...actionScope};
|
||||
}
|
||||
|
||||
export function handleEvent(simtime: number, event: RT_Event, statechart: Statechart, activeParent: StableState, {environment, mode, history, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
const arenasFired = new Set<OrState>();
|
||||
for (const state of activeParent.children) {
|
||||
if (mode.has(state.uid)) {
|
||||
|
|
@ -249,7 +254,7 @@ export function handleEvent(simtime: number, event: RT_Event, statechart: Statec
|
|||
event.param,
|
||||
);
|
||||
}
|
||||
({mode, environment, ...raised} = fireTransition2(simtime, t, statechart.transitions, l, arena, {mode, environment, ...raised}));
|
||||
({mode, environment, history, ...raised} = fireTransition(simtime, t, statechart.transitions, l, arena, {mode, environment, history, ...raised}));
|
||||
if (event.kind === "input" && event.param !== undefined) {
|
||||
environment = environment.popScope();
|
||||
}
|
||||
|
|
@ -261,57 +266,53 @@ export function handleEvent(simtime: number, event: RT_Event, statechart: Statec
|
|||
}
|
||||
else {
|
||||
// no enabled outgoing transitions, try the children:
|
||||
({environment, mode, ...raised} = handleEvent(simtime, event, statechart, state, {environment, mode, ...raised}));
|
||||
({environment, mode, history, ...raised} = handleEvent(simtime, event, statechart, state, {environment, mode, history, ...raised}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return {environment, mode, ...raised};
|
||||
return {environment, mode, history, ...raised};
|
||||
}
|
||||
|
||||
export function handleInputEvent(simtime: number, event: RT_Event, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStepOutput {
|
||||
export function handleInputEvent(simtime: number, event: RT_Event, statechart: Statechart, {mode, environment, history}: {mode: Mode, environment: Environment, history: RT_History}): BigStepOutput {
|
||||
let raised = initialRaised;
|
||||
|
||||
({mode, environment, ...raised} = handleEvent(simtime, event, statechart, statechart.root, {mode, environment, ...raised}));
|
||||
({mode, environment, ...raised} = handleEvent(simtime, event, statechart, statechart.root, {mode, environment, history, ...raised}));
|
||||
|
||||
return handleInternalEvents(simtime, statechart, {mode, environment, ...raised});
|
||||
return handleInternalEvents(simtime, statechart, {mode, environment, history, ...raised});
|
||||
}
|
||||
|
||||
export function handleInternalEvents(simtime: number, statechart: Statechart, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
|
||||
export function handleInternalEvents(simtime: number, statechart: Statechart, {mode, environment, history, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
|
||||
while (raised.internalEvents.length > 0) {
|
||||
const [internalEvent, ...rest] = raised.internalEvents;
|
||||
({mode, environment, ...raised} = handleEvent(simtime,
|
||||
{kind: "input", ...internalEvent}, // internal event becomes input event
|
||||
statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
|
||||
statechart, statechart.root, {mode, environment, history, internalEvents: rest, outputEvents: raised.outputEvents}));
|
||||
}
|
||||
return {mode, environment, outputEvents: raised.outputEvents};
|
||||
return {mode, environment, history, outputEvents: raised.outputEvents};
|
||||
}
|
||||
|
||||
function transitionDescription(t: Transition) {
|
||||
return stateDescription(t.src) + ' ➔ ' + stateDescription(t.tgt);
|
||||
}
|
||||
|
||||
export function fireTransition2(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
export function fireTransition(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, history, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
console.log('fire', transitionDescription(t));
|
||||
|
||||
const srcPath = computePath({ancestor: arena, descendant: t.src as ConcreteState}).reverse();
|
||||
const srcPath = computePath({ancestor: arena, descendant: t.src as ConcreteState}).reverse() as ConcreteState[];
|
||||
|
||||
// exit src and other states up to arena
|
||||
({environment, ...raised} = exitPath(simtime, srcPath, {environment, enteredStates: mode, ...raised}));
|
||||
({environment, history, ...raised} = exitCurrent(simtime, srcPath[0], {environment, enteredStates: mode, history, ...raised}))
|
||||
const toExit = getDescendants(arena);
|
||||
toExit.delete(arena.uid); // do not exit the arena itself
|
||||
const exitedMode = mode.difference(toExit); // active states after exiting the states we need to exit
|
||||
|
||||
// console.log({exitedMode});
|
||||
|
||||
return fireSecondHalfOfTransition(simtime, t, ts, label, arena, {mode: exitedMode, environment, ...raised});
|
||||
return fireSecondHalfOfTransition(simtime, t, ts, label, arena, {mode: exitedMode, history, environment, ...raised});
|
||||
}
|
||||
|
||||
// assuming we've already exited the source state of the transition, now enter the target state
|
||||
// IF however, the target is a pseudo-state, DON'T enter it (pseudo-states are NOT states), instead fire the first pseudo-outgoing transition.
|
||||
export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: Map<string, Transition[]>, label: TransitionLabel, arena: OrState, {mode, environment, history, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||
// exec transition actions
|
||||
for (const action of label.actions) {
|
||||
({environment, ...raised} = execAction(action, {environment, ...raised}));
|
||||
({environment, history, ...raised} = execAction(action, {environment, history, ...raised}));
|
||||
}
|
||||
|
||||
if (t.tgt.kind === "pseudo") {
|
||||
|
|
@ -322,7 +323,7 @@ export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: M
|
|||
if (evalExpr(nextLabel.guard, environment)) {
|
||||
console.log('fire', transitionDescription(nextT));
|
||||
// found ourselves an enabled transition
|
||||
return fireSecondHalfOfTransition(simtime, nextT, ts, nextLabel, arena, {mode, environment, ...raised});
|
||||
return fireSecondHalfOfTransition(simtime, nextT, ts, nextLabel, arena, {mode, environment, history, ...raised});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -331,13 +332,25 @@ export function fireSecondHalfOfTransition(simtime: number, t: Transition, ts: M
|
|||
}
|
||||
else {
|
||||
const tgtPath = computePath({ancestor: arena, descendant: t.tgt});
|
||||
const state = tgtPath[0] as ConcreteState;
|
||||
let toEnter;
|
||||
if (t.tgt.kind === "deep" || t.tgt.kind === "shallow") {
|
||||
toEnter = new Set([
|
||||
...tgtPath.slice(0,-1).map(s => s.uid),
|
||||
...history.get(t.tgt.uid)!
|
||||
]) as Set<string>;
|
||||
}
|
||||
else {
|
||||
toEnter = new Set(tgtPath.map(s=>s.uid));
|
||||
}
|
||||
|
||||
// enter tgt
|
||||
let enteredStates;
|
||||
({enteredStates, environment, ...raised} = enterPath(simtime, tgtPath, {environment, ...raised}));
|
||||
({enteredStates, environment, history, ...raised} = enterStates(simtime, state, toEnter, {environment, history, ...raised}));
|
||||
const enteredMode = mode.union(enteredStates);
|
||||
|
||||
// console.log({enteredMode});
|
||||
|
||||
return {mode: enteredMode, environment, ...raised};
|
||||
return {mode: enteredMode, environment, history, ...raised};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue