Skip to content

Commit 7adc3f3

Browse files
committed
refactor: merge alternation wrapper effects into body instructions
1 parent f7d1813 commit 7adc3f3

20 files changed

Lines changed: 246 additions & 299 deletions

crates/plotnik-lib/src/compile/capture.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,26 @@ use crate::parser::ast::{self, Expr};
1313
use super::Compiler;
1414
use super::navigation::{inner_creates_scope, is_star_or_plus_quantifier, is_truly_empty_scope};
1515

16-
/// Capture effects to attach to the innermost match instruction.
16+
/// Capture effects to attach to match instructions.
1717
///
18-
/// Instead of emitting a separate epsilon transition for capture effects,
18+
/// Instead of emitting separate epsilon transitions for wrapper effects,
1919
/// these effects are propagated through the compilation chain and attached
20-
/// directly to the match instruction that captures the node.
20+
/// directly to match instructions.
21+
///
22+
/// For sequences `{a b c}`:
23+
/// - `pre` effects go on the first item (entry)
24+
/// - `post` effects go on the last item (exit)
25+
///
26+
/// For tagged alternations `[A: body]`:
27+
/// - `pre` contains `Enum(variant)` for branch entry
28+
/// - `post` contains `EndEnum` for branch exit
2129
#[derive(Clone, Default)]
2230
pub struct CaptureEffects {
23-
/// Effects to place as post_effects on the matching instruction.
24-
/// Typically: [Node/Text, Set(member)] or [Node/Text, Push]
31+
/// Effects to place as pre_effects on the entry instruction.
32+
/// Used for: Enum(variant) in tagged alternations.
33+
pub pre: Vec<EffectIR>,
34+
/// Effects to place as post_effects on the exit instruction.
35+
/// Typically: [Node/Text, Set(member)], [Push], or [EndEnum].
2536
pub post: Vec<EffectIR>,
2637
}
2738

crates/plotnik-lib/src/compile/expressions.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ impl Compiler<'_> {
4646
nav,
4747
node_type,
4848
node_field: None,
49-
pre_effects: vec![],
49+
pre_effects: capture.pre,
5050
neg_fields,
5151
post_effects: capture.post,
5252
successors: vec![exit],
@@ -115,7 +115,7 @@ impl Compiler<'_> {
115115
nav,
116116
node_type,
117117
node_field: None,
118-
pre_effects: vec![],
118+
pre_effects: capture.pre,
119119
neg_fields,
120120
post_effects: capture.post,
121121
successors: vec![items_entry],
@@ -199,7 +199,7 @@ impl Compiler<'_> {
199199
nav,
200200
node_type,
201201
node_field: None,
202-
pre_effects: vec![],
202+
pre_effects: capture.pre,
203203
neg_fields,
204204
post_effects: capture.post,
205205
successors: vec![down_wildcard],
@@ -230,7 +230,7 @@ impl Compiler<'_> {
230230
nav,
231231
node_type,
232232
node_field: None,
233-
pre_effects: vec![],
233+
pre_effects: capture.pre,
234234
neg_fields: vec![],
235235
post_effects: capture.post,
236236
successors: vec![exit],
@@ -280,7 +280,8 @@ impl Compiler<'_> {
280280

281281
let nav = nav_override.unwrap_or(Nav::Stay);
282282

283-
if needs_scope {
283+
// Call instructions don't have pre_effects, so emit epsilon if needed
284+
let call_entry = if needs_scope {
284285
// Captured ref returning struct: Obj → Call → EndObj → Set → exit
285286
// The Obj creates an isolated scope for the definition's internal captures.
286287
let set_step = self.emit_effects_epsilon(exit, capture.post, CaptureEffects::default());
@@ -295,7 +296,14 @@ impl Compiler<'_> {
295296
} else {
296297
// Uncaptured ref: just Call → exit (def's Sets go to parent scope)
297298
self.emit_call(nav, field_override, exit, target)
299+
};
300+
301+
if capture.pre.is_empty() {
302+
return call_entry;
298303
}
304+
305+
// Wrap with pre-effects epsilon (e.g., Enum for tagged alternations)
306+
self.emit_effects_epsilon(call_entry, capture.pre, CaptureEffects::default())
299307
}
300308

301309
/// Compile a field constraint with capture effects (passed to inner pattern).
@@ -500,7 +508,10 @@ impl Compiler<'_> {
500508
&inner,
501509
exit,
502510
nav_override,
503-
CaptureEffects { post: combined },
511+
CaptureEffects {
512+
pre: outer_capture.pre,
513+
post: combined,
514+
},
504515
)
505516
}
506517

