show execution history
This commit is contained in:
parent
a8b522fdd8
commit
d5272e30f3
6 changed files with 90 additions and 42 deletions
|
|
@ -26,6 +26,18 @@
|
||||||
details {
|
details {
|
||||||
padding-left :10;
|
padding-left :10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.runtimeState:hover {
|
||||||
|
/* background-color: rgba(255, 140, 0, 0.2); */
|
||||||
|
background-color: rgba(0,0,255,0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.runtimeState.active {
|
||||||
|
/* background-color: rgba(255, 140, 0, 0.2); */
|
||||||
|
background-color: rgba(0,0,255,0.2);
|
||||||
|
/* border: solid black 3px; */
|
||||||
|
border: solid blue 2px;
|
||||||
|
}
|
||||||
/* details:not(:has(details)) > summary::marker {
|
/* details:not(:has(details)) > summary::marker {
|
||||||
color: white;
|
color: white;
|
||||||
} */
|
} */
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { ConcreteState, emptyStatechart, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
|
import { ConcreteState, emptyStatechart, isAncestorOf, Statechart, stateDescription, Transition } from "../VisualEditor/ast";
|
||||||
import { VisualEditor } from "../VisualEditor/VisualEditor";
|
import { VisualEditor } from "../VisualEditor/VisualEditor";
|
||||||
import { RT_Statechart } from "../VisualEditor/runtime_types";
|
import { Environment, Mode, RT_Statechart } from "../VisualEditor/runtime_types";
|
||||||
import { initialize, handleEvent } from "../VisualEditor/interpreter";
|
import { initialize, handleEvent, handleInputEvent } from "../VisualEditor/interpreter";
|
||||||
import { Action, Expression } from "../VisualEditor/label_ast";
|
import { Action, Expression } from "../VisualEditor/label_ast";
|
||||||
|
|
||||||
import "../index.css";
|
import "../index.css";
|
||||||
|
|
@ -69,7 +69,9 @@ export function AST(props: {root: ConcreteState, transitions: Map<string, Transi
|
||||||
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<[string,string][]>([]);
|
||||||
const [rt, setRT] = useState<RT_Statechart|null>(null);
|
const [rt, setRT] = useState<RT_Statechart[]>([]);
|
||||||
|
const [rtIdx, setRTIdx] = useState<number|null>(null);
|
||||||
|
const [timeMs, setTimeMs] = useState(0);
|
||||||
|
|
||||||
const [paused, setPaused] = useState(true);
|
const [paused, setPaused] = useState(true);
|
||||||
const [timescale, setTimescale] = useState(1);
|
const [timescale, setTimescale] = useState(1);
|
||||||
|
|
@ -77,61 +79,83 @@ export function App() {
|
||||||
function restart() {
|
function restart() {
|
||||||
const rt = initialize(ast);
|
const rt = initialize(ast);
|
||||||
console.log('runtime: ', rt);
|
console.log('runtime: ', rt);
|
||||||
setRT(rt);
|
setRT([rt]);
|
||||||
|
setRTIdx(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
setRT(null);
|
setRT([]);
|
||||||
|
setRTIdx(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function raise(event: string) {
|
function raise(event: string) {
|
||||||
if (rt && ast.inputEvents.has(event)) {
|
console.log(rtIdx);
|
||||||
const nextConfig = handleEvent(event, ast, ast.root, rt);
|
if (rt.length>0 && rtIdx!==null && ast.inputEvents.has(event)) {
|
||||||
setRT(nextConfig);
|
const nextConfig = handleInputEvent(event, ast, rt[rtIdx]!);
|
||||||
// console.log({nextConfigs});
|
setRT([...rt.slice(0, rtIdx+1), nextConfig]);
|
||||||
// if (nextConfigs.length > 0) {
|
setRTIdx(rtIdx+1);
|
||||||
// if (nextConfigs.length > 1) {
|
|
||||||
// console.warn('non-determinism, blindly selecting first next run-time state!');
|
|
||||||
// }
|
|
||||||
// setRT(nextConfigs[0]);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="layoutVertical">
|
return <div className="layoutVertical">
|
||||||
|
<div className="panel">
|
||||||
|
|
||||||
|
</div>
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
<button onClick={restart}>(re)start</button>
|
<button onClick={restart}>(re)start</button>
|
||||||
<select disabled={rt===null} value="raise event..." onChange={e => raise(e.target.value)}>
|
<button onClick={stop} disabled={rt===null}>stop</button>
|
||||||
<option value="">raise event...</option>
|
|
||||||
{[...ast.inputEvents].map(event =>
|
|
||||||
<option value={event}>{event}</option>
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
<button onClick={stop} >stop</button>
|
|
||||||
 
|
 
|
||||||
<input type="radio" name="paused" id="radio-paused" checked={paused} disabled={rt===null} onChange={e => setPaused(e.target.checked)}/>
|
raise
|
||||||
|
{[...ast.inputEvents].map(event => <button disabled={rtIdx===null} onClick={() => raise(event)}>{event}</button>)}
|
||||||
|
 
|
||||||
|
<input type="radio" name="paused" id="radio-paused" checked={paused} disabled={rtIdx===null} onChange={e => setPaused(e.target.checked)}/>
|
||||||
<label htmlFor="radio-paused">paused</label>
|
<label htmlFor="radio-paused">paused</label>
|
||||||
<input type="radio" name="realtime" id="radio-realtime" checked={!paused} disabled={rt===null} onChange={e => setPaused(!e.target.checked)}/>
|
<input type="radio" name="realtime" id="radio-realtime" checked={!paused} disabled={rtIdx===null} onChange={e => setPaused(!e.target.checked)}/>
|
||||||
<label htmlFor="radio-realtime">real-time</label>
|
<label htmlFor="radio-realtime">real-time</label>
|
||||||
 
|
 
|
||||||
<label htmlFor="number-timescale">timescale</label>
|
<label htmlFor="number-timescale">timescale</label>
|
||||||
<input type="number" id="number-timescale" disabled={rt===null} value={timescale} style={{width:40}}/>
|
<input type="number" id="number-timescale" disabled={rtIdx===null} value={timescale} style={{width:40}}/>
|
||||||
|
 
|
||||||
|
time is {timeMs} ms
|
||||||
</div>
|
</div>
|
||||||
<div className="layout">
|
<div className="layout">
|
||||||
<main className="content">
|
<main className="content">
|
||||||
<VisualEditor {...{ast, setAST, rt, setRT, errors, setErrors}}/>
|
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors}}/>
|
||||||
</main>
|
</main>
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<AST {...ast}/>
|
<AST {...ast}/>
|
||||||
<hr/>
|
{rt.map((rt, idx) => <><hr/><div className={"runtimeState"+(idx===rtIdx?" active":"")} onClick={() => setRTIdx(idx)}>
|
||||||
{rt &&
|
<ShowEnvironment environment={rt.environment}/>
|
||||||
[...rt.environment.entries()].map(([variable,value]) => <>
|
<br/>
|
||||||
{variable}: {value}<br/>
|
<ShowMode mode={rt.mode} statechart={ast}/>
|
||||||
</>)
|
</div></>)}
|
||||||
}
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ShowEnvironment(props: {environment: Environment}) {
|
||||||
|
return <>{[...props.environment.entries()].map(([variable,value]) =>
|
||||||
|
`${variable}: ${value}`
|
||||||
|
).join(', ')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowMode(props: {mode: Mode, statechart: Statechart}) {
|
||||||
|
const activeLeafs = getActiveLeafs(props.mode, props.statechart);
|
||||||
|
return <>{[...activeLeafs].map(uid =>
|
||||||
|
stateDescription(props.statechart.uid2State.get(uid)!)).join(",")}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveLeafs(mode: Mode, sc: Statechart) {
|
||||||
|
const toDelete = [];
|
||||||
|
for (const stateA of mode) {
|
||||||
|
for (const stateB of mode) {
|
||||||
|
if (sc.uid2State.get(stateA)!.parent === sc.uid2State.get(stateB)) {
|
||||||
|
toDelete.push(stateB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode.difference(new Set(toDelete));
|
||||||
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,10 @@ text.highlight {
|
||||||
stroke: rgb(230,0,0);
|
stroke: rgb(230,0,0);
|
||||||
}
|
}
|
||||||
.rountangle.active {
|
.rountangle.active {
|
||||||
fill: rgb(255, 196, 0);
|
fill: darkorange;
|
||||||
fill-opacity: 0.2;
|
fill-opacity: 0.2;
|
||||||
|
/* filter: drop-shadow( 3px 3px 2px rgba(0, 0, 0, .7)); */
|
||||||
|
stroke-width: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected:hover {
|
.selected:hover {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { act } from "react";
|
|
||||||
import { evalExpr } from "./actionlang_interpreter";
|
import { evalExpr } from "./actionlang_interpreter";
|
||||||
import { computeArena, ConcreteState, getDescendants, isAncestorOf, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./ast";
|
import { computeArena, ConcreteState, getDescendants, isAncestorOf, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./ast";
|
||||||
import { Action } from "./label_ast";
|
import { Action } from "./label_ast";
|
||||||
|
|
@ -209,13 +208,24 @@ export function handleEvent(event: string, statechart: Statechart, activeParent:
|
||||||
return {environment, mode, ...raised};
|
return {environment, mode, ...raised};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function handleInputEvent(event: string, statechart: Statechart, rt: RT_Statechart): RT_Statechart {
|
||||||
|
let {mode, environment, internalEvents, outputEvents} = handleEvent(event, statechart, statechart.root, rt);
|
||||||
|
|
||||||
|
while (internalEvents.length > 0) {
|
||||||
|
const [event, ...rest] = internalEvents;
|
||||||
|
({mode, environment, internalEvents, outputEvents} = handleEvent(event, statechart, statechart.root, {mode, environment, internalEvents: rest, outputEvents}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {mode, environment, internalEvents, outputEvents};
|
||||||
|
}
|
||||||
|
|
||||||
function transitionDescription(t: Transition) {
|
function transitionDescription(t: Transition) {
|
||||||
return stateDescription(t.src) + ' ➔ ' + stateDescription(t.tgt);
|
return stateDescription(t.src) + ' ➔ ' + stateDescription(t.tgt);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fireTransition(t: Transition, arena: OrState, srcPath: ConcreteState[], tgtPath: ConcreteState[], {mode, environment, ...raised}: RT_Statechart): {mode: Mode, environment: Environment} & RaisedEvents {
|
export function fireTransition(t: Transition, arena: OrState, srcPath: ConcreteState[], tgtPath: ConcreteState[], {mode, environment, ...raised}: RT_Statechart): RT_Statechart {
|
||||||
|
|
||||||
console.log('fire ', transitionDescription(t), {arena, srcPath, tgtPath});
|
// console.log('fire ', transitionDescription(t), {arena, srcPath, tgtPath});
|
||||||
|
|
||||||
// exit src
|
// exit src
|
||||||
({environment, ...raised} = exitPath(srcPath.slice(1), {environment, enteredStates: mode, ...raised}));
|
({environment, ...raised} = exitPath(srcPath.slice(1), {environment, enteredStates: mode, ...raised}));
|
||||||
|
|
@ -223,7 +233,7 @@ export function fireTransition(t: Transition, arena: OrState, srcPath: ConcreteS
|
||||||
toExit.delete(arena.uid); // do not exit the arena itself
|
toExit.delete(arena.uid); // do not exit the arena itself
|
||||||
const exitedMode = mode.difference(toExit);
|
const exitedMode = mode.difference(toExit);
|
||||||
|
|
||||||
console.log('exitedMode', exitedMode);
|
// console.log('exitedMode', exitedMode);
|
||||||
|
|
||||||
// exec transition actions
|
// exec transition actions
|
||||||
for (const action of t.label[0].actions) {
|
for (const action of t.label[0].actions) {
|
||||||
|
|
@ -235,7 +245,7 @@ export function fireTransition(t: Transition, arena: OrState, srcPath: ConcreteS
|
||||||
({enteredStates, environment, ...raised} = enterPath(tgtPath.slice(1), {environment, ...raised}));
|
({enteredStates, environment, ...raised} = enterPath(tgtPath.slice(1), {environment, ...raised}));
|
||||||
const enteredMode = exitedMode.union(enteredStates);
|
const enteredMode = exitedMode.union(enteredStates);
|
||||||
|
|
||||||
console.log('enteredMode', enteredMode);
|
// console.log('enteredMode', enteredMode);
|
||||||
|
|
||||||
return {mode: enteredMode, environment, ...raised};
|
return {mode: enteredMode, environment, ...raised};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ function peg$parse(input, options) {
|
||||||
const peg$c19 = "//";
|
const peg$c19 = "//";
|
||||||
const peg$c20 = "\n";
|
const peg$c20 = "\n";
|
||||||
|
|
||||||
const peg$r0 = /^[a-zA-Z0-9]/;
|
const peg$r0 = /^[0-9A-Z_a-z]/;
|
||||||
const peg$r1 = /^[0-9]/;
|
const peg$r1 = /^[0-9]/;
|
||||||
const peg$r2 = /^[<>]/;
|
const peg$r2 = /^[<>]/;
|
||||||
const peg$r3 = /^[+\-]/;
|
const peg$r3 = /^[+\-]/;
|
||||||
|
|
@ -203,7 +203,7 @@ function peg$parse(input, options) {
|
||||||
const peg$e7 = peg$literalExpectation("s", false);
|
const peg$e7 = peg$literalExpectation("s", false);
|
||||||
const peg$e8 = peg$literalExpectation(";", false);
|
const peg$e8 = peg$literalExpectation(";", false);
|
||||||
const peg$e9 = peg$literalExpectation("=", false);
|
const peg$e9 = peg$literalExpectation("=", false);
|
||||||
const peg$e10 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false, false);
|
const peg$e10 = peg$classExpectation([["0", "9"], ["A", "Z"], "_", ["a", "z"]], false, false, false);
|
||||||
const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false);
|
const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false);
|
||||||
const peg$e12 = peg$literalExpectation("==", false);
|
const peg$e12 = peg$literalExpectation("==", false);
|
||||||
const peg$e13 = peg$literalExpectation("!=", false);
|
const peg$e13 = peg$literalExpectation("!=", false);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ assignment = lhs:identifier _ "=" _ rhs:expr {
|
||||||
return {kind: "assignment", lhs, rhs};
|
return {kind: "assignment", lhs, rhs};
|
||||||
}
|
}
|
||||||
|
|
||||||
identifier = [a-zA-Z0-9]+ {
|
identifier = ("_" / [a-zA-Z0-9])+ {
|
||||||
return text();
|
return text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue