You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
3.**Match**: Validate node kind/fields. If fail, rollback to watermark and abort.
196
+
4.**Post-Effects**: Emit `post_effects` (uses new `current` value).
197
+
5.**Exit**: Pop `Frame` (validate return).
196
198
197
-
This order ensures that if a definition call succeeds, its effects are present. If it fails later, the watermark saved during `Enter` allows rolling back all effects emitted by that definition.
199
+
This order ensures correct behavior during epsilon elimination. Pre-effects run before the match overwrites `current`, allowing effects like `PushElement` to be safely merged from preceding epsilon transitions. Post-effects run after, for effects that need the newly matched node.
Effect stream (annotated with pre/post classification):
212
214
213
215
```
214
-
StartObject
215
-
(match "foo")
216
-
Field("name")
217
-
StartArray
218
-
(match "a")
219
-
ToString
220
-
PushElement
221
-
(match "b")
222
-
ToString
223
-
PushElement
224
-
EndArray
225
-
Field("params")
226
-
EndObject
216
+
pre: StartObject
217
+
(match "foo")
218
+
post: Field("name")
219
+
pre: StartArray
220
+
(match "a")
221
+
post: ToString
222
+
post: PushElement
223
+
(match "b")
224
+
post: ToString
225
+
post: PushElement
226
+
post: EndArray
227
+
post: Field("params")
228
+
post: EndObject
227
229
```
228
230
231
+
Note: In the raw graph, effects live on epsilon transitions between matches. The pre/post classification determines where they land after epsilon elimination. `StartObject` and `StartArray` are pre-effects (setup before matching). `Field`, `PushElement`, `ToString`, and `End*` are post-effects (consume the matched node or finalize containers).
232
+
229
233
Execution trace:
230
234
231
235
| Effect | current | stack |
@@ -304,27 +308,31 @@ Same structure, different `next` order. The first successor has priority.
304
308
Array construction uses epsilon transitions with effects:
305
309
306
310
```
307
-
T0: ε + StartArray next: [T1]
308
-
T1: ε (branch) next: [T2, T5] // try match or exit
T5: ε + Field("items") next: [...] // post-effect: assign to field
313
317
```
314
318
319
+
After epsilon elimination, `PushElement` from T3 merges into T2 as a post-effect. `StartArray` from T0 merges into T2 as a pre-effect (first iteration only—loop iterations enter from T3, not T0).
T3: ε + Field("name") next: [...] // post-effect: assign to field
326
332
```
327
333
334
+
`StartObject` is a pre-effect (merges forward). `EndObject` and `Field` are post-effects (merge backward onto preceding match).
335
+
328
336
### Tagged Alternations
329
337
330
338
Tagged branches use `StartVariant` to create explicit tagged structures.
@@ -420,19 +428,28 @@ struct Interpreter<'a> {
420
428
421
429
### Epsilon Elimination (Optimization)
422
430
423
-
After initial construction, epsilon transitions can be eliminated by computing epsilon closures:
431
+
After initial construction, epsilon transitions can be eliminated by computing epsilon closures. The `pre_effects`/`post_effects` split is essential for correctness here.
432
+
433
+
**Why the split matters**: A match transition overwrites `current` with the matched node. Effects from *preceding* epsilon transitions (like `PushElement`) need the *previous*`current` value. Without the split, merging them into a single post-match list would use the wrong value.
424
434
425
435
```
426
-
Before:
427
-
T0: ε + StartArray next: [T1]
428
-
T1: ε + Field next: [T2]
429
-
T2: Match(kind) next: [T3]
436
+
Before (raw graph):
437
+
T1: Match(A) next: [T2] // current = A
438
+
T2: ε + PushElementnext: [T3] // pushes A (correct)
0 commit comments