cleanup code a bit

This commit is contained in:
Joeri Exelmans 2025-10-14 13:29:13 +02:00
parent 5e7b944978
commit b14b9e205c
11 changed files with 327 additions and 155 deletions

View file

@ -1,7 +1,7 @@
import { evalExpr } from "./actionlang_interpreter";
import { computeArena, ConcreteState, getDescendants, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./abstract_syntax";
import { Action } from "./label_ast";
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput, TimerElapseEvent, Timers } from "./runtime_types";
import { Action, EventTrigger } from "./label_ast";
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput, Timers, RT_Event } from "./runtime_types";
export function initialize(ast: Statechart): BigStepOutput {
let {enteredStates, environment, ...raised} = enterDefault(0, ast.root, {
@ -27,7 +27,7 @@ export function entryActions(simtime: number, state: ConcreteState, actionScope:
const timers: Timers = [...(environment.get("_timers") || [])];
for (const timeOffset of state.timers) {
const futureSimTime = simtime + timeOffset; // point in simtime when after-trigger becomes enabled
timers.push([futureSimTime, {state: state.uid, timeDurMs: timeOffset}]);
timers.push([futureSimTime, {kind: "timer", state: state.uid, timeDurMs: timeOffset}]);
}
timers.sort((a,b) => a[0] - b[0]); // smallest futureSimTime comes first
environment.set("_timers", timers);
@ -167,40 +167,46 @@ export function execAction(action: Action, rt: ActionScope): ActionScope {
};
}
else if (action.kind === "raise") {
const raisedEvent = {
name: action.event,
param: action.param && evalExpr(action.param, rt.environment),
};
if (action.event.startsWith('_')) {
// append to internal events
return {
...rt,
internalEvents: [...rt.internalEvents, action.event],
internalEvents: [...rt.internalEvents, raisedEvent],
};
}
else {
// append to output events
return {
...rt,
outputEvents: [...rt.outputEvents, action.event],
outputEvents: [...rt.outputEvents, raisedEvent],
}
}
}
throw new Error("should never reach here");
}
export function handleEvent(simtime: number, event: string | TimerElapseEvent, statechart: Statechart, activeParent: ConcreteState, {environment, mode, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
export function handleEvent(simtime: number, event: RT_Event, statechart: Statechart, activeParent: ConcreteState, {environment, mode, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
const arenasFired = new Set<OrState>();
for (const state of activeParent.children) {
if (mode.has(state.uid)) {
const outgoing = statechart.transitions.get(state.uid) || [];
let triggered;
if (typeof event === 'string') {
if (event.kind === "input") {
// get transitions triggered by event
triggered = outgoing.filter(transition => {
const trigger = transition.label[0].trigger;
if (trigger.kind === "event") {
return trigger.event === event;
return trigger.event === event.name;
}
return false;
});
}
else {
// get transitions triggered by timeout
triggered = outgoing.filter(transition => {
const trigger = transition.label[0].trigger;
if (trigger.kind === "after") {
@ -209,6 +215,7 @@ export function handleEvent(simtime: number, event: string | TimerElapseEvent, s
return false;
});
}
// eval guard
const enabled = triggered.filter(transition =>
evalExpr(transition.label[0].guard, environment)
);
@ -227,7 +234,24 @@ export function handleEvent(simtime: number, event: string | TimerElapseEvent, s
}
if (!overlapping) {
console.log('^ firing');
let oldValue;
if (event.kind === "input" && event.param !== undefined) {
// input events may have a parameter
// *temporarily* add event to environment (dirty!)
oldValue = environment.get(event.param.name);
environment = new Map([
...environment,
[(t.label[0].trigger as EventTrigger).paramName as string, event.param.value],
]);
}
({mode, environment, ...raised} = fireTransition(simtime, t, arena, srcPath, tgtPath, {mode, environment, ...raised}));
if (event.kind === "input" && event.param) {
// restore original value of variable that had same name as input parameter
environment = new Map([
...environment,
[(t.label[0].trigger as EventTrigger).paramName as string, oldValue],
]);
}
arenasFired.add(arena);
}
else {
@ -243,7 +267,7 @@ export function handleEvent(simtime: number, event: string | TimerElapseEvent, s
return {environment, mode, ...raised};
}
export function handleInputEvent(simtime: number, event: string, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStepOutput {
export function handleInputEvent(simtime: number, event: RT_Event, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStepOutput {
let raised = initialRaised;
({mode, environment, ...raised} = handleEvent(simtime, event, statechart, statechart.root, {mode, environment, ...raised}));
@ -254,7 +278,9 @@ export function handleInputEvent(simtime: number, event: string, statechart: Sta
export function handleInternalEvents(simtime: number, statechart: Statechart, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
while (raised.internalEvents.length > 0) {
const [internalEvent, ...rest] = raised.internalEvents;
({mode, environment, ...raised} = handleEvent(simtime, internalEvent, statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
({mode, environment, ...raised} = handleEvent(simtime,
{kind: "input", ...internalEvent}, // internal event becomes input event
statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
}
return {mode, environment, outputEvents: raised.outputEvents};
}

View file

@ -19,6 +19,7 @@ export type Trigger = EventTrigger | AfterTrigger | EntryTrigger | ExitTrigger;
export type EventTrigger = {
kind: "event";
event: string;
paramName?: string;
}
export type AfterTrigger = {
@ -45,6 +46,7 @@ export type Assignment = {
export type RaiseEvent = {
kind: "raise";
event: string;
param?: Expression;
}

View file

@ -167,19 +167,19 @@ function peg$parse(input, options) {
const peg$c0 = "[";
const peg$c1 = "]";
const peg$c2 = "/";
const peg$c3 = "after";
const peg$c4 = "entry";
const peg$c5 = "exit";
const peg$c6 = "ms";
const peg$c7 = "s";
const peg$c8 = ";";
const peg$c9 = "=";
const peg$c10 = "==";
const peg$c11 = "!=";
const peg$c12 = "<=";
const peg$c13 = ">=";
const peg$c14 = "(";
const peg$c15 = ")";
const peg$c3 = "(";
const peg$c4 = ")";
const peg$c5 = "after";
const peg$c6 = "entry";
const peg$c7 = "exit";
const peg$c8 = "ms";
const peg$c9 = "s";
const peg$c10 = ";";
const peg$c11 = "=";
const peg$c12 = "==";
const peg$c13 = "!=";
const peg$c14 = "<=";
const peg$c15 = ">=";
const peg$c16 = "true";
const peg$c17 = "false";
const peg$c18 = "^";
@ -196,24 +196,24 @@ function peg$parse(input, options) {
const peg$e0 = peg$literalExpectation("[", false);
const peg$e1 = peg$literalExpectation("]", false);
const peg$e2 = peg$literalExpectation("/", false);
const peg$e3 = peg$literalExpectation("after", false);
const peg$e4 = peg$literalExpectation("entry", false);
const peg$e5 = peg$literalExpectation("exit", false);
const peg$e6 = peg$literalExpectation("ms", false);
const peg$e7 = peg$literalExpectation("s", false);
const peg$e8 = peg$literalExpectation(";", false);
const peg$e9 = peg$literalExpectation("=", false);
const peg$e10 = peg$classExpectation([["0", "9"], ["A", "Z"], "_", ["a", "z"]], false, false, false);
const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false);
const peg$e12 = peg$literalExpectation("==", false);
const peg$e13 = peg$literalExpectation("!=", false);
const peg$e14 = peg$classExpectation(["<", ">"], false, false, false);
const peg$e15 = peg$literalExpectation("<=", false);
const peg$e16 = peg$literalExpectation(">=", false);
const peg$e17 = peg$classExpectation(["+", "-"], false, false, false);
const peg$e18 = peg$classExpectation(["*", "/"], false, false, false);
const peg$e19 = peg$literalExpectation("(", false);
const peg$e20 = peg$literalExpectation(")", false);
const peg$e3 = peg$literalExpectation("(", false);
const peg$e4 = peg$literalExpectation(")", false);
const peg$e5 = peg$literalExpectation("after", false);
const peg$e6 = peg$literalExpectation("entry", false);
const peg$e7 = peg$literalExpectation("exit", false);
const peg$e8 = peg$literalExpectation("ms", false);
const peg$e9 = peg$literalExpectation("s", false);
const peg$e10 = peg$literalExpectation(";", false);
const peg$e11 = peg$literalExpectation("=", false);
const peg$e12 = peg$classExpectation([["0", "9"], ["A", "Z"], "_", ["a", "z"]], false, false, false);
const peg$e13 = peg$classExpectation([["0", "9"]], false, false, false);
const peg$e14 = peg$literalExpectation("==", false);
const peg$e15 = peg$literalExpectation("!=", false);
const peg$e16 = peg$classExpectation(["<", ">"], false, false, false);
const peg$e17 = peg$literalExpectation("<=", false);
const peg$e18 = peg$literalExpectation(">=", false);
const peg$e19 = peg$classExpectation(["+", "-"], false, false, false);
const peg$e20 = peg$classExpectation(["*", "/"], false, false, false);
const peg$e21 = peg$literalExpectation("true", false);
const peg$e22 = peg$literalExpectation("false", false);
const peg$e23 = peg$literalExpectation("^", false);
@ -230,8 +230,8 @@ function peg$parse(input, options) {
actions: actions ? actions[2] : [],
};
}
function peg$f1(event) {
return {kind: "event", event};
function peg$f1(event, param) {
return {kind: "event", event, param: param ? param[1] : undefined};
}
function peg$f2(dur) {
return {kind: "after", durationMs: dur};
@ -305,8 +305,8 @@ function peg$parse(input, options) {
function peg$f17() {
return text() === "true";
}
function peg$f18(event) {
return {kind: "raise", event};
function peg$f18(event, param) {
return {kind: "raise", event, param: param ? param[1] : undefined};
}
function peg$f19() { return null; }
function peg$f20(text) {
@ -597,15 +597,53 @@ function peg$parse(input, options) {
}
function peg$parseeventTrigger() {
let s0, s1;
let s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
s1 = peg$parseidentifier();
if (s1 !== peg$FAILED) {
s2 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 40) {
s3 = peg$c3;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e3); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parseidentifier();
if (s4 !== peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 41) {
s5 = peg$c4;
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e4); }
}
if (s5 !== peg$FAILED) {
s3 = [s3, s4, s5];
s2 = s3;
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
if (s2 === peg$FAILED) {
s2 = null;
}
peg$savedPos = s0;
s1 = peg$f1(s1);
s0 = peg$f1(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
s0 = s1;
return s0;
}
@ -614,12 +652,12 @@ function peg$parse(input, options) {
let s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 5) === peg$c3) {
s1 = peg$c3;
if (input.substr(peg$currPos, 5) === peg$c5) {
s1 = peg$c5;
peg$currPos += 5;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e3); }
if (peg$silentFails === 0) { peg$fail(peg$e5); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parse_();
@ -643,12 +681,12 @@ function peg$parse(input, options) {
let s0, s1;
s0 = peg$currPos;
if (input.substr(peg$currPos, 5) === peg$c4) {
s1 = peg$c4;
if (input.substr(peg$currPos, 5) === peg$c6) {
s1 = peg$c6;
peg$currPos += 5;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e4); }
if (peg$silentFails === 0) { peg$fail(peg$e6); }
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
@ -663,12 +701,12 @@ function peg$parse(input, options) {
let s0, s1;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c5) {
s1 = peg$c5;
if (input.substr(peg$currPos, 4) === peg$c7) {
s1 = peg$c7;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e5); }
if (peg$silentFails === 0) { peg$fail(peg$e7); }
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
@ -705,21 +743,21 @@ function peg$parse(input, options) {
function peg$parsetimeUnit() {
let s0, s1;
if (input.substr(peg$currPos, 2) === peg$c6) {
s0 = peg$c6;
if (input.substr(peg$currPos, 2) === peg$c8) {
s0 = peg$c8;
peg$currPos += 2;
} else {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e6); }
if (peg$silentFails === 0) { peg$fail(peg$e8); }
}
if (s0 === peg$FAILED) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 115) {
s1 = peg$c7;
s1 = peg$c9;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e7); }
if (peg$silentFails === 0) { peg$fail(peg$e9); }
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
@ -741,11 +779,11 @@ function peg$parse(input, options) {
s3 = peg$currPos;
s4 = peg$parse_();
if (input.charCodeAt(peg$currPos) === 59) {
s5 = peg$c8;
s5 = peg$c10;
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e8); }
if (peg$silentFails === 0) { peg$fail(peg$e10); }
}
if (s5 !== peg$FAILED) {
s6 = peg$parse_();
@ -766,11 +804,11 @@ function peg$parse(input, options) {
s3 = peg$currPos;
s4 = peg$parse_();
if (input.charCodeAt(peg$currPos) === 59) {
s5 = peg$c8;
s5 = peg$c10;
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e8); }
if (peg$silentFails === 0) { peg$fail(peg$e10); }
}
if (s5 !== peg$FAILED) {
s6 = peg$parse_();
@ -789,11 +827,11 @@ function peg$parse(input, options) {
}
s3 = peg$parse_();
if (input.charCodeAt(peg$currPos) === 59) {
s4 = peg$c8;
s4 = peg$c10;
peg$currPos++;
} else {
s4 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e8); }
if (peg$silentFails === 0) { peg$fail(peg$e10); }
}
if (s4 === peg$FAILED) {
s4 = null;
@ -827,11 +865,11 @@ function peg$parse(input, options) {
if (s1 !== peg$FAILED) {
s2 = peg$parse_();
if (input.charCodeAt(peg$currPos) === 61) {
s3 = peg$c9;
s3 = peg$c11;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e9); }
if (peg$silentFails === 0) { peg$fail(peg$e11); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parse_();
@ -865,7 +903,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e10); }
if (peg$silentFails === 0) { peg$fail(peg$e12); }
}
if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) {
@ -875,7 +913,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e10); }
if (peg$silentFails === 0) { peg$fail(peg$e12); }
}
}
} else {
@ -900,7 +938,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e11); }
if (peg$silentFails === 0) { peg$fail(peg$e13); }
}
if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) {
@ -910,7 +948,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e11); }
if (peg$silentFails === 0) { peg$fail(peg$e13); }
}
}
} else {
@ -934,20 +972,20 @@ function peg$parse(input, options) {
s2 = peg$currPos;
s3 = peg$currPos;
s4 = peg$parse_();
if (input.substr(peg$currPos, 2) === peg$c10) {
s5 = peg$c10;
if (input.substr(peg$currPos, 2) === peg$c12) {
s5 = peg$c12;
peg$currPos += 2;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e12); }
if (peg$silentFails === 0) { peg$fail(peg$e14); }
}
if (s5 === peg$FAILED) {
if (input.substr(peg$currPos, 2) === peg$c11) {
s5 = peg$c11;
if (input.substr(peg$currPos, 2) === peg$c13) {
s5 = peg$c13;
peg$currPos += 2;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e13); }
if (peg$silentFails === 0) { peg$fail(peg$e15); }
}
if (s5 === peg$FAILED) {
s5 = input.charAt(peg$currPos);
@ -955,23 +993,23 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e14); }
if (peg$silentFails === 0) { peg$fail(peg$e16); }
}
if (s5 === peg$FAILED) {
if (input.substr(peg$currPos, 2) === peg$c12) {
s5 = peg$c12;
if (input.substr(peg$currPos, 2) === peg$c14) {
s5 = peg$c14;
peg$currPos += 2;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
if (peg$silentFails === 0) { peg$fail(peg$e17); }
}
if (s5 === peg$FAILED) {
if (input.substr(peg$currPos, 2) === peg$c13) {
s5 = peg$c13;
if (input.substr(peg$currPos, 2) === peg$c15) {
s5 = peg$c15;
peg$currPos += 2;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
if (peg$silentFails === 0) { peg$fail(peg$e18); }
}
}
}
@ -1025,7 +1063,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e17); }
if (peg$silentFails === 0) { peg$fail(peg$e19); }
}
if (s5 !== peg$FAILED) {
s6 = peg$parse_();
@ -1075,7 +1113,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e18); }
if (peg$silentFails === 0) { peg$fail(peg$e20); }
}
if (s5 !== peg$FAILED) {
s6 = peg$parse_();
@ -1130,11 +1168,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 40) {
s1 = peg$c14;
s1 = peg$c3;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e19); }
if (peg$silentFails === 0) { peg$fail(peg$e3); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parse_();
@ -1142,11 +1180,11 @@ function peg$parse(input, options) {
if (s3 !== peg$FAILED) {
s4 = peg$parse_();
if (input.charCodeAt(peg$currPos) === 41) {
s5 = peg$c15;
s5 = peg$c4;
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e20); }
if (peg$silentFails === 0) { peg$fail(peg$e4); }
}
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
@ -1228,7 +1266,7 @@ function peg$parse(input, options) {
}
function peg$parseraise() {
let s0, s1, s2, s3;
let s0, s1, s2, s3, s4, s5, s6, s7;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 94) {
@ -1242,8 +1280,44 @@ function peg$parse(input, options) {
s2 = peg$parse_();
s3 = peg$parseidentifier();
if (s3 !== peg$FAILED) {
s4 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 40) {
s5 = peg$c3;
peg$currPos++;
} else {
s5 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e3); }
}
if (s5 !== peg$FAILED) {
s6 = peg$parsecompare();
if (s6 !== peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 41) {
s7 = peg$c4;
peg$currPos++;
} else {
s7 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e4); }
}
if (s7 !== peg$FAILED) {
s5 = [s5, s6, s7];
s4 = s5;
} else {
peg$currPos = s4;
s4 = peg$FAILED;
}
} else {
peg$currPos = s4;
s4 = peg$FAILED;
}
} else {
peg$currPos = s4;
s4 = peg$FAILED;
}
if (s4 === peg$FAILED) {
s4 = null;
}
peg$savedPos = s0;
s0 = peg$f18(s3);
s0 = peg$f18(s3, s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;

View file

@ -5,8 +5,14 @@ import { Action, Expression, ParsedText } from "./label_ast";
import { parse as parseLabel, SyntaxError } from "./label_parser";
export function parseStatechart(state: VisualEditorState): [Statechart, [string,string][]] {
const errorShapes: [string, string][] = [];
export type TraceableError = {
shapeUid: string;
message: string;
data?: any;
}
export function parseStatechart(state: VisualEditorState): [Statechart, TraceableError[]] {
const errors: TraceableError[] = [];
// implicitly, the root is always an Or-state
const root: OrState = {
@ -37,6 +43,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
// we assume that the rountangles are sorted from big to small:
for (const rt of state.rountangles) {
// @ts-ignore
const state: ConcreteState = {
kind: rt.kind,
uid: rt.uid,
@ -45,11 +52,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
entryActions: [],
exitActions: [],
timers: [],
}
};
if (state.kind === "or") {
state.initial = [];
(state as unknown as OrState).initial = [];
}
uid2State.set(rt.uid, state);
uid2State.set(rt.uid, (state));
// iterate in reverse:
for (let i=parentCandidates.length-1; i>=0; i--) {
@ -57,7 +64,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
if (candidate.uid === "root" || isEntirelyWithin(rt, candidate)) {
// found our parent :)
const parentState = uid2State.get(candidate.uid)!;
parentState.children.push(state);
parentState.children.push(state as unknown as ConcreteState);
parentCandidates.push(rt);
parentLinks.set(rt.uid, candidate.uid);
state.parent = parentState;
@ -78,7 +85,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
if (!srcUID) {
if (!tgtUID) {
// dangling edge - todo: display error...
errorShapes.push([arr.uid, "dangling"]);
errors.push({shapeUid: arr.uid, message: "dangling"});
}
else {
// target but no source, so we treat is as an 'initial' marking
@ -89,13 +96,19 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
}
else {
// and states do not have an 'initial' state - todo: display error...
errorShapes.push([arr.uid, "AND-state cannot have an initial state"]);
errors.push({
shapeUid: arr.uid,
message: "AND-state cannot have an initial state",
});
}
}
}
else {
if (!tgtUID) {
errorShapes.push([arr.uid, "no target"]);
errors.push({
shapeUid: arr.uid,
message: "no target",
});
}
else {
// add transition
@ -116,10 +129,16 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
for (const state of uid2State.values()) {
if (state.kind === "or") {
if (state.initial.length > 1) {
errorShapes.push(...state.initial.map(([uid,childState])=>[uid,"multiple initial states"] as [string, string]));
errors.push(...state.initial.map(([uid,childState]) => ({
shapeUid: uid,
message: "multiple initial states",
})));
}
else if (state.initial.length === 0) {
errorShapes.push([state.uid, "no initial state"]);
errors.push({
shapeUid: state.uid,
message: "no initial state",
});
}
}
}
@ -138,7 +157,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
parsed = parseLabel(text.text); // may throw
} catch (e) {
if (e instanceof SyntaxError) {
errorShapes.push([text.uid, e]);
errors.push({
shapeUid: text.uid,
message: e.message,
data: e,
});
continue;
}
else {
@ -202,10 +225,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
belongsToState.exitActions.push(...parsed.actions);
}
else {
errorShapes.push([text.uid, {
errors.push({
shapeUid: text.uid,
message: "states can only have entry/exit triggers",
location: {start: {offset: 0}, end: {offset: text.text.length}},
}]);
data: {start: {offset: 0}, end: {offset: text.text.length}},
});
}
}
@ -217,10 +241,16 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
for (const transition of uid2Transition.values()) {
if (transition.label.length === 0) {
errorShapes.push([transition.uid, "no label"]);
errors.push({
shapeUid: transition.uid,
message: "no label",
});
}
else if (transition.label.length > 1) {
errorShapes.push([transition.uid, "multiple labels"]);
errors.push({
shapeUid: transition.uid,
message: "multiple labels",
});
}
}
@ -232,7 +262,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
internalEvents,
outputEvents,
uid2State,
}, errorShapes];
}, errors];
}
function findVariables(expr: Expression): Set<string> {

View file

@ -1,9 +1,22 @@
export type Timestamp = number; // milliseconds since begin of simulation
export type Event = string;
export type RT_Event = InputEvent | TimerElapseEvent;
export type InputEvent = {
kind: "input",
name: string,
param?: any,
}
export type TimerElapseEvent = {
kind: "timer",
state: string,
timeDurMs: number,
}
export type Mode = Set<string>; // set of active states
export type Environment = ReadonlyMap<string, any>; // variable name -> value
export type RT_Statechart = {
@ -13,7 +26,7 @@ export type RT_Statechart = {
}
export type BigStepOutput = RT_Statechart & {
outputEvents: string[],
outputEvents: RaisedEvent[],
};
export type BigStep = {
@ -21,10 +34,16 @@ export type BigStep = {
simtime: number,
} & BigStepOutput;
// internal or output event
export type RaisedEvent = {
name: string,
param?: any,
}
export type RaisedEvents = {
internalEvents: string[];
outputEvents: string[];
internalEvents: RaisedEvent[];
outputEvents: RaisedEvent[];
};
// export type Timers = Map<string, number>; // transition uid -> timestamp
@ -34,6 +53,4 @@ export const initialRaised: RaisedEvents = {
outputEvents: [],
};
export type TimerElapseEvent = { state: string; timeDurMs: number; };
export type Timers = [number, TimerElapseEvent][];

View file

@ -11,8 +11,8 @@ tlabel = _ trigger:trigger _ guard:("[" _ guard _ "]")? _ actions:("/" _ actions
trigger = afterTrigger / entryTrigger / exitTrigger / eventTrigger
eventTrigger = event:identifier {
return {kind: "event", event};
eventTrigger = event:identifier param:("(" identifier ")")? {
return {kind: "event", event, param: param ? param[1] : undefined};
}
afterTrigger = "after" _ dur:durationMs {
@ -111,8 +111,8 @@ boolean = ("true" / "false") {
return text() === "true";
}
raise = "^" _ event:identifier {
return {kind: "raise", event};
raise = "^" _ event:identifier param:("(" expr ")")? {
return {kind: "raise", event, param: param ? param[1] : undefined};
}
_ "whitespace"