cleanup code a bit
This commit is contained in:
parent
5e7b944978
commit
b14b9e205c
11 changed files with 327 additions and 155 deletions
11
src/App/AST.css
Normal file
11
src/App/AST.css
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
details.active {
|
||||||
|
background-color: rgba(255, 140, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
border: 1px black solid;
|
||||||
|
/* border-radius: 5px; */
|
||||||
|
background-color: white;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import { ConcreteState, stateDescription, Transition } from "../statecharts/abstract_syntax";
|
import { ConcreteState, stateDescription, Transition } from "../statecharts/abstract_syntax";
|
||||||
import { Action, Expression } from "../statecharts/label_ast";
|
import { Action, Expression } from "../statecharts/label_ast";
|
||||||
|
import { RT_Statechart } from "../statecharts/runtime_types";
|
||||||
|
|
||||||
|
import "./AST.css";
|
||||||
|
|
||||||
export function ShowTransition(props: {transition: Transition}) {
|
export function ShowTransition(props: {transition: Transition}) {
|
||||||
return <>➔ {stateDescription(props.transition.tgt)}</>;
|
return <>➝ {stateDescription(props.transition.tgt)}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ShowExpr(props: {expr: Expression}) {
|
export function ShowExpr(props: {expr: Expression}) {
|
||||||
|
|
@ -29,11 +32,11 @@ export function ShowAction(props: {action: Action}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AST(props: {root: ConcreteState, transitions: Map<string, Transition[]>}) {
|
export function AST(props: {root: ConcreteState, transitions: Map<string, Transition[]>, rt: RT_Statechart | undefined}) {
|
||||||
const description = stateDescription(props.root);
|
const description = stateDescription(props.root);
|
||||||
const outgoing = props.transitions.get(props.root.uid) || [];
|
const outgoing = props.transitions.get(props.root.uid) || [];
|
||||||
|
|
||||||
return <details open={true}>
|
return <details open={true} className={props.rt?.mode.has(props.root.uid) ? "active" : ""}>
|
||||||
<summary>{props.root.kind}: {description}</summary>
|
<summary>{props.root.kind}: {description}</summary>
|
||||||
|
|
||||||
{props.root.entryActions.length>0 &&
|
{props.root.entryActions.length>0 &&
|
||||||
|
|
@ -48,7 +51,7 @@ export function AST(props: {root: ConcreteState, transitions: Map<string, Transi
|
||||||
}
|
}
|
||||||
{props.root.children.length>0 &&
|
{props.root.children.length>0 &&
|
||||||
props.root.children.map(child =>
|
props.root.children.map(child =>
|
||||||
<AST root={child} transitions={props.transitions} />
|
<AST root={child} transitions={props.transitions} rt={props.rt} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{outgoing.length>0 &&
|
{outgoing.length>0 &&
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,11 @@ import { Box, Stack } from "@mui/material";
|
||||||
import { TopPanel } from "./TopPanel";
|
import { TopPanel } from "./TopPanel";
|
||||||
import { RTHistory } from "./RTHistory";
|
import { RTHistory } from "./RTHistory";
|
||||||
import { AST } from "./AST";
|
import { AST } from "./AST";
|
||||||
|
import { TraceableError } from "../statecharts/parser";
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
const [ast, setAST] = useState<Statechart>(emptyStatechart);
|
||||||
const [errors, setErrors] = useState<[string,string][]>([]);
|
const [errors, setErrors] = useState<TraceableError[]>([]);
|
||||||
|
|
||||||
const [rt, setRT] = useState<BigStep[]>([]);
|
const [rt, setRT] = useState<BigStep[]>([]);
|
||||||
const [rtIdx, setRTIdx] = useState<number|undefined>();
|
const [rtIdx, setRTIdx] = useState<number|undefined>();
|
||||||
|
|
@ -40,7 +41,7 @@ export function App() {
|
||||||
function onRaise(inputEvent: string) {
|
function onRaise(inputEvent: string) {
|
||||||
if (rt.length>0 && rtIdx!==undefined && ast.inputEvents.has(inputEvent)) {
|
if (rt.length>0 && rtIdx!==undefined && ast.inputEvents.has(inputEvent)) {
|
||||||
const simtime = getSimTime(time, performance.now());
|
const simtime = getSimTime(time, performance.now());
|
||||||
const nextConfig = handleInputEvent(simtime, inputEvent, ast, rt[rtIdx]!);
|
const nextConfig = handleInputEvent(simtime, {kind: "input", name: inputEvent}, ast, rt[rtIdx]!);
|
||||||
appendNewConfig(inputEvent, simtime, nextConfig);
|
appendNewConfig(inputEvent, simtime, nextConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +109,7 @@ export function App() {
|
||||||
paddingRight: 1,
|
paddingRight: 1,
|
||||||
paddingLeft: 1,
|
paddingLeft: 1,
|
||||||
}}>
|
}}>
|
||||||
<AST {...ast}/>
|
<AST {...{...ast, rt: rt.at(rtIdx!)}}/>
|
||||||
<hr/>
|
<hr/>
|
||||||
<RTHistory {...{ast, rt, rtIdx, setTime, setRTIdx}}/>
|
<RTHistory {...{ast, rt, rtIdx, setTime, setRTIdx}}/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.svgCanvas.active {
|
||||||
|
background-color: rgb(255, 140, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
text, text.highlight {
|
text, text.highlight {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
/* text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; */
|
/* text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; */
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
|
import * as lz4 from "@nick/lz4";
|
||||||
import { Dispatch, MouseEventHandler, SetStateAction, useEffect, useRef, useState } from "react";
|
import { Dispatch, MouseEventHandler, SetStateAction, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { Statechart } from "../statecharts/abstract_syntax";
|
||||||
|
import { Arrow, ArrowPart, Rountangle, RountanglePart, VisualEditorState, emptyState, findNearestArrow, findNearestRountangleSide, findRountangle } from "../statecharts/concrete_syntax";
|
||||||
|
import { parseStatechart, TraceableError } from "../statecharts/parser";
|
||||||
|
import { BigStep } from "../statecharts/runtime_types";
|
||||||
import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, euclideanDistance, 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 { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
|
||||||
|
import { getBBoxInSvgCoords } from "./svg_helper";
|
||||||
|
|
||||||
import "./VisualEditor.css";
|
import "./VisualEditor.css";
|
||||||
|
|
||||||
import { getBBoxInSvgCoords } from "./svg_helper";
|
|
||||||
import { VisualEditorState, Rountangle, emptyState, Arrow, ArrowPart, RountanglePart, findNearestRountangleSide, findNearestArrow, Text, findRountangle } from "../statecharts/concrete_syntax";
|
|
||||||
import { parseStatechart } from "../statecharts/parser";
|
|
||||||
import { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
|
|
||||||
|
|
||||||
import * as lz4 from "@nick/lz4";
|
|
||||||
import { BigStep, RT_Statechart } from "../statecharts/runtime_types";
|
|
||||||
import { Statechart } from "../statecharts/abstract_syntax";
|
|
||||||
|
|
||||||
|
|
||||||
type DraggingState = {
|
type DraggingState = {
|
||||||
lastMousePos: Vec2D;
|
lastMousePos: Vec2D;
|
||||||
|
|
@ -52,8 +51,8 @@ export const sides: [RountanglePart, (r:Rect2D)=>Line2D][] = [
|
||||||
type VisualEditorProps = {
|
type VisualEditorProps = {
|
||||||
setAST: Dispatch<SetStateAction<Statechart>>,
|
setAST: Dispatch<SetStateAction<Statechart>>,
|
||||||
rt: BigStep|undefined,
|
rt: BigStep|undefined,
|
||||||
errors: [string,string][],
|
errors: TraceableError[],
|
||||||
setErrors: Dispatch<SetStateAction<[string,string][]>>,
|
setErrors: Dispatch<SetStateAction<TraceableError[]>>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps) {
|
export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps) {
|
||||||
|
|
@ -204,6 +203,7 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
nextID: state.nextID+1,
|
nextID: state.nextID+1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw new Error("unreachable"); // shut up typescript
|
||||||
});
|
});
|
||||||
setDragging({
|
setDragging({
|
||||||
lastMousePos: currentPointer,
|
lastMousePos: currentPointer,
|
||||||
|
|
@ -221,13 +221,13 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
// if the mouse button is pressed outside of the current selection, we reset the selection to whatever shape the mouse is on
|
// if the mouse button is pressed outside of the current selection, we reset the selection to whatever shape the mouse is on
|
||||||
let allPartsInSelection = true;
|
let allPartsInSelection = true;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
if (!(selection.find(s => s.uid === uid)?.parts || []).includes(part)) {
|
if (!(selection.find(s => s.uid === uid)?.parts || [] as string[]).includes(part)) {
|
||||||
allPartsInSelection = false;
|
allPartsInSelection = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!allPartsInSelection) {
|
if (!allPartsInSelection) {
|
||||||
setSelection([{uid, parts}]);
|
setSelection([{uid, parts}] as Selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// start dragging
|
// start dragging
|
||||||
|
|
@ -513,10 +513,10 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
|
|
||||||
const active = rt?.mode || new Set();
|
const active = rt?.mode || new Set();
|
||||||
|
|
||||||
const rootErrors = errors.filter(([uid]) => uid === "root").map(err=>err[1]);
|
const rootErrors = errors.filter(({shapeUid}) => shapeUid === "root").map(({message}) => message);
|
||||||
|
|
||||||
return <svg width="4000px" height="4000px"
|
return <svg width="4000px" height="4000px"
|
||||||
className="svgCanvas"
|
className={"svgCanvas"+(active.has("root")?" active":"")}
|
||||||
onMouseDown={onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
onContextMenu={e => e.preventDefault()}
|
onContextMenu={e => e.preventDefault()}
|
||||||
ref={refSVG}
|
ref={refSVG}
|
||||||
|
|
@ -540,22 +540,26 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
key={rountangle.uid}
|
key={rountangle.uid}
|
||||||
rountangle={rountangle}
|
rountangle={rountangle}
|
||||||
selected={selection.find(r => r.uid === rountangle.uid)?.parts || []}
|
selected={selection.find(r => r.uid === rountangle.uid)?.parts || []}
|
||||||
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[])]}
|
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[]) as RountanglePart[]]}
|
||||||
errors={errors.filter(([uid,msg])=>uid===rountangle.uid).map(err=>err[1])}
|
errors={errors
|
||||||
|
.filter(({shapeUid}) => shapeUid === rountangle.uid)
|
||||||
|
.map(({message}) => message)}
|
||||||
active={active.has(rountangle.uid)}
|
active={active.has(rountangle.uid)}
|
||||||
/>)}
|
/>)}
|
||||||
|
|
||||||
{state.arrows.map(arrow => {
|
{state.arrows.map(arrow => {
|
||||||
const sides = arrow2SideMap.get(arrow.uid);
|
const sides = arrow2SideMap.get(arrow.uid);
|
||||||
let arc = "no" as ArcDirection;
|
let arc = "no" as ArcDirection;
|
||||||
if (sides && sides[0]?.uid === sides[1]?.uid && sides[0].uid !== undefined) {
|
if (sides && sides[0]?.uid === sides[1]?.uid && sides[0]!.uid !== undefined) {
|
||||||
arc = arcDirection(sides[0]?.part, sides[1]?.part);
|
arc = arcDirection(sides[0]!.part, sides[1]!.part);
|
||||||
}
|
}
|
||||||
return <ArrowSVG
|
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(({shapeUid}) => shapeUid === arrow.uid)
|
||||||
|
.map(({message}) => message)}
|
||||||
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
highlight={arrowsToHighlight.hasOwnProperty(arrow.uid)}
|
||||||
arc={arc}
|
arc={arc}
|
||||||
/>;
|
/>;
|
||||||
|
|
@ -563,7 +567,7 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{state.texts.map(txt => {
|
{state.texts.map(txt => {
|
||||||
const err = errors.find(([uid]) => txt.uid === uid)?.[1];
|
const err = errors.find(({shapeUid}) => txt.uid === shapeUid);
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
"data-uid": txt.uid,
|
"data-uid": txt.uid,
|
||||||
"data-parts": "text",
|
"data-parts": "text",
|
||||||
|
|
@ -574,7 +578,7 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
}
|
}
|
||||||
let textNode;
|
let textNode;
|
||||||
if (err) {
|
if (err) {
|
||||||
const {start,end} = err.location;
|
const {start,end} = err.data;
|
||||||
textNode = <><text {...commonProps}>
|
textNode = <><text {...commonProps}>
|
||||||
{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">
|
||||||
|
|
@ -620,7 +624,7 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
|
|
||||||
{selectingState && <Selecting {...selectingState} />}
|
{selectingState && <Selecting {...selectingState} />}
|
||||||
|
|
||||||
{showHelp ? <>
|
{/* {showHelp ? <>
|
||||||
<text x={5} y={20}>
|
<text x={5} y={20}>
|
||||||
Left mouse button: Select/Drag.
|
Left mouse button: Select/Drag.
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -641,7 +645,7 @@ export function VisualEditor({setAST, rt, errors, setErrors}: VisualEditorProps)
|
||||||
<text x={5} y={140}>
|
<text x={5} y={140}>
|
||||||
[H] Show/hide this help.
|
[H] Show/hide this help.
|
||||||
</text>
|
</text>
|
||||||
</> : <text x={5} y={20}>[H] To show help.</text>}
|
</> : <text x={5} y={20}>[H] To show help.</text>} */}
|
||||||
|
|
||||||
</svg>;
|
</svg>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { evalExpr } from "./actionlang_interpreter";
|
import { evalExpr } from "./actionlang_interpreter";
|
||||||
import { computeArena, ConcreteState, getDescendants, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./abstract_syntax";
|
import { computeArena, ConcreteState, getDescendants, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./abstract_syntax";
|
||||||
import { Action } from "./label_ast";
|
import { Action, EventTrigger } from "./label_ast";
|
||||||
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput, TimerElapseEvent, Timers } from "./runtime_types";
|
import { Environment, RaisedEvents, Mode, RT_Statechart, initialRaised, BigStepOutput, Timers, RT_Event } from "./runtime_types";
|
||||||
|
|
||||||
export function initialize(ast: Statechart): BigStepOutput {
|
export function initialize(ast: Statechart): BigStepOutput {
|
||||||
let {enteredStates, environment, ...raised} = enterDefault(0, ast.root, {
|
let {enteredStates, environment, ...raised} = enterDefault(0, ast.root, {
|
||||||
|
|
@ -27,7 +27,7 @@ export function entryActions(simtime: number, state: ConcreteState, actionScope:
|
||||||
const timers: Timers = [...(environment.get("_timers") || [])];
|
const timers: Timers = [...(environment.get("_timers") || [])];
|
||||||
for (const timeOffset of state.timers) {
|
for (const timeOffset of state.timers) {
|
||||||
const futureSimTime = simtime + timeOffset; // point in simtime when after-trigger becomes enabled
|
const futureSimTime = simtime + timeOffset; // point in simtime when after-trigger becomes enabled
|
||||||
timers.push([futureSimTime, {state: state.uid, timeDurMs: timeOffset}]);
|
timers.push([futureSimTime, {kind: "timer", state: state.uid, timeDurMs: timeOffset}]);
|
||||||
}
|
}
|
||||||
timers.sort((a,b) => a[0] - b[0]); // smallest futureSimTime comes first
|
timers.sort((a,b) => a[0] - b[0]); // smallest futureSimTime comes first
|
||||||
environment.set("_timers", timers);
|
environment.set("_timers", timers);
|
||||||
|
|
@ -167,40 +167,46 @@ export function execAction(action: Action, rt: ActionScope): ActionScope {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (action.kind === "raise") {
|
else if (action.kind === "raise") {
|
||||||
|
const raisedEvent = {
|
||||||
|
name: action.event,
|
||||||
|
param: action.param && evalExpr(action.param, rt.environment),
|
||||||
|
};
|
||||||
if (action.event.startsWith('_')) {
|
if (action.event.startsWith('_')) {
|
||||||
// append to internal events
|
// append to internal events
|
||||||
return {
|
return {
|
||||||
...rt,
|
...rt,
|
||||||
internalEvents: [...rt.internalEvents, action.event],
|
internalEvents: [...rt.internalEvents, raisedEvent],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// append to output events
|
// append to output events
|
||||||
return {
|
return {
|
||||||
...rt,
|
...rt,
|
||||||
outputEvents: [...rt.outputEvents, action.event],
|
outputEvents: [...rt.outputEvents, raisedEvent],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("should never reach here");
|
throw new Error("should never reach here");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleEvent(simtime: number, event: string | TimerElapseEvent, statechart: Statechart, activeParent: ConcreteState, {environment, mode, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
export function handleEvent(simtime: number, event: RT_Event, statechart: Statechart, activeParent: ConcreteState, {environment, mode, ...raised}: RT_Statechart & RaisedEvents): RT_Statechart & RaisedEvents {
|
||||||
const arenasFired = new Set<OrState>();
|
const arenasFired = new Set<OrState>();
|
||||||
for (const state of activeParent.children) {
|
for (const state of activeParent.children) {
|
||||||
if (mode.has(state.uid)) {
|
if (mode.has(state.uid)) {
|
||||||
const outgoing = statechart.transitions.get(state.uid) || [];
|
const outgoing = statechart.transitions.get(state.uid) || [];
|
||||||
let triggered;
|
let triggered;
|
||||||
if (typeof event === 'string') {
|
if (event.kind === "input") {
|
||||||
|
// get transitions triggered by event
|
||||||
triggered = outgoing.filter(transition => {
|
triggered = outgoing.filter(transition => {
|
||||||
const trigger = transition.label[0].trigger;
|
const trigger = transition.label[0].trigger;
|
||||||
if (trigger.kind === "event") {
|
if (trigger.kind === "event") {
|
||||||
return trigger.event === event;
|
return trigger.event === event.name;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// get transitions triggered by timeout
|
||||||
triggered = outgoing.filter(transition => {
|
triggered = outgoing.filter(transition => {
|
||||||
const trigger = transition.label[0].trigger;
|
const trigger = transition.label[0].trigger;
|
||||||
if (trigger.kind === "after") {
|
if (trigger.kind === "after") {
|
||||||
|
|
@ -209,6 +215,7 @@ export function handleEvent(simtime: number, event: string | TimerElapseEvent, s
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// eval guard
|
||||||
const enabled = triggered.filter(transition =>
|
const enabled = triggered.filter(transition =>
|
||||||
evalExpr(transition.label[0].guard, environment)
|
evalExpr(transition.label[0].guard, environment)
|
||||||
);
|
);
|
||||||
|
|
@ -227,7 +234,24 @@ export function handleEvent(simtime: number, event: string | TimerElapseEvent, s
|
||||||
}
|
}
|
||||||
if (!overlapping) {
|
if (!overlapping) {
|
||||||
console.log('^ firing');
|
console.log('^ firing');
|
||||||
|
let oldValue;
|
||||||
|
if (event.kind === "input" && event.param !== undefined) {
|
||||||
|
// input events may have a parameter
|
||||||
|
// *temporarily* add event to environment (dirty!)
|
||||||
|
oldValue = environment.get(event.param.name);
|
||||||
|
environment = new Map([
|
||||||
|
...environment,
|
||||||
|
[(t.label[0].trigger as EventTrigger).paramName as string, event.param.value],
|
||||||
|
]);
|
||||||
|
}
|
||||||
({mode, environment, ...raised} = fireTransition(simtime, t, arena, srcPath, tgtPath, {mode, environment, ...raised}));
|
({mode, environment, ...raised} = fireTransition(simtime, t, arena, srcPath, tgtPath, {mode, environment, ...raised}));
|
||||||
|
if (event.kind === "input" && event.param) {
|
||||||
|
// restore original value of variable that had same name as input parameter
|
||||||
|
environment = new Map([
|
||||||
|
...environment,
|
||||||
|
[(t.label[0].trigger as EventTrigger).paramName as string, oldValue],
|
||||||
|
]);
|
||||||
|
}
|
||||||
arenasFired.add(arena);
|
arenasFired.add(arena);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -243,7 +267,7 @@ export function handleEvent(simtime: number, event: string | TimerElapseEvent, s
|
||||||
return {environment, mode, ...raised};
|
return {environment, mode, ...raised};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleInputEvent(simtime: number, event: string, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStepOutput {
|
export function handleInputEvent(simtime: number, event: RT_Event, statechart: Statechart, {mode, environment}: {mode: Mode, environment: Environment}): BigStepOutput {
|
||||||
let raised = initialRaised;
|
let raised = initialRaised;
|
||||||
|
|
||||||
({mode, environment, ...raised} = handleEvent(simtime, event, statechart, statechart.root, {mode, environment, ...raised}));
|
({mode, environment, ...raised} = handleEvent(simtime, event, statechart, statechart.root, {mode, environment, ...raised}));
|
||||||
|
|
@ -254,7 +278,9 @@ export function handleInputEvent(simtime: number, event: string, statechart: Sta
|
||||||
export function handleInternalEvents(simtime: number, statechart: Statechart, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
|
export function handleInternalEvents(simtime: number, statechart: Statechart, {mode, environment, ...raised}: RT_Statechart & RaisedEvents): BigStepOutput {
|
||||||
while (raised.internalEvents.length > 0) {
|
while (raised.internalEvents.length > 0) {
|
||||||
const [internalEvent, ...rest] = raised.internalEvents;
|
const [internalEvent, ...rest] = raised.internalEvents;
|
||||||
({mode, environment, ...raised} = handleEvent(simtime, internalEvent, statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
|
({mode, environment, ...raised} = handleEvent(simtime,
|
||||||
|
{kind: "input", ...internalEvent}, // internal event becomes input event
|
||||||
|
statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents: raised.outputEvents}));
|
||||||
}
|
}
|
||||||
return {mode, environment, outputEvents: raised.outputEvents};
|
return {mode, environment, outputEvents: raised.outputEvents};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export type Trigger = EventTrigger | AfterTrigger | EntryTrigger | ExitTrigger;
|
||||||
export type EventTrigger = {
|
export type EventTrigger = {
|
||||||
kind: "event";
|
kind: "event";
|
||||||
event: string;
|
event: string;
|
||||||
|
paramName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AfterTrigger = {
|
export type AfterTrigger = {
|
||||||
|
|
@ -45,6 +46,7 @@ export type Assignment = {
|
||||||
export type RaiseEvent = {
|
export type RaiseEvent = {
|
||||||
kind: "raise";
|
kind: "raise";
|
||||||
event: string;
|
event: string;
|
||||||
|
param?: Expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,19 +167,19 @@ function peg$parse(input, options) {
|
||||||
const peg$c0 = "[";
|
const peg$c0 = "[";
|
||||||
const peg$c1 = "]";
|
const peg$c1 = "]";
|
||||||
const peg$c2 = "/";
|
const peg$c2 = "/";
|
||||||
const peg$c3 = "after";
|
const peg$c3 = "(";
|
||||||
const peg$c4 = "entry";
|
const peg$c4 = ")";
|
||||||
const peg$c5 = "exit";
|
const peg$c5 = "after";
|
||||||
const peg$c6 = "ms";
|
const peg$c6 = "entry";
|
||||||
const peg$c7 = "s";
|
const peg$c7 = "exit";
|
||||||
const peg$c8 = ";";
|
const peg$c8 = "ms";
|
||||||
const peg$c9 = "=";
|
const peg$c9 = "s";
|
||||||
const peg$c10 = "==";
|
const peg$c10 = ";";
|
||||||
const peg$c11 = "!=";
|
const peg$c11 = "=";
|
||||||
const peg$c12 = "<=";
|
const peg$c12 = "==";
|
||||||
const peg$c13 = ">=";
|
const peg$c13 = "!=";
|
||||||
const peg$c14 = "(";
|
const peg$c14 = "<=";
|
||||||
const peg$c15 = ")";
|
const peg$c15 = ">=";
|
||||||
const peg$c16 = "true";
|
const peg$c16 = "true";
|
||||||
const peg$c17 = "false";
|
const peg$c17 = "false";
|
||||||
const peg$c18 = "^";
|
const peg$c18 = "^";
|
||||||
|
|
@ -196,24 +196,24 @@ function peg$parse(input, options) {
|
||||||
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("(", false);
|
||||||
const peg$e4 = peg$literalExpectation("entry", false);
|
const peg$e4 = peg$literalExpectation(")", false);
|
||||||
const peg$e5 = peg$literalExpectation("exit", false);
|
const peg$e5 = peg$literalExpectation("after", false);
|
||||||
const peg$e6 = peg$literalExpectation("ms", false);
|
const peg$e6 = peg$literalExpectation("entry", false);
|
||||||
const peg$e7 = peg$literalExpectation("s", false);
|
const peg$e7 = peg$literalExpectation("exit", false);
|
||||||
const peg$e8 = peg$literalExpectation(";", false);
|
const peg$e8 = peg$literalExpectation("ms", false);
|
||||||
const peg$e9 = peg$literalExpectation("=", false);
|
const peg$e9 = peg$literalExpectation("s", false);
|
||||||
const peg$e10 = peg$classExpectation([["0", "9"], ["A", "Z"], "_", ["a", "z"]], false, false, false);
|
const peg$e10 = peg$literalExpectation(";", false);
|
||||||
const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false);
|
const peg$e11 = peg$literalExpectation("=", false);
|
||||||
const peg$e12 = peg$literalExpectation("==", false);
|
const peg$e12 = peg$classExpectation([["0", "9"], ["A", "Z"], "_", ["a", "z"]], false, false, false);
|
||||||
const peg$e13 = peg$literalExpectation("!=", false);
|
const peg$e13 = peg$classExpectation([["0", "9"]], false, false, false);
|
||||||
const peg$e14 = peg$classExpectation(["<", ">"], false, false, false);
|
const peg$e14 = peg$literalExpectation("==", false);
|
||||||
const peg$e15 = peg$literalExpectation("<=", false);
|
const peg$e15 = peg$literalExpectation("!=", false);
|
||||||
const peg$e16 = peg$literalExpectation(">=", false);
|
const peg$e16 = peg$classExpectation(["<", ">"], false, false, false);
|
||||||
const peg$e17 = peg$classExpectation(["+", "-"], false, false, false);
|
const peg$e17 = peg$literalExpectation("<=", false);
|
||||||
const peg$e18 = peg$classExpectation(["*", "/"], false, false, false);
|
const peg$e18 = peg$literalExpectation(">=", false);
|
||||||
const peg$e19 = peg$literalExpectation("(", false);
|
const peg$e19 = peg$classExpectation(["+", "-"], false, false, false);
|
||||||
const peg$e20 = peg$literalExpectation(")", false);
|
const peg$e20 = peg$classExpectation(["*", "/"], false, false, false);
|
||||||
const peg$e21 = peg$literalExpectation("true", false);
|
const peg$e21 = peg$literalExpectation("true", false);
|
||||||
const peg$e22 = peg$literalExpectation("false", false);
|
const peg$e22 = peg$literalExpectation("false", false);
|
||||||
const peg$e23 = peg$literalExpectation("^", false);
|
const peg$e23 = peg$literalExpectation("^", false);
|
||||||
|
|
@ -230,8 +230,8 @@ function peg$parse(input, options) {
|
||||||
actions: actions ? actions[2] : [],
|
actions: actions ? actions[2] : [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function peg$f1(event) {
|
function peg$f1(event, param) {
|
||||||
return {kind: "event", event};
|
return {kind: "event", event, param: param ? param[1] : undefined};
|
||||||
}
|
}
|
||||||
function peg$f2(dur) {
|
function peg$f2(dur) {
|
||||||
return {kind: "after", durationMs: dur};
|
return {kind: "after", durationMs: dur};
|
||||||
|
|
@ -305,8 +305,8 @@ function peg$parse(input, options) {
|
||||||
function peg$f17() {
|
function peg$f17() {
|
||||||
return text() === "true";
|
return text() === "true";
|
||||||
}
|
}
|
||||||
function peg$f18(event) {
|
function peg$f18(event, param) {
|
||||||
return {kind: "raise", event};
|
return {kind: "raise", event, param: param ? param[1] : undefined};
|
||||||
}
|
}
|
||||||
function peg$f19() { return null; }
|
function peg$f19() { return null; }
|
||||||
function peg$f20(text) {
|
function peg$f20(text) {
|
||||||
|
|
@ -597,15 +597,53 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function peg$parseeventTrigger() {
|
function peg$parseeventTrigger() {
|
||||||
let s0, s1;
|
let s0, s1, s2, s3, s4, s5;
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
s1 = peg$parseidentifier();
|
s1 = peg$parseidentifier();
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
|
s2 = peg$currPos;
|
||||||
|
if (input.charCodeAt(peg$currPos) === 40) {
|
||||||
|
s3 = peg$c3;
|
||||||
|
peg$currPos++;
|
||||||
|
} else {
|
||||||
|
s3 = peg$FAILED;
|
||||||
|
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
||||||
|
}
|
||||||
|
if (s3 !== peg$FAILED) {
|
||||||
|
s4 = peg$parseidentifier();
|
||||||
|
if (s4 !== peg$FAILED) {
|
||||||
|
if (input.charCodeAt(peg$currPos) === 41) {
|
||||||
|
s5 = peg$c4;
|
||||||
|
peg$currPos++;
|
||||||
|
} else {
|
||||||
|
s5 = peg$FAILED;
|
||||||
|
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
||||||
|
}
|
||||||
|
if (s5 !== peg$FAILED) {
|
||||||
|
s3 = [s3, s4, s5];
|
||||||
|
s2 = s3;
|
||||||
|
} else {
|
||||||
|
peg$currPos = s2;
|
||||||
|
s2 = peg$FAILED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peg$currPos = s2;
|
||||||
|
s2 = peg$FAILED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peg$currPos = s2;
|
||||||
|
s2 = peg$FAILED;
|
||||||
|
}
|
||||||
|
if (s2 === peg$FAILED) {
|
||||||
|
s2 = null;
|
||||||
|
}
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s1 = peg$f1(s1);
|
s0 = peg$f1(s1, s2);
|
||||||
|
} else {
|
||||||
|
peg$currPos = s0;
|
||||||
|
s0 = peg$FAILED;
|
||||||
}
|
}
|
||||||
s0 = s1;
|
|
||||||
|
|
||||||
return s0;
|
return s0;
|
||||||
}
|
}
|
||||||
|
|
@ -614,12 +652,12 @@ function peg$parse(input, options) {
|
||||||
let s0, s1, s2, s3;
|
let s0, s1, s2, s3;
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.substr(peg$currPos, 5) === peg$c3) {
|
if (input.substr(peg$currPos, 5) === peg$c5) {
|
||||||
s1 = peg$c3;
|
s1 = peg$c5;
|
||||||
peg$currPos += 5;
|
peg$currPos += 5;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
if (peg$silentFails === 0) { peg$fail(peg$e5); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
s2 = peg$parse_();
|
s2 = peg$parse_();
|
||||||
|
|
@ -643,12 +681,12 @@ function peg$parse(input, options) {
|
||||||
let s0, s1;
|
let s0, s1;
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.substr(peg$currPos, 5) === peg$c4) {
|
if (input.substr(peg$currPos, 5) === peg$c6) {
|
||||||
s1 = peg$c4;
|
s1 = peg$c6;
|
||||||
peg$currPos += 5;
|
peg$currPos += 5;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
|
|
@ -663,12 +701,12 @@ function peg$parse(input, options) {
|
||||||
let s0, s1;
|
let s0, s1;
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.substr(peg$currPos, 4) === peg$c5) {
|
if (input.substr(peg$currPos, 4) === peg$c7) {
|
||||||
s1 = peg$c5;
|
s1 = peg$c7;
|
||||||
peg$currPos += 4;
|
peg$currPos += 4;
|
||||||
} 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;
|
||||||
|
|
@ -705,21 +743,21 @@ function peg$parse(input, options) {
|
||||||
function peg$parsetimeUnit() {
|
function peg$parsetimeUnit() {
|
||||||
let s0, s1;
|
let s0, s1;
|
||||||
|
|
||||||
if (input.substr(peg$currPos, 2) === peg$c6) {
|
if (input.substr(peg$currPos, 2) === peg$c8) {
|
||||||
s0 = peg$c6;
|
s0 = peg$c8;
|
||||||
peg$currPos += 2;
|
peg$currPos += 2;
|
||||||
} else {
|
} else {
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e6); }
|
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
||||||
}
|
}
|
||||||
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$c7;
|
s1 = peg$c9;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e7); }
|
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
|
|
@ -741,11 +779,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$c8;
|
s5 = peg$c10;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -766,11 +804,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$c8;
|
s5 = peg$c10;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -789,11 +827,11 @@ 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$c8;
|
s4 = peg$c10;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s4 = peg$FAILED;
|
s4 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e8); }
|
if (peg$silentFails === 0) { peg$fail(peg$e10); }
|
||||||
}
|
}
|
||||||
if (s4 === peg$FAILED) {
|
if (s4 === peg$FAILED) {
|
||||||
s4 = null;
|
s4 = null;
|
||||||
|
|
@ -827,11 +865,11 @@ 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$c9;
|
s3 = peg$c11;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s3 = peg$FAILED;
|
s3 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
||||||
}
|
}
|
||||||
if (s3 !== peg$FAILED) {
|
if (s3 !== peg$FAILED) {
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
|
|
@ -865,7 +903,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$e10); }
|
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||||
}
|
}
|
||||||
if (s2 !== peg$FAILED) {
|
if (s2 !== peg$FAILED) {
|
||||||
while (s2 !== peg$FAILED) {
|
while (s2 !== peg$FAILED) {
|
||||||
|
|
@ -875,7 +913,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$e10); }
|
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -900,7 +938,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$e11); }
|
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
||||||
}
|
}
|
||||||
if (s2 !== peg$FAILED) {
|
if (s2 !== peg$FAILED) {
|
||||||
while (s2 !== peg$FAILED) {
|
while (s2 !== peg$FAILED) {
|
||||||
|
|
@ -910,7 +948,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$e11); }
|
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -934,20 +972,20 @@ function peg$parse(input, options) {
|
||||||
s2 = peg$currPos;
|
s2 = peg$currPos;
|
||||||
s3 = peg$currPos;
|
s3 = peg$currPos;
|
||||||
s4 = peg$parse_();
|
s4 = peg$parse_();
|
||||||
if (input.substr(peg$currPos, 2) === peg$c10) {
|
if (input.substr(peg$currPos, 2) === peg$c12) {
|
||||||
s5 = peg$c10;
|
s5 = peg$c12;
|
||||||
peg$currPos += 2;
|
peg$currPos += 2;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
if (peg$silentFails === 0) { peg$fail(peg$e14); }
|
||||||
}
|
}
|
||||||
if (s5 === peg$FAILED) {
|
if (s5 === peg$FAILED) {
|
||||||
if (input.substr(peg$currPos, 2) === peg$c11) {
|
if (input.substr(peg$currPos, 2) === peg$c13) {
|
||||||
s5 = peg$c11;
|
s5 = peg$c13;
|
||||||
peg$currPos += 2;
|
peg$currPos += 2;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
||||||
}
|
}
|
||||||
if (s5 === peg$FAILED) {
|
if (s5 === peg$FAILED) {
|
||||||
s5 = input.charAt(peg$currPos);
|
s5 = input.charAt(peg$currPos);
|
||||||
|
|
@ -955,23 +993,23 @@ function peg$parse(input, options) {
|
||||||
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$e16); }
|
||||||
}
|
}
|
||||||
if (s5 === peg$FAILED) {
|
if (s5 === peg$FAILED) {
|
||||||
if (input.substr(peg$currPos, 2) === peg$c12) {
|
if (input.substr(peg$currPos, 2) === peg$c14) {
|
||||||
s5 = peg$c12;
|
s5 = peg$c14;
|
||||||
peg$currPos += 2;
|
peg$currPos += 2;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
if (peg$silentFails === 0) { peg$fail(peg$e17); }
|
||||||
}
|
}
|
||||||
if (s5 === peg$FAILED) {
|
if (s5 === peg$FAILED) {
|
||||||
if (input.substr(peg$currPos, 2) === peg$c13) {
|
if (input.substr(peg$currPos, 2) === peg$c15) {
|
||||||
s5 = peg$c13;
|
s5 = peg$c15;
|
||||||
peg$currPos += 2;
|
peg$currPos += 2;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e16); }
|
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1025,7 +1063,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e17); }
|
if (peg$silentFails === 0) { peg$fail(peg$e19); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -1075,7 +1113,7 @@ function peg$parse(input, options) {
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
if (peg$silentFails === 0) { peg$fail(peg$e20); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
s6 = peg$parse_();
|
s6 = peg$parse_();
|
||||||
|
|
@ -1130,11 +1168,11 @@ 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$c14;
|
s1 = peg$c3;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s1 = peg$FAILED;
|
s1 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e19); }
|
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
||||||
}
|
}
|
||||||
if (s1 !== peg$FAILED) {
|
if (s1 !== peg$FAILED) {
|
||||||
s2 = peg$parse_();
|
s2 = peg$parse_();
|
||||||
|
|
@ -1142,11 +1180,11 @@ function peg$parse(input, options) {
|
||||||
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$c15;
|
s5 = peg$c4;
|
||||||
peg$currPos++;
|
peg$currPos++;
|
||||||
} else {
|
} else {
|
||||||
s5 = peg$FAILED;
|
s5 = peg$FAILED;
|
||||||
if (peg$silentFails === 0) { peg$fail(peg$e20); }
|
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
||||||
}
|
}
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
|
|
@ -1228,7 +1266,7 @@ function peg$parse(input, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function peg$parseraise() {
|
function peg$parseraise() {
|
||||||
let s0, s1, s2, s3;
|
let s0, s1, s2, s3, s4, s5, s6, s7;
|
||||||
|
|
||||||
s0 = peg$currPos;
|
s0 = peg$currPos;
|
||||||
if (input.charCodeAt(peg$currPos) === 94) {
|
if (input.charCodeAt(peg$currPos) === 94) {
|
||||||
|
|
@ -1242,8 +1280,44 @@ function peg$parse(input, options) {
|
||||||
s2 = peg$parse_();
|
s2 = peg$parse_();
|
||||||
s3 = peg$parseidentifier();
|
s3 = peg$parseidentifier();
|
||||||
if (s3 !== peg$FAILED) {
|
if (s3 !== peg$FAILED) {
|
||||||
|
s4 = peg$currPos;
|
||||||
|
if (input.charCodeAt(peg$currPos) === 40) {
|
||||||
|
s5 = peg$c3;
|
||||||
|
peg$currPos++;
|
||||||
|
} else {
|
||||||
|
s5 = peg$FAILED;
|
||||||
|
if (peg$silentFails === 0) { peg$fail(peg$e3); }
|
||||||
|
}
|
||||||
|
if (s5 !== peg$FAILED) {
|
||||||
|
s6 = peg$parsecompare();
|
||||||
|
if (s6 !== peg$FAILED) {
|
||||||
|
if (input.charCodeAt(peg$currPos) === 41) {
|
||||||
|
s7 = peg$c4;
|
||||||
|
peg$currPos++;
|
||||||
|
} else {
|
||||||
|
s7 = peg$FAILED;
|
||||||
|
if (peg$silentFails === 0) { peg$fail(peg$e4); }
|
||||||
|
}
|
||||||
|
if (s7 !== peg$FAILED) {
|
||||||
|
s5 = [s5, s6, s7];
|
||||||
|
s4 = s5;
|
||||||
|
} else {
|
||||||
|
peg$currPos = s4;
|
||||||
|
s4 = peg$FAILED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peg$currPos = s4;
|
||||||
|
s4 = peg$FAILED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peg$currPos = s4;
|
||||||
|
s4 = peg$FAILED;
|
||||||
|
}
|
||||||
|
if (s4 === peg$FAILED) {
|
||||||
|
s4 = null;
|
||||||
|
}
|
||||||
peg$savedPos = s0;
|
peg$savedPos = s0;
|
||||||
s0 = peg$f18(s3);
|
s0 = peg$f18(s3, s4);
|
||||||
} else {
|
} else {
|
||||||
peg$currPos = s0;
|
peg$currPos = s0;
|
||||||
s0 = peg$FAILED;
|
s0 = peg$FAILED;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,14 @@ import { Action, Expression, ParsedText } from "./label_ast";
|
||||||
|
|
||||||
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
import { parse as parseLabel, SyntaxError } from "./label_parser";
|
||||||
|
|
||||||
export function parseStatechart(state: VisualEditorState): [Statechart, [string,string][]] {
|
export type TraceableError = {
|
||||||
const errorShapes: [string, string][] = [];
|
shapeUid: string;
|
||||||
|
message: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseStatechart(state: VisualEditorState): [Statechart, TraceableError[]] {
|
||||||
|
const errors: TraceableError[] = [];
|
||||||
|
|
||||||
// implicitly, the root is always an Or-state
|
// implicitly, the root is always an Or-state
|
||||||
const root: OrState = {
|
const root: OrState = {
|
||||||
|
|
@ -37,6 +43,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
|
|
||||||
// we assume that the rountangles are sorted from big to small:
|
// we assume that the rountangles are sorted from big to small:
|
||||||
for (const rt of state.rountangles) {
|
for (const rt of state.rountangles) {
|
||||||
|
// @ts-ignore
|
||||||
const state: ConcreteState = {
|
const state: ConcreteState = {
|
||||||
kind: rt.kind,
|
kind: rt.kind,
|
||||||
uid: rt.uid,
|
uid: rt.uid,
|
||||||
|
|
@ -45,11 +52,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
entryActions: [],
|
entryActions: [],
|
||||||
exitActions: [],
|
exitActions: [],
|
||||||
timers: [],
|
timers: [],
|
||||||
}
|
};
|
||||||
if (state.kind === "or") {
|
if (state.kind === "or") {
|
||||||
state.initial = [];
|
(state as unknown as OrState).initial = [];
|
||||||
}
|
}
|
||||||
uid2State.set(rt.uid, state);
|
uid2State.set(rt.uid, (state));
|
||||||
|
|
||||||
// iterate in reverse:
|
// iterate in reverse:
|
||||||
for (let i=parentCandidates.length-1; i>=0; i--) {
|
for (let i=parentCandidates.length-1; i>=0; i--) {
|
||||||
|
|
@ -57,7 +64,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
if (candidate.uid === "root" || isEntirelyWithin(rt, candidate)) {
|
if (candidate.uid === "root" || isEntirelyWithin(rt, candidate)) {
|
||||||
// found our parent :)
|
// found our parent :)
|
||||||
const parentState = uid2State.get(candidate.uid)!;
|
const parentState = uid2State.get(candidate.uid)!;
|
||||||
parentState.children.push(state);
|
parentState.children.push(state as unknown as ConcreteState);
|
||||||
parentCandidates.push(rt);
|
parentCandidates.push(rt);
|
||||||
parentLinks.set(rt.uid, candidate.uid);
|
parentLinks.set(rt.uid, candidate.uid);
|
||||||
state.parent = parentState;
|
state.parent = parentState;
|
||||||
|
|
@ -78,7 +85,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
if (!srcUID) {
|
if (!srcUID) {
|
||||||
if (!tgtUID) {
|
if (!tgtUID) {
|
||||||
// dangling edge - todo: display error...
|
// dangling edge - todo: display error...
|
||||||
errorShapes.push([arr.uid, "dangling"]);
|
errors.push({shapeUid: arr.uid, message: "dangling"});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// target but no source, so we treat is as an 'initial' marking
|
// target but no source, so we treat is as an 'initial' marking
|
||||||
|
|
@ -89,13 +96,19 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// and states do not have an 'initial' state - todo: display error...
|
// and states do not have an 'initial' state - todo: display error...
|
||||||
errorShapes.push([arr.uid, "AND-state cannot have an initial state"]);
|
errors.push({
|
||||||
|
shapeUid: arr.uid,
|
||||||
|
message: "AND-state cannot have an initial state",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!tgtUID) {
|
if (!tgtUID) {
|
||||||
errorShapes.push([arr.uid, "no target"]);
|
errors.push({
|
||||||
|
shapeUid: arr.uid,
|
||||||
|
message: "no target",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// add transition
|
// add transition
|
||||||
|
|
@ -116,10 +129,16 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
for (const state of uid2State.values()) {
|
for (const state of uid2State.values()) {
|
||||||
if (state.kind === "or") {
|
if (state.kind === "or") {
|
||||||
if (state.initial.length > 1) {
|
if (state.initial.length > 1) {
|
||||||
errorShapes.push(...state.initial.map(([uid,childState])=>[uid,"multiple initial states"] as [string, string]));
|
errors.push(...state.initial.map(([uid,childState]) => ({
|
||||||
|
shapeUid: uid,
|
||||||
|
message: "multiple initial states",
|
||||||
|
})));
|
||||||
}
|
}
|
||||||
else if (state.initial.length === 0) {
|
else if (state.initial.length === 0) {
|
||||||
errorShapes.push([state.uid, "no initial state"]);
|
errors.push({
|
||||||
|
shapeUid: state.uid,
|
||||||
|
message: "no initial state",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +157,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
parsed = parseLabel(text.text); // may throw
|
parsed = parseLabel(text.text); // may throw
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SyntaxError) {
|
if (e instanceof SyntaxError) {
|
||||||
errorShapes.push([text.uid, e]);
|
errors.push({
|
||||||
|
shapeUid: text.uid,
|
||||||
|
message: e.message,
|
||||||
|
data: e,
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -202,10 +225,11 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
belongsToState.exitActions.push(...parsed.actions);
|
belongsToState.exitActions.push(...parsed.actions);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
errorShapes.push([text.uid, {
|
errors.push({
|
||||||
|
shapeUid: text.uid,
|
||||||
message: "states can only have entry/exit triggers",
|
message: "states can only have entry/exit triggers",
|
||||||
location: {start: {offset: 0}, end: {offset: text.text.length}},
|
data: {start: {offset: 0}, end: {offset: text.text.length}},
|
||||||
}]);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -217,10 +241,16 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
|
|
||||||
for (const transition of uid2Transition.values()) {
|
for (const transition of uid2Transition.values()) {
|
||||||
if (transition.label.length === 0) {
|
if (transition.label.length === 0) {
|
||||||
errorShapes.push([transition.uid, "no label"]);
|
errors.push({
|
||||||
|
shapeUid: transition.uid,
|
||||||
|
message: "no label",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (transition.label.length > 1) {
|
else if (transition.label.length > 1) {
|
||||||
errorShapes.push([transition.uid, "multiple labels"]);
|
errors.push({
|
||||||
|
shapeUid: transition.uid,
|
||||||
|
message: "multiple labels",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +262,7 @@ export function parseStatechart(state: VisualEditorState): [Statechart, [string,
|
||||||
internalEvents,
|
internalEvents,
|
||||||
outputEvents,
|
outputEvents,
|
||||||
uid2State,
|
uid2State,
|
||||||
}, errorShapes];
|
}, errors];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findVariables(expr: Expression): Set<string> {
|
function findVariables(expr: Expression): Set<string> {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,22 @@
|
||||||
export type Timestamp = number; // milliseconds since begin of simulation
|
export type Timestamp = number; // milliseconds since begin of simulation
|
||||||
export type Event = string;
|
|
||||||
|
export type RT_Event = InputEvent | TimerElapseEvent;
|
||||||
|
|
||||||
|
export type InputEvent = {
|
||||||
|
kind: "input",
|
||||||
|
name: string,
|
||||||
|
param?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TimerElapseEvent = {
|
||||||
|
kind: "timer",
|
||||||
|
state: string,
|
||||||
|
timeDurMs: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type Mode = Set<string>; // set of active states
|
export type Mode = Set<string>; // set of active states
|
||||||
|
|
||||||
|
|
||||||
export type Environment = ReadonlyMap<string, any>; // variable name -> value
|
export type Environment = ReadonlyMap<string, any>; // variable name -> value
|
||||||
|
|
||||||
export type RT_Statechart = {
|
export type RT_Statechart = {
|
||||||
|
|
@ -13,7 +26,7 @@ export type RT_Statechart = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BigStepOutput = RT_Statechart & {
|
export type BigStepOutput = RT_Statechart & {
|
||||||
outputEvents: string[],
|
outputEvents: RaisedEvent[],
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BigStep = {
|
export type BigStep = {
|
||||||
|
|
@ -21,10 +34,16 @@ export type BigStep = {
|
||||||
simtime: number,
|
simtime: number,
|
||||||
} & BigStepOutput;
|
} & BigStepOutput;
|
||||||
|
|
||||||
|
// internal or output event
|
||||||
|
export type RaisedEvent = {
|
||||||
|
name: string,
|
||||||
|
param?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type RaisedEvents = {
|
export type RaisedEvents = {
|
||||||
internalEvents: string[];
|
internalEvents: RaisedEvent[];
|
||||||
outputEvents: string[];
|
outputEvents: RaisedEvent[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// export type Timers = Map<string, number>; // transition uid -> timestamp
|
// export type Timers = Map<string, number>; // transition uid -> timestamp
|
||||||
|
|
@ -34,6 +53,4 @@ export const initialRaised: RaisedEvents = {
|
||||||
outputEvents: [],
|
outputEvents: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TimerElapseEvent = { state: string; timeDurMs: number; };
|
|
||||||
export type Timers = [number, TimerElapseEvent][];
|
export type Timers = [number, TimerElapseEvent][];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ tlabel = _ trigger:trigger _ guard:("[" _ guard _ "]")? _ actions:("/" _ actions
|
||||||
|
|
||||||
trigger = afterTrigger / entryTrigger / exitTrigger / eventTrigger
|
trigger = afterTrigger / entryTrigger / exitTrigger / eventTrigger
|
||||||
|
|
||||||
eventTrigger = event:identifier {
|
eventTrigger = event:identifier param:("(" identifier ")")? {
|
||||||
return {kind: "event", event};
|
return {kind: "event", event, param: param ? param[1] : undefined};
|
||||||
}
|
}
|
||||||
|
|
||||||
afterTrigger = "after" _ dur:durationMs {
|
afterTrigger = "after" _ dur:durationMs {
|
||||||
|
|
@ -111,8 +111,8 @@ boolean = ("true" / "false") {
|
||||||
return text() === "true";
|
return text() === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
raise = "^" _ event:identifier {
|
raise = "^" _ event:identifier param:("(" expr ")")? {
|
||||||
return {kind: "raise", event};
|
return {kind: "raise", event, param: param ? param[1] : undefined};
|
||||||
}
|
}
|
||||||
|
|
||||||
_ "whitespace"
|
_ "whitespace"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue