arcs nicely curve when they connect a rountangle to itself
This commit is contained in:
parent
e009f718d2
commit
da0e56e17c
11 changed files with 526 additions and 153 deletions
|
|
@ -82,6 +82,7 @@ text.highlight {
|
|||
}
|
||||
|
||||
.arrow {
|
||||
fill: none;
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { MouseEventHandler, SetStateAction, useEffect, useRef, useState } from "react";
|
||||
import { Line2D, Rect2D, Vec2D, addV2D, area, getBottomSide, getLeftSide, getRightSide, getTopSide, isEntirelyWithin, normalizeRect, subtractV2D, transformLine, transformRect } from "./geometry";
|
||||
import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, euclideanDistance, getBottomSide, getLeftSide, getRightSide, getTopSide, isEntirelyWithin, normalizeRect, subtractV2D, transformLine, transformRect } from "./geometry";
|
||||
|
||||
import "./VisualEditor.css";
|
||||
|
||||
|
|
@ -396,38 +396,65 @@ export function VisualEditor() {
|
|||
};
|
||||
}, [selectingState, dragging]);
|
||||
|
||||
// whenever an arrow is selected, highlight the rountangle sides it connects to
|
||||
// just for visual feedback
|
||||
let sidesToHighlight: {[key: string]: RountanglePart[]} = {};
|
||||
let arrowsToHighlight: {[key: string]: Arrow} = {};
|
||||
let textsToHighlight: {[key: string]: Text} = {};
|
||||
// detect what is 'connected'
|
||||
const arrow2SideMap = new Map<string,[{ uid: string; part: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>();
|
||||
const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>();
|
||||
const text2ArrowMap = new Map<string,string>();
|
||||
const arrow2TextMap = new Map<string,string[]>();
|
||||
for (const arrow of state.arrows) {
|
||||
const startSide = findNearestRountangleSide(arrow, "start", state.rountangles);
|
||||
const endSide = findNearestRountangleSide(arrow, "end", state.rountangles);
|
||||
if (startSide || endSide) {
|
||||
arrow2SideMap.set(arrow.uid, [startSide, endSide]);
|
||||
}
|
||||
if (startSide) {
|
||||
const arrowConns = side2ArrowMap.get(startSide.uid) || new Set();
|
||||
arrowConns.add(["start", arrow.uid]);
|
||||
side2ArrowMap.set(startSide.uid, arrowConns);
|
||||
}
|
||||
if (endSide) {
|
||||
const arrowConns = side2ArrowMap.get(endSide.uid) || new Set();
|
||||
arrowConns.add(["end", arrow.uid]);
|
||||
side2ArrowMap.set(endSide.uid, arrowConns);
|
||||
}
|
||||
}
|
||||
for (const text of state.texts) {
|
||||
const nearestArrow = findNearestArrow(text.topLeft, state.arrows);
|
||||
if (nearestArrow) {
|
||||
text2ArrowMap.set(text.uid, nearestArrow.uid);
|
||||
const textsOfArrow = arrow2TextMap.get(nearestArrow.uid) || [];
|
||||
textsOfArrow.push(text.uid);
|
||||
arrow2TextMap.set(nearestArrow.uid, textsOfArrow);
|
||||
}
|
||||
}
|
||||
|
||||
// for visual feedback, when selecting/moving one thing, we also highlight (in green) all the things that belong to the thing we selected.
|
||||
const sidesToHighlight: {[key: string]: RountanglePart[]} = {};
|
||||
const arrowsToHighlight: {[key: string]: boolean} = {};
|
||||
const textsToHighlight: {[key: string]: boolean} = {};
|
||||
for (const selected of selection) {
|
||||
for (const arrow of state.arrows) {
|
||||
if (arrow.uid === selected.uid) {
|
||||
const rSideStart = findNearestRountangleSide(arrow, "start", state.rountangles);
|
||||
if (rSideStart) {
|
||||
sidesToHighlight[rSideStart.uid] = [...(sidesToHighlight[rSideStart.uid] || []), rSideStart.part];
|
||||
}
|
||||
const rSideEnd = findNearestRountangleSide(arrow, "end", state.rountangles);
|
||||
if (rSideEnd) {
|
||||
sidesToHighlight[rSideEnd.uid] = [...(sidesToHighlight[rSideEnd.uid] || []), rSideEnd.part];
|
||||
}
|
||||
for (const text of state.texts) {
|
||||
const belongsToArrow = findNearestArrow(text.topLeft, state.arrows);
|
||||
if (belongsToArrow === arrow) {
|
||||
textsToHighlight[text.uid] = text;
|
||||
}
|
||||
}
|
||||
const sides = arrow2SideMap.get(selected.uid);
|
||||
if (sides) {
|
||||
const [startSide, endSide] = sides;
|
||||
if (startSide) sidesToHighlight[startSide.uid] = [...sidesToHighlight[startSide.uid]||[], startSide.part];
|
||||
if (endSide) sidesToHighlight[endSide.uid] = [...sidesToHighlight[endSide.uid]||[], endSide.part];
|
||||
}
|
||||
const texts = arrow2TextMap.get(selected.uid);
|
||||
if (texts) {
|
||||
for (const textUid of texts) {
|
||||
textsToHighlight[textUid] = true;
|
||||
}
|
||||
}
|
||||
for (const text of state.texts) {
|
||||
if (text.uid === selected.uid) {
|
||||
const belongsToArrow = findNearestArrow(text.topLeft, state.arrows);
|
||||
if (belongsToArrow) {
|
||||
arrowsToHighlight[belongsToArrow.uid] = belongsToArrow;
|
||||
}
|
||||
const arrows = side2ArrowMap.get(selected.uid);
|
||||
if (arrows) {
|
||||
for (const [arrowPart, arrowUid] of arrows) {
|
||||
arrowsToHighlight[arrowUid] = true;
|
||||
}
|
||||
}
|
||||
const arrow2 = text2ArrowMap.get(selected.uid);
|
||||
if (arrow2) {
|
||||
arrowsToHighlight[arrow2] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const rootErrors = errors.filter(([uid]) => uid === "root").map(err=>err[1]);
|
||||
|
|
@ -461,13 +488,22 @@ export function VisualEditor() {
|
|||
errors={errors.filter(([uid,msg])=>uid===rountangle.uid).map(err=>err[1])}
|
||||
/>)}
|
||||
|
||||
{state.arrows.map(arrow => <ArrowSVG
|
||||
key={arrow.uid}
|
||||
arrow={arrow}
|
||||
selected={selection.find(a => a.uid === arrow.uid)?.parts || []}
|
||||
errors={errors.filter(([uid,msg])=>uid===arrow.uid).map(err=>err[1])}
|
||||
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
||||
/>
|
||||
{state.arrows.map(arrow => {
|
||||
const sides = arrow2SideMap.get(arrow.uid);
|
||||
console.log(sides, arrow);
|
||||
let arc = "no" as ArcDirection;
|
||||
if (sides && sides[0]?.uid === sides[1]?.uid && sides[0].uid !== undefined) {
|
||||
arc = arcDirection(sides[0]?.part, sides[1]?.part);
|
||||
}
|
||||
return <ArrowSVG
|
||||
key={arrow.uid}
|
||||
arrow={arrow}
|
||||
selected={selection.find(a => a.uid === arrow.uid)?.parts || []}
|
||||
errors={errors.filter(([uid,msg])=>uid===arrow.uid).map(err=>err[1])}
|
||||
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
||||
arc={arc}
|
||||
/>;
|
||||
}
|
||||
)}
|
||||
|
||||
{state.texts.map(txt => {
|
||||
|
|
@ -479,6 +515,7 @@ export function VisualEditor() {
|
|||
{txt.text.slice(0, start.offset)}
|
||||
<tspan className="error" data-uid={txt.uid} data-parts="text">
|
||||
{txt.text.slice(start.offset, end.offset)}
|
||||
{start.offset === end.offset && <>_</>}
|
||||
</tspan>
|
||||
{txt.text.slice(end.offset)}
|
||||
</>;
|
||||
|
|
@ -486,18 +523,11 @@ export function VisualEditor() {
|
|||
else {
|
||||
markedText = <>{txt.text}</>;
|
||||
}
|
||||
// const annotatedText = err ? [...txt.text].map((char,i) => {
|
||||
// if (i >= err.location.start.offset && i < err.location.end.offset) {
|
||||
// return char+'\u0332';
|
||||
// }
|
||||
// return char;
|
||||
// }).join('') : txt.text;
|
||||
return <text
|
||||
key={txt.uid}
|
||||
className={
|
||||
(selection.find(s => s.uid === txt.uid)?.parts?.length ? "selected":"")
|
||||
+(textsToHighlight.hasOwnProperty(txt.uid)?" highlight":"")
|
||||
// +(errors.some(([uid]) => uid === txt.uid)?" error":"")
|
||||
}
|
||||
x={txt.topLeft.x}
|
||||
y={txt.topLeft.y}
|
||||
|
|
@ -677,20 +707,23 @@ export function RountangleSVG(props: {rountangle: Rountangle, selected: string[]
|
|||
</g>;
|
||||
}
|
||||
|
||||
export function ArrowSVG(props: {arrow: Arrow, selected: string[], errors: string[], highlight: boolean}) {
|
||||
export function ArrowSVG(props: {arrow: Arrow, selected: string[], errors: string[], highlight: boolean, arc: ArcDirection}) {
|
||||
const {start, end, uid} = props.arrow;
|
||||
const radius = euclideanDistance(start, end)/1.6;
|
||||
const largeArc = "1";
|
||||
const arcOrLine = props.arc === "no" ? "L" :
|
||||
`A ${radius} ${radius} 0 ${largeArc} ${props.arc === "ccw" ? "0" : "1"}`;
|
||||
return <g>
|
||||
<line
|
||||
<path
|
||||
className={"arrow"
|
||||
+(props.selected.length===2?" selected":"")
|
||||
+(props.errors.length>0?" error":"")
|
||||
+(props.highlight?" highlight":"")
|
||||
}
|
||||
markerEnd='url(#arrowEnd)'
|
||||
x1={start.x}
|
||||
y1={start.y}
|
||||
x2={end.x}
|
||||
y2={end.y}
|
||||
d={`M ${start.x} ${start.y}
|
||||
${arcOrLine}
|
||||
${end.x} ${end.y}`}
|
||||
data-uid={uid}
|
||||
data-parts="start end"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ export type Transition = {
|
|||
}
|
||||
|
||||
export type Statechart = {
|
||||
root: ConcreteState;
|
||||
root: OrState;
|
||||
transitions: Map<string, Transition[]>; // key: source state uid
|
||||
|
||||
variables: Set<string>;
|
||||
|
||||
inputEvents: Set<string>;
|
||||
internalEvents: Set<string>;
|
||||
outputEvents: Set<string>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { RountanglePart } from "./editor_types";
|
||||
|
||||
export type Vec2D = {
|
||||
x: number;
|
||||
y: number;
|
||||
|
|
@ -161,3 +163,36 @@ export function getBottomSide(rect: Rect2D): Line2D {
|
|||
end: { x: rect.topLeft.x + rect.size.x, y: rect.topLeft.y + rect.size.y },
|
||||
};
|
||||
}
|
||||
|
||||
export type ArcDirection = "no" | "cw" | "ccw";
|
||||
|
||||
export function arcDirection(start: RountanglePart, end: RountanglePart): ArcDirection {
|
||||
if (start === end) {
|
||||
if (start === "left" || start === "top") {
|
||||
return "ccw";
|
||||
}
|
||||
else {
|
||||
return "cw";
|
||||
}
|
||||
}
|
||||
const both = [start, end];
|
||||
if (both.includes("top") && both.includes("bottom")) {
|
||||
return "no";
|
||||
}
|
||||
if (both.includes("left") && both.includes("right")) {
|
||||
return "no";
|
||||
}
|
||||
if (start === "top" && end === "left") {
|
||||
return "ccw";
|
||||
}
|
||||
if (start === "left" && end === "bottom") {
|
||||
return "ccw";
|
||||
}
|
||||
if (start === "bottom" && end === "right") {
|
||||
return "ccw";
|
||||
}
|
||||
if (start === "right" && end === "top") {
|
||||
return "ccw";
|
||||
}
|
||||
return "cw";
|
||||
}
|
||||
26
src/VisualEditor/interpreter.ts
Normal file
26
src/VisualEditor/interpreter.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { ConcreteState, Statechart } from "./ast";
|
||||
|
||||
export function initialize(ast: Statechart): RT_Statechart {
|
||||
const rt_root = recursiveEnter(ast.root) as RT_OrState;
|
||||
return {
|
||||
root: rt_root,
|
||||
variables: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
export function recursiveEnter(state: ConcreteState): RT_ConcreteState {
|
||||
if (state.kind === "and") {
|
||||
return {
|
||||
kind: "and",
|
||||
children: state.children.map(child => recursiveEnter(child)),
|
||||
};
|
||||
}
|
||||
else {
|
||||
const currentState = state.initial[0][1];
|
||||
return {
|
||||
kind: "or",
|
||||
current: currentState.uid,
|
||||
current_rt: recursiveEnter(currentState),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ export type RaiseEvent = {
|
|||
}
|
||||
|
||||
|
||||
export type Expression = BinaryExpression | UnaryExpression | VarRef;
|
||||
export type Expression = BinaryExpression | UnaryExpression | VarRef | Literal;
|
||||
|
||||
export type BinaryExpression = {
|
||||
kind: "binaryExpr";
|
||||
|
|
@ -51,3 +51,8 @@ export type VarRef = {
|
|||
kind: "ref";
|
||||
variable: string;
|
||||
}
|
||||
|
||||
export type Literal = {
|
||||
kind: "literal";
|
||||
value: any;
|
||||
}
|
||||
|
|
@ -168,40 +168,54 @@ function peg$parse(input, options) {
|
|||
const peg$c1 = "]";
|
||||
const peg$c2 = "/";
|
||||
const peg$c3 = "after";
|
||||
const peg$c4 = "ms";
|
||||
const peg$c5 = "s";
|
||||
const peg$c6 = ";";
|
||||
const peg$c7 = "=";
|
||||
const peg$c8 = "(";
|
||||
const peg$c9 = ")";
|
||||
const peg$c10 = "true";
|
||||
const peg$c11 = "false";
|
||||
const peg$c12 = "^";
|
||||
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$c16 = "true";
|
||||
const peg$c17 = "false";
|
||||
const peg$c18 = "^";
|
||||
|
||||
const peg$r0 = /^[a-zA-Z0-9]/;
|
||||
const peg$r1 = /^[0-9]/;
|
||||
const peg$r2 = /^[ \t\n\r]/;
|
||||
const peg$r3 = /^[+\-]/;
|
||||
const peg$r4 = /^[*\/]/;
|
||||
const peg$r3 = /^[<>]/;
|
||||
const peg$r4 = /^[+\-]/;
|
||||
const peg$r5 = /^[*\/]/;
|
||||
|
||||
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("ms", false);
|
||||
const peg$e5 = peg$literalExpectation("s", false);
|
||||
const peg$e6 = peg$literalExpectation(";", false);
|
||||
const peg$e7 = peg$literalExpectation("=", false);
|
||||
const peg$e8 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false, false);
|
||||
const peg$e9 = peg$classExpectation([["0", "9"]], false, false, false);
|
||||
const peg$e10 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
|
||||
const peg$e11 = peg$classExpectation(["+", "-"], false, false, false);
|
||||
const peg$e12 = peg$classExpectation(["*", "/"], false, false, false);
|
||||
const peg$e13 = peg$literalExpectation("(", false);
|
||||
const peg$e14 = peg$literalExpectation(")", false);
|
||||
const peg$e15 = peg$literalExpectation("true", false);
|
||||
const peg$e16 = peg$literalExpectation("false", false);
|
||||
const peg$e17 = peg$literalExpectation("^", 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([["a", "z"], ["A", "Z"], ["0", "9"]], false, false, false);
|
||||
const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false);
|
||||
const peg$e12 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
|
||||
const peg$e13 = peg$literalExpectation("==", false);
|
||||
const peg$e14 = peg$literalExpectation("!=", false);
|
||||
const peg$e15 = peg$classExpectation(["<", ">"], false, false, false);
|
||||
const peg$e16 = peg$literalExpectation("<=", false);
|
||||
const peg$e17 = peg$literalExpectation(">=", false);
|
||||
const peg$e18 = peg$classExpectation(["+", "-"], false, false, false);
|
||||
const peg$e19 = peg$classExpectation(["*", "/"], false, false, false);
|
||||
const peg$e20 = peg$literalExpectation("(", false);
|
||||
const peg$e21 = peg$literalExpectation(")", false);
|
||||
const peg$e22 = peg$literalExpectation("true", false);
|
||||
const peg$e23 = peg$literalExpectation("false", false);
|
||||
const peg$e24 = peg$literalExpectation("^", false);
|
||||
|
||||
function peg$f0(trigger, guard, actions) {
|
||||
return {
|
||||
|
|
@ -216,59 +230,76 @@ function peg$parse(input, options) {
|
|||
function peg$f2(dur) {
|
||||
return {kind: "after", durationMs: dur};
|
||||
}
|
||||
function peg$f3(num, u) {
|
||||
return num * (u === "s" ? 1000 : 1);
|
||||
function peg$f3() {
|
||||
return {kind: "entry"};
|
||||
}
|
||||
function peg$f4() {
|
||||
return {kind: "exit"};
|
||||
}
|
||||
function peg$f5(num, u) {
|
||||
return num * (u === "s" ? 1000 : 1);
|
||||
}
|
||||
function peg$f6() {
|
||||
return text();
|
||||
}
|
||||
function peg$f5(head, tail) {
|
||||
function peg$f7(head, tail) {
|
||||
return [head, ...tail.map(t => t[3])];
|
||||
}
|
||||
function peg$f6(lhs, rhs) {
|
||||
function peg$f8(lhs, rhs) {
|
||||
return {kind: "assignment", lhs, rhs};
|
||||
}
|
||||
function peg$f7() {
|
||||
function peg$f9() {
|
||||
return text();
|
||||
}
|
||||
function peg$f8() {
|
||||
function peg$f10() {
|
||||
return parseInt(text());
|
||||
}
|
||||
function peg$f9(prod, rest) {
|
||||
function peg$f11(sum, rest) {
|
||||
if (rest === null) {
|
||||
return sum;
|
||||
}
|
||||
return {
|
||||
kind: "binaryExpr",
|
||||
operator: rest[0][1],
|
||||
lhs: sum,
|
||||
rhs: rest[1],
|
||||
};
|
||||
}
|
||||
function peg$f12(prod, rest) {
|
||||
if (rest === null) {
|
||||
return prod;
|
||||
}
|
||||
return {
|
||||
kind:"binaryExpr",
|
||||
kind: "binaryExpr",
|
||||
operator: rest[0][1],
|
||||
lhs: prod,
|
||||
rhs: rest[1],
|
||||
};
|
||||
}
|
||||
function peg$f10(atom, rest) {
|
||||
function peg$f13(atom, rest) {
|
||||
if (rest === null) {
|
||||
return atom;
|
||||
}
|
||||
return {
|
||||
kind:"binaryExpr",
|
||||
kind: "binaryExpr",
|
||||
operator: rest[0][1],
|
||||
lhs: atom,
|
||||
rhs: rest[1],
|
||||
};
|
||||
}
|
||||
function peg$f11(expr) {
|
||||
function peg$f14(expr) {
|
||||
return expr;
|
||||
}
|
||||
function peg$f12(value) {
|
||||
function peg$f15(value) {
|
||||
return {kind: "literal", value}
|
||||
}
|
||||
function peg$f13(variable) {
|
||||
function peg$f16(variable) {
|
||||
return {kind: "ref", variable}
|
||||
}
|
||||
function peg$f14() {
|
||||
function peg$f17() {
|
||||
return text() === "true";
|
||||
}
|
||||
function peg$f15(event) {
|
||||
function peg$f18(event) {
|
||||
return {kind: "raise", event};
|
||||
}
|
||||
let peg$currPos = options.peg$currPos | 0;
|
||||
|
|
@ -459,7 +490,7 @@ function peg$parse(input, options) {
|
|||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
s7 = peg$parsesum();
|
||||
s7 = peg$parsecompare();
|
||||
if (s7 !== peg$FAILED) {
|
||||
s8 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 93) {
|
||||
|
|
@ -529,7 +560,13 @@ function peg$parse(input, options) {
|
|||
|
||||
s0 = peg$parseafterTrigger();
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$parseeventTrigger();
|
||||
s0 = peg$parseentryTrigger();
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$parseexitTrigger();
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$parseeventTrigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s0;
|
||||
|
|
@ -578,6 +615,46 @@ function peg$parse(input, options) {
|
|||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseentryTrigger() {
|
||||
let s0, s1;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 5) === peg$c4) {
|
||||
s1 = peg$c4;
|
||||
peg$currPos += 5;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f3();
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parseexitTrigger() {
|
||||
let s0, s1;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 4) === peg$c5) {
|
||||
s1 = peg$c5;
|
||||
peg$currPos += 4;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e5); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f4();
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsedurationMs() {
|
||||
let s0, s1, s2, s3;
|
||||
|
||||
|
|
@ -588,7 +665,7 @@ function peg$parse(input, options) {
|
|||
s3 = peg$parsetimeUnit();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f3(s1, s3);
|
||||
s0 = peg$f5(s1, s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
@ -604,25 +681,25 @@ function peg$parse(input, options) {
|
|||
function peg$parsetimeUnit() {
|
||||
let s0, s1;
|
||||
|
||||
if (input.substr(peg$currPos, 2) === peg$c4) {
|
||||
s0 = peg$c4;
|
||||
if (input.substr(peg$currPos, 2) === peg$c6) {
|
||||
s0 = peg$c6;
|
||||
peg$currPos += 2;
|
||||
} else {
|
||||
s0 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||
}
|
||||
if (s0 === peg$FAILED) {
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 115) {
|
||||
s1 = peg$c5;
|
||||
s1 = peg$c7;
|
||||
peg$currPos++;
|
||||
} 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;
|
||||
s1 = peg$f4();
|
||||
s1 = peg$f6();
|
||||
}
|
||||
s0 = s1;
|
||||
}
|
||||
|
|
@ -640,11 +717,11 @@ function peg$parse(input, options) {
|
|||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 59) {
|
||||
s5 = peg$c6;
|
||||
s5 = peg$c8;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
|
|
@ -665,11 +742,11 @@ function peg$parse(input, options) {
|
|||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 59) {
|
||||
s5 = peg$c6;
|
||||
s5 = peg$c8;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
|
|
@ -688,17 +765,17 @@ function peg$parse(input, options) {
|
|||
}
|
||||
s3 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 59) {
|
||||
s4 = peg$c6;
|
||||
s4 = peg$c8;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s4 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
}
|
||||
if (s4 === peg$FAILED) {
|
||||
s4 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f5(s1, s2);
|
||||
s0 = peg$f7(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
@ -726,18 +803,18 @@ function peg$parse(input, options) {
|
|||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 61) {
|
||||
s3 = peg$c7;
|
||||
s3 = peg$c9;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s3 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e7); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parse_();
|
||||
s5 = peg$parsesum();
|
||||
s5 = peg$parsecompare();
|
||||
if (s5 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f6(s1, s5);
|
||||
s0 = peg$f8(s1, s5);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
@ -764,7 +841,7 @@ function peg$parse(input, options) {
|
|||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
|
|
@ -774,7 +851,7 @@ function peg$parse(input, options) {
|
|||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -782,7 +859,7 @@ function peg$parse(input, options) {
|
|||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f7();
|
||||
s1 = peg$f9();
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
|
|
@ -799,7 +876,7 @@ function peg$parse(input, options) {
|
|||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
||||
}
|
||||
if (s2 !== peg$FAILED) {
|
||||
while (s2 !== peg$FAILED) {
|
||||
|
|
@ -809,7 +886,7 @@ function peg$parse(input, options) {
|
|||
peg$currPos++;
|
||||
} else {
|
||||
s2 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -817,7 +894,7 @@ function peg$parse(input, options) {
|
|||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f8();
|
||||
s1 = peg$f10();
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
|
|
@ -834,7 +911,7 @@ function peg$parse(input, options) {
|
|||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||
}
|
||||
while (s1 !== peg$FAILED) {
|
||||
s0.push(s1);
|
||||
|
|
@ -843,7 +920,7 @@ function peg$parse(input, options) {
|
|||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||
}
|
||||
}
|
||||
peg$silentFails--;
|
||||
|
|
@ -851,6 +928,92 @@ function peg$parse(input, options) {
|
|||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsecompare() {
|
||||
let s0, s1, s2, s3, s4, s5, s6;
|
||||
|
||||
s0 = peg$currPos;
|
||||
s1 = peg$parsesum();
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$currPos;
|
||||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
if (input.substr(peg$currPos, 2) === peg$c10) {
|
||||
s5 = peg$c10;
|
||||
peg$currPos += 2;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
||||
}
|
||||
if (s5 === peg$FAILED) {
|
||||
if (input.substr(peg$currPos, 2) === peg$c11) {
|
||||
s5 = peg$c11;
|
||||
peg$currPos += 2;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e14); }
|
||||
}
|
||||
if (s5 === peg$FAILED) {
|
||||
s5 = input.charAt(peg$currPos);
|
||||
if (peg$r3.test(s5)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
||||
}
|
||||
if (s5 === peg$FAILED) {
|
||||
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$e16); }
|
||||
}
|
||||
if (s5 === peg$FAILED) {
|
||||
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$e17); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
s4 = [s4, s5, s6];
|
||||
s3 = s4;
|
||||
} else {
|
||||
peg$currPos = s3;
|
||||
s3 = peg$FAILED;
|
||||
}
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parsecompare();
|
||||
if (s4 !== peg$FAILED) {
|
||||
s3 = [s3, s4];
|
||||
s2 = s3;
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
} else {
|
||||
peg$currPos = s2;
|
||||
s2 = peg$FAILED;
|
||||
}
|
||||
if (s2 === peg$FAILED) {
|
||||
s2 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f11(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
}
|
||||
|
||||
return s0;
|
||||
}
|
||||
|
||||
function peg$parsesum() {
|
||||
let s0, s1, s2, s3, s4, s5, s6;
|
||||
|
||||
|
|
@ -861,11 +1024,11 @@ function peg$parse(input, options) {
|
|||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
s5 = input.charAt(peg$currPos);
|
||||
if (peg$r3.test(s5)) {
|
||||
if (peg$r4.test(s5)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
|
|
@ -892,7 +1055,7 @@ function peg$parse(input, options) {
|
|||
s2 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f9(s1, s2);
|
||||
s0 = peg$f12(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
@ -911,11 +1074,11 @@ function peg$parse(input, options) {
|
|||
s3 = peg$currPos;
|
||||
s4 = peg$parse_();
|
||||
s5 = input.charAt(peg$currPos);
|
||||
if (peg$r4.test(s5)) {
|
||||
if (peg$r5.test(s5)) {
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e19); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
s6 = peg$parse_();
|
||||
|
|
@ -942,7 +1105,7 @@ function peg$parse(input, options) {
|
|||
s2 = null;
|
||||
}
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f10(s1, s2);
|
||||
s0 = peg$f13(s1, s2);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
@ -970,27 +1133,27 @@ function peg$parse(input, options) {
|
|||
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 40) {
|
||||
s1 = peg$c8;
|
||||
s1 = peg$c14;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e20); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parse_();
|
||||
s3 = peg$parsesum();
|
||||
s3 = peg$parsecompare();
|
||||
if (s3 !== peg$FAILED) {
|
||||
s4 = peg$parse_();
|
||||
if (input.charCodeAt(peg$currPos) === 41) {
|
||||
s5 = peg$c9;
|
||||
s5 = peg$c15;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s5 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e14); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e21); }
|
||||
}
|
||||
if (s5 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f11(s3);
|
||||
s0 = peg$f14(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
@ -1017,7 +1180,7 @@ function peg$parse(input, options) {
|
|||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f12(s1);
|
||||
s1 = peg$f15(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
|
|
@ -1031,7 +1194,7 @@ function peg$parse(input, options) {
|
|||
s1 = peg$parseidentifier();
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f13(s1);
|
||||
s1 = peg$f16(s1);
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
|
|
@ -1042,25 +1205,25 @@ function peg$parse(input, options) {
|
|||
let s0, s1;
|
||||
|
||||
s0 = peg$currPos;
|
||||
if (input.substr(peg$currPos, 4) === peg$c10) {
|
||||
s1 = peg$c10;
|
||||
if (input.substr(peg$currPos, 4) === peg$c16) {
|
||||
s1 = peg$c16;
|
||||
peg$currPos += 4;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e22); }
|
||||
}
|
||||
if (s1 === peg$FAILED) {
|
||||
if (input.substr(peg$currPos, 5) === peg$c11) {
|
||||
s1 = peg$c11;
|
||||
if (input.substr(peg$currPos, 5) === peg$c17) {
|
||||
s1 = peg$c17;
|
||||
peg$currPos += 5;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e16); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e23); }
|
||||
}
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s1 = peg$f14();
|
||||
s1 = peg$f17();
|
||||
}
|
||||
s0 = s1;
|
||||
|
||||
|
|
@ -1072,18 +1235,18 @@ function peg$parse(input, options) {
|
|||
|
||||
s0 = peg$currPos;
|
||||
if (input.charCodeAt(peg$currPos) === 94) {
|
||||
s1 = peg$c12;
|
||||
s1 = peg$c18;
|
||||
peg$currPos++;
|
||||
} else {
|
||||
s1 = peg$FAILED;
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e17); }
|
||||
if (peg$silentFails === 0) { peg$fail(peg$e24); }
|
||||
}
|
||||
if (s1 !== peg$FAILED) {
|
||||
s2 = peg$parse_();
|
||||
s3 = peg$parseidentifier();
|
||||
if (s3 !== peg$FAILED) {
|
||||
peg$savedPos = s0;
|
||||
s0 = peg$f15(s3);
|
||||
s0 = peg$f18(s3);
|
||||
} else {
|
||||
peg$currPos = s0;
|
||||
s0 = peg$FAILED;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
export const ARROW_SNAP_THRESHOLD = 20;
|
||||
export const TEXT_SNAP_THRESHOLD = 20;
|
||||
export const TEXT_SNAP_THRESHOLD = 30;
|
||||
|
||||
export const ROUNTANGLE_RADIUS = 20;
|
||||
export const MIN_ROUNTANGLE_SIZE = { x: ROUNTANGLE_RADIUS*2, y: ROUNTANGLE_RADIUS*2 };
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { act } from "react";
|
||||
import { ConcreteState, OrState, Statechart, Transition } from "./ast";
|
||||
import { findNearestArrow, findNearestRountangleSide, Rountangle, VisualEditorState } from "./editor_types";
|
||||
import { isEntirelyWithin } from "./geometry";
|
||||
import { TransitionLabel } from "./label_ast";
|
||||
import { isEntirelyWithin, transformLine } from "./geometry";
|
||||
import { Action, Expression, TransitionLabel } from "./label_ast";
|
||||
|
||||
import { parse as parseLabel } from "./label_parser";
|
||||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||
|
||||
export function parseStatechart(state: VisualEditorState): [Statechart, [string,string][]] {
|
||||
const errorShapes: [string, string][] = [];
|
||||
|
|
@ -113,6 +114,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
|||
}
|
||||
}
|
||||
|
||||
let variables = new Set<string>();
|
||||
const inputEvents = new Set<string>();
|
||||
const outputEvents = new Set<string>();
|
||||
const internalEvents = new Set<string>();
|
||||
|
||||
// step 3: figure out labels
|
||||
|
||||
for (const text of state.texts) {
|
||||
|
|
@ -125,10 +131,42 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
|||
try {
|
||||
transitionLabel = parseLabel(text.text); // may throw
|
||||
belongsToTransition.label.push(transitionLabel);
|
||||
// collect events
|
||||
if (transitionLabel.trigger.kind === "event") {
|
||||
const {event} = transitionLabel.trigger;
|
||||
if (event.startsWith("_")) {
|
||||
internalEvents.add(event);
|
||||
}
|
||||
else {
|
||||
inputEvents.add(event);
|
||||
}
|
||||
}
|
||||
for (const action of transitionLabel.actions) {
|
||||
if (action.kind === "raise") {
|
||||
const {event} = action;
|
||||
if (event.startsWith("_")) {
|
||||
internalEvents.add(event);
|
||||
}
|
||||
else {
|
||||
outputEvents.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
// collect variables
|
||||
variables = variables
|
||||
.union(findVariables(transitionLabel.guard));
|
||||
for (const action of transitionLabel.actions) {
|
||||
variables = variables.union(findVariablesAction(action));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log({e});
|
||||
errorShapes.push([text.uid, e]);
|
||||
if (e instanceof SyntaxError) {
|
||||
belongsToTransition.label.push(null);
|
||||
errorShapes.push([text.uid, e]);
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,5 +184,32 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
|||
return [{
|
||||
root,
|
||||
transitions,
|
||||
variables,
|
||||
inputEvents,
|
||||
internalEvents,
|
||||
outputEvents,
|
||||
}, errorShapes];
|
||||
}
|
||||
}
|
||||
|
||||
function findVariables(expr: Expression): Set<string> {
|
||||
if (expr.kind === "ref") {
|
||||
return new Set([expr.variable]);
|
||||
}
|
||||
else if (expr.kind === "unaryExpr") {
|
||||
return findVariables(expr.expr);
|
||||
}
|
||||
else if (expr.kind === "binaryExpr") {
|
||||
return findVariables(expr.lhs).union(findVariables(expr.rhs));
|
||||
}
|
||||
else if (expr.kind === "literal") {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
function findVariablesAction(action: Action): Set<string> {
|
||||
if (action.kind === "assignment") {
|
||||
return new Set([action.lhs, ...findVariables(action.rhs)]);
|
||||
}
|
||||
return new Set();
|
||||
}
|
||||
|
||||
|
|
|
|||
18
src/VisualEditor/runtime_types.ts
Normal file
18
src/VisualEditor/runtime_types.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
type RT_ConcreteState = RT_OrState | RT_AndState;
|
||||
|
||||
type RT_OrState = {
|
||||
kind: "or";
|
||||
current: string;
|
||||
current_rt: RT_ConcreteState; // keep the runtime configuration only of the current state
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ start = _ trigger:trigger _ guard:("[" _ guard _ "]")? _ actions:("/" _ actions
|
|||
};
|
||||
}
|
||||
|
||||
trigger = afterTrigger / eventTrigger
|
||||
trigger = afterTrigger / entryTrigger / exitTrigger / eventTrigger
|
||||
|
||||
eventTrigger = event:identifier {
|
||||
return {kind: "event", event};
|
||||
|
|
@ -16,6 +16,15 @@ afterTrigger = "after" _ dur:durationMs {
|
|||
return {kind: "after", durationMs: dur};
|
||||
}
|
||||
|
||||
entryTrigger = "entry" {
|
||||
return {kind: "entry"};
|
||||
}
|
||||
|
||||
exitTrigger = "exit" {
|
||||
return {kind: "exit"};
|
||||
}
|
||||
|
||||
|
||||
durationMs = num:number _ u:timeUnit {
|
||||
return num * (u === "s" ? 1000 : 1);
|
||||
}
|
||||
|
|
@ -46,14 +55,26 @@ number = [0-9]+ {
|
|||
_ "whitespace"
|
||||
= [ \t\n\r]*
|
||||
|
||||
expr = sum
|
||||
expr = compare
|
||||
|
||||
compare = sum:sum rest:((_ ("==" / "!=" / "<" / ">" / "<=" / ">=") _) compare)? {
|
||||
if (rest === null) {
|
||||
return sum;
|
||||
}
|
||||
return {
|
||||
kind: "binaryExpr",
|
||||
operator: rest[0][1],
|
||||
lhs: sum,
|
||||
rhs: rest[1],
|
||||
};
|
||||
}
|
||||
|
||||
sum = prod:product rest:((_ ("+" / "-") _) sum)? {
|
||||
if (rest === null) {
|
||||
return prod;
|
||||
}
|
||||
return {
|
||||
kind:"binaryExpr",
|
||||
kind: "binaryExpr",
|
||||
operator: rest[0][1],
|
||||
lhs: prod,
|
||||
rhs: rest[1],
|
||||
|
|
@ -65,7 +86,7 @@ product = atom:atom rest:((_ ("*" / "/") _) product)? {
|
|||
return atom;
|
||||
}
|
||||
return {
|
||||
kind:"binaryExpr",
|
||||
kind: "binaryExpr",
|
||||
operator: rest[0][1],
|
||||
lhs: atom,
|
||||
rhs: rest[1],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue