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 = {
+// };