diff --git a/crates/plotnik-lib/src/bytecode/constants.rs b/crates/plotnik-lib/src/bytecode/constants.rs index 0bd5f1f0..589347bc 100644 --- a/crates/plotnik-lib/src/bytecode/constants.rs +++ b/crates/plotnik-lib/src/bytecode/constants.rs @@ -12,13 +12,6 @@ pub const SECTION_ALIGN: usize = 64; /// Step size in bytes (all instructions are 8-byte aligned). pub const STEP_SIZE: usize = 8; -/// Sentinel value for "any named node" wildcard `(_)`. -/// -/// When `node_type` equals this value, the VM checks `node.is_named()` -/// instead of comparing type IDs. This distinguishes `(_)` (any named) -/// from `_` (any node including anonymous). -pub const NAMED_WILDCARD: u16 = 0xFFFF; - /// Maximum payload slots for Match instructions. /// /// Match64 (the largest variant) supports up to 28 u16 slots for diff --git a/crates/plotnik-lib/src/bytecode/dump.rs b/crates/plotnik-lib/src/bytecode/dump.rs index 2fd6f068..df9e9b8e 100644 --- a/crates/plotnik-lib/src/bytecode/dump.rs +++ b/crates/plotnik-lib/src/bytecode/dump.rs @@ -7,10 +7,10 @@ use std::fmt::Write as _; use crate::colors::Colors; -use super::NAMED_WILDCARD; -use super::format::{LineBuilder, Symbol, format_effect, nav_symbol_epsilon, width_for_count}; +use super::format::{LineBuilder, Symbol, format_effect, nav_symbol, width_for_count}; use super::ids::TypeId; use super::instructions::StepId; +use super::ir::NodeTypeIR; use super::module::{Instruction, Module}; use super::type_meta::{TypeData, TypeKind}; use super::{Call, Match, Return, Trampoline}; @@ -446,7 +446,7 @@ fn format_match( step_width: usize, ) -> String { let builder = LineBuilder::new(step_width); - let symbol = nav_symbol_epsilon(m.nav, m.is_epsilon()); + let symbol = nav_symbol(m.nav); let prefix = format!(" {:0sw$} {} ", step, symbol.format(), sw = step_width); let content = format_match_content(m, ctx); @@ -464,17 +464,20 @@ fn format_match_content(m: &Match, ctx: &DumpContext) -> String { parts.push(format!("[{}]", pre.join(" "))); } - for field_id in m.neg_fields() { - let name = ctx - .node_field_name(field_id) - .map(String::from) - .unwrap_or_else(|| format!("field#{field_id}")); - parts.push(format!("-{name}")); - } + // Skip neg_fields and node pattern for epsilon (no node interaction) + if !m.is_epsilon() { + for field_id in m.neg_fields() { + let name = ctx + .node_field_name(field_id) + .map(String::from) + .unwrap_or_else(|| format!("field#{field_id}")); + parts.push(format!("-{name}")); + } - let node_part = format_node_pattern(m, ctx); - if !node_part.is_empty() { - parts.push(node_part); + let node_part = format_node_pattern(m, ctx); + if !node_part.is_empty() { + parts.push(node_part); + } } let post: Vec<_> = m.post_effects().map(|e| format_effect(&e)).collect(); @@ -485,7 +488,7 @@ fn format_match_content(m: &Match, ctx: &DumpContext) -> String { parts.join(" ") } -/// Format node pattern: `field: (type)` or `(type)` or `field: _` or `(_)` +/// Format node pattern: `field: (type)` or `(type)` or `field: _` or `(_)` or `"text"` fn format_node_pattern(m: &Match, ctx: &DumpContext) -> String { let mut result = String::new(); @@ -498,11 +501,17 @@ fn format_node_pattern(m: &Match, ctx: &DumpContext) -> String { result.push_str(": "); } - if let Some(type_id) = m.node_type { - if type_id.get() == NAMED_WILDCARD { + match m.node_type { + NodeTypeIR::Any => { + // Any node wildcard: `_` + result.push('_'); + } + NodeTypeIR::Named(None) => { // Named wildcard: any named node result.push_str("(_)"); - } else { + } + NodeTypeIR::Named(Some(type_id)) => { + // Specific named node type let name = ctx .node_type_name(type_id.get()) .map(String::from) @@ -511,8 +520,20 @@ fn format_node_pattern(m: &Match, ctx: &DumpContext) -> String { result.push_str(&name); result.push(')'); } - } else if m.node_field.is_some() { - result.push('_'); + NodeTypeIR::Anonymous(None) => { + // Anonymous wildcard: any anonymous node (future syntax) + result.push_str("\"_\""); + } + NodeTypeIR::Anonymous(Some(type_id)) => { + // Specific anonymous node (literal token) + let name = ctx + .node_type_name(type_id.get()) + .map(String::from) + .unwrap_or_else(|| format!("anon#{}", type_id.get())); + result.push('"'); + result.push_str(&name); + result.push('"'); + } } result @@ -538,7 +559,7 @@ fn format_call( ) -> String { let c = &ctx.colors; let builder = LineBuilder::new(step_width); - let symbol = nav_symbol_epsilon(call.nav, false); + let symbol = nav_symbol(call.nav()); let prefix = format!(" {:0sw$} {} ", step, symbol.format(), sw = step_width); // Format field constraint if present diff --git a/crates/plotnik-lib/src/bytecode/format.rs b/crates/plotnik-lib/src/bytecode/format.rs index 04d2557b..a6b34cc5 100644 --- a/crates/plotnik-lib/src/bytecode/format.rs +++ b/crates/plotnik-lib/src/bytecode/format.rs @@ -72,8 +72,8 @@ impl Symbol { /// /// | Nav | Symbol | Notes | /// | --------------- | ------- | ----------------------------------- | +/// | Epsilon | ε | Pure control flow, no cursor check | /// | Stay | (blank) | No movement, 5 spaces | -/// | Stay (epsilon) | ε | Only when no type/field constraints | /// | StayExact | ! | Stay at position, exact match only | /// | Down | ▽ | First child, skip any | /// | DownSkip | !▽ | First child, skip trivia | @@ -86,6 +86,7 @@ impl Symbol { /// | UpExact(n) | !!△ⁿ | Ascend n, must be last child | pub fn nav_symbol(nav: Nav) -> Symbol { match nav { + Nav::Epsilon => Symbol::EPSILON, Nav::Stay => Symbol::EMPTY, Nav::StayExact => Symbol::new(" ", "!", " "), Nav::Down => Symbol::new(" ", "▽", " "), @@ -100,20 +101,6 @@ pub fn nav_symbol(nav: Nav) -> Symbol { } } -/// Format navigation for epsilon transitions (when is_epsilon is true). -/// -/// True epsilon transitions require all three conditions: -/// - `nav == Stay` (no cursor movement) -/// - `node_type == None` (no type constraint) -/// - `node_field == None` (no field constraint) -pub fn nav_symbol_epsilon(nav: Nav, is_epsilon: bool) -> Symbol { - if is_epsilon { - Symbol::EPSILON - } else { - nav_symbol(nav) - } -} - /// Trace sub-line symbols. pub mod trace { use super::Symbol; diff --git a/crates/plotnik-lib/src/bytecode/instructions.rs b/crates/plotnik-lib/src/bytecode/instructions.rs index c53dfecf..af5ae71a 100644 --- a/crates/plotnik-lib/src/bytecode/instructions.rs +++ b/crates/plotnik-lib/src/bytecode/instructions.rs @@ -7,6 +7,7 @@ use std::num::NonZeroU16; use super::constants::{SECTION_ALIGN, STEP_SIZE}; use super::effects::EffectOp; +use super::ir::NodeTypeIR; use super::nav::Nav; /// Step address in bytecode (raw u16). @@ -129,12 +130,12 @@ impl Opcode { #[derive(Clone, Copy, Debug)] pub struct Match<'a> { bytes: &'a [u8], - /// Segment index (0-15, currently only 0 is used). + /// Segment index (0-3, currently only 0 is used). pub segment: u8, - /// Navigation command. + /// Navigation command. `Epsilon` means no cursor movement or node check. pub nav: Nav, - /// Node type constraint (None = wildcard). - pub node_type: Option, + /// Node type constraint (Any = wildcard, Named/Anonymous for specific checks). + pub node_type: NodeTypeIR, /// Field constraint (None = wildcard). pub node_field: Option, /// Whether this is Match8 (no payload) or extended. @@ -153,18 +154,23 @@ impl<'a> Match<'a> { /// /// The slice must start at the instruction and contain at least /// the full instruction size (determined by opcode). + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` #[inline] pub fn from_bytes(bytes: &'a [u8]) -> Self { debug_assert!(bytes.len() >= 8, "Match instruction too short"); let type_id_byte = bytes[0]; - let segment = type_id_byte >> 4; - debug_assert!(segment == 0, "non-zero segment not yet supported"); + // Header byte: segment(2) | node_kind(2) | opcode(4) + let segment = (type_id_byte >> 6) & 0x3; + let node_kind = (type_id_byte >> 4) & 0x3; let opcode = Opcode::from_u8(type_id_byte & 0xF); + debug_assert!(segment == 0, "non-zero segment not yet supported"); debug_assert!(opcode.is_match(), "expected Match opcode"); let nav = Nav::from_byte(bytes[1]); - let node_type = NonZeroU16::new(u16::from_le_bytes([bytes[2], bytes[3]])); + let node_type_val = u16::from_le_bytes([bytes[2], bytes[3]]); + let node_type = NodeTypeIR::from_bytes(node_kind, node_type_val); let node_field = NonZeroU16::new(u16::from_le_bytes([bytes[4], bytes[5]])); let (is_match8, match8_next, pre_count, neg_count, post_count, succ_count) = @@ -207,7 +213,7 @@ impl<'a> Match<'a> { /// Check if this is an epsilon transition (no node interaction). #[inline] pub fn is_epsilon(&self) -> bool { - self.nav == Nav::Stay && self.node_type.is_none() && self.node_field.is_none() + self.nav == Nav::Epsilon } /// Number of successors. @@ -282,7 +288,7 @@ impl<'a> Match<'a> { /// Call instruction for invoking definitions (recursion). #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Call { - /// Segment index (0-15). + /// Segment index (0-3). pub(crate) segment: u8, /// Navigation to apply before jumping to target. pub(crate) nav: Nav, @@ -307,14 +313,17 @@ impl Call { } /// Decode from 8-byte bytecode. + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` + /// For Call, node_kind bits are ignored (always 0). pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self { let type_id_byte = bytes[0]; - let segment = type_id_byte >> 4; + let segment = (type_id_byte >> 6) & 0x3; + let opcode = Opcode::from_u8(type_id_byte & 0xF); assert!( segment == 0, "non-zero segment not yet supported: {segment}" ); - let opcode = Opcode::from_u8(type_id_byte & 0xF); assert_eq!(opcode, Opcode::Call, "expected Call opcode"); Self { @@ -327,9 +336,12 @@ impl Call { } /// Encode to 8-byte bytecode. + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` pub fn to_bytes(&self) -> [u8; 8] { let mut bytes = [0u8; 8]; - bytes[0] = (self.segment << 4) | (Opcode::Call as u8); + // node_kind = 0 for Call + bytes[0] = (self.segment << 6) | (Opcode::Call as u8); bytes[1] = self.nav.to_byte(); bytes[2..4].copy_from_slice(&self.node_field.map_or(0, |v| v.get()).to_le_bytes()); bytes[4..6].copy_from_slice(&self.next.get().to_le_bytes()); @@ -354,7 +366,7 @@ impl Call { /// Return instruction for returning from definitions. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Return { - /// Segment index (0-15). + /// Segment index (0-3). pub(crate) segment: u8, } @@ -365,23 +377,29 @@ impl Return { } /// Decode from 8-byte bytecode. + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` + /// For Return, node_kind bits are ignored (always 0). pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self { let type_id_byte = bytes[0]; - let segment = type_id_byte >> 4; + let segment = (type_id_byte >> 6) & 0x3; + let opcode = Opcode::from_u8(type_id_byte & 0xF); assert!( segment == 0, "non-zero segment not yet supported: {segment}" ); - let opcode = Opcode::from_u8(type_id_byte & 0xF); assert_eq!(opcode, Opcode::Return, "expected Return opcode"); Self { segment } } /// Encode to 8-byte bytecode. + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` pub fn to_bytes(&self) -> [u8; 8] { let mut bytes = [0u8; 8]; - bytes[0] = (self.segment << 4) | (Opcode::Return as u8); + // node_kind = 0 for Return + bytes[0] = (self.segment << 6) | (Opcode::Return as u8); // bytes[1..8] are reserved/padding bytes } @@ -400,7 +418,7 @@ impl Default for Return { /// the entry preamble: `Obj → Trampoline → EndObj → Accept`. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Trampoline { - /// Segment index (0-15). + /// Segment index (0-3). pub(crate) segment: u8, /// Return address (where to continue after entrypoint returns). pub(crate) next: StepId, @@ -413,14 +431,17 @@ impl Trampoline { } /// Decode from 8-byte bytecode. + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` + /// For Trampoline, node_kind bits are ignored (always 0). pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self { let type_id_byte = bytes[0]; - let segment = type_id_byte >> 4; + let segment = (type_id_byte >> 6) & 0x3; + let opcode = Opcode::from_u8(type_id_byte & 0xF); assert!( segment == 0, "non-zero segment not yet supported: {segment}" ); - let opcode = Opcode::from_u8(type_id_byte & 0xF); assert_eq!(opcode, Opcode::Trampoline, "expected Trampoline opcode"); Self { @@ -430,9 +451,12 @@ impl Trampoline { } /// Encode to 8-byte bytecode. + /// + /// Header byte layout: `segment(2) | node_kind(2) | opcode(4)` pub fn to_bytes(&self) -> [u8; 8] { let mut bytes = [0u8; 8]; - bytes[0] = (self.segment << 4) | (Opcode::Trampoline as u8); + // node_kind = 0 for Trampoline + bytes[0] = (self.segment << 6) | (Opcode::Trampoline as u8); // bytes[1] is padding bytes[2..4].copy_from_slice(&self.next.get().to_le_bytes()); // bytes[4..8] are reserved/padding diff --git a/crates/plotnik-lib/src/bytecode/instructions_tests.rs b/crates/plotnik-lib/src/bytecode/instructions_tests.rs index ffdfcdeb..e780e7cc 100644 --- a/crates/plotnik-lib/src/bytecode/instructions_tests.rs +++ b/crates/plotnik-lib/src/bytecode/instructions_tests.rs @@ -7,7 +7,7 @@ use super::effects::EffectOpcode; use super::instructions::{ Call, Match, Opcode, Return, StepId, align_to_section, select_match_opcode, }; -use super::ir::{EffectIR, Label, MatchIR}; +use super::ir::{EffectIR, Label, MatchIR, NodeTypeIR}; use super::nav::Nav; #[test] @@ -94,7 +94,7 @@ fn match_basic() { let bytes = MatchIR::at(Label(0)) .nav(Nav::Down) - .node_type(NonZeroU16::new(42)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(42))) .node_field(NonZeroU16::new(7)) .next(Label(1)) .resolve(&map, |_, _| None, |_| None); @@ -103,7 +103,7 @@ fn match_basic() { let m = Match::from_bytes(&bytes); assert_eq!(m.nav, Nav::Down); - assert_eq!(m.node_type, NonZeroU16::new(42)); + assert_eq!(m.node_type, NodeTypeIR::Named(NonZeroU16::new(42))); assert_eq!(m.node_field, NonZeroU16::new(7)); assert!(!m.is_terminal()); assert!(!m.is_epsilon()); @@ -134,7 +134,7 @@ fn match_extended() { let bytes = MatchIR::at(Label(0)) .nav(Nav::Next) - .node_type(NonZeroU16::new(100)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(100))) .pre_effect(EffectIR::start_obj()) .neg_field(5) .neg_field(6) @@ -151,7 +151,7 @@ fn match_extended() { let m = Match::from_bytes(&bytes); assert_eq!(m.nav, Nav::Next); - assert_eq!(m.node_type, NonZeroU16::new(100)); + assert_eq!(m.node_type, NodeTypeIR::Named(NonZeroU16::new(100))); assert!(!m.is_terminal()); let pre: Vec<_> = m.pre_effects().collect(); diff --git a/crates/plotnik-lib/src/bytecode/ir.rs b/crates/plotnik-lib/src/bytecode/ir.rs index a5fd70b8..32ac9489 100644 --- a/crates/plotnik-lib/src/bytecode/ir.rs +++ b/crates/plotnik-lib/src/bytecode/ir.rs @@ -14,6 +14,79 @@ use super::instructions::{ use super::nav::Nav; use crate::analyze::type_check::TypeId; +/// Node type constraint for Match instructions. +/// +/// Distinguishes between named nodes (`(identifier)`), anonymous nodes (`"text"`), +/// and wildcards (`_`, `(_)`). Encoded in bytecode header byte bits 5-4. +/// +/// | `node_kind` | Value | Meaning | `node_type=0` | `node_type>0` | +/// | ----------- | ----- | ------------ | ------------------- | ----------------- | +/// | `00` | Any | `_` pattern | No check | (invalid) | +/// | `01` | Named | `(_)`/`(t)` | Check `is_named()` | Check `kind_id()` | +/// | `10` | Anon | `"text"` | Check `!is_named()` | Check `kind_id()` | +/// | `11` | - | Reserved | Error | Error | +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum NodeTypeIR { + /// Any node (`_` pattern) - no type check performed. + #[default] + Any, + /// Named node constraint (`(_)` or `(identifier)`). + /// - `None` = any named node (check `is_named()`) + /// - `Some(id)` = specific named type (check `kind_id()`) + Named(Option), + /// Anonymous node constraint (`"text"` literals). + /// - `None` = any anonymous node (check `!is_named()`) + /// - `Some(id)` = specific anonymous type (check `kind_id()`) + Anonymous(Option), +} + +impl NodeTypeIR { + /// Encode to bytecode: returns (node_kind bits, node_type value). + /// + /// `node_kind` is 2 bits for header byte bits 5-4. + /// `node_type` is u16 for bytes 2-3. + pub fn to_bytes(self) -> (u8, u16) { + match self { + Self::Any => (0b00, 0), + Self::Named(opt) => (0b01, opt.map(|n| n.get()).unwrap_or(0)), + Self::Anonymous(opt) => (0b10, opt.map(|n| n.get()).unwrap_or(0)), + } + } + + /// Decode from bytecode: node_kind bits (2 bits) and node_type value (u16). + pub fn from_bytes(node_kind: u8, node_type: u16) -> Self { + match node_kind { + 0b00 => Self::Any, + 0b01 => Self::Named(NonZeroU16::new(node_type)), + 0b10 => Self::Anonymous(NonZeroU16::new(node_type)), + _ => panic!("invalid node_kind: {node_kind}"), + } + } + + /// Check if this represents a specific type ID (not a wildcard). + pub fn type_id(&self) -> Option { + match self { + Self::Any => None, + Self::Named(opt) | Self::Anonymous(opt) => *opt, + } + } + + /// Check if this is the Any wildcard. + pub fn is_any(&self) -> bool { + matches!(self, Self::Any) + } + + /// Check if this is a Named constraint (wildcard or specific). + pub fn is_named(&self) -> bool { + matches!(self, Self::Named(_)) + } + + /// Check if this is an Anonymous constraint (wildcard or specific). + pub fn is_anonymous(&self) -> bool { + matches!(self, Self::Anonymous(_)) + } +} + /// Symbolic reference, resolved to step address at layout time. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct Label(pub u32); @@ -277,10 +350,10 @@ impl InstructionIR { pub struct MatchIR { /// Where this instruction lives. pub label: Label, - /// Navigation command. + /// Navigation command. `Epsilon` means pure control flow (no node check). pub nav: Nav, - /// Node type constraint (None = wildcard). - pub node_type: Option, + /// Node type constraint (Any = wildcard, Named/Anonymous for specific checks). + pub node_type: NodeTypeIR, /// Field constraint (None = wildcard). pub node_field: Option, /// Effects to execute before match attempt. @@ -298,8 +371,8 @@ impl MatchIR { pub fn terminal(label: Label) -> Self { Self { label, - nav: Nav::Stay, - node_type: None, + nav: Nav::Epsilon, + node_type: NodeTypeIR::Any, node_field: None, pre_effects: vec![], neg_fields: vec![], @@ -325,8 +398,8 @@ impl MatchIR { } /// Set the node type constraint. - pub fn node_type(mut self, t: impl Into>) -> Self { - self.node_type = t.into(); + pub fn node_type(mut self, t: NodeTypeIR) -> Self { + self.node_type = t; self } @@ -437,9 +510,10 @@ impl MatchIR { let size = opcode.size(); let mut bytes = vec![0u8; size]; - bytes[0] = opcode as u8; // segment 0 + // Header byte layout: segment(2) | node_kind(2) | opcode(4) + let (node_kind, node_type_val) = self.node_type.to_bytes(); + bytes[0] = (node_kind << 4) | (opcode as u8); // segment 0 bytes[1] = self.nav.to_byte(); - let node_type_val = self.node_type.map(|n| n.get()).unwrap_or(0); bytes[2..4].copy_from_slice(&node_type_val.to_le_bytes()); let node_field_val = self.node_field.map(|n| n.get()).unwrap_or(0); bytes[4..6].copy_from_slice(&node_field_val.to_le_bytes()); @@ -488,7 +562,7 @@ impl MatchIR { /// Check if this is an epsilon transition (no node interaction). #[inline] pub fn is_epsilon(&self) -> bool { - self.nav == Nav::Stay && self.node_type.is_none() && self.node_field.is_none() + self.nav == Nav::Epsilon } } diff --git a/crates/plotnik-lib/src/bytecode/ir_tests.rs b/crates/plotnik-lib/src/bytecode/ir_tests.rs index 47707ef6..3dc69b27 100644 --- a/crates/plotnik-lib/src/bytecode/ir_tests.rs +++ b/crates/plotnik-lib/src/bytecode/ir_tests.rs @@ -4,7 +4,7 @@ use std::num::NonZeroU16; use plotnik_core::Symbol; use super::effects::EffectOpcode; -use super::ir::{CallIR, EffectIR, InstructionIR, Label, MatchIR, MemberRef, ReturnIR}; +use super::ir::{CallIR, EffectIR, InstructionIR, Label, MatchIR, MemberRef, NodeTypeIR, ReturnIR}; use super::nav::Nav; use crate::analyze::type_check::TypeId; @@ -12,7 +12,7 @@ use crate::analyze::type_check::TypeId; fn match_ir_size_match8() { let m = MatchIR::at(Label(0)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .next(Label(1)); assert_eq!(m.size(), 8); @@ -22,7 +22,7 @@ fn match_ir_size_match8() { fn match_ir_size_extended() { let m = MatchIR::at(Label(0)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .pre_effect(EffectIR::start_obj()) .post_effect(EffectIR::node()) .next(Label(1)); diff --git a/crates/plotnik-lib/src/bytecode/mod.rs b/crates/plotnik-lib/src/bytecode/mod.rs index 8a85e860..f14e2d52 100644 --- a/crates/plotnik-lib/src/bytecode/mod.rs +++ b/crates/plotnik-lib/src/bytecode/mod.rs @@ -16,9 +16,7 @@ mod nav; mod sections; mod type_meta; -pub use constants::{ - MAGIC, MAX_MATCH_PAYLOAD_SLOTS, NAMED_WILDCARD, SECTION_ALIGN, STEP_SIZE, VERSION, -}; +pub use constants::{MAGIC, MAX_MATCH_PAYLOAD_SLOTS, SECTION_ALIGN, STEP_SIZE, VERSION}; pub use ids::{StringId, TypeId}; @@ -47,12 +45,12 @@ pub use module::{ pub use dump::dump; pub use format::{ - LineBuilder, Symbol, cols, format_effect, nav_symbol, nav_symbol_epsilon, superscript, trace, - truncate_text, width_for_count, + LineBuilder, Symbol, cols, format_effect, nav_symbol, superscript, trace, truncate_text, + width_for_count, }; pub use ir::{ - CallIR, EffectIR, InstructionIR, Label, LayoutResult, MatchIR, MemberRef, ReturnIR, + CallIR, EffectIR, InstructionIR, Label, LayoutResult, MatchIR, MemberRef, NodeTypeIR, ReturnIR, TrampolineIR, }; diff --git a/crates/plotnik-lib/src/bytecode/nav.rs b/crates/plotnik-lib/src/bytecode/nav.rs index 2ab8c4d9..a3cce71b 100644 --- a/crates/plotnik-lib/src/bytecode/nav.rs +++ b/crates/plotnik-lib/src/bytecode/nav.rs @@ -5,7 +5,11 @@ /// Navigation command for VM execution. #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum Nav { + /// Epsilon transition: pure control flow, no cursor movement or node check. + /// Used for branching, quantifier loops, and effect-only transitions. #[default] + Epsilon, + /// Stay at current position. Stay, /// Stay at current position, exact match only (no continue_search). StayExact, @@ -32,14 +36,15 @@ impl Nav { match mode { 0b00 => match payload { - 0 => Self::Stay, - 1 => Self::StayExact, - 2 => Self::Next, - 3 => Self::NextSkip, - 4 => Self::NextExact, - 5 => Self::Down, - 6 => Self::DownSkip, - 7 => Self::DownExact, + 0 => Self::Epsilon, + 1 => Self::Stay, + 2 => Self::StayExact, + 3 => Self::Next, + 4 => Self::NextSkip, + 5 => Self::NextExact, + 6 => Self::Down, + 7 => Self::DownSkip, + 8 => Self::DownExact, _ => panic!("invalid nav standard: {payload}"), }, 0b01 => { @@ -61,14 +66,15 @@ impl Nav { /// Encode to bytecode byte. pub fn to_byte(self) -> u8 { match self { - Self::Stay => 0, - Self::StayExact => 1, - Self::Next => 2, - Self::NextSkip => 3, - Self::NextExact => 4, - Self::Down => 5, - Self::DownSkip => 6, - Self::DownExact => 7, + Self::Epsilon => 0, + Self::Stay => 1, + Self::StayExact => 2, + Self::Next => 3, + Self::NextSkip => 4, + Self::NextExact => 5, + Self::Down => 6, + Self::DownSkip => 7, + Self::DownExact => 8, Self::Up(n) => { debug_assert!((1..=63).contains(&n)); 0b01_000000 | n @@ -91,6 +97,7 @@ impl Nav { /// the parent context (quantifier's skip-retry, sequence advancement). pub fn to_exact(self) -> Self { match self { + Self::Epsilon => Self::Epsilon, // Epsilon stays epsilon Self::Down | Self::DownSkip => Self::DownExact, Self::Next | Self::NextSkip => Self::NextExact, Self::Stay => Self::StayExact, diff --git a/crates/plotnik-lib/src/bytecode/nav_tests.rs b/crates/plotnik-lib/src/bytecode/nav_tests.rs index 10fe2f3c..f856e3a9 100644 --- a/crates/plotnik-lib/src/bytecode/nav_tests.rs +++ b/crates/plotnik-lib/src/bytecode/nav_tests.rs @@ -3,6 +3,7 @@ use super::*; #[test] fn nav_standard_roundtrip() { for nav in [ + Nav::Epsilon, Nav::Stay, Nav::StayExact, Nav::Next, @@ -30,9 +31,10 @@ fn nav_up_roundtrip() { #[test] fn nav_byte_encoding() { - assert_eq!(Nav::Stay.to_byte(), 0b00_000000); - assert_eq!(Nav::StayExact.to_byte(), 0b00_000001); - assert_eq!(Nav::Down.to_byte(), 0b00_000101); + assert_eq!(Nav::Epsilon.to_byte(), 0b00_000000); + assert_eq!(Nav::Stay.to_byte(), 0b00_000001); + assert_eq!(Nav::StayExact.to_byte(), 0b00_000010); + assert_eq!(Nav::Down.to_byte(), 0b00_000110); assert_eq!(Nav::Up(5).to_byte(), 0b01_000101); assert_eq!(Nav::UpSkipTrivia(3).to_byte(), 0b10_000011); assert_eq!(Nav::UpExact(1).to_byte(), 0b11_000001); @@ -41,7 +43,8 @@ fn nav_byte_encoding() { #[test] #[should_panic(expected = "invalid nav standard")] fn nav_invalid_standard_panics() { - Nav::from_byte(0b00_111111); + // 9 and above are invalid (0-8 are valid standard values) + Nav::from_byte(0b00_001001); } #[test] diff --git a/crates/plotnik-lib/src/compile/expressions.rs b/crates/plotnik-lib/src/compile/expressions.rs index 844f5562..a374a87a 100644 --- a/crates/plotnik-lib/src/compile/expressions.rs +++ b/crates/plotnik-lib/src/compile/expressions.rs @@ -10,9 +10,8 @@ use std::num::NonZeroU16; use crate::analyze::type_check::TypeShape; -use crate::bytecode::NAMED_WILDCARD; use crate::bytecode::Nav; -use crate::bytecode::{EffectIR, InstructionIR, Label, MatchIR}; +use crate::bytecode::{EffectIR, InstructionIR, Label, MatchIR, NodeTypeIR}; use crate::parser::ast::{self, Expr}; use super::Compiler; @@ -136,7 +135,7 @@ impl Compiler<'_> { entry: Label, exit: Label, nav: Nav, - node_type: Option, + node_type: NodeTypeIR, neg_fields: Vec, items: &[ast::SeqItem], up_nav: Nav, @@ -215,11 +214,11 @@ impl Compiler<'_> { let entry = self.fresh_label(); let nav = nav_override.unwrap_or(Nav::Next); - // Extract literal value (None for wildcard `_`) - let node_type = node.value().and_then(|token| { - let text = token.text(); - self.resolve_anonymous_node_type(text) - }); + // Extract literal value (Any for wildcard `_`, Anonymous for literals) + let node_type = match node.value() { + Some(token) => self.resolve_anonymous_node_type(token.text()), + None => NodeTypeIR::Any, // `_` wildcard matches any node + }; self.instructions.push( MatchIR::epsilon(entry, exit) @@ -523,54 +522,56 @@ impl Compiler<'_> { self.emit_effects_epsilon(inner_entry, suppress_begin, CaptureEffects::default()) } - /// Resolve an anonymous node's literal text to its node type ID. + /// Resolve an anonymous node's literal text to its node type constraint. /// - /// In linked mode, returns the grammar NodeTypeId for the literal. - /// In unlinked mode, returns the StringId of the literal text. - pub(super) fn resolve_anonymous_node_type(&mut self, text: &str) -> Option { + /// Returns `NodeTypeIR::Anonymous` with the type ID. + pub(super) fn resolve_anonymous_node_type(&mut self, text: &str) -> NodeTypeIR { if let Some(ids) = self.node_type_ids { // Linked mode: resolve to NodeTypeId from grammar for (&sym, &id) in ids { if self.interner.resolve(sym) == text { - return NonZeroU16::new(id.get()); + return NodeTypeIR::Anonymous(NonZeroU16::new(id.get())); } } - // If not found in grammar, treat as no constraint - None + // If not found in grammar, treat as anonymous wildcard + NodeTypeIR::Anonymous(None) } else { // Unlinked mode: store StringId referencing the literal text let string_id = self.strings.intern_str(text); - Some(string_id.0) + NodeTypeIR::Anonymous(Some(string_id.0)) } } - /// Resolve a NamedNode to its node type ID. + /// Resolve a NamedNode to its node type constraint. /// - /// In linked mode, returns the grammar NodeTypeId. - /// In unlinked mode, returns the StringId of the type name. - /// For the wildcard `(_)`, returns `NAMED_WILDCARD` sentinel. - pub(super) fn resolve_node_type(&mut self, node: &ast::NamedNode) -> Option { - // For wildcard (_), return sentinel for "any named node" + /// Returns `NodeTypeIR::Named` with: + /// - `None` for wildcard `(_)` (any named node) + /// - `Some(id)` for specific types like `(identifier)` + pub(super) fn resolve_node_type(&mut self, node: &ast::NamedNode) -> NodeTypeIR { + // For wildcard (_), return Named(None) for "any named node" if node.is_any() { - return NonZeroU16::new(NAMED_WILDCARD); + return NodeTypeIR::Named(None); } - let type_token = node.node_type()?; + let Some(type_token) = node.node_type() else { + // No type specified - treat as any named + return NodeTypeIR::Named(None); + }; let type_name = type_token.text(); if let Some(ids) = self.node_type_ids { // Linked mode: resolve to NodeTypeId from grammar for (&sym, &id) in ids { if self.interner.resolve(sym) == type_name { - return NonZeroU16::new(id.get()); + return NodeTypeIR::Named(NonZeroU16::new(id.get())); } } - // If not found in grammar, treat as no constraint (linked mode) - None + // If not found in grammar, treat as any named (linked mode) + NodeTypeIR::Named(None) } else { // Unlinked mode: store StringId referencing the type name let string_id = self.strings.intern_str(type_name); - Some(string_id.0) + NodeTypeIR::Named(Some(string_id.0)) } } diff --git a/crates/plotnik-lib/src/emit/layout_tests.rs b/crates/plotnik-lib/src/emit/layout_tests.rs index 61add329..adde9e2b 100644 --- a/crates/plotnik-lib/src/emit/layout_tests.rs +++ b/crates/plotnik-lib/src/emit/layout_tests.rs @@ -2,7 +2,7 @@ use std::num::NonZeroU16; use super::layout::CacheAligned; use crate::bytecode::Nav; -use crate::bytecode::{CallIR, EffectIR, Label, MatchIR, ReturnIR}; +use crate::bytecode::{CallIR, EffectIR, Label, MatchIR, NodeTypeIR, ReturnIR}; #[test] fn layout_empty() { @@ -17,7 +17,7 @@ fn layout_single_instruction() { let instructions = vec![ MatchIR::terminal(Label(0)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .into(), ]; @@ -33,12 +33,12 @@ fn layout_linear_chain() { let instructions = vec![ MatchIR::at(Label(0)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .next(Label(1)) .into(), MatchIR::at(Label(1)) .nav(Nav::Next) - .node_type(NonZeroU16::new(20)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(20))) .next(Label(2)) .into(), MatchIR::terminal(Label(2)).nav(Nav::Up(1)).into(), @@ -58,7 +58,7 @@ fn layout_call_return() { let instructions = vec![ MatchIR::at(Label(0)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .next(Label(1)) .into(), CallIR::new(Label(1), Label(2), Label(3)) @@ -66,7 +66,7 @@ fn layout_call_return() { .into(), MatchIR::at(Label(2)) .nav(Nav::Down) - .node_type(NonZeroU16::new(20)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(20))) .next(Label(4)) .into(), MatchIR::terminal(Label(3)).nav(Nav::Up(1)).into(), @@ -92,11 +92,11 @@ fn layout_branch() { .into(), MatchIR::terminal(Label(1)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .into(), MatchIR::terminal(Label(2)) .nav(Nav::Down) - .node_type(NonZeroU16::new(20)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(20))) .into(), ]; @@ -118,7 +118,7 @@ fn layout_large_instruction_cache_alignment() { // Start at step 5 (offset 40), would straddle - should pad let large_match = MatchIR::at(Label(1)) .nav(Nav::Down) - .node_type(NonZeroU16::new(10)) + .node_type(NodeTypeIR::Named(NonZeroU16::new(10))) .pre_effect(EffectIR::start_obj()) .pre_effect(EffectIR::start_obj()) .pre_effect(EffectIR::start_obj()) diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap index c81acee8..dbaf8752 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap @@ -55,7 +55,7 @@ Test: 08 ε [Arr] 10 10 ε 39, 20 12 ε [EndArr Set(M5)] 14 - 14 △ 19 + 14 △ _ 19 15 ε [EndObj Push] 17 17 ε 45, 12 19 ▶ @@ -65,11 +65,11 @@ Test: 27 ! [Enum(M3)] (shorthand_property_identifier) [Node Set(M1) EndEnum] 22 30 ε 24, 27 32 ε [Obj] 30 - 34 ▷ 37 + 34 ▷ _ 37 35 ε 34, 20 37 ε 32, 35 - 39 ▽ 37 - 40 ▷ 43 + 39 ▽ _ 37 + 40 ▷ _ 43 41 ε 40, 12 43 ε 32, 41 - 45 ▷ 43 + 45 ▷ _ 43 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_no_internal_captures.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_no_internal_captures.snap index e871df09..074ebb41 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_no_internal_captures.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_no_internal_captures.snap @@ -41,4 +41,4 @@ Test: 10 ▶ 11 !!▽ (identifier) [Node Set(M0)] 15 13 !!▽ (number) [Node Set(M0)] 15 - 15 △ 10 + 15 △ _ 10 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_tagged_in_field_constraint.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_tagged_in_field_constraint.snap index de28a18d..bc6ca767 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_tagged_in_field_constraint.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_tagged_in_field_constraint.snap @@ -52,4 +52,4 @@ Test: 11 ▶ 12 ! [Enum(M1)] (x) [Node Set(M0) EndEnum Set(M3)] 17 15 ! [Enum(M2)] (y) [EndEnum Set(M3)] 17 - 17 △ 11 + 17 △ _ 11 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_between_siblings.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_between_siblings.snap index 06c6c832..26d120c4 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_between_siblings.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_between_siblings.snap @@ -36,5 +36,5 @@ Test: 07 ! (parent) 08 08 ▽ (a) 09 09 !▷ (b) 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_first_child.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_first_child.snap index 65dc1103..32f5e25d 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_first_child.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_first_child.snap @@ -34,5 +34,5 @@ Test: 06 ε 07 07 ! (parent) 08 08 !▽ (first) 09 - 09 △ 10 + 09 △ _ 10 10 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_last_child.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_last_child.snap index 2661121a..bbb7160d 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_last_child.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_last_child.snap @@ -33,9 +33,9 @@ _ObjWrap: Test: 06 ε 07 07 ! (parent) 08 - 08 ▽ 13 + 08 ▽ _ 13 09 ▶ - 10 !△ 09 + 10 !△ _ 09 11 ! (last) 10 - 12 ▷ 13 + 12 ▷ _ 13 13 ε 11, 12 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_no_anchor.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_no_anchor.snap index 44890a0b..e4ffd8ee 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_no_anchor.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_no_anchor.snap @@ -36,5 +36,5 @@ Test: 07 ! (parent) 08 08 ▽ (a) 09 09 ▷ (b) 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_with_anonymous.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_with_anonymous.snap index b5edf8fb..cffb239a 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_with_anonymous.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__anchors_with_anonymous.snap @@ -34,7 +34,7 @@ _ObjWrap: Test: 06 ε 07 07 ! (parent) 08 - 08 ▽ (+) 09 + 08 ▽ "+" 09 09 !!▷ (next) 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap index 35f2172e..869449ac 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap @@ -52,10 +52,10 @@ Test: 08 ▽ (b) 09 09 ▽ (c) 10 10 ▽ (d) [Node Set(M3)] 12 - 12 △ 13 + 12 △ _ 13 13 ε [Node Set(M2)] 15 - 15 △ 16 + 15 △ _ 16 16 ε [Node Set(M1)] 18 - 18 △ 19 + 18 △ _ 19 19 ε [Node Set(M0)] 21 21 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_multiple.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_multiple.snap index 59a6b26b..7330d93e 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_multiple.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_multiple.snap @@ -45,5 +45,5 @@ Test: 07 ! (binary_expression) 08 08 ▽ (identifier) [Node Set(M0)] 10 10 ▷ (number) [Node Set(M1)] 12 - 12 △ 13 + 12 △ _ 13 13 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap index 2523aeaf..e3abac12 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap @@ -45,8 +45,8 @@ Test: 07 ! (a) 08 08 ▽ (b) 09 09 ▽ (c) [Node Set(M2)] 11 - 11 △ 12 + 11 △ _ 12 12 ε [Node Set(M1)] 14 - 14 △ 15 + 14 △ _ 15 15 ε [Node Set(M0)] 17 17 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_optional_wrapper_struct.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_optional_wrapper_struct.snap index 482a155b..2310be82 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_optional_wrapper_struct.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_optional_wrapper_struct.snap @@ -52,7 +52,7 @@ Test: 16 ! (identifier) [Node Set(M0)] 14 18 ε [Obj] 16 20 ε [Null Set(M1)] 12 - 22 ▷ 25 + 22 ▷ _ 25 23 ε 22, 20 25 ε 18, 23 - 27 ! 25 + 27 ! _ 25 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_wrapper_struct.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_wrapper_struct.snap index 7d02f790..8dbd5f08 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_wrapper_struct.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_wrapper_struct.snap @@ -59,11 +59,11 @@ Test: 22 ! (identifier) [Node Set(M0)] 20 24 ε [Obj] 22 26 ε [Obj] 24 - 28 ▷ 31 + 28 ▷ _ 31 29 ε 28, 16 31 ε 26, 29 - 33 ! 31 - 34 ▷ 37 + 33 ! _ 31 + 34 ▷ _ 37 35 ε 34, 16 37 ε 26, 35 - 39 ▷ 37 + 39 ▷ _ 37 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__comprehensive_multi_definition.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__comprehensive_multi_definition.snap index 3685bb61..a3b7c146 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__comprehensive_multi_definition.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__comprehensive_multi_definition.snap @@ -83,7 +83,7 @@ Assignment: 15 ▽ left: (identifier) [Node Set(M6)] 17 17 ▷ right: (Expression) 10 : 18 18 ε [Set(M5)] 20 - 20 △ 21 + 20 △ _ 21 21 ▶ 22 ▶ 23 ! [Enum(M3)] (number) [Node Set(M1) EndEnum] 22 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap index 03d41c62..3b24a489 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap @@ -49,7 +49,7 @@ Inner: 06 ε 07 07 ! (call) 08 08 ▽ (identifier) [Node Set(M0)] 10 - 10 △ 11 + 10 △ _ 11 11 ▶ Outer: @@ -58,7 +58,7 @@ Outer: 14 ε [Arr] 16 16 ε 42, 26 18 ε [EndArr Set(M2)] 20 - 20 △ 25 + 20 △ _ 25 21 ε [EndObj Push] 23 23 ε 48, 18 25 ▶ @@ -68,11 +68,11 @@ Outer: 32 ! (Inner) 06 : 30 33 ε [Obj] 32 35 ε [Obj] 33 - 37 ▷ 40 + 37 ▷ _ 40 38 ε 37, 26 40 ε 35, 38 - 42 ▽ 40 - 43 ▷ 46 + 42 ▽ _ 40 + 43 ▷ _ 46 44 ε 43, 18 46 ε 35, 44 - 48 ▷ 46 + 48 ▷ _ 46 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_reference.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_reference.snap index e5c466d3..145eb38d 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_reference.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_reference.snap @@ -53,7 +53,7 @@ Root: 09 ε 10 10 ! (function_declaration) 11 11 ▽ name: (identifier) [Node Set(M2)] 13 - 13 △ 14 + 13 △ _ 14 14 ▶ 15 ▶ 16 ! [Null Set(M1)] (identifier) [Node Set(M0)] 15 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_alternation.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_alternation.snap index 168d3511..9fc7657a 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_alternation.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_alternation.snap @@ -50,4 +50,4 @@ Test: 11 ▶ 12 ! [Null Set(M1)] (identifier) [Node Set(M0)] 18 15 ! [Null Set(M0)] (number) [Node Set(M1)] 18 - 18 △ 11 + 18 △ _ 11 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_multiple.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_multiple.snap index 52992d5c..c9604a7d 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_multiple.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_multiple.snap @@ -45,5 +45,5 @@ Test: 07 ! (binary_expression) 08 08 ▽ left: (_) [Node Set(M0)] 10 10 ▷ right: (_) [Node Set(M1)] 12 - 12 △ 13 + 12 △ _ 13 13 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_negated.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_negated.snap index e27c1da5..09677307 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_negated.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_negated.snap @@ -38,5 +38,5 @@ Test: 06 ε 07 07 ! -type_parameters (function_declaration) 09 09 ▽ name: (identifier) [Node Set(M0)] 11 - 11 △ 12 + 11 △ _ 12 12 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_single.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_single.snap index bdfdfe85..0230b1f2 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_single.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__fields_single.snap @@ -37,5 +37,5 @@ Test: 06 ε 07 07 ! (function_declaration) 08 08 ▽ name: (identifier) [Node Set(M0)] 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_anonymous.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_anonymous.snap index f9579b89..058c27c5 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_anonymous.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_anonymous.snap @@ -36,6 +36,6 @@ _ObjWrap: Test: 06 ε 07 07 ! (binary_expression) 08 - 08 ▽ (+) [Node Set(M0)] 10 - 10 △ 11 + 08 ▽ "+" [Node Set(M0)] 10 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_any.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_any.snap index f41d05e1..5ae3522e 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_any.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_any.snap @@ -36,5 +36,5 @@ Test: 06 ε 07 07 ! (pair) 08 08 ▽ key: _ [Node Set(M0)] 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_named.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_named.snap index 1cf04d26..2e6422e7 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_named.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__nodes_wildcard_named.snap @@ -36,5 +36,5 @@ Test: 06 ε 07 07 ! (pair) 08 08 ▽ key: (_) [Node Set(M0)] 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_first_child.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_first_child.snap index fc1475be..76e026e9 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_first_child.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_first_child.snap @@ -50,8 +50,8 @@ Test: 13 ▷ (number) [Node Set(M1)] 25 15 ε [Null Set(M0)] 11 17 ! (identifier) [Node Set(M0)] 13 - 19 ▷ 22 + 19 ▷ _ 22 20 ε 19, 15 22 ε 17, 20 - 24 ▽ 22 - 25 △ 10 + 24 ▽ _ 22 + 25 △ _ 10 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap index dc20973d..86afecda 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap @@ -39,10 +39,10 @@ Test: 07 ! (function_declaration) 08 08 ε 21, 14 10 ! (decorator) [Node Set(M0)] 12 - 12 △ 13 + 12 △ _ 13 13 ▶ 14 ε [Null Set(M0)] 13 - 16 ▷ 19 + 16 ▷ _ 19 17 ε 16, 14 19 ε 10, 17 - 21 ▽ 19 + 21 ▽ _ 19 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_first_child_array.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_first_child_array.snap index bcceafb0..f7377413 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_first_child_array.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_first_child_array.snap @@ -53,12 +53,12 @@ Test: 19 ▷ (number) [Node Set(M1)] 37 21 ε [EndArr Set(M0)] 19 23 ε [EndArr Set(M0)] 17 - 25 ▷ 28 + 25 ▷ _ 28 26 ε 25, 23 28 ε 12, 26 - 30 ▽ 28 - 31 ▷ 34 + 30 ▽ _ 28 + 31 ▷ _ 34 32 ε 31, 21 34 ε 12, 32 - 36 ▷ 34 - 37 △ 16 + 36 ▷ _ 34 + 37 △ _ 16 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap index dc20973d..86afecda 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap @@ -39,10 +39,10 @@ Test: 07 ! (function_declaration) 08 08 ε 21, 14 10 ! (decorator) [Node Set(M0)] 12 - 12 △ 13 + 12 △ _ 13 13 ▶ 14 ε [Null Set(M0)] 13 - 16 ▷ 19 + 16 ▷ _ 19 17 ε 16, 14 19 ε 10, 17 - 21 ▽ 19 + 21 ▽ _ 19 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap index d64bef9c..b32ad97f 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap @@ -39,10 +39,10 @@ Test: 07 ! (function_declaration) 08 08 ε 14, 21 10 ! (decorator) [Node Set(M0)] 12 - 12 △ 13 + 12 △ _ 13 13 ▶ 14 ε [Null Set(M0)] 13 - 16 ▷ 19 + 16 ▷ _ 19 17 ε 14, 16 19 ε 17, 10 - 21 ▽ 19 + 21 ▽ _ 19 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus.snap index e56a1c68..aeeb8005 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus.snap @@ -36,14 +36,14 @@ _ObjWrap: Test: 06 ε 07 07 ε [Arr] 09 - 09 ! 18 + 09 ! _ 18 10 ! (identifier) [Node Push] 12 12 ε 25, 15 14 ▶ 15 ε [EndArr Set(M0)] 14 - 17 ▷ 18 + 17 ▷ _ 18 18 ε 10, 17 - 20 ▷ 23 + 20 ▷ _ 23 21 ε 20, 15 23 ε 10, 21 - 25 ▷ 23 + 25 ▷ _ 23 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus_nongreedy.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus_nongreedy.snap index 02836e65..f3d7d448 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus_nongreedy.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_plus_nongreedy.snap @@ -36,14 +36,14 @@ _ObjWrap: Test: 06 ε 07 07 ε [Arr] 09 - 09 ! 18 + 09 ! _ 18 10 ! (identifier) [Node Push] 12 12 ε 15, 25 14 ▶ 15 ε [EndArr Set(M0)] 14 - 17 ▷ 18 + 17 ▷ _ 18 18 ε 17, 10 - 20 ▷ 23 + 20 ▷ _ 23 21 ε 15, 20 23 ε 21, 10 - 25 ▷ 23 + 25 ▷ _ 23 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap index adb22792..3aee6c74 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap @@ -40,16 +40,16 @@ Test: 08 ε [Arr] 10 10 ε 27, 20 12 ε [EndArr Set(M0)] 14 - 14 △ 19 + 14 △ _ 19 15 ! (decorator) [Node Push] 17 17 ε 33, 12 19 ▶ 20 ε [EndArr Set(M0)] 19 - 22 ▷ 25 + 22 ▷ _ 25 23 ε 22, 20 25 ε 15, 23 - 27 ▽ 25 - 28 ▷ 31 + 27 ▽ _ 25 + 28 ▷ _ 31 29 ε 28, 12 31 ε 15, 29 - 33 ▷ 31 + 33 ▷ _ 31 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_sequence_in_called_def.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_sequence_in_called_def.snap index e9a4e8ce..e845018e 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_sequence_in_called_def.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_sequence_in_called_def.snap @@ -62,7 +62,7 @@ Test: 15 ε 16 16 ! (parent) 17 17 ▽ (Collect) 10 : 18 - 18 △ 19 + 18 △ _ 19 19 ▶ 20 ε [EndObj Push] 22 22 ε 47, 25 @@ -73,11 +73,11 @@ Test: 31 ! (Item) 06 : 29 32 ε [Obj] 31 34 ε [Obj] 32 - 36 ▷ 39 + 36 ▷ _ 39 37 ε 36, 25 39 ε 34, 37 - 41 ! 39 - 42 ▷ 45 + 41 ! _ 39 + 42 ▷ _ 45 43 ε 42, 25 45 ε 34, 43 - 47 ▷ 45 + 47 ▷ _ 45 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star.snap index 050ee9e2..165bba28 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star.snap @@ -41,11 +41,11 @@ Test: 13 ε 29, 16 15 ▶ 16 ε [EndArr Set(M0)] 15 - 18 ▷ 21 + 18 ▷ _ 21 19 ε 18, 16 21 ε 11, 19 - 23 ! 21 - 24 ▷ 27 + 23 ! _ 21 + 24 ▷ _ 27 25 ε 24, 16 27 ε 11, 25 - 29 ▷ 27 + 29 ▷ _ 27 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star_nongreedy.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star_nongreedy.snap index ae8ecb54..de9c056a 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star_nongreedy.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_star_nongreedy.snap @@ -41,11 +41,11 @@ Test: 13 ε 16, 29 15 ▶ 16 ε [EndArr Set(M0)] 15 - 18 ▷ 21 + 18 ▷ _ 21 19 ε 16, 18 21 ε 19, 11 - 23 ! 21 - 24 ▷ 27 + 23 ! _ 21 + 24 ▷ _ 27 25 ε 16, 24 27 ε 25, 11 - 29 ▷ 27 + 29 ▷ _ 27 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap index 69fb02a8..b2ea4754 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap @@ -50,7 +50,7 @@ Test: 08 ε [Arr] 10 10 ε 33, 20 12 ε [EndArr Set(M2)] 14 - 14 △ 19 + 14 △ _ 19 15 ε [EndObj Push] 17 17 ε 39, 12 19 ▶ @@ -58,11 +58,11 @@ Test: 22 ▷ (number) [Node Set(M1)] 15 24 ! (identifier) [Node Set(M0)] 22 26 ε [Obj] 24 - 28 ▷ 31 + 28 ▷ _ 31 29 ε 28, 20 31 ε 26, 29 - 33 ▽ 31 - 34 ▷ 37 + 33 ▽ _ 31 + 34 ▷ _ 37 35 ε 34, 12 37 ε 26, 35 - 39 ▷ 37 + 39 ▷ _ 37 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_simple.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_simple.snap index b771baec..9787f8ff 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_simple.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_simple.snap @@ -58,7 +58,7 @@ Expr: 06 ε 07 07 ε 13, 21 09 ε [Set(M2)] 11 - 11 △ 16 + 11 △ _ 16 12 ▶ 13 ! [Enum(M3)] (number) [Text Set(M0) EndEnum] 12 16 ε [EndEnum] 12 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_with_structured_result.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_with_structured_result.snap index 73153463..cc310e38 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_with_structured_result.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__recursion_with_structured_result.snap @@ -72,10 +72,10 @@ Test: 10 ! (program) 11 11 ▽ (Expr) 06 : 12 12 ε [Set(M5)] 14 - 14 △ 15 + 14 △ _ 15 15 ▶ 16 ε [Set(M2)] 18 - 18 △ 23 + 18 △ _ 23 19 ▶ 20 ! [Enum(M3)] (number) [Text Set(M0) EndEnum] 19 23 ε [EndEnum] 19 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_basic.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_basic.snap index 70f4032a..579c8944 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_basic.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_basic.snap @@ -36,5 +36,5 @@ Test: 07 ! (parent) 08 08 ▽ (a) 09 09 ▷ (b) 10 - 10 △ 11 + 10 △ _ 11 11 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap index d6881223..6875afe7 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap @@ -41,17 +41,17 @@ Test: 08 ε [Arr] 10 10 ε 28, 20 12 ε [EndArr Set(M0)] 14 - 14 △ 19 + 14 △ _ 19 15 ▷ (b) [Node Push] 17 17 ε 34, 12 19 ▶ 20 ε [EndArr Set(M0)] 19 22 ! (a) 15 - 23 ▷ 26 + 23 ▷ _ 26 24 ε 23, 20 26 ε 22, 24 - 28 ▽ 26 - 29 ▷ 32 + 28 ▽ _ 26 + 29 ▷ _ 32 30 ε 29, 12 32 ε 22, 30 - 34 ▷ 32 + 34 ▷ _ 32 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_nested.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_nested.snap index 502fb1ac..6a5e0590 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_nested.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_nested.snap @@ -40,5 +40,5 @@ Test: 09 ▷ (b) 10 10 ▷ (c) 11 11 ▷ (d) 12 - 12 △ 13 + 12 △ _ 13 13 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_with_captures.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_with_captures.snap index 21ba2c45..61b16d11 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_with_captures.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_with_captures.snap @@ -43,5 +43,5 @@ Test: 07 ! (parent) 08 08 ▽ (a) [Node Set(M0)] 10 10 ▷ (b) [Node Set(M1)] 12 - 12 △ 13 + 12 △ _ 13 13 ▶ diff --git a/crates/plotnik-lib/src/engine/cursor.rs b/crates/plotnik-lib/src/engine/cursor.rs index 9a0d20df..398b6aed 100644 --- a/crates/plotnik-lib/src/engine/cursor.rs +++ b/crates/plotnik-lib/src/engine/cursor.rs @@ -103,6 +103,14 @@ impl<'t> CursorWrapper<'t> { /// or None if navigation failed (no children/siblings). pub fn navigate(&mut self, nav: Nav) -> Option { match nav { + // Epsilon should never reach here - VM skips navigate for epsilon + Nav::Epsilon => { + debug_assert!( + false, + "navigate called with Epsilon - should be skipped by VM" + ); + Some(SkipPolicy::Any) + } Nav::Stay => Some(SkipPolicy::Any), Nav::StayExact => Some(SkipPolicy::Exact), Nav::Down => self.go_first_child().then_some(SkipPolicy::Any), diff --git a/crates/plotnik-lib/src/engine/trace.rs b/crates/plotnik-lib/src/engine/trace.rs index 824c6c1c..2ec6f145 100644 --- a/crates/plotnik-lib/src/engine/trace.rs +++ b/crates/plotnik-lib/src/engine/trace.rs @@ -25,8 +25,8 @@ use arborium_tree_sitter::Node; use crate::Colors; use crate::bytecode::{ - EffectOpcode, Instruction, LineBuilder, Match, Module, Nav, Symbol, cols, format_effect, trace, - truncate_text, width_for_count, + EffectOpcode, Instruction, LineBuilder, Match, Module, Nav, NodeTypeIR, Symbol, cols, + format_effect, trace, truncate_text, width_for_count, }; use super::effect::RuntimeEffect; @@ -68,6 +68,9 @@ pub trait Tracer { /// Called after navigation succeeds. fn trace_nav(&mut self, nav: Nav, node: Node<'_>); + /// Called when navigation fails (no child/sibling exists). + fn trace_nav_failure(&mut self, nav: Nav); + /// Called after type check succeeds. fn trace_match_success(&mut self, node: Node<'_>); @@ -119,6 +122,9 @@ impl Tracer for NoopTracer { #[inline(always)] fn trace_nav(&mut self, _nav: Nav, _node: Node<'_>) {} + #[inline(always)] + fn trace_nav_failure(&mut self, _nav: Nav) {} + #[inline(always)] fn trace_match_success(&mut self, _node: Node<'_>) {} @@ -299,9 +305,24 @@ impl<'s> PrintTracer<'s> { self.entrypoint_by_ip.get(&ip).map_or("?", |s| s.as_str()) } + /// Format kind without text content. + /// + /// - Named nodes: `kind` (e.g., `identifier`) + /// - Anonymous nodes: `kind` dim green (e.g., `let`) + fn format_kind_simple(&self, kind: &str, is_named: bool) -> String { + if is_named { + kind.to_string() + } else { + let c = &self.colors; + format!("{}{}{}{}", c.dim, c.green, kind, c.reset) + } + } + /// Format kind with source text, dynamically truncated to fit content width. - /// Text is displayed dimmed and green, no quotes. - fn format_kind_with_text(&self, kind: &str, text: &str) -> String { + /// + /// - Named nodes: `kind text` (e.g., `identifier fetchData`) + /// - Anonymous nodes: just `text` in green (kind == text, no redundancy) + fn format_kind_with_text(&self, kind: &str, text: &str, is_named: bool) -> String { let c = &self.colors; // Available content width = TOTAL_WIDTH - prefix_width + step_width @@ -311,11 +332,16 @@ impl<'s> PrintTracer<'s> { // This simplifies to: TOTAL_WIDTH - 9 = 35 let available = cols::TOTAL_WIDTH - 9; - // Text budget = available - kind.len() - 1 (space), minimum 12 - let text_budget = available.saturating_sub(kind.len() + 1).max(12); - - let truncated = truncate_text(text, text_budget); - format!("{} {}{}{}{}", kind, c.dim, c.green, truncated, c.reset) + if is_named { + // Named: show kind + text + let text_budget = available.saturating_sub(kind.len() + 1).max(12); + let truncated = truncate_text(text, text_budget); + format!("{} {}{}{}{}", kind, c.dim, c.green, truncated, c.reset) + } else { + // Anonymous: just text dim green (kind == text, no redundancy) + let truncated = truncate_text(text, available); + format!("{}{}{}{}", c.dim, c.green, truncated, c.reset) + } } /// Format a runtime effect for display. @@ -369,16 +395,19 @@ impl<'s> PrintTracer<'s> { parts.push(format!("[{}]", pre.join(" "))); } - // Negated fields: !field1 !field2 - for field_id in m.neg_fields() { - let name = self.node_field_name(field_id); - parts.push(format!("!{name}")); - } + // Skip neg_fields and node pattern for epsilon (no node interaction) + if !m.is_epsilon() { + // Negated fields: !field1 !field2 + for field_id in m.neg_fields() { + let name = self.node_field_name(field_id); + parts.push(format!("!{name}")); + } - // Node pattern: field: (type) / (type) / field: _ / empty - let node_part = self.format_node_pattern(m); - if !node_part.is_empty() { - parts.push(node_part); + // Node pattern: field: (type) / (type) / field: _ / empty + let node_part = self.format_node_pattern(m); + if !node_part.is_empty() { + parts.push(node_part); + } } // Post-effects: [Effect1 Effect2] @@ -390,7 +419,7 @@ impl<'s> PrintTracer<'s> { parts.join(" ") } - /// Format node pattern: `field: (type)` or `(type)` or `field: _` or empty. + /// Format node pattern: `field: (type)` or `(type)` or `field: _` or `"text"` or empty. fn format_node_pattern(&self, m: &Match<'_>) -> String { let mut result = String::new(); @@ -399,12 +428,31 @@ impl<'s> PrintTracer<'s> { result.push_str(": "); } - if let Some(t) = m.node_type { - result.push('('); - result.push_str(self.node_type_name(t.get())); - result.push(')'); - } else if m.node_field.is_some() { - result.push('_'); + match m.node_type { + NodeTypeIR::Any => { + // Any node wildcard: `_` + result.push('_'); + } + NodeTypeIR::Named(None) => { + // Named wildcard: any named node + result.push_str("(_)"); + } + NodeTypeIR::Named(Some(t)) => { + // Specific named node type + result.push('('); + result.push_str(self.node_type_name(t.get())); + result.push(')'); + } + NodeTypeIR::Anonymous(None) => { + // Anonymous wildcard: any anonymous node + result.push_str("\"_\""); + } + NodeTypeIR::Anonymous(Some(t)) => { + // Specific anonymous node (literal token) + result.push('"'); + result.push_str(self.node_type_name(t.get())); + result.push('"'); + } } result @@ -500,6 +548,7 @@ impl Tracer for PrintTracer<'_> { let kind = node.kind(); let symbol = match nav { + Nav::Epsilon => Symbol::EPSILON, Nav::Down | Nav::DownSkip | Nav::DownExact => trace::NAV_DOWN, Nav::Next | Nav::NextSkip | Nav::NextExact => trace::NAV_NEXT, Nav::Up(_) | Nav::UpSkipTrivia(_) | Nav::UpExact(_) => trace::NAV_UP, @@ -509,23 +558,42 @@ impl Tracer for PrintTracer<'_> { // Text only in VeryVerbose if self.verbosity == Verbosity::VeryVerbose { let text = node.utf8_text(self.source).unwrap_or("?"); - let content = self.format_kind_with_text(kind, text); + let content = self.format_kind_with_text(kind, text, node.is_named()); self.add_subline(symbol, &content); } else { - self.add_subline(symbol, kind); + let content = self.format_kind_simple(kind, node.is_named()); + self.add_subline(symbol, &content); } } + fn trace_nav_failure(&mut self, nav: Nav) { + // Navigation failure sub-lines hidden in default verbosity + if self.verbosity == Verbosity::Default { + return; + } + + // Show the failed navigation direction + let nav_symbol = match nav { + Nav::Down | Nav::DownSkip | Nav::DownExact => "▽", + Nav::Next | Nav::NextSkip | Nav::NextExact => "▷", + Nav::Up(_) | Nav::UpSkipTrivia(_) | Nav::UpExact(_) => "△", + Nav::Stay | Nav::StayExact | Nav::Epsilon => "·", + }; + + self.add_subline(trace::MATCH_FAILURE, nav_symbol); + } + fn trace_match_success(&mut self, node: Node<'_>) { let kind = node.kind(); // Text on match/failure in Verbose+ if self.verbosity != Verbosity::Default { let text = node.utf8_text(self.source).unwrap_or("?"); - let content = self.format_kind_with_text(kind, text); + let content = self.format_kind_with_text(kind, text, node.is_named()); self.add_subline(trace::MATCH_SUCCESS, &content); } else { - self.add_subline(trace::MATCH_SUCCESS, kind); + let content = self.format_kind_simple(kind, node.is_named()); + self.add_subline(trace::MATCH_SUCCESS, &content); } } @@ -535,10 +603,11 @@ impl Tracer for PrintTracer<'_> { // Text on match/failure in Verbose+ if self.verbosity != Verbosity::Default { let text = node.utf8_text(self.source).unwrap_or("?"); - let content = self.format_kind_with_text(kind, text); + let content = self.format_kind_with_text(kind, text, node.is_named()); self.add_subline(trace::MATCH_FAILURE, &content); } else { - self.add_subline(trace::MATCH_FAILURE, kind); + let content = self.format_kind_simple(kind, node.is_named()); + self.add_subline(trace::MATCH_FAILURE, &content); } } diff --git a/crates/plotnik-lib/src/engine/verify.rs b/crates/plotnik-lib/src/engine/verify.rs index 233dcbfc..980a6043 100644 --- a/crates/plotnik-lib/src/engine/verify.rs +++ b/crates/plotnik-lib/src/engine/verify.rs @@ -4,7 +4,10 @@ //! Zero-cost in release builds. use crate::Colors; -use crate::bytecode::{Module, StringsView, TypeData, TypeId, TypeKind, TypesView}; +use crate::bytecode::{Module, TypeId}; +#[cfg(debug_assertions)] +use crate::bytecode::{StringsView, TypeData, TypeKind, TypesView}; +#[cfg(debug_assertions)] use crate::typegen::typescript::{self, Config, VoidType}; use super::Value; diff --git a/crates/plotnik-lib/src/engine/vm.rs b/crates/plotnik-lib/src/engine/vm.rs index ca9712ed..c59b2f5e 100644 --- a/crates/plotnik-lib/src/engine/vm.rs +++ b/crates/plotnik-lib/src/engine/vm.rs @@ -2,21 +2,24 @@ use arborium_tree_sitter::{Node, Tree}; -use crate::bytecode::NAMED_WILDCARD; use crate::bytecode::{ - Call, EffectOp, EffectOpcode, Entrypoint, Instruction, Match, Module, Nav, StepAddr, Trampoline, + Call, EffectOp, EffectOpcode, Entrypoint, Instruction, Match, Module, Nav, NodeTypeIR, + StepAddr, Trampoline, }; /// Get the nav for continue_search (always a sibling move). -/// Up/Stay variants return Next as a default since they don't do sibling search. +/// Up/Stay/Epsilon variants return Next as a default since they don't do sibling search. fn continuation_nav(nav: Nav) -> Nav { match nav { Nav::Down | Nav::Next => Nav::Next, Nav::DownSkip | Nav::NextSkip => Nav::NextSkip, Nav::DownExact | Nav::NextExact => Nav::NextExact, - Nav::Up(_) | Nav::UpSkipTrivia(_) | Nav::UpExact(_) | Nav::Stay | Nav::StayExact => { - Nav::Next - } + Nav::Epsilon + | Nav::Up(_) + | Nav::UpSkipTrivia(_) + | Nav::UpExact(_) + | Nav::Stay + | Nav::StayExact => Nav::Next, } } @@ -25,13 +28,18 @@ use super::cursor::{CursorWrapper, SkipPolicy}; /// Derive skip policy from navigation mode without navigating. /// Used when retrying a Call to determine the policy for the next checkpoint. -/// Stay/Up variants return None since they don't retry among siblings. +/// Epsilon/Stay/Up variants return None since they don't retry among siblings. fn skip_policy_for_nav(nav: Nav) -> Option { match nav { Nav::Down | Nav::Next => Some(SkipPolicy::Any), Nav::DownSkip | Nav::NextSkip => Some(SkipPolicy::Trivia), Nav::DownExact | Nav::NextExact => Some(SkipPolicy::Exact), - Nav::Stay | Nav::StayExact | Nav::Up(_) | Nav::UpSkipTrivia(_) | Nav::UpExact(_) => None, + Nav::Epsilon + | Nav::Stay + | Nav::StayExact + | Nav::Up(_) + | Nav::UpSkipTrivia(_) + | Nav::UpExact(_) => None, } } use super::effect::{EffectLog, RuntimeEffect}; @@ -284,6 +292,7 @@ impl<'t> VM<'t> { tracer: &mut T, ) -> Result<(), RuntimeError> { let Some(policy) = self.cursor.navigate(m.nav) else { + tracer.trace_nav_failure(m.nav); return self.backtrack(tracer); }; tracer.trace_nav(m.nav, self.cursor.node()); @@ -315,22 +324,48 @@ impl<'t> VM<'t> { /// Check if current node matches type and field constraints. fn node_matches(&self, m: Match<'_>, tracer: &mut T) -> bool { - if let Some(expected) = m.node_type { - if expected.get() == NAMED_WILDCARD { - // Special case: `(_)` wildcard matches any named node - if !self.cursor.node().is_named() { - tracer.trace_match_failure(self.cursor.node()); + let node = self.cursor.node(); + + // Check node type constraint + match m.node_type { + NodeTypeIR::Any => { + // Any node matches - no check needed + } + NodeTypeIR::Named(None) => { + // `(_)` wildcard: must be a named node + if !node.is_named() { + tracer.trace_match_failure(node); + return false; + } + } + NodeTypeIR::Named(Some(expected)) => { + // Specific named type: check kind_id + if node.kind_id() != expected.get() { + tracer.trace_match_failure(node); + return false; + } + } + NodeTypeIR::Anonymous(None) => { + // Any anonymous node: must NOT be named + if node.is_named() { + tracer.trace_match_failure(node); + return false; + } + } + NodeTypeIR::Anonymous(Some(expected)) => { + // Specific anonymous type: check kind_id + if node.kind_id() != expected.get() { + tracer.trace_match_failure(node); return false; } - } else if self.cursor.node().kind_id() != expected.get() { - tracer.trace_match_failure(self.cursor.node()); - return false; } } + + // Check field constraint if let Some(expected) = m.node_field && self.cursor.field_id() != Some(expected) { - tracer.trace_field_failure(self.cursor.node()); + tracer.trace_field_failure(node); return false; } true @@ -427,6 +462,7 @@ impl<'t> VM<'t> { } let Some(policy) = self.cursor.navigate(nav) else { + tracer.trace_nav_failure(nav); return Err(self.backtrack(tracer).unwrap_err()); }; tracer.trace_nav(nav, self.cursor.node()); diff --git a/docs/binary-format/06-transitions.md b/docs/binary-format/06-transitions.md index 7e2c7b55..04468bbb 100644 --- a/docs/binary-format/06-transitions.md +++ b/docs/binary-format/06-transitions.md @@ -13,23 +13,34 @@ Multi-step instructions (Match16–Match64) consume consecutive StepIds. A Match ### Future: Segment-Based Addressing -The `type_id` byte reserves 4 bits for segment selection, enabling future expansion to 16 segments × 512 KB = 8 MB. Currently, only segment 0 is used. Compilers must emit `segment = 0`; runtimes should reject non-zero segments until implemented. +The `type_id` byte reserves 2 bits for segment selection, enabling future expansion to 4 segments × 512 KB = 2 MB. Currently, only segment 0 is used. Compilers must emit `segment = 0`; runtimes should reject non-zero segments until implemented. When implemented: Address = `(segment * 512KB) + (StepId * 8)`. Each instruction's successors must reside in the same segment; cross-segment jumps require trampoline steps. ## 2. Step Types -The first byte of every step encodes segment and opcode: +The first byte of every step encodes segment, node kind, and opcode: ```text type_id (u8) -┌────────────┬────────────┐ -│ segment(4) │ opcode(4) │ -└────────────┴────────────┘ +┌───────────┬──────────────┬────────────┐ +│ segment(2)│ node_kind(2) │ opcode(4) │ +└───────────┴──────────────┴────────────┘ + bits 7-6 bits 5-4 bits 3-0 ``` -- **Bits 4-7 (Segment)**: Reserved for future multi-segment addressing. Must be 0. -- **Bits 0-3 (Opcode)**: Step type and size. +- **Bits 7-6 (Segment)**: Reserved for future multi-segment addressing. Must be 0. +- **Bits 5-4 (NodeKind)**: Node type constraint category for Match instructions. Ignored for Call/Return/Trampoline. +- **Bits 3-0 (Opcode)**: Step type and size. + +**NodeKind values** (for Match instructions): + +| Value | Name | Meaning | +| :---- | :-------- | :----------------------------------------- | +| `00` | Any | No type check (`_` pattern) | +| `01` | Named | Named node check (`(_)` or `(identifier)`) | +| `10` | Anonymous | Anonymous node check (`"text"` literals) | +| `11` | Reserved | Reserved for future use | | Opcode | Name | Size | Description | | :----- | :------ | :------- | :----------------------------------- | @@ -64,14 +75,15 @@ Bit-packed navigation command. **Standard Modes**: -- `0`: `Stay` (No movement) -- `1`: `StayExact` (No movement, exact match only) -- `2`: `Next` (Sibling, skip any) -- `3`: `NextSkip` (Sibling, skip trivia) -- `4`: `NextExact` (Sibling, exact) -- `5`: `Down` (Child, skip any) -- `6`: `DownSkip` (Child, skip trivia) -- `7`: `DownExact` (Child, exact) +- `0`: `Epsilon` (Pure control flow, no cursor movement or node check) +- `1`: `Stay` (No movement) +- `2`: `StayExact` (No movement, exact match only) +- `3`: `Next` (Sibling, skip any) +- `4`: `NextSkip` (Sibling, skip trivia) +- `5`: `NextExact` (Sibling, exact) +- `6`: `Down` (Child, skip any) +- `7`: `DownSkip` (Child, skip trivia) +- `8`: `DownExact` (Child, exact) ### 3.2. EffectOp (u16) @@ -125,15 +137,21 @@ Optimized fast-path transition. Used when there are no side effects, no negated ```rust #[repr(C)] struct Match8 { - type_id: u8, // segment(4) | 0x0 + type_id: u8, // segment(2) | node_kind(2) | 0x0 nav: u8, // Nav - node_type: Option, // None (0) means "any" - node_field: Option, // None (0) means "any" + node_type: u16, // Type ID (interpretation depends on node_kind) + node_field: Option, // None (0) means "any field" next: u16, // Next StepId. 0 = Accept. } ``` -**Note**: The value 0 indicates wildcard (no constraint). +**NodeKind + node_type interpretation**: + +| `node_kind` | `node_type=0` | `node_type>0` | +| :----------- | :------------------ | :------------------------- | +| `00` (Any) | No check (any node) | Invalid | +| `01` (Named) | Check `is_named()` | Check `kind_id() == value` | +| `10` (Anon) | Check `!is_named()` | Check `kind_id() == value` | **Linked vs Unlinked Interpretation**: @@ -156,10 +174,10 @@ Extended transitions with inline payload. Used for side effects, negated fields, ```rust #[repr(C)] struct MatchHeader { - type_id: u8, // segment(4) | opcode(1-5) + type_id: u8, // segment(2) | node_kind(2) | opcode(1-5) nav: u8, // Nav - node_type: Option, // None (0) means "any" - node_field: Option, // None (0) means "any" + node_type: u16, // Type ID (interpretation depends on node_kind) + node_field: Option, // None (0) means "any field" counts: u16, // Bit-packed element counts } ``` @@ -229,11 +247,18 @@ The compiler places effects based on semantic requirements: scope openers often ### 4.3. Epsilon Transitions -A Match8 or Match16–64 with `node_type: None`, `node_field: None`, and `nav: Stay` is an **epsilon transition**—it succeeds unconditionally without cursor interaction. This enables: +A Match instruction with `nav == Epsilon` is an **epsilon transition**—it succeeds unconditionally without cursor movement or node checking. The VM skips navigation and node matching entirely, only executing effects and proceeding to successors. This enables: - **Branching at EOF**: `(a)?` must succeed when no node exists to match. - **Pure control flow**: Decision points for quantifiers. -- **Trailing navigation**: Queries ending with `Up(n)` to restore cursor position. +- **Effect-only steps**: Scope openers/closers (`Obj`, `EndObj`) without node interaction. + +When `nav == Epsilon`: + +- No cursor movement occurs +- No node type or field checks are performed +- `node_kind`, `node_type`, and `node_field` are ignored +- Only `pre_effects`, `post_effects`, and successors are meaningful ### 4.4. Call @@ -242,7 +267,7 @@ Invokes another definition (recursion). Executes navigation (with optional field ```rust #[repr(C)] struct Call { - type_id: u8, // segment(4) | 0x6 + type_id: u8, // segment(2) | 0 | 0x6 nav: u8, // Nav node_field: Option, // None (0) means "any" next: u16, // Return address (StepId, current segment) @@ -251,7 +276,7 @@ struct Call { ``` - **Nav + Field**: Call handles navigation and field constraint. The callee's first Match checks node type. This allows `field: (Ref)` patterns to check field and type on the same node. -- **Target Segment**: Defined by `type_id >> 4`. +- **Target Segment**: Defined by `(type_id >> 6) & 0x3`. - **Return Segment**: Implicitly the current segment. ### 4.5. Return @@ -261,7 +286,7 @@ Returns from a definition. Pops the return address from the call stack. ```rust #[repr(C)] struct Return { - type_id: u8, // segment(4) | 0x7 + type_id: u8, // segment(2) | 0 | 0x7 _pad: [u8; 7], } ``` @@ -270,25 +295,33 @@ struct Return { ### 5.1. Match8 Execution -1. Execute `nav` movement. -2. Check `node_type` (if not wildcard). -3. Check `node_field` (if not wildcard). -4. On failure: backtrack. -5. On success: if `next == 0` → accept; else `ip = next`. +1. If `nav == Epsilon`: skip steps 2-4, go directly to step 5. +2. Execute `nav` movement. +3. Check `node_type` according to `node_kind`: + - `Any`: no check + - `Named(0)`: check `is_named()` + - `Named(id)`: check `kind_id() == id` + - `Anonymous(0)`: check `!is_named()` + - `Anonymous(id)`: check `kind_id() == id` +4. Check `node_field` (if not 0). +5. On failure: backtrack. +6. On success: if `next == 0` → accept; else `ip = next`. ### 5.2. Match16–64 Execution 1. Execute `pre_effects`. -2. Clear `matched_node`. -3. Execute `nav` movement (skip for epsilon transitions). -4. Check `node_type` and `node_field` (skip for epsilon). -5. On success: `matched_node = cursor.node()`. -6. Verify all `negated_fields` are absent on current node. -7. Execute `post_effects`. -8. Continuation: - - `succ_count == 0` → accept. - - `succ_count == 1` → `ip = successors[0]`. - - `succ_count >= 2` → push checkpoints for `successors[1..n]`, execute `successors[0]`. +2. If `nav == Epsilon`: skip steps 3-6, go to step 7. +3. Clear `matched_node`. +4. Execute `nav` movement. +5. Check `node_type` according to `node_kind` (see Match8 Execution). +6. Check `node_field` (if not 0). +7. On success: `matched_node = cursor.node()`. +8. Verify all `negated_fields` are absent on current node. +9. Execute `post_effects`. +10. Continuation: + - `succ_count == 0` → accept. + - `succ_count == 1` → `ip = successors[0]`. + - `succ_count >= 2` → push checkpoints for `successors[1..n]`, execute `successors[0]`. ### 5.3. Backtracking