show execution history

This commit is contained in:
Joeri Exelmans 2025-10-09 16:25:03 +02:00
parent a8b522fdd8
commit d5272e30f3
6 changed files with 90 additions and 42 deletions

View file

@ -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;
} */ } */

View file

@ -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>
&emsp; &emsp;
<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>)}
&emsp;
<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>
&emsp; &emsp;
<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}}/>
&emsp;
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;

View file

@ -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 {

View file

@ -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};
} }

View file

@ -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);

View file

@ -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();
} }