diff --git a/crates/plotnik-lib/src/bytecode/ir.rs b/crates/plotnik-lib/src/bytecode/ir.rs
index 2c7d9be6..e117a7fb 100644
--- a/crates/plotnik-lib/src/bytecode/ir.rs
+++ b/crates/plotnik-lib/src/bytecode/ir.rs
@@ -130,6 +130,66 @@ impl EffectIR {
}
}
+ /// Capture current node value.
+ pub fn node() -> Self {
+ Self::simple(EffectOpcode::Node, 0)
+ }
+
+ /// Capture current node text.
+ pub fn text() -> Self {
+ Self::simple(EffectOpcode::Text, 0)
+ }
+
+ /// Push null value.
+ pub fn null() -> Self {
+ Self::simple(EffectOpcode::Null, 0)
+ }
+
+ /// Push accumulated value to array.
+ pub fn push() -> Self {
+ Self::simple(EffectOpcode::Push, 0)
+ }
+
+ /// Begin array scope.
+ pub fn start_arr() -> Self {
+ Self::simple(EffectOpcode::Arr, 0)
+ }
+
+ /// End array scope.
+ pub fn end_arr() -> Self {
+ Self::simple(EffectOpcode::EndArr, 0)
+ }
+
+ /// Begin object scope.
+ pub fn start_obj() -> Self {
+ Self::simple(EffectOpcode::Obj, 0)
+ }
+
+ /// End object scope.
+ pub fn end_obj() -> Self {
+ Self::simple(EffectOpcode::EndObj, 0)
+ }
+
+ /// Begin enum scope.
+ pub fn start_enum() -> Self {
+ Self::simple(EffectOpcode::Enum, 0)
+ }
+
+ /// End enum scope.
+ pub fn end_enum() -> Self {
+ Self::simple(EffectOpcode::EndEnum, 0)
+ }
+
+ /// Begin suppression (suppress effects within).
+ pub fn suppress_begin() -> Self {
+ Self::simple(EffectOpcode::SuppressBegin, 0)
+ }
+
+ /// End suppression.
+ pub fn suppress_end() -> Self {
+ Self::simple(EffectOpcode::SuppressEnd, 0)
+ }
+
/// Resolve this IR effect to a concrete EffectOp.
///
/// - `lookup_member`: maps (field_name Symbol, field_type TypeId) to member index
@@ -235,6 +295,96 @@ pub struct MatchIR {
}
impl MatchIR {
+ /// Create a terminal/accept state (empty successors).
+ pub fn terminal(label: Label) -> Self {
+ Self {
+ label,
+ nav: Nav::Stay,
+ node_type: None,
+ node_field: None,
+ pre_effects: vec![],
+ neg_fields: vec![],
+ post_effects: vec![],
+ successors: vec![],
+ }
+ }
+
+ /// Start building a match instruction at the given label.
+ pub fn at(label: Label) -> Self {
+ Self::terminal(label)
+ }
+
+ /// Create an epsilon transition (no node interaction) to a single successor.
+ pub fn epsilon(label: Label, next: Label) -> Self {
+ Self::at(label).next(next)
+ }
+
+ /// Set the navigation command.
+ pub fn nav(mut self, nav: Nav) -> Self {
+ self.nav = nav;
+ self
+ }
+
+ /// Set the node type constraint.
+ pub fn node_type(mut self, t: impl Into