parse() and createStreamParser() produce different final results for the same completed OpenUI Lang program when a statement ID is defined more than once.
The batch parser behaves as last definition wins, while the streaming parser behaves as first definition wins.
Minimal reproduction
const text = root = Stack([a]) a = Title("x") a = Title("y") ;
const batch = parse(text, schema);
// root -> Title("y")
const stream = createStreamParser(schema);
stream.push(text);
const streamed = stream.getResult();
// root -> Title("x")
Actual behavior
For the same completed input:
parse(text) -> Title("y")
createStreamParser().push() -> Title("x")
I also tested multiple delivery schedules:
the whole input in a single push()
line-by-line
character-by-character
multiple two-chunk splits
The streaming parser consistently returns "x" in all of these cases, while parse() consistently returns "y".
So this does not appear to be a chunk-boundary race. It looks like a systematic difference in duplicate/redefined statement semantics between the batch and streaming parsers.
Likely cause
The batch parser builds its statement map using normal Map.set() behavior, so a later statement with the same ID replaces the earlier one.
The streaming parser caches completed statements and skips a later completed statement when the ID is already present, effectively making that path first-definition-wins.
In particular, the completed-statement cache together with the existing guard around an already-present statement ID appears to preserve the first definition.
Expected behavior / question
I think parse() and createStreamParser() should have the same final semantics for the same completed input.
I'm not sure which duplicate-ID behavior is intended:
last definition wins
first definition wins
duplicate statement IDs are invalid and should produce a parser error
Happy to contribute the corresponding fix and regression coverage once the intended behavior is confirmed.
Additional context
I found this while building a streaming-invariance test harness for createStreamParser().
The harness currently checks valid OpenUI Lang programs across:
exhaustive single-cut partitions
exhaustive two-cut partitions
seeded multi-cut partitions
trailing-newline variants
no-prefix-crash behavior
error convergence
determinism
Those tests are currently green. Duplicate statement IDs were deliberately excluded because their intended semantics were unclear; testing that excluded case exposed this batch-vs-stream divergence.
parse() and createStreamParser() produce different final results for the same completed OpenUI Lang program when a statement ID is defined more than once.
The batch parser behaves as last definition wins, while the streaming parser behaves as first definition wins.
Minimal reproduction
const text =
root = Stack([a]) a = Title("x") a = Title("y");const batch = parse(text, schema);
// root -> Title("y")
const stream = createStreamParser(schema);
stream.push(text);
const streamed = stream.getResult();
// root -> Title("x")
Actual behavior
For the same completed input:
parse(text) -> Title("y")
createStreamParser().push() -> Title("x")
I also tested multiple delivery schedules:
the whole input in a single push()
line-by-line
character-by-character
multiple two-chunk splits
The streaming parser consistently returns "x" in all of these cases, while parse() consistently returns "y".
So this does not appear to be a chunk-boundary race. It looks like a systematic difference in duplicate/redefined statement semantics between the batch and streaming parsers.
Likely cause
The batch parser builds its statement map using normal Map.set() behavior, so a later statement with the same ID replaces the earlier one.
The streaming parser caches completed statements and skips a later completed statement when the ID is already present, effectively making that path first-definition-wins.
In particular, the completed-statement cache together with the existing guard around an already-present statement ID appears to preserve the first definition.
Expected behavior / question
I think parse() and createStreamParser() should have the same final semantics for the same completed input.
I'm not sure which duplicate-ID behavior is intended:
last definition wins
first definition wins
duplicate statement IDs are invalid and should produce a parser error
Happy to contribute the corresponding fix and regression coverage once the intended behavior is confirmed.
Additional context
I found this while building a streaming-invariance test harness for createStreamParser().
The harness currently checks valid OpenUI Lang programs across:
exhaustive single-cut partitions
exhaustive two-cut partitions
seeded multi-cut partitions
trailing-newline variants
no-prefix-crash behavior
error convergence
determinism
Those tests are currently green. Duplicate statement IDs were deliberately excluded because their intended semantics were unclear; testing that excluded case exposed this batch-vs-stream divergence.