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 {
|
.rountangle:hover {
|
||||||
/* fill: lightgrey; */
|
/* stroke: darkcyan; */
|
||||||
|
/* stroke-opacity: 0.2; */
|
||||||
|
/* fill: #eee; */
|
||||||
/* stroke-width: 4px; */
|
/* stroke-width: 4px; */
|
||||||
/* cursor: grab; */
|
/* cursor: grab; */
|
||||||
}
|
}
|
||||||
|
|
@ -65,15 +67,29 @@ text.highlight {
|
||||||
stroke-width: 16px;
|
stroke-width: 16px;
|
||||||
}
|
}
|
||||||
.lineHelper:hover {
|
.lineHelper:hover {
|
||||||
stroke: rgba(0, 255, 0, 0.2);
|
stroke: darkcyan;
|
||||||
|
stroke-opacity: 0.2;
|
||||||
cursor: grab;
|
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 {
|
.circleHelper {
|
||||||
fill: rgba(0, 0, 0, 0);
|
fill: rgba(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
.circleHelper:hover {
|
.circleHelper:hover {
|
||||||
fill: rgba(0, 255, 0, 0.2);
|
fill: darkcyan;
|
||||||
|
fill-opacity: 0.2;
|
||||||
cursor: grab;
|
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 { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
|
||||||
|
|
||||||
import * as lz4 from "@nick/lz4";
|
import * as lz4 from "@nick/lz4";
|
||||||
|
import { initialize } from "./interpreter";
|
||||||
|
|
||||||
|
|
||||||
type DraggingState = {
|
type DraggingState = {
|
||||||
|
|
@ -138,6 +139,9 @@ export function VisualEditor() {
|
||||||
const [statechart, errors] = parseStatechart(state);
|
const [statechart, errors] = parseStatechart(state);
|
||||||
console.log('statechart: ', statechart, 'errors:', errors);
|
console.log('statechart: ', statechart, 'errors:', errors);
|
||||||
setErrors(errors);
|
setErrors(errors);
|
||||||
|
|
||||||
|
const rt = initialize(statechart);
|
||||||
|
console.log('runtime:', rt);
|
||||||
}, 100);
|
}, 100);
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
@ -183,7 +187,7 @@ export function VisualEditor() {
|
||||||
...state,
|
...state,
|
||||||
texts: [...state.texts, {
|
texts: [...state.texts, {
|
||||||
uid: newID,
|
uid: newID,
|
||||||
text: "Double-click to edit text",
|
text: "// Double-click to edit",
|
||||||
topLeft: currentPointer,
|
topLeft: currentPointer,
|
||||||
}],
|
}],
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
|
|
@ -538,7 +542,7 @@ export function VisualEditor() {
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
"data-uid": txt.uid,
|
"data-uid": txt.uid,
|
||||||
"data-parts": "text",
|
"data-parts": "text",
|
||||||
textAnchor: "middle",
|
textAnchor: "middle" as "middle",
|
||||||
className:
|
className:
|
||||||
(selection.find(s => s.uid === txt.uid)?.parts?.length ? "selected":"")
|
(selection.find(s => s.uid === txt.uid)?.parts?.length ? "selected":"")
|
||||||
+(textsToHighlight.hasOwnProperty(txt.uid)?" highlight":""),
|
+(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>}
|
{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
|
<path
|
||||||
className="lineHelper"
|
className="pathHelper"
|
||||||
x1={start.x}
|
// markerEnd='url(#arrowEnd)'
|
||||||
y1={start.y}
|
d={`M ${start.x} ${start.y}
|
||||||
x2={end.x}
|
${arcOrLine}
|
||||||
y2={end.y}
|
${end.x} ${end.y}`}
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="start end"
|
data-parts="start end"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,126 @@
|
||||||
import { ConcreteState, Statechart } from "./ast";
|
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 {
|
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 {
|
return {
|
||||||
root: rt_root,
|
mode,
|
||||||
variables: new Map(),
|
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") {
|
if (state.kind === "and") {
|
||||||
return {
|
const mode: {[uid:string]: Mode} = {};
|
||||||
kind: "and",
|
for (const child of state.children) {
|
||||||
children: state.children.map(child => recursiveEnter(child)),
|
let childMode;
|
||||||
};
|
({mode: childMode, environment, raised} = enter(child, {environment, raised}));
|
||||||
|
mode[child.uid] = childMode;
|
||||||
|
}
|
||||||
|
return { mode, environment, raised };
|
||||||
}
|
}
|
||||||
else {
|
else if (state.kind === "or") {
|
||||||
const currentState = state.initial[0][1];
|
const mode: {[uid:string]: Mode} = {};
|
||||||
return {
|
// same as AND-state, but we only enter the initial state(s)
|
||||||
kind: "or",
|
for (const [_, child] of state.initial) {
|
||||||
current: currentState.uid,
|
let childMode;
|
||||||
current_rt: recursiveEnter(currentState),
|
({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, {
|
errorShapes.push([text.uid, {
|
||||||
message: "states can only have entry/exit triggers",
|
message: "states can only have entry/exit triggers",
|
||||||
location: {start: {offset: 0}, end: {offset: text.text.length}},
|
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") {
|
else if (expr.kind === "binaryExpr") {
|
||||||
return findVariables(expr.lhs).union(findVariables(expr.rhs));
|
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> {
|
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 = {
|
export type RT_Statechart = {
|
||||||
kind: "or";
|
mode: Mode;
|
||||||
current: string;
|
environment: Environment;
|
||||||
current_rt: RT_ConcreteState; // keep the runtime configuration only of the current state
|
// history: // TODO
|
||||||
|
|
||||||
|
inputEvents: string[];
|
||||||
|
} & RaisedEvents;
|
||||||
|
|
||||||
|
export type RaisedEvents = {
|
||||||
|
internalEvents: string[];
|
||||||
|
outputEvents: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialRaised: RaisedEvents = {
|
||||||
|
internalEvents: [],
|
||||||
|
outputEvents: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
type RT_AndState = {
|
// export type RT_Events = {
|
||||||
kind: "and";
|
// };
|
||||||
children: RT_ConcreteState[]; // keep the runtime configuration of every child
|
|
||||||
}
|
|
||||||
|
|
||||||
type RT_Statechart = {
|
|
||||||
root: RT_OrState;
|
|
||||||
variables: Map<string, any>;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue