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 {
|
.arrow {
|
||||||
|
fill: none;
|
||||||
stroke: black;
|
stroke: black;
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { MouseEventHandler, SetStateAction, useEffect, useRef, useState } from "react";
|
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";
|
import "./VisualEditor.css";
|
||||||
|
|
||||||
|
|
@ -396,37 +396,64 @@ export function VisualEditor() {
|
||||||
};
|
};
|
||||||
}, [selectingState, dragging]);
|
}, [selectingState, dragging]);
|
||||||
|
|
||||||
// whenever an arrow is selected, highlight the rountangle sides it connects to
|
// detect what is 'connected'
|
||||||
// just for visual feedback
|
const arrow2SideMap = new Map<string,[{ uid: string; part: RountanglePart; } | undefined, { uid: string; part: RountanglePart; } | undefined]>();
|
||||||
let sidesToHighlight: {[key: string]: RountanglePart[]} = {};
|
const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>();
|
||||||
let arrowsToHighlight: {[key: string]: Arrow} = {};
|
const text2ArrowMap = new Map<string,string>();
|
||||||
let textsToHighlight: {[key: string]: Text} = {};
|
const arrow2TextMap = new Map<string,string[]>();
|
||||||
for (const selected of selection) {
|
|
||||||
for (const arrow of state.arrows) {
|
for (const arrow of state.arrows) {
|
||||||
if (arrow.uid === selected.uid) {
|
const startSide = findNearestRountangleSide(arrow, "start", state.rountangles);
|
||||||
const rSideStart = findNearestRountangleSide(arrow, "start", state.rountangles);
|
const endSide = findNearestRountangleSide(arrow, "end", state.rountangles);
|
||||||
if (rSideStart) {
|
if (startSide || endSide) {
|
||||||
sidesToHighlight[rSideStart.uid] = [...(sidesToHighlight[rSideStart.uid] || []), rSideStart.part];
|
arrow2SideMap.set(arrow.uid, [startSide, endSide]);
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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) {
|
for (const text of state.texts) {
|
||||||
if (text.uid === selected.uid) {
|
const nearestArrow = findNearestArrow(text.topLeft, state.arrows);
|
||||||
const belongsToArrow = findNearestArrow(text.topLeft, state.arrows);
|
if (nearestArrow) {
|
||||||
if (belongsToArrow) {
|
text2ArrowMap.set(text.uid, nearestArrow.uid);
|
||||||
arrowsToHighlight[belongsToArrow.uid] = belongsToArrow;
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,13 +488,22 @@ export function VisualEditor() {
|
||||||
errors={errors.filter(([uid,msg])=>uid===rountangle.uid).map(err=>err[1])}
|
errors={errors.filter(([uid,msg])=>uid===rountangle.uid).map(err=>err[1])}
|
||||||
/>)}
|
/>)}
|
||||||
|
|
||||||
{state.arrows.map(arrow => <ArrowSVG
|
{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}
|
key={arrow.uid}
|
||||||
arrow={arrow}
|
arrow={arrow}
|
||||||
selected={selection.find(a => a.uid === arrow.uid)?.parts || []}
|
selected={selection.find(a => a.uid === arrow.uid)?.parts || []}
|
||||||
errors={errors.filter(([uid,msg])=>uid===arrow.uid).map(err=>err[1])}
|
errors={errors.filter(([uid,msg])=>uid===arrow.uid).map(err=>err[1])}
|
||||||
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
||||||
/>
|
arc={arc}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{state.texts.map(txt => {
|
{state.texts.map(txt => {
|
||||||
|
|
@ -479,6 +515,7 @@ export function VisualEditor() {
|
||||||
{txt.text.slice(0, start.offset)}
|
{txt.text.slice(0, start.offset)}
|
||||||
<tspan className="error" data-uid={txt.uid} data-parts="text">
|
<tspan className="error" data-uid={txt.uid} data-parts="text">
|
||||||
{txt.text.slice(start.offset, end.offset)}
|
{txt.text.slice(start.offset, end.offset)}
|
||||||
|
{start.offset === end.offset && <>_</>}
|
||||||
</tspan>
|
</tspan>
|
||||||
{txt.text.slice(end.offset)}
|
{txt.text.slice(end.offset)}
|
||||||
</>;
|
</>;
|
||||||
|
|
@ -486,18 +523,11 @@ export function VisualEditor() {
|
||||||
else {
|
else {
|
||||||
markedText = <>{txt.text}</>;
|
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
|
return <text
|
||||||
key={txt.uid}
|
key={txt.uid}
|
||||||
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":"")
|
||||||
// +(errors.some(([uid]) => uid === txt.uid)?" error":"")
|
|
||||||
}
|
}
|
||||||
x={txt.topLeft.x}
|
x={txt.topLeft.x}
|
||||||
y={txt.topLeft.y}
|
y={txt.topLeft.y}
|
||||||
|
|
@ -677,20 +707,23 @@ export function RountangleSVG(props: {rountangle: Rountangle, selected: string[]
|
||||||
</g>;
|
</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 {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>
|
return <g>
|
||||||
<line
|
<path
|
||||||
className={"arrow"
|
className={"arrow"
|
||||||
+(props.selected.length===2?" selected":"")
|
+(props.selected.length===2?" selected":"")
|
||||||
+(props.errors.length>0?" error":"")
|
+(props.errors.length>0?" error":"")
|
||||||
+(props.highlight?" highlight":"")
|
+(props.highlight?" highlight":"")
|
||||||
}
|
}
|
||||||
markerEnd='url(#arrowEnd)'
|
markerEnd='url(#arrowEnd)'
|
||||||
x1={start.x}
|
d={`M ${start.x} ${start.y}
|
||||||
y1={start.y}
|
${arcOrLine}
|
||||||
x2={end.x}
|
${end.x} ${end.y}`}
|
||||||
y2={end.y}
|
|
||||||
data-uid={uid}
|
data-uid={uid}
|
||||||
data-parts="start end"
|
data-parts="start end"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,12 @@ export type Transition = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Statechart = {
|
export type Statechart = {
|
||||||
root: ConcreteState;
|
root: OrState;
|
||||||
transitions: Map<string, Transition[]>; // key: source state uid
|
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 = {
|
export type Vec2D = {
|
||||||
x: number;
|
x: number;
|
||||||
y: 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 },
|
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 = {
|
export type BinaryExpression = {
|
||||||
kind: "binaryExpr";
|
kind: "binaryExpr";
|
||||||
|
|
@ -51,3 +51,8 @@ export type VarRef = {
|
||||||
kind: "ref";
|
kind: "ref";
|
||||||
variable: string;
|
variable: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Literal = {
|
||||||
|
kind: "literal";
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
@ -168,40 +168,54 @@ function peg$parse(input, options) {
|
||||||
const peg$c1 = "]";
|
const peg$c1 = "]";
|
||||||
const peg$c2 = "/";
|
const peg$c2 = "/";
|
||||||
const peg$c3 = "after";
|
const peg$c3 = "after";
|
||||||
const peg$c4 = "ms";
|
const peg$c4 = "entry";
|
||||||
const peg$c5 = "s";
|
const peg$c5 = "exit";
|
||||||
const peg$c6 = ";";
|
const peg$c6 = "ms";
|
||||||
const peg$c7 = "=";
|
const peg$c7 = "s";
|
||||||
const peg$c8 = "(";
|
const peg$c8 = ";";
|
||||||
const peg$c9 = ")";
|
const peg$c9 = "=";
|
||||||
const peg$c10 = "true";
|
const peg$c10 = "==";
|
||||||
const peg$c11 = "false";
|
const peg$c11 = "!=";
|
||||||
const peg$c12 = "^";
|
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$r0 = /^[a-zA-Z0-9]/;
|
||||||
const peg$r1 = /^[0-9]/;
|
const peg$r1 = /^[0-9]/;
|
||||||
const peg$r2 = /^[ \t\n\r]/;
|
const peg$r2 = /^[ \t\n\r]/;
|
||||||
const peg$r3 = /^[+\-]/;
|
const peg$r3 = /^[<>]/;
|
||||||
const peg$r4 = /^[*\/]/;
|
const peg$r4 = /^[+\-]/;
|
||||||
|
const peg$r5 = /^[*\/]/;
|
||||||
|
|
||||||
const peg$e0 = peg$literalExpectation("[", false);
|
const peg$e0 = peg$literalExpectation("[", false);
|
||||||
const peg$e1 = peg$literalExpectation("]", false);
|
const peg$e1 = peg$literalExpectation("]", false);
|
||||||
const peg$e2 = peg$literalExpectation("/", false);
|
const peg$e2 = peg$literalExpectation("/", false);
|
||||||
const peg$e3 = peg$literalExpectation("after", false);
|
const peg$e3 = peg$literalExpectation("after", false);
|
||||||
const peg$e4 = peg$literalExpectation("ms", false);
|
const peg$e4 = peg$literalExpectation("entry", false);
|
||||||
const peg$e5 = peg$literalExpectation("s", false);
|
const peg$e5 = peg$literalExpectation("exit", false);
|
||||||
const peg$e6 = peg$literalExpectation(";", false);
|
const peg$e6 = peg$literalExpectation("ms", false);
|
||||||
const peg$e7 = peg$literalExpectation("=", false);
|
const peg$e7 = peg$literalExpectation("s", false);
|
||||||
const peg$e8 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false, false);
|
const peg$e8 = peg$literalExpectation(";", false);
|
||||||
const peg$e9 = peg$classExpectation([["0", "9"]], false, false, false);
|
const peg$e9 = peg$literalExpectation("=", false);
|
||||||
const peg$e10 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
|
const peg$e10 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false, false);
|
||||||
const peg$e11 = peg$classExpectation(["+", "-"], false, false, false);
|
const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false);
|
||||||
const peg$e12 = peg$classExpectation(["*", "/"], false, false, false);
|
const peg$e12 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false);
|
||||||
const peg$e13 = peg$literalExpectation("(", false);
|
const peg$e13 = peg$literalExpectation("==", false);
|
||||||
const peg$e14 = peg$literalExpectation(")", false);
|
const peg$e14 = peg$literalExpectation("!=", false);
|
||||||
const peg$e15 = peg$literalExpectation("true", false);
|
const peg$e15 = peg$classExpectation(["<", ">"], false, false, false);
|
||||||
const peg$e16 = peg$literalExpectation("false", false);
|
const peg$e16 = peg$literalExpectation("<=", false);
|
||||||
const peg$e17 = 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) {
|
function peg$f0(trigger, guard, actions) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -216,25 +230,42 @@ function peg$parse(input, options) {
|
||||||
function peg$f2(dur) {
|
function peg$f2(dur) {
|
||||||
return {kind: "after", durationMs: dur};
|
return {kind: "after", durationMs: dur};
|
||||||
}
|
}
|
||||||
function peg$f3(num, u) {
|
function peg$f3() {
|
||||||
return num * (u === "s" ? 1000 : 1);
|
return {kind: "entry"};
|
||||||
}
|
}
|
||||||
function peg$f4() {
|
function peg$f4() {
|
||||||
|
return {kind: "exit"};
|
||||||
|
}
|
||||||
|
function peg$f5(num, u) {
|
||||||
|
return num * (u === "s" ? 1000 : 1);
|
||||||
|
}
|
||||||
|
function peg$f6() {
|
||||||
return text();
|
return text();
|
||||||
}
|
}
|
||||||
function peg$f5(head, tail) {
|
function peg$f7(head, tail) {
|
||||||
return [head, ...tail.map(t => t[3])];
|
return [head, ...tail.map(t => t[3])];
|
||||||
}
|
}
|
||||||
function peg$f6(lhs, rhs) {
|
function peg$f8(lhs, rhs) {
|
||||||
return {kind: "assignment", lhs, rhs};
|
return {kind: "assignment", lhs, rhs};
|
||||||
}
|
}
|
||||||
function peg$f7() {
|
function peg$f9() {
|
||||||
return text();
|
return text();
|
||||||
}
|
}
|
||||||
function peg$f8() {
|
function peg$f10() {
|
||||||
return parseInt(text());
|
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) {
|
if (rest === null) {
|
||||||
return prod;
|
return prod;
|
||||||
}
|
}
|
||||||
|
|
@ -245,7 +276,7 @@ function peg$parse(input, options) {
|
||||||
rhs: rest[1],
|
rhs: rest[1],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function peg$f10(atom, rest) {
|
function peg$f13(atom, rest) {
|
||||||
if (rest === null) {
|
if (rest === null) {
|
||||||
return atom;
|
return atom;
|
||||||
}
|
}
|
||||||
|
|
@ -256,19 +287,19 @@ function peg$parse(input, options) {
|
||||||
rhs: rest[1],
|
rhs: rest[1],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function peg$f11(expr) {
|
function peg$f14(expr) {
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
function peg$f12(value) {
|
function peg$f15(value) {
|
||||||
return {kind: "literal", value}
|
return {kind: "literal", value}
|
||||||
}
|
}
|
||||||
function peg$f13(variable) {
|
function peg$f16(variable) {
|
||||||
return {kind: "ref", variable}
|
return {kind: "ref", variable}
|
||||||
}
|
}
|
||||||
function peg$f14() {
|
function peg$f17() {
|
||||||
return text() === "true";
|
return text() === "true";
|
||||||
}
|
}
|
||||||
function peg$f15(event) {
|
function peg$f18(event) {
|
||||||
return {kind: "raise", event};
|
return {kind: "raise", event};
|
||||||
}
|
}
|
||||||
let peg$currPos = options.peg$currPos | 0;
|
let peg$currPos = options.peg$currPos | 0;
|
||||||
|
|
@ -459,7 +490,7 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
s7 = peg$parsesum();
|
s7 = peg$parsecompare();
|
||||||
if (s7 !== peg$FAILED) {
|
if (s7 !== peg$FAILED) {
|
||||||
s8 = peg$parse_();
|
s8 = peg$parse_();
|
||||||
if (input.charCodeAt(peg$currPos) === 93) {
|
if (input.charCodeAt(peg$currPos) === 93) {
|
||||||
|
|
@ -528,9 +559,15 @@ function peg$parse(input, options) {
|
||||||
let s0;
|
let s0;
|
||||||
|
|
||||||
s0 = peg$parseafterTrigger();
|
s0 = peg$parseafterTrigger();
|
||||||
|
if (s0 === peg$FAILED) {
|
||||||
|
s0 = peg$parseentryTrigger();
|
||||||
|
if (s0 === peg$FAILED) {
|
||||||
|
s0 = peg$parseexitTrigger();
|
||||||
if (s0 === peg$FAILED) {
|
if (s0 === peg$FAILED) {
|
||||||
s0 = peg$parseeventTrigger();
|
s0 = peg$parseeventTrigger();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s0;
|
return s0;
|
||||||
}
|
}
|
||||||
|
|
@ -578,6 +615,46 @@ function peg$parse(input, options) {
|
||||||
return s0;
|
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() {
|
function peg$parsedurationMs() {
|
||||||
let s0, s1, s2, s3;
|
let s0, s1, s2, s3;
|
||||||
|
|
||||||
|
|
@ -588,7 +665,7 @@ function peg$parse(input, options) {
|
||||||
s3 = peg$parsetimeUnit();
|
s3 = peg$parsetimeUnit();
|
||||||
if (s3 !== peg$FAILED) {
|
if (s3 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f3(s1, s3);
|
s0 = peg$f5(s1, s3);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
@ -604,25 +681,25 @@ function peg$parse(input, options) {
|
||||||
function peg$parsetimeUnit() {
|
function peg$parsetimeUnit() {
|
||||||
let s0, s1;
|
let s0, s1;
|
||||||
|
|
||||||
if (input.substr(peg$currPos, 2) === peg$c4) {
|
if (input.substr(peg$currPos, 2) === peg$c6) {
|
||||||
s0 = peg$c4;
|
s0 = peg$c6;
|
||||||
peg$currPos += 2;
|
peg$currPos += 2;
|
||||||
} else {
|
} else {
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||||
}
|
}
|
||||||
if (s0 === peg$FAILED) {
|
if (s0 === peg$FAILED) {
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.charCodeAt(peg$currPos) === 115) {
|
if (input.charCodeAt(peg$currPos) === 115) {
|
||||||
s1 = peg$c5;
|
s1 = peg$c7;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e5); }
|
if (peg$silentFails === 0) { peg$fail(peg$e7); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f4();
|
s1 = peg$f6();
|
||||||
}
|
}
|
||||||
s0 = s1;
|
s0 = s1;
|
||||||
}
|
}
|
||||||
|
|
@ -640,11 +717,11 @@ function peg$parse(input, options) {
|
||||||
s3 = peg$currPos;
|
s3 = peg$currPos;
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
if (input.charCodeAt(peg$currPos) === 59) {
|
if (input.charCodeAt(peg$currPos) === 59) {
|
||||||
s5 = peg$c6;
|
s5 = peg$c8;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -665,11 +742,11 @@ function peg$parse(input, options) {
|
||||||
s3 = peg$currPos;
|
s3 = peg$currPos;
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
if (input.charCodeAt(peg$currPos) === 59) {
|
if (input.charCodeAt(peg$currPos) === 59) {
|
||||||
s5 = peg$c6;
|
s5 = peg$c8;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -688,17 +765,17 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
s3 = peg$parse_();
|
s3 = peg$parse_();
|
||||||
if (input.charCodeAt(peg$currPos) === 59) {
|
if (input.charCodeAt(peg$currPos) === 59) {
|
||||||
s4 = peg$c6;
|
s4 = peg$c8;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s4 = peg$FAILED;
|
s4 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||||
}
|
}
|
||||||
if (s4 === peg$FAILED) {
|
if (s4 === peg$FAILED) {
|
||||||
s4 = null;
|
s4 = null;
|
||||||
}
|
}
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f5(s1, s2);
|
s0 = peg$f7(s1, s2);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
@ -726,18 +803,18 @@ function peg$parse(input, options) {
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
s2 = peg$parse_();
|
s2 = peg$parse_();
|
||||||
if (input.charCodeAt(peg$currPos) === 61) {
|
if (input.charCodeAt(peg$currPos) === 61) {
|
||||||
s3 = peg$c7;
|
s3 = peg$c9;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s3 = peg$FAILED;
|
s3 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e7); }
|
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
||||||
}
|
}
|
||||||
if (s3 !== peg$FAILED) {
|
if (s3 !== peg$FAILED) {
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
s5 = peg$parsesum();
|
s5 = peg$parsecompare();
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f6(s1, s5);
|
s0 = peg$f8(s1, s5);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
@ -764,7 +841,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s2 = peg$FAILED;
|
s2 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||||
}
|
}
|
||||||
if (s2 !== peg$FAILED) {
|
if (s2 !== peg$FAILED) {
|
||||||
while (s2 !== peg$FAILED) {
|
while (s2 !== peg$FAILED) {
|
||||||
|
|
@ -774,7 +851,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s2 = peg$FAILED;
|
s2 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -782,7 +859,7 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f7();
|
s1 = peg$f9();
|
||||||
}
|
}
|
||||||
s0 = s1;
|
s0 = s1;
|
||||||
|
|
||||||
|
|
@ -799,7 +876,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s2 = peg$FAILED;
|
s2 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
||||||
}
|
}
|
||||||
if (s2 !== peg$FAILED) {
|
if (s2 !== peg$FAILED) {
|
||||||
while (s2 !== peg$FAILED) {
|
while (s2 !== peg$FAILED) {
|
||||||
|
|
@ -809,7 +886,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s2 = peg$FAILED;
|
s2 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -817,7 +894,7 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f8();
|
s1 = peg$f10();
|
||||||
}
|
}
|
||||||
s0 = s1;
|
s0 = s1;
|
||||||
|
|
||||||
|
|
@ -834,7 +911,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||||
}
|
}
|
||||||
while (s1 !== peg$FAILED) {
|
while (s1 !== peg$FAILED) {
|
||||||
s0.push(s1);
|
s0.push(s1);
|
||||||
|
|
@ -843,7 +920,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
peg$silentFails--;
|
peg$silentFails--;
|
||||||
|
|
@ -851,6 +928,92 @@ function peg$parse(input, options) {
|
||||||
return s0;
|
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() {
|
function peg$parsesum() {
|
||||||
let s0, s1, s2, s3, s4, s5, s6;
|
let s0, s1, s2, s3, s4, s5, s6;
|
||||||
|
|
||||||
|
|
@ -861,11 +1024,11 @@ function peg$parse(input, options) {
|
||||||
s3 = peg$currPos;
|
s3 = peg$currPos;
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
s5 = input.charAt(peg$currPos);
|
s5 = input.charAt(peg$currPos);
|
||||||
if (peg$r3.test(s5)) {
|
if (peg$r4.test(s5)) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -892,7 +1055,7 @@ function peg$parse(input, options) {
|
||||||
s2 = null;
|
s2 = null;
|
||||||
}
|
}
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f9(s1, s2);
|
s0 = peg$f12(s1, s2);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
@ -911,11 +1074,11 @@ function peg$parse(input, options) {
|
||||||
s3 = peg$currPos;
|
s3 = peg$currPos;
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
s5 = input.charAt(peg$currPos);
|
s5 = input.charAt(peg$currPos);
|
||||||
if (peg$r4.test(s5)) {
|
if (peg$r5.test(s5)) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
if (peg$silentFails === 0) { peg$fail(peg$e19); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -942,7 +1105,7 @@ function peg$parse(input, options) {
|
||||||
s2 = null;
|
s2 = null;
|
||||||
}
|
}
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f10(s1, s2);
|
s0 = peg$f13(s1, s2);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
@ -970,27 +1133,27 @@ function peg$parse(input, options) {
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.charCodeAt(peg$currPos) === 40) {
|
if (input.charCodeAt(peg$currPos) === 40) {
|
||||||
s1 = peg$c8;
|
s1 = peg$c14;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
if (peg$silentFails === 0) { peg$fail(peg$e20); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
s2 = peg$parse_();
|
s2 = peg$parse_();
|
||||||
s3 = peg$parsesum();
|
s3 = peg$parsecompare();
|
||||||
if (s3 !== peg$FAILED) {
|
if (s3 !== peg$FAILED) {
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
if (input.charCodeAt(peg$currPos) === 41) {
|
if (input.charCodeAt(peg$currPos) === 41) {
|
||||||
s5 = peg$c9;
|
s5 = peg$c15;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e14); }
|
if (peg$silentFails === 0) { peg$fail(peg$e21); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f11(s3);
|
s0 = peg$f14(s3);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
@ -1017,7 +1180,7 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f12(s1);
|
s1 = peg$f15(s1);
|
||||||
}
|
}
|
||||||
s0 = s1;
|
s0 = s1;
|
||||||
|
|
||||||
|
|
@ -1031,7 +1194,7 @@ function peg$parse(input, options) {
|
||||||
s1 = peg$parseidentifier();
|
s1 = peg$parseidentifier();
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f13(s1);
|
s1 = peg$f16(s1);
|
||||||
}
|
}
|
||||||
s0 = s1;
|
s0 = s1;
|
||||||
|
|
||||||
|
|
@ -1042,25 +1205,25 @@ function peg$parse(input, options) {
|
||||||
let s0, s1;
|
let s0, s1;
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.substr(peg$currPos, 4) === peg$c10) {
|
if (input.substr(peg$currPos, 4) === peg$c16) {
|
||||||
s1 = peg$c10;
|
s1 = peg$c16;
|
||||||
peg$currPos += 4;
|
peg$currPos += 4;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
if (peg$silentFails === 0) { peg$fail(peg$e22); }
|
||||||
}
|
}
|
||||||
if (s1 === peg$FAILED) {
|
if (s1 === peg$FAILED) {
|
||||||
if (input.substr(peg$currPos, 5) === peg$c11) {
|
if (input.substr(peg$currPos, 5) === peg$c17) {
|
||||||
s1 = peg$c11;
|
s1 = peg$c17;
|
||||||
peg$currPos += 5;
|
peg$currPos += 5;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e16); }
|
if (peg$silentFails === 0) { peg$fail(peg$e23); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f14();
|
s1 = peg$f17();
|
||||||
}
|
}
|
||||||
s0 = s1;
|
s0 = s1;
|
||||||
|
|
||||||
|
|
@ -1072,18 +1235,18 @@ function peg$parse(input, options) {
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.charCodeAt(peg$currPos) === 94) {
|
if (input.charCodeAt(peg$currPos) === 94) {
|
||||||
s1 = peg$c12;
|
s1 = peg$c18;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e17); }
|
if (peg$silentFails === 0) { peg$fail(peg$e24); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
s2 = peg$parse_();
|
s2 = peg$parse_();
|
||||||
s3 = peg$parseidentifier();
|
s3 = peg$parseidentifier();
|
||||||
if (s3 !== peg$FAILED) {
|
if (s3 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f15(s3);
|
s0 = peg$f18(s3);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
export const ARROW_SNAP_THRESHOLD = 20;
|
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 ROUNTANGLE_RADIUS = 20;
|
||||||
export const MIN_ROUNTANGLE_SIZE = { x: ROUNTANGLE_RADIUS*2, y: ROUNTANGLE_RADIUS*2 };
|
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 { ConcreteState, OrState, Statechart, Transition } from "./ast";
|
||||||
import { findNearestArrow, findNearestRountangleSide, Rountangle, VisualEditorState } from "./editor_types";
|
import { findNearestArrow, findNearestRountangleSide, Rountangle, VisualEditorState } from "./editor_types";
|
||||||
import { isEntirelyWithin } from "./geometry";
|
import { isEntirelyWithin, transformLine } from "./geometry";
|
||||||
import { TransitionLabel } from "./label_ast";
|
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][]] {
|
export function parseStatechart(state: VisualEditorState): [Statechart, [string,string][]] {
|
||||||
const errorShapes: [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
|
// step 3: figure out labels
|
||||||
|
|
||||||
for (const text of state.texts) {
|
for (const text of state.texts) {
|
||||||
|
|
@ -125,11 +131,43 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
try {
|
try {
|
||||||
transitionLabel = parseLabel(text.text); // may throw
|
transitionLabel = parseLabel(text.text); // may throw
|
||||||
belongsToTransition.label.push(transitionLabel);
|
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) {
|
catch (e) {
|
||||||
console.log({e});
|
if (e instanceof SyntaxError) {
|
||||||
|
belongsToTransition.label.push(null);
|
||||||
errorShapes.push([text.uid, e]);
|
errorShapes.push([text.uid, e]);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -146,5 +184,32 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
return [{
|
return [{
|
||||||
root,
|
root,
|
||||||
transitions,
|
transitions,
|
||||||
|
variables,
|
||||||
|
inputEvents,
|
||||||
|
internalEvents,
|
||||||
|
outputEvents,
|
||||||
}, errorShapes];
|
}, 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 {
|
eventTrigger = event:identifier {
|
||||||
return {kind: "event", event};
|
return {kind: "event", event};
|
||||||
|
|
@ -16,6 +16,15 @@ afterTrigger = "after" _ dur:durationMs {
|
||||||
return {kind: "after", durationMs: dur};
|
return {kind: "after", durationMs: dur};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entryTrigger = "entry" {
|
||||||
|
return {kind: "entry"};
|
||||||
|
}
|
||||||
|
|
||||||
|
exitTrigger = "exit" {
|
||||||
|
return {kind: "exit"};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
durationMs = num:number _ u:timeUnit {
|
durationMs = num:number _ u:timeUnit {
|
||||||
return num * (u === "s" ? 1000 : 1);
|
return num * (u === "s" ? 1000 : 1);
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +55,19 @@ number = [0-9]+ {
|
||||||
_ "whitespace"
|
_ "whitespace"
|
||||||
= [ \t\n\r]*
|
= [ \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)? {
|
sum = prod:product rest:((_ ("+" / "-") _) sum)? {
|
||||||
if (rest === null) {
|
if (rest === null) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue