From d5272e30f3b7e6c9f0ce544c64094f33d7d6c41c Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 9 Oct 2025 16:25:03 +0200 Subject: [PATCH] show execution history --- src/App/App.css | 12 +++ src/App/App.tsx | 90 ++++++++++++++--------- src/VisualEditor/VisualEditor.css | 4 +- src/VisualEditor/interpreter.ts | 20 +++-- src/VisualEditor/label_parser.js | 4 +- src/VisualEditor/transition_label.grammar | 2 +- 6 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/App/App.css b/src/App/App.css index 132cc86..0111196 100644 --- a/src/App/App.css +++ b/src/App/App.css @@ -26,6 +26,18 @@ details { 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 { color: white; } */ \ No newline at end of file diff --git a/src/App/App.tsx b/src/App/App.tsx index 8dac4b1..9423dd9 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -1,9 +1,9 @@ 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 { RT_Statechart } from "../VisualEditor/runtime_types"; -import { initialize, handleEvent } from "../VisualEditor/interpreter"; +import { Environment, Mode, RT_Statechart } from "../VisualEditor/runtime_types"; +import { initialize, handleEvent, handleInputEvent } from "../VisualEditor/interpreter"; import { Action, Expression } from "../VisualEditor/label_ast"; import "../index.css"; @@ -69,7 +69,9 @@ export function AST(props: {root: ConcreteState, transitions: Map(emptyStatechart); const [errors, setErrors] = useState<[string,string][]>([]); - const [rt, setRT] = useState(null); + const [rt, setRT] = useState([]); + const [rtIdx, setRTIdx] = useState(null); + const [timeMs, setTimeMs] = useState(0); const [paused, setPaused] = useState(true); const [timescale, setTimescale] = useState(1); @@ -77,61 +79,83 @@ export function App() { function restart() { const rt = initialize(ast); console.log('runtime: ', rt); - setRT(rt); + setRT([rt]); + setRTIdx(0); } function stop() { - setRT(null); + setRT([]); + setRTIdx(null); } function raise(event: string) { - if (rt && ast.inputEvents.has(event)) { - const nextConfig = handleEvent(event, ast, ast.root, rt); - setRT(nextConfig); - // console.log({nextConfigs}); - // if (nextConfigs.length > 0) { - // if (nextConfigs.length > 1) { - // console.warn('non-determinism, blindly selecting first next run-time state!'); - // } - // setRT(nextConfigs[0]); - // } + console.log(rtIdx); + if (rt.length>0 && rtIdx!==null && ast.inputEvents.has(event)) { + const nextConfig = handleInputEvent(event, ast, rt[rtIdx]!); + setRT([...rt.slice(0, rtIdx+1), nextConfig]); + setRTIdx(rtIdx+1); } } return
+
+ +
- - +   - setPaused(e.target.checked)}/> + raise + {[...ast.inputEvents].map(event => )} +   + setPaused(e.target.checked)}/> - setPaused(!e.target.checked)}/> + setPaused(!e.target.checked)}/> - + +   + time is {timeMs} ms
- +
; } +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; diff --git a/src/VisualEditor/VisualEditor.css b/src/VisualEditor/VisualEditor.css index a525fc7..af97567 100644 --- a/src/VisualEditor/VisualEditor.css +++ b/src/VisualEditor/VisualEditor.css @@ -59,8 +59,10 @@ text.highlight { stroke: rgb(230,0,0); } .rountangle.active { - fill: rgb(255, 196, 0); + fill: darkorange; fill-opacity: 0.2; + /* filter: drop-shadow( 3px 3px 2px rgba(0, 0, 0, .7)); */ + stroke-width: 3px; } .selected:hover { diff --git a/src/VisualEditor/interpreter.ts b/src/VisualEditor/interpreter.ts index 3f2b645..9a9ed0d 100644 --- a/src/VisualEditor/interpreter.ts +++ b/src/VisualEditor/interpreter.ts @@ -1,4 +1,3 @@ -import { act } from "react"; import { evalExpr } from "./actionlang_interpreter"; import { computeArena, ConcreteState, getDescendants, isAncestorOf, isOverlapping, OrState, Statechart, stateDescription, Transition } from "./ast"; import { Action } from "./label_ast"; @@ -209,13 +208,24 @@ export function handleEvent(event: string, statechart: Statechart, activeParent: 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) { 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 ({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 const exitedMode = mode.difference(toExit); - console.log('exitedMode', exitedMode); + // console.log('exitedMode', exitedMode); // exec transition 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})); const enteredMode = exitedMode.union(enteredStates); - console.log('enteredMode', enteredMode); + // console.log('enteredMode', enteredMode); return {mode: enteredMode, environment, ...raised}; } diff --git a/src/VisualEditor/label_parser.js b/src/VisualEditor/label_parser.js index 5bd0d23..fac7cda 100644 --- a/src/VisualEditor/label_parser.js +++ b/src/VisualEditor/label_parser.js @@ -186,7 +186,7 @@ function peg$parse(input, options) { const peg$c19 = "//"; 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$r2 = /^[<>]/; const peg$r3 = /^[+\-]/; @@ -203,7 +203,7 @@ function peg$parse(input, options) { const peg$e7 = peg$literalExpectation("s", false); const peg$e8 = peg$literalExpectation(";", false); const peg$e9 = peg$literalExpectation("=", false); - const peg$e10 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false, false); + const peg$e10 = peg$classExpectation([["0", "9"], ["A", "Z"], "_", ["a", "z"]], false, false, false); const peg$e11 = peg$classExpectation([["0", "9"]], false, false, false); const peg$e12 = peg$literalExpectation("==", false); const peg$e13 = peg$literalExpectation("!=", false); diff --git a/src/VisualEditor/transition_label.grammar b/src/VisualEditor/transition_label.grammar index 302b7bb..ed317e1 100644 --- a/src/VisualEditor/transition_label.grammar +++ b/src/VisualEditor/transition_label.grammar @@ -47,7 +47,7 @@ assignment = lhs:identifier _ "=" _ rhs:expr { return {kind: "assignment", lhs, rhs}; } -identifier = [a-zA-Z0-9]+ { +identifier = ("_" / [a-zA-Z0-9])+ { return text(); }