pseudo-states appear to be working + variables only exist within the scope where they are created

This commit is contained in:
Joeri Exelmans 2025-10-16 17:10:37 +02:00
parent db1479bfc4
commit d4930eb13d
22 changed files with 742 additions and 569 deletions

View file

@ -1,28 +1,3 @@
/* .layoutVertical {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
}
.panel {
height: 1.5rem;
background-color: lightgrey;
}
.layout {
display: flex;
width: 100%;
height: calc(100vh - 1.5rem);
}
.sidebar {
flex: 0 0 content;
padding-right: 4px;
}
.content {
flex: 1 1 auto;
overflow: auto;
} */
details {
padding-left: 20;
/* margin-left: 30; */
@ -68,4 +43,10 @@ summary {
}
.toolbar > input {
height: 20px;
}
button.active {
border: solid blue 2px;
background-color: rgba(0,0,255,0.2);
color: black;
}

View file

@ -12,9 +12,10 @@ import "./App.css";
import { Box, Stack } from "@mui/material";
import { TopPanel } from "./TopPanel";
import { RTHistory } from "./RTHistory";
import { AST } from "./AST";
import { ShowAST } from "./ShowAST";
import { TraceableError } from "../statecharts/parser";
import { getKeyHandler } from "./shortcut_handler";
import { BottomPanel } from "./BottomPanel";
export function App() {
const [mode, setMode] = useState<InsertMode>("and");
@ -106,7 +107,7 @@ export function App() {
{...{ast, time, setTime, onInit, onClear, onRaise, mode, setMode}}
/>
</Box>
<Stack direction="row" sx={{height:'calc(100vh - 32px)'}}>
<Stack direction="row" sx={{height:'calc(100vh - 64px)'}}>
{/* main */}
<Box sx={{flexGrow:1, overflow:'auto'}}>
<VisualEditor {...{ast, setAST, rt: rt.at(rtIdx!), setRT, errors, setErrors, mode}}/>
@ -117,14 +118,17 @@ export function App() {
borderLeft: 1,
borderColor: "divider",
flex: '0 0 content',
paddingRight: 1,
paddingLeft: 1,
// paddingRight: 1,
// paddingLeft: 1,
}}>
<AST {...{...ast, rt: rt.at(rtIdx!)}}/>
<ShowAST {...{...ast, rt: rt.at(rtIdx!)}}/>
<br/>
<RTHistory {...{ast, rt, rtIdx, setTime, setRTIdx}}/>
</Box>
</Stack>
<Box>
<BottomPanel {...{errors}}/>
</Box>
</Stack>;
}

3
src/App/BottomPanel.css Normal file
View file

@ -0,0 +1,3 @@
.errorStatus {
color: rgb(230,0,0);
}

10
src/App/BottomPanel.tsx Normal file
View file

@ -0,0 +1,10 @@
import { TraceableError } from "../statecharts/parser";
import "./BottomPanel.css";
export function BottomPanel(props: {errors: TraceableError[]}) {
return <div className="toolbar">
<div className="errorStatus">{
props.errors.length>0 && <>{props.errors.length} errors {props.errors.map(({message})=>message).join(',')}</>}</div>
</div>;
}

View file

@ -1,4 +1,4 @@
import { ConcreteState, stateDescription, Transition } from "../statecharts/abstract_syntax";
import { ConcreteState, PseudoState, stateDescription, Transition } from "../statecharts/abstract_syntax";
import { Action, Expression } from "../statecharts/label_ast";
import { RT_Statechart } from "../statecharts/runtime_types";
@ -32,7 +32,7 @@ export function ShowAction(props: {action: Action}) {
}
}
export function AST(props: {root: ConcreteState, transitions: Map<string, Transition[]>, rt: RT_Statechart | undefined}) {
export function ShowAST(props: {root: ConcreteState | PseudoState, transitions: Map<string, Transition[]>, rt: RT_Statechart | undefined}) {
const description = stateDescription(props.root);
const outgoing = props.transitions.get(props.root.uid) || [];
@ -49,9 +49,9 @@ export function AST(props: {root: ConcreteState, transitions: Map<string, Transi
<div>&emsp;exit / <ShowAction action={action}/></div>
)
}
{props.root.children.length>0 &&
{props.root.kind !== "pseudo" && props.root.children.length>0 &&
props.root.children.map(child =>
<AST root={child} transitions={props.transitions} rt={props.rt} />
<ShowAST root={child} transitions={props.transitions} rt={props.rt} />
)
}
{outgoing.length>0 &&

View file

@ -10,10 +10,11 @@ import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import BoltIcon from '@mui/icons-material/Bolt';
import SkipNextIcon from '@mui/icons-material/SkipNext';
import TrendingFlatIcon from '@mui/icons-material/TrendingFlat';
import AccessAlarmIcon from '@mui/icons-material/AccessAlarm';
import StopIcon from '@mui/icons-material/Stop';
import { formatTime } from "./util";
import { InsertMode } from "../VisualEditor/VisualEditor";
import { DiamondShape } from "../VisualEditor/RountangleSVG";
export type TopPanelProps = {
rt?: BigStep,
@ -33,16 +34,21 @@ function RountangleIcon(props: {kind: string}) {
x={1} y={1}
width={18} height={18}
className={`rountangle ${props.kind}`}
style={props.kind === "or" ? {strokeDasharray: '3 2'}: {}}
style={{...(props.kind === "or" ? {strokeDasharray: '3 2'}: {}), strokeWidth: 1.2}}
/>
</svg>;
}
function PseudoStateIcon(props: {}) {
return <svg width={20} height={20}>
<g transform="translate(2,1)">
<DiamondShape geometry={{topLeft:{x:0,y:0}, size:{x:16,y:18}}} extraAttrs={{className: 'rountangle pseudo'}}/>
</g>
const w=20, h=20;
return <svg width={w} height={h}>
<polygon
points={`
${w/2} ${1},
${w-1} ${h/2},
${w/2} ${h-1},
${1} ${h/2},
`} fill="white" stroke="black" strokeWidth={1.2}/>
</svg>;
}
@ -101,28 +107,29 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
return <>
<div className="toolbar">
{([
["and", "AND-states", <RountangleIcon kind="and"/>],
["or", "OR-states", <RountangleIcon kind="or"/>],
["pseudo", "pseudo-states", <PseudoStateIcon/>],
["transition", "transitions", <TrendingFlatIcon fontSize="small"/>],
["text", "text", <>T</>],
] as [InsertMode, string, ReactElement][]).map(([m, hint, buttonTxt]) =>
<button
title={"insert "+hint}
disabled={mode===m}
onClick={() => setMode(m)}
>{buttonTxt}</button>)}
</div>
&emsp;
<div className="toolbar">
<button title="(re)initialize simulation" onClick={onInit} ><CachedIcon fontSize="small"/><PlayArrowIcon fontSize="small"/></button>
<button title="clear the simulation" onClick={onClear} disabled={!rt}><ClearIcon fontSize="small"/></button>
{([
["and", "AND-states", <RountangleIcon kind="and"/>],
["or", "OR-states", <RountangleIcon kind="or"/>],
["pseudo", "pseudo-states", <PseudoStateIcon/>],
["transition", "transitions", <TrendingFlatIcon fontSize="small"/>],
["text", "text", <>&nbsp;T&nbsp;</>],
] as [InsertMode, string, ReactElement][]).map(([m, hint, buttonTxt]) =>
<button
title={"insert "+hint}
disabled={mode===m}
className={mode===m ? "active":""}
onClick={() => setMode(m)}
>{buttonTxt}</button>)}
&emsp;
<button title="pause the simulation" disabled={!rt || time.kind==="paused"} onClick={() => onChangePaused(true, performance.now())}><PauseIcon fontSize="small"/></button>
<button title="run the simulation in real time" disabled={!rt || time.kind==="realtime"} onClick={() => onChangePaused(false, performance.now())}><PlayArrowIcon fontSize="small"/></button>
<button title="(re)initialize simulation" onClick={onInit} ><PlayArrowIcon fontSize="small"/><CachedIcon fontSize="small"/></button>
<button title="clear the simulation" onClick={onClear} disabled={!rt}><StopIcon fontSize="small"/></button>
&emsp;
<button title="pause the simulation" disabled={!rt || time.kind==="paused"} className={(rt && time.kind==="paused") ? "active":""} onClick={() => onChangePaused(true, performance.now())}><PauseIcon fontSize="small"/></button>
<button title="run the simulation in real time" disabled={!rt || time.kind==="realtime"} className={(rt && time.kind==="realtime") ? "active":""} onClick={() => onChangePaused(false, performance.now())}><PlayArrowIcon fontSize="small"/></button>
{/* <ToggleButtonGroup value={time.kind} exclusive onChange={(_,newValue) => onChangePaused(newValue==="paused", performance.now())} size="small">
<ToggleButton disableRipple value="paused" disabled={!rt}><PauseIcon/></ToggleButton>
@ -155,12 +162,15 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
return {kind: "realtime", scale: time.scale, since: {simtime: nextTimedTransition[0], wallclktime: now}};
}
});
}}><SkipNextIcon fontSize="small"/></button>
}}><SkipNextIcon fontSize="small"/><AccessAlarmIcon fontSize="small"/></button>
&emsp;
{ast.inputEvents &&
<>
{ast.inputEvents.map(({event, paramName}) =>
<>&emsp;<button title={`raise input event '${event}'`} disabled={!rt} onClick={() => {
<><button title={`raise input event '${event}'`} disabled={!rt} onClick={() => {
// @ts-ignore
const param = document.getElementById(`input-${event}-param`)?.value;
let paramParsed;
try {
@ -176,7 +186,7 @@ export function TopPanel({rt, time, setTime, onInit, onClear, onRaise, ast, mode
}}>
<BoltIcon fontSize="small"/>
{event}
</button>{paramName && <><input id={`input-${event}-param`} style={{width: 20}} placeholder={paramName}/></>}</>)}
</button>{paramName && <><input id={`input-${event}-param`} style={{width: 20}} placeholder={paramName}/></>}&nbsp;</>)}
</>
}