crates/plotnik-lib/src/compile/quantifier.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ impl Compiler<'_> {
193193
// Non-array capture: build capture effects and recurse
194194
let capture_effects = self.build_capture_effects(cap, Some(&inner));
195195
let mut combined = CaptureEffects {
196+
pre: capture.pre.clone(),
196197
post: capture_effects,
197198
};
198199
combined.post.extend(capture.post);
@@ -259,6 +260,7 @@ impl Compiler<'_> {
259260
let skip_endarr = self.emit_endarr_step(&capture_effects, &outer_capture.post, skip_exit);
260261

261262
let push_effects = CaptureEffects {
263+
pre: vec![],
262264
post: if self.quantifier_needs_node_for_push(inner) {
263265
let opcode = if cap.has_string_annotation() {
264266
EffectOpcode::Text

crates/plotnik-lib/src/compile/scope.rs

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ impl Compiler<'_> {
145145

146146
// Compile inner with capture_effects on the match instruction
147147
let inner_capture = CaptureEffects {
148+
pre: outer_capture.pre,
148149
post: capture_effects,
149150
};
150151
return self.compile_expr_inner(inner, actual_exit, nav_override, inner_capture);
@@ -168,7 +169,9 @@ impl Compiler<'_> {
168169
}));
169170

170171
// Compile inner WITH capture_effects on the match instruction
172+
// Note: pre effects don't propagate through Obj/EndObj scope wrapper
171173
let inner_capture = CaptureEffects {
174+
pre: vec![],
172175
post: capture_effects,
173176
};
174177
let inner_entry = self.with_scope(scope_type_id.unwrap(), |this| {
@@ -220,6 +223,7 @@ impl Compiler<'_> {
220223
}));
221224

222225
let push_effects = CaptureEffects {
226+
pre: vec![],
223227
post: if self.quantifier_needs_node_for_push(inner) {
224228
// Use Text if the capture has `:: string` annotation, else Node
225229
let opcode = if use_text_for_elements {
@@ -491,39 +495,34 @@ impl Compiler<'_> {
491495
use crate::bytecode::MAX_MATCH_PAYLOAD_SLOTS;
492496

493497
if successors.len() <= MAX_MATCH_PAYLOAD_SLOTS {
494-
self.instructions.push(Instruction::Match(MatchIR {
495-
label,
496-
nav: Nav::Stay,
497-
node_type: None,
498-
node_field: None,
499-
pre_effects: vec![],
500-
neg_fields: vec![],
501-
post_effects: vec![],
502-
successors,
503-
}));
504-
} else {
505-
// Split: first (MAX-1) successors + intermediate for rest.
506-
// This preserves priority order: VM tries s0, s1, ..., then intermediate.
507-
let split_at = MAX_MATCH_PAYLOAD_SLOTS - 1;
508-
let (first_batch, rest) = successors.split_at(split_at);
509-
510-
let intermediate = self.fresh_label();
511-
self.emit_epsilon(intermediate, rest.to_vec());
512-
513-
let mut batch = first_batch.to_vec();
514-
batch.push(intermediate);
515-
516-
self.instructions.push(Instruction::Match(MatchIR {
517-
label,
518-
nav: Nav::Stay,
519-
node_type: None,
520-
node_field: None,
521-
pre_effects: vec![],
522-
neg_fields: vec![],
523-
post_effects: vec![],
524-
successors: batch,
525-
}));
498+
self.push_epsilon(label, successors);
499+
return;
526500
}
501+
502+
// Split: first (MAX-1) successors + intermediate for rest.
503+
// This preserves priority order: VM tries s0, s1, ..., then intermediate.
504+
let split_at = MAX_MATCH_PAYLOAD_SLOTS - 1;
505+
let (first_batch, rest) = successors.split_at(split_at);
506+
507+
let intermediate = self.fresh_label();
508+
self.emit_epsilon(intermediate, rest.to_vec());
509+
510+
let mut batch = first_batch.to_vec();
511+
batch.push(intermediate);
512+
self.push_epsilon(label, batch);
513+
}
514+
515+
fn push_epsilon(&mut self, label: Label, successors: Vec<Label>) {
516+
self.instructions.push(Instruction::Match(MatchIR {
517+
label,
518+
nav: Nav::Stay,
519+
node_type: None,
520+
node_field: None,
521+
pre_effects: vec![],
522+
neg_fields: vec![],
523+
post_effects: vec![],
524+
successors,
525+
}));
527526
}
528527

529528
/// Emit a wildcard navigation step that accepts any node.

0 commit comments

Comments
 (0)