interpreter initializes statechart
This commit is contained in:
parent
692c052e11
commit
b9327d2eb0
5 changed files with 171 additions and 44 deletions
|
|
@ -36,7 +36,9 @@ text.highlight {
|
|||
}
|
||||
|
||||
.rountangle:hover {
|
||||
/* fill: lightgrey; */
|
||||
/* stroke: darkcyan; */
|
||||
/* stroke-opacity: 0.2; */
|
||||
/* fill: #eee; */
|
||||
/* stroke-width: 4px; */
|
||||
/* cursor: grab; */
|
||||
}
|
||||
|
|
@ -65,15 +67,29 @@ text.highlight {
|
|||
stroke-width: 16px;
|
||||
}
|
||||
.lineHelper:hover {
|
||||
stroke: rgba(0, 255, 0, 0.2);
|
||||
stroke: darkcyan;
|
||||
stroke-opacity: 0.2;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.pathHelper {
|
||||
fill: none;
|
||||
stroke: rgba(0, 0, 0, 0);
|
||||
stroke-width: 16px;
|
||||
}
|
||||
.pathHelper:hover {
|
||||
stroke: darkcyan;
|
||||
stroke-opacity: 0.2;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
|
||||
.circleHelper {
|
||||
fill: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.circleHelper:hover {
|
||||
fill: rgba(0, 255, 0, 0.2);
|
||||
fill: darkcyan;
|
||||
fill-opacity: 0.2;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { parseStatechart } from "./parser";
|
|||
import { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
|
||||
|
||||
import * as lz4 from "@nick/lz4";
|
||||
import { initialize } from "./interpreter";
|
||||
|
||||
|
||||
type DraggingState = {
|
||||
|
|
@ -138,6 +139,9 @@ export function VisualEditor() {
|
|||
const [statechart, errors] = parseStatechart(state);
|
||||
console.log('statechart: ', statechart, 'errors:', errors);
|
||||
setErrors(errors);
|
||||
|
||||
const rt = initialize(statechart);
|
||||
console.log('runtime:', rt);
|
||||
}, 100);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [state]);
|
||||
|
|
@ -183,7 +187,7 @@ export function VisualEditor() {
|
|||
...state,
|
||||
texts: [...state.texts, {
|
||||
uid: newID,
|
||||
text: "Double-click to edit text",
|
||||
text: "// Double-click to edit",
|
||||
topLeft: currentPointer,
|
||||
}],
|
||||
nextID: state.nextID+1,
|
||||
|
|
@ -538,7 +542,7 @@ export function VisualEditor() {
|
|||
const commonProps = {
|
||||
"data-uid": txt.uid,
|
||||
"data-parts": "text",
|
||||
textAnchor: "middle",
|
||||
textAnchor: "middle" as "middle",
|
||||
className:
|
||||
(selection.find(s => s.uid === txt.uid)?.parts?.length ? "selected":"")
|
||||
+(textsToHighlight.hasOwnProperty(txt.uid)?" highlight":""),
|
||||
|
|
@ -763,12 +767,12 @@ export function ArrowSVG(props: {arrow: Arrow, selected: string[], errors: strin
|
|||
|
||||
{props.errors.length>0 && <text className="error" x={(start.x+end.x)/2+5} y={(start.y+end.y)/2} data-uid={uid} data-parts="start end">{props.errors.join(' ')}</text>}
|
||||
|
||||
<line
|
||||
className="lineHelper"
|
||||
x1={start.x}
|
||||
y1={start.y}
|
||||
x2={end.x}
|
||||
y2={end.y}
|
||||
<path
|
||||
className="pathHelper"
|
||||
// markerEnd='url(#arrowEnd)'
|
||||
d={`M ${start.x} ${start.y}
|
||||
${arcOrLine}
|
||||
${end.x} ${end.y}`}
|
||||
data-uid={uid}
|
||||
data-parts="start end"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,126 @@
|
|||
import { ConcreteState, Statechart } from "./ast";
|
||||
import { Action, Expression } from "./label_ast";
|
||||
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised } from "./runtime_types";
|
||||
|
||||
export function initialize(ast: Statechart): RT_Statechart {
|
||||
const rt_root = recursiveEnter(ast.root) as RT_OrState;
|
||||
const {mode, environment, raised} = enter(ast.root, {
|
||||
environment: new Map(),
|
||||
raised: initialRaised,
|
||||
});
|
||||
return {
|
||||
root: rt_root,
|
||||
variables: new Map(),
|
||||
mode,
|
||||
environment,
|
||||
inputEvents: [],
|
||||
...raised,
|
||||
};
|
||||
}
|
||||
|
||||
export function recursiveEnter(state: ConcreteState): RT_ConcreteState {
|
||||
type ActionScope = {
|
||||
environment: Environment,
|
||||
raised: RaisedEvents,
|
||||
};
|
||||
|
||||
export function enter(state: ConcreteState, rt: ActionScope): ({mode: Mode} & ActionScope) {
|
||||
let {environment, raised} = rt;
|
||||
for (const action of state.entryActions) {
|
||||
({environment, raised} = execAction(action, {environment, raised}));
|
||||
}
|
||||
if (state.kind === "and") {
|
||||
return {
|
||||
kind: "and",
|
||||
children: state.children.map(child => recursiveEnter(child)),
|
||||
};
|
||||
const mode: {[uid:string]: Mode} = {};
|
||||
for (const child of state.children) {
|
||||
let childMode;
|
||||
({mode: childMode, environment, raised} = enter(child, {environment, raised}));
|
||||
mode[child.uid] = childMode;
|
||||
}
|
||||
return { mode, environment, raised };
|
||||
}
|
||||
else {
|
||||
const currentState = state.initial[0][1];
|
||||
return {
|
||||
kind: "or",
|
||||
current: currentState.uid,
|
||||
current_rt: recursiveEnter(currentState),
|
||||
};
|
||||
else if (state.kind === "or") {
|
||||
const mode: {[uid:string]: Mode} = {};
|
||||
// same as AND-state, but we only enter the initial state(s)
|
||||
for (const [_, child] of state.initial) {
|
||||
let childMode;
|
||||
({mode: childMode, environment, raised} = enter(child, {environment, raised}));
|
||||
mode[child.uid] = childMode;
|
||||
return { mode, environment, raised };
|
||||
}
|
||||
}
|
||||
throw new Error("should never reach here");
|
||||
}
|
||||
|
||||
export function execAction(action: Action, rt: ActionScope): ActionScope {
|
||||
if (action.kind === "assignment") {
|
||||
const rhs = evalExpr(action.rhs, rt.environment);
|
||||
const newEnvironment = new Map(rt.environment);
|
||||
newEnvironment.set(action.lhs, rhs);
|
||||
return {
|
||||
...rt,
|
||||
environment: newEnvironment,
|
||||
};
|
||||
}
|
||||
else if (action.kind === "raise") {
|
||||
if (action.event.startsWith('_')) {
|
||||
// append to internal events
|
||||
return {
|
||||
...rt,
|
||||
raised: {
|
||||
...rt.raised,
|
||||
internalEvents: [...rt.raised.internalEvents, action.event]},
|
||||
};
|
||||
}
|
||||
else {
|
||||
// append to output events
|
||||
return {
|
||||
...rt,
|
||||
raised: {
|
||||
...rt.raised,
|
||||
outputEvents: [...rt.raised.outputEvents, action.event],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("should never reach here");
|
||||
}
|
||||
|
||||
|
||||
const UNARY_OPERATOR_MAP: Map<string, (x:any)=>any> = new Map([
|
||||
["!", x => !x],
|
||||
["-", x => -x as any],
|
||||
]);
|
||||
|
||||
const BINARY_OPERATOR_MAP: Map<string, (a:any,b:any)=>any> = new Map([
|
||||
["+", (a,b) => a+b],
|
||||
["-", (a,b) => a-b],
|
||||
["*", (a,b) => a*b],
|
||||
["/", (a,b) => a/b],
|
||||
["&&", (a,b) => a&&b],
|
||||
["||", (a,b) => a||b],
|
||||
["==", (a,b) => a==b],
|
||||
["<=", (a,b) => a<=b],
|
||||
[">=", (a,b) => a>=b],
|
||||
["<", (a,b) => a<b],
|
||||
[">", (a,b) => a>b],
|
||||
]);
|
||||
|
||||
export function evalExpr(expr: Expression, environment: Environment): any {
|
||||
if (expr.kind === "literal") {
|
||||
return expr.value;
|
||||
}
|
||||
else if (expr.kind === "ref") {
|
||||
const found = environment.get(expr.variable);
|
||||
if (found === undefined) {
|
||||
throw new Error(`variable '${expr.variable}' does not exist in environment`)
|
||||
}
|
||||
return found;
|
||||
}
|
||||
else if (expr.kind === "unaryExpr") {
|
||||
const arg = evalExpr(expr.expr, environment);
|
||||
return UNARY_OPERATOR_MAP.get(expr.operator)!(arg);
|
||||
}
|
||||
else if (expr.kind === "binaryExpr") {
|
||||
const argA = evalExpr(expr.lhs, environment);
|
||||
const argB = evalExpr(expr.rhs, environment);
|
||||
return BINARY_OPERATOR_MAP.get(expr.operator)!(argA,argB);
|
||||
}
|
||||
throw new Error("should never reach here");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
|||
errorShapes.push([text.uid, {
|
||||
message: "states can only have entry/exit triggers",
|
||||
location: {start: {offset: 0}, end: {offset: text.text.length}},
|
||||
} as unknown as string]);
|
||||
}]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -235,9 +235,7 @@ function findVariables(expr: Expression): Set<string> {
|
|||
else if (expr.kind === "binaryExpr") {
|
||||
return findVariables(expr.lhs).union(findVariables(expr.rhs));
|
||||
}
|
||||
else if (expr.kind === "literal") {
|
||||
return new Set();
|
||||
}
|
||||
return new Set();
|
||||
}
|
||||
|
||||
function findVariablesAction(action: Action): Set<string> {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
// modal configuration: maps child-uid to modal configuration of the child
|
||||
// for OR-states, only the modal configuration of the current state is kept
|
||||
// for AND-states, the modal configuration of every child is kept
|
||||
// for basic states (= empty AND-states), the modal configuration is just an empty object
|
||||
export type Mode = {[uid:string]: Mode};
|
||||
|
||||
type RT_ConcreteState = RT_OrState | RT_AndState;
|
||||
export type Environment = ReadonlyMap<string, any>; // variable name -> value
|
||||
|
||||
type RT_OrState = {
|
||||
kind: "or";
|
||||
current: string;
|
||||
current_rt: RT_ConcreteState; // keep the runtime configuration only of the current state
|
||||
export type RT_Statechart = {
|
||||
mode: Mode;
|
||||
environment: Environment;
|
||||
// history: // TODO
|
||||
|
||||
inputEvents: string[];
|
||||
} & RaisedEvents;
|
||||
|
||||
export type RaisedEvents = {
|
||||
internalEvents: string[];
|
||||
outputEvents: string[];
|
||||
};
|
||||
|
||||
export const initialRaised: RaisedEvents = {
|
||||
internalEvents: [],
|
||||
outputEvents: [],
|
||||
}
|
||||
|
||||
type RT_AndState = {
|
||||
kind: "and";
|
||||
children: RT_ConcreteState[]; // keep the runtime configuration of every child
|
||||
}
|
||||
|
||||
type RT_Statechart = {
|
||||
root: RT_OrState;
|
||||
variables: Map<string, any>;
|
||||
}
|
||||
// export type RT_Events = {
|
||||
// };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue