diff --git a/src/VisualEditor/VisualEditor.css b/src/VisualEditor/VisualEditor.css index ce04107..af828c4 100644 --- a/src/VisualEditor/VisualEditor.css +++ b/src/VisualEditor/VisualEditor.css @@ -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; } diff --git a/src/VisualEditor/VisualEditor.tsx b/src/VisualEditor/VisualEditor.tsx index 644b6bc..e71b54f 100644 --- a/src/VisualEditor/VisualEditor.tsx +++ b/src/VisualEditor/VisualEditor.tsx @@ -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 && {props.errors.join(' ')}} - diff --git a/src/VisualEditor/interpreter.ts b/src/VisualEditor/interpreter.ts index 429ceab..467ee07 100644 --- a/src/VisualEditor/interpreter.ts +++ b/src/VisualEditor/interpreter.ts @@ -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: Mapany> = new Map([ + ["!", x => !x], + ["-", x => -x as any], +]); + +const BINARY_OPERATOR_MAP: Mapany> = 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", (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"); } diff --git a/src/VisualEditor/parser.ts b/src/VisualEditor/parser.ts index 02b876c..b19b2ec 100644 --- a/src/VisualEditor/parser.ts +++ b/src/VisualEditor/parser.ts @@ -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 { 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 { diff --git a/src/VisualEditor/runtime_types.ts b/src/VisualEditor/runtime_types.ts index d7af957..093c340 100644 --- a/src/VisualEditor/runtime_types.ts +++ b/src/VisualEditor/runtime_types.ts @@ -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; // 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; -} +// export type RT_Events = { +// };