feat(dsl): composition operators, durations and one_of (p8-s2) - #109
Merged
Conversation
A composition contributes exactly one thing to the IR: the start trigger on a phase's Event. No new runtime concept, no new IR node, no scheduler change — which is what keeps the two frontends sharing one runtime rather than two that resemble each other (ADR-0031). A concrete `duration` is arithmetic done at load time. The storyboard starts at t = 0 and every duration that lowers is a constant, so a phase's absolute start time is the sum of the ones before it. Lowering tracks, for each point in the `do` directive, an absolute time when one is known and the set of groups that must have completed otherwise; a trigger ANDs whichever of the two it has. Two obvious alternatives were probed against the engine and both fail: a stop trigger only cuts a phase short, and a start trigger delayed on the predecessor's runningState never fires at all. A parallel join is one ConditionGroup, which is already an AND, so members that end at a known time and members that end when their actions do mix for free. The default `overlap: start` is what absent start triggers already give; the other seven kinds are reported. `one_of` picks by label from LowerOptions::alternative, fed by `scena-run --select`, defaulting to the first alternative in declaration order. The engine has no seed machinery, and a seed the scenario does not state would be exactly the hidden input determinism rules out. `wait elapsed(d)` lowers to nothing but the offset the next phase starts from. A range duration, the non-default overlaps, and §7.6.2.5's event directives are reported rather than approximated. Fixes a pre-existing lexer defect the sprint surfaced: §7.2.2.6.7 spells the range constructor `[a '..' b]` while §7.2.1.5.2 makes a float's leading digits optional, so `[2..4]` is a race the operator must win. It did not — the lexer emitted no `..` at all and the parser looked for `...`. The two agreed with each other, so every range in the suite was written the wrong way and nothing noticed until `duration: [10s..30s]`. 1378 gtest (+12), 194 pytest, ADR-0031.
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A composition contributes exactly one thing to the IR: the start trigger on
a phase's Event. No new runtime concept, no new IR node, no scheduler change —
which is what keeps the two frontends sharing one runtime rather than two that
resemble each other. ADR-0031 records it.
A concrete duration is arithmetic, done at load time
The storyboard starts at t = 0 and every duration that lowers is a constant, so
a phase's absolute start time is the sum of the durations before it. Lowering
tracks, for each point in the
dodirective, what load time knows:runtime knows.
A trigger ANDs whichever of the two it has. A phase starting at t = 0 with
nothing to wait for gets no trigger, because
t >= 0is a tautology and§7.6.1.1 already says a trigger-less element starts with its parent.
Two obvious alternatives were probed against the engine and both fail, which
is why the arithmetic route is the one taken:
groups do, so a step-shaped action ends the phase immediately whatever the
stop trigger says — the duration would be silently ignored for exactly the
scenarios easiest to write.
runningStatenever fires.The predecessor is no longer running by the time the delayed lookup happens.
A parallel join is an AND the trigger model already has
A
ConditionGroupis a conjunction, so "every member has finished" is one groupwith one condition per member. Members ending at a known time collapse into a
single
SimulationTimeConditionat the latest of them; members ending whentheir actions do each contribute their group's completion — so the two mix for
free, and the common case reduces to one comparison.
§7.6.2.1.4's default
overlap: startis exactly what absent start triggers give.The other seven overlap kinds and
start_to_start/end_to_endare reported.one_ofpicks by label, defaulting to the first§7.6.2.1.3's latitude is real and an executor has to pick. Picking at random
would put a hidden input in the run — and the engine has no seed machinery at
all, so such a choice could not even be made reproducible. The alternative is
therefore an input:
LowerOptions::alternative, fed byscena-run --select.Alternatives not chosen never reach the IR, so the engine stays free of a
concept the XML side has no counterpart for.
wait elapsed(d)lowers to nothing but the clock§7.6.2.4.2 introduces a phase in which nothing is specified, so nothing is what
it produces — no group, no event, no action. It advances the offset the next
phase starts from, and the clock is already running.
A range
duration(§7.6.2.4) constrains accepted traces rather than fixing atime; choosing from it needs a solver (ADR-0004), so it is reported. §7.6.2.5's
emit,wait @event,onanduntilare reported too: those events areabstract control objects with no runtime carrier in v0.0.1.
A pre-existing lexer defect this sprint surfaced
§7.2.2.6.7 spells the range constructor
'[' expression '..' expression ']',and §7.2.1.5.2's
float-literal ::= digit* '.' digit+makes the leading digitsoptional — so
[2..4]is a race between the..operator and the float.4,and the float was winning. The lexer emitted no
..token at all, and theparser looked for
..., a spelling the standard does not have.Because the two agreed with each other, every range in the test suite was
written the wrong way and nothing noticed.
duration: [10s..30s]is what madeit visible. Fixed in both, with the affected test expectations corrected to the
standard's spelling and the race pinned in
dsl_lexer_test.cpp.Tests and docs
dsl_lowering_test.cpp37 → 45: absolute start times from durations,completion chaining without them, a nested parallel inside a serial, an
AND-shaped join,
wait elapsed,one_ofdefault and named, an alternativethat is not there, a range duration, and a non-default overlap.
scena_run_test.cpp+2:--selectchoosing an alternative end to end, and aserial scenario whose phases take over at the second it named.
dsl_lexer_test.cpp+2: the..-versus-.4race, in both plain and physicalliterals.
scena-runuser guide and the DSL frontend README.1378 gtest (+12), 194 pytest.
Closes #45