plotnik_lib/ir/
emit.rs

1//! Query emitter: transforms BuildGraph + TypeInferenceResult into CompiledQuery.
2//!
3//! Three-pass construction:
4//! 1. Analysis: count elements, intern strings, collect data
5//! 2. Layout: compute aligned offsets, allocate once
6//! 3. Emission: write via ptr::write
7
8use std::collections::HashMap;
9use std::ptr;
10
11use super::compiled::{CompiledQuery, CompiledQueryBuffer, align_up};
12use super::ids::{NodeFieldId, NodeTypeId, RefId, StringId, TYPE_NODE, TransitionId};
13use super::strings::StringInterner;
14use super::{
15    EffectOp, Entrypoint, MAX_INLINE_SUCCESSORS, Matcher, RefTransition, Slice, StringRef,
16    Transition, TypeDef, TypeMember,
17};
18
19use crate::query::graph::{BuildEffect, BuildGraph, BuildMatcher, BuildNode, RefMarker};
20use crate::query::infer::TypeInferenceResult;
21
22/// Callback for resolving node kind names to IDs.
23pub trait NodeKindResolver {
24    /// Resolves a named node kind to its ID. Returns `None` if unknown.
25    fn resolve_kind(&self, name: &str) -> Option<NodeTypeId>;
26
27    /// Resolves a field name to its ID. Returns `None` if unknown.
28    fn resolve_field(&self, name: &str) -> Option<NodeFieldId>;
29}
30
31/// A resolver that always fails (for testing without tree-sitter).
32pub struct NullResolver;
33
34impl NodeKindResolver for NullResolver {
35    fn resolve_kind(&self, _name: &str) -> Option<NodeTypeId> {
36        None
37    }
38    fn resolve_field(&self, _name: &str) -> Option<NodeFieldId> {
39        None
40    }
41}
42
43/// Map-based resolver for testing.
44pub struct MapResolver {
45    kinds: HashMap<String, NodeTypeId>,
46    fields: HashMap<String, NodeFieldId>,
47}
48
49impl MapResolver {
50    pub fn new() -> Self {
51        Self {
52            kinds: HashMap::new(),
53            fields: HashMap::new(),
54        }
55    }
56
57    pub fn add_kind(&mut self, name: impl Into<String>, id: NodeTypeId) {
58        self.kinds.insert(name.into(), id);
59    }
60
61    pub fn add_field(&mut self, name: impl Into<String>, id: NodeFieldId) {
62        self.fields.insert(name.into(), id);
63    }
64}
65
66impl Default for MapResolver {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl NodeKindResolver for MapResolver {
73    fn resolve_kind(&self, name: &str) -> Option<NodeTypeId> {
74        self.kinds.get(name).copied()
75    }
76
77    fn resolve_field(&self, name: &str) -> Option<NodeFieldId> {
78        self.fields.get(name).copied()
79    }
80}
81
82/// Query emitter error.
83#[derive(Debug, Clone)]
84pub enum EmitError {
85    /// Unknown node kind encountered.
86    UnknownNodeKind(String),
87    /// Unknown field name encountered.
88    UnknownField(String),
89    /// Too many transitions (exceeds u32::MAX).
90    TooManyTransitions,
91    /// Too many successors (exceeds u32::MAX).
92    TooManySuccessors,
93    /// Too many effects (exceeds u32::MAX).
94    TooManyEffects,
95    /// Internal consistency error.
96    InternalError(String),
97}
98
99impl std::fmt::Display for EmitError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            EmitError::UnknownNodeKind(s) => write!(f, "unknown node kind: {}", s),
103            EmitError::UnknownField(s) => write!(f, "unknown field: {}", s),
104            EmitError::TooManyTransitions => write!(f, "too many transitions"),
105            EmitError::TooManySuccessors => write!(f, "too many successors"),
106            EmitError::TooManyEffects => write!(f, "too many effects"),
107            EmitError::InternalError(s) => write!(f, "internal error: {}", s),
108        }
109    }
110}
111
112impl std::error::Error for EmitError {}
113
114/// Result type for emit operations.
115pub type EmitResult<T> = Result<T, EmitError>;
116
117/// Emitter state during analysis phase.
118struct EmitContext<'src, 'g> {
119    graph: &'g BuildGraph<'src>,
120    type_info: &'g TypeInferenceResult<'src>,
121    strings: StringInterner<'src>,
122
123    // Collected data
124    effects: Vec<EffectOp>,
125    negated_fields: Vec<NodeFieldId>,
126    /// Spilled successors (for transitions with >8 successors)
127    spilled_successors: Vec<TransitionId>,
128
129    // Maps from BuildGraph to IR
130    /// For each transition, its effects slice
131    transition_effects: Vec<Slice<EffectOp>>,
132    /// For each transition, its negated fields slice
133    transition_negated_fields: Vec<Slice<NodeFieldId>>,
134    /// For each transition, if successors spill: (start_index in spilled_successors, count)
135    transition_spilled: Vec<Option<(u32, u32)>>,
136}
137
138impl<'src, 'g> EmitContext<'src, 'g> {
139    fn new(graph: &'g BuildGraph<'src>, type_info: &'g TypeInferenceResult<'src>) -> Self {
140        let node_count = graph.len();
141        Self {
142            graph,
143            type_info,
144            strings: StringInterner::new(),
145            effects: Vec::new(),
146            negated_fields: Vec::new(),
147            spilled_successors: Vec::new(),
148            transition_effects: Vec::with_capacity(node_count),
149            transition_negated_fields: Vec::with_capacity(node_count),
150            transition_spilled: Vec::with_capacity(node_count),
151        }
152    }
153
154    fn intern(&mut self, s: &'src str) -> StringId {
155        self.strings.intern(s)
156    }
157}
158
159/// Layout information computed in pass 2.
160struct LayoutInfo {
161    buffer_len: usize,
162    successors_offset: u32,
163    effects_offset: u32,
164    negated_fields_offset: u32,
165    string_refs_offset: u32,
166    string_bytes_offset: u32,
167    type_defs_offset: u32,
168    type_members_offset: u32,
169    entrypoints_offset: u32,
170    trivia_kinds_offset: u32,
171
172    // Counts
173    transition_count: u32,
174    successor_count: u32,
175    effect_count: u32,
176    negated_field_count: u16,
177    string_ref_count: u16,
178    type_def_count: u16,
179    type_member_count: u16,
180    entrypoint_count: u16,
181    trivia_kind_count: u16,
182}
183
184/// Emits a compiled query from a BuildGraph.
185pub struct QueryEmitter<'src, 'g, R> {
186    ctx: EmitContext<'src, 'g>,
187    resolver: R,
188    trivia_kinds: Vec<NodeTypeId>,
189}
190
191impl<'src, 'g, R: NodeKindResolver> QueryEmitter<'src, 'g, R> {
192    /// Creates a new emitter.
193    pub fn new(
194        graph: &'g BuildGraph<'src>,
195        type_info: &'g TypeInferenceResult<'src>,
196        resolver: R,
197    ) -> Self {
198        Self {
199            ctx: EmitContext::new(graph, type_info),
200            resolver,
201            trivia_kinds: Vec::new(),
202        }
203    }
204
205    /// Sets trivia node kinds (e.g., comments) to skip during execution.
206    pub fn with_trivia_kinds(mut self, kinds: Vec<NodeTypeId>) -> Self {
207        self.trivia_kinds = kinds;
208        self
209    }
210
211    /// Emits the compiled query.
212    pub fn emit(mut self) -> EmitResult<CompiledQuery> {
213        // Pass 1: Analysis
214        self.analyze()?;
215
216        // Pass 2: Layout
217        let layout = self.compute_layout()?;
218
219        // Pass 3: Emission
220        self.emit_buffer(layout)
221    }
222
223    fn analyze(&mut self) -> EmitResult<()> {
224        // Pre-intern definition names for entrypoints
225        for (name, _) in self.ctx.graph.definitions() {
226            self.ctx.intern(name);
227        }
228
229        // Pre-intern type names
230        for type_def in &self.ctx.type_info.type_defs {
231            if let Some(name) = type_def.name {
232                self.ctx.intern(name);
233            }
234            for member in &type_def.members {
235                self.ctx.intern(member.name);
236            }
237        }
238
239        // Analyze each transition
240        for (_, node) in self.ctx.graph.iter() {
241            self.analyze_node(node)?;
242        }
243
244        Ok(())
245    }
246
247    fn analyze_node(&mut self, node: &BuildNode<'src>) -> EmitResult<()> {
248        // Collect effects
249        let effects_start = self.ctx.effects.len() as u32;
250        for effect in &node.effects {
251            let ir_effect = self.convert_effect(effect)?;
252            self.ctx.effects.push(ir_effect);
253        }
254        let effects_len = (self.ctx.effects.len() as u32 - effects_start) as u16;
255        self.ctx
256            .transition_effects
257            .push(Slice::new(effects_start, effects_len));
258
259        // Collect negated fields
260        let negated_start = self.ctx.negated_fields.len() as u32;
261        if let BuildMatcher::Node { negated_fields, .. } = &node.matcher {
262            for field_name in negated_fields {
263                let field_id = self
264                    .resolver
265                    .resolve_field(field_name)
266                    .ok_or_else(|| EmitError::UnknownField((*field_name).to_string()))?;
267                self.ctx.negated_fields.push(field_id);
268            }
269        }
270        let negated_len = (self.ctx.negated_fields.len() as u32 - negated_start) as u16;
271        self.ctx
272            .transition_negated_fields
273            .push(Slice::new(negated_start, negated_len));
274
275        // Check if successors need to spill
276        if node.successors.len() > MAX_INLINE_SUCCESSORS {
277            let start = self.ctx.spilled_successors.len() as u32;
278            for &succ in &node.successors {
279                self.ctx.spilled_successors.push(succ);
280            }
281            self.ctx
282                .transition_spilled
283                .push(Some((start, node.successors.len() as u32)));
284        } else {
285            self.ctx.transition_spilled.push(None);
286        }
287
288        Ok(())
289    }
290
291    fn convert_effect(&mut self, effect: &BuildEffect<'src>) -> EmitResult<EffectOp> {
292        Ok(match effect {
293            BuildEffect::CaptureNode => EffectOp::CaptureNode,
294            BuildEffect::ClearCurrent => EffectOp::ClearCurrent,
295            BuildEffect::StartArray { .. } => EffectOp::StartArray,
296            BuildEffect::PushElement => EffectOp::PushElement,
297            BuildEffect::EndArray => EffectOp::EndArray,
298            BuildEffect::StartObject { .. } => EffectOp::StartObject,
299            BuildEffect::EndObject => EffectOp::EndObject,
300            BuildEffect::Field { name, .. } => {
301                let id = self.ctx.intern(name);
302                EffectOp::Field(id)
303            }
304            BuildEffect::StartVariant(tag) => {
305                let id = self.ctx.intern(tag);
306                EffectOp::StartVariant(id)
307            }
308            BuildEffect::EndVariant => EffectOp::EndVariant,
309            BuildEffect::ToString => EffectOp::ToString,
310        })
311    }
312
313    fn compute_layout(&self) -> EmitResult<LayoutInfo> {
314        let transition_count = self.ctx.graph.len() as u32;
315        let successor_count = self.ctx.spilled_successors.len() as u32;
316        let effect_count = self.ctx.effects.len() as u32;
317        let negated_field_count = self.ctx.negated_fields.len() as u16;
318        let string_ref_count = self.ctx.strings.len() as u16;
319        let type_def_count = self.ctx.type_info.type_defs.len() as u16;
320        let type_member_count: u16 = self
321            .ctx
322            .type_info
323            .type_defs
324            .iter()
325            .map(|td| td.members.len() as u16)
326            .sum();
327        let entrypoint_count = self.ctx.graph.definitions().count() as u16;
328        let trivia_kind_count = self.trivia_kinds.len() as u16;
329
330        // Compute offsets with proper alignment
331        let mut offset: u32 = 0;
332
333        // Transitions at offset 0, 64-byte aligned
334        offset += transition_count * 64;
335
336        // Successors: align 4
337        let successors_offset = align_up(offset, 4);
338        offset = successors_offset + successor_count * 4;
339
340        // Effects: align 4 (EffectOp is 4 bytes with repr(C, u16) but discriminant+payload)
341        let effects_offset = align_up(offset, 4);
342        offset = effects_offset + effect_count * 4;
343
344        // Negated fields: align 2
345        let negated_fields_offset = align_up(offset, 2);
346        offset = negated_fields_offset + (negated_field_count as u32) * 2;
347
348        // String refs: align 4
349        let string_refs_offset = align_up(offset, 4);
350        offset = string_refs_offset + (string_ref_count as u32) * 8;
351
352        // String bytes: align 1
353        let string_bytes_offset = offset;
354        offset += self.ctx.strings.total_bytes() as u32;
355
356        // Type defs: align 4
357        let type_defs_offset = align_up(offset, 4);
358        offset = type_defs_offset + (type_def_count as u32) * 12;
359
360        // Type members: align 2
361        let type_members_offset = align_up(offset, 2);
362        offset = type_members_offset + (type_member_count as u32) * 4;
363
364        // Entrypoints: align 4
365        let entrypoints_offset = align_up(offset, 4);
366        offset = entrypoints_offset + (entrypoint_count as u32) * 12;
367
368        // Trivia kinds: align 2
369        let trivia_kinds_offset = if trivia_kind_count > 0 {
370            let aligned = align_up(offset, 2);
371            offset = aligned + (trivia_kind_count as u32) * 2;
372            aligned
373        } else {
374            0
375        };
376
377        // Final buffer size, aligned to 64 for potential mmap
378        let buffer_len = align_up(offset, 64) as usize;
379
380        Ok(LayoutInfo {
381            buffer_len,
382            successors_offset,
383            effects_offset,
384            negated_fields_offset,
385            string_refs_offset,
386            string_bytes_offset,
387            type_defs_offset,
388            type_members_offset,
389            entrypoints_offset,
390            trivia_kinds_offset,
391            transition_count,
392            successor_count,
393            effect_count,
394            negated_field_count,
395            string_ref_count,
396            type_def_count,
397            type_member_count,
398            entrypoint_count,
399            trivia_kind_count,
400        })
401    }
402
403    fn emit_buffer(self, layout: LayoutInfo) -> EmitResult<CompiledQuery> {
404        let mut buffer = CompiledQueryBuffer::allocate(layout.buffer_len);
405        let base = buffer.as_mut_ptr();
406
407        // Emit transitions
408        self.emit_transitions(base, &layout)?;
409
410        // Emit successors
411        self.emit_successors(base, &layout);
412
413        // Emit effects
414        self.emit_effects(base, &layout);
415
416        // Emit negated fields
417        self.emit_negated_fields(base, &layout);
418
419        // Emit strings
420        self.emit_strings(base, &layout);
421
422        // Emit type metadata
423        self.emit_types(base, &layout);
424
425        // Emit entrypoints
426        self.emit_entrypoints(base, &layout)?;
427
428        // Emit trivia kinds
429        self.emit_trivia_kinds(base, &layout);
430
431        Ok(CompiledQuery::new(
432            buffer,
433            layout.successors_offset,
434            layout.effects_offset,
435            layout.negated_fields_offset,
436            layout.string_refs_offset,
437            layout.string_bytes_offset,
438            layout.type_defs_offset,
439            layout.type_members_offset,
440            layout.entrypoints_offset,
441            layout.trivia_kinds_offset,
442            layout.transition_count,
443            layout.successor_count,
444            layout.effect_count,
445            layout.negated_field_count,
446            layout.string_ref_count,
447            layout.type_def_count,
448            layout.type_member_count,
449            layout.entrypoint_count,
450            layout.trivia_kind_count,
451        ))
452    }
453
454    fn emit_transitions(&self, base: *mut u8, _layout: &LayoutInfo) -> EmitResult<()> {
455        let transitions_ptr = base as *mut Transition;
456
457        for (idx, (_, node)) in self.ctx.graph.iter().enumerate() {
458            let transition = self.build_transition(node, idx)?;
459            // SAFETY: buffer is properly sized and aligned
460            unsafe {
461                ptr::write(transitions_ptr.add(idx), transition);
462            }
463        }
464
465        Ok(())
466    }
467
468    fn build_transition(&self, node: &BuildNode<'src>, idx: usize) -> EmitResult<Transition> {
469        let matcher = self.convert_matcher(&node.matcher)?;
470        let ref_marker = self.convert_ref_marker(&node.ref_marker);
471        let effects = self.ctx.transition_effects[idx];
472        let negated_fields_slice = self.ctx.transition_negated_fields[idx];
473
474        // Build successor data
475        let (successor_count, successor_data) =
476            if let Some((start, count)) = self.ctx.transition_spilled[idx] {
477                // Spilled: store index in successor_data[0]
478                let mut data = [0u32; MAX_INLINE_SUCCESSORS];
479                data[0] = start;
480                (count, data)
481            } else {
482                // Inline
483                let mut data = [0u32; MAX_INLINE_SUCCESSORS];
484                for (i, &succ) in node.successors.iter().enumerate() {
485                    data[i] = succ;
486                }
487                (node.successors.len() as u32, data)
488            };
489
490        // Inject negated_fields into matcher if applicable
491        let matcher = match matcher {
492            Matcher::Node { kind, field, .. } => Matcher::Node {
493                kind,
494                field,
495                negated_fields: negated_fields_slice,
496            },
497            Matcher::Anonymous { kind, field, .. } => Matcher::Anonymous {
498                kind,
499                field,
500                negated_fields: Slice::empty(),
501            },
502            other => other,
503        };
504
505        let transition = Transition::new(
506            matcher,
507            ref_marker,
508            node.nav,
509            effects,
510            successor_count,
511            successor_data,
512        );
513
514        Ok(transition)
515    }
516
517    fn convert_matcher(&self, matcher: &BuildMatcher<'src>) -> EmitResult<Matcher> {
518        Ok(match matcher {
519            BuildMatcher::Epsilon => Matcher::Epsilon,
520            BuildMatcher::Node { kind, field, .. } => {
521                let kind_id = self
522                    .resolver
523                    .resolve_kind(kind)
524                    .ok_or_else(|| EmitError::UnknownNodeKind((*kind).to_string()))?;
525                let field_id = match field {
526                    Some(f) => self.resolver.resolve_field(f),
527                    None => None,
528                };
529                Matcher::Node {
530                    kind: kind_id,
531                    field: field_id,
532                    negated_fields: Slice::empty(), // Will be filled in build_transition
533                }
534            }
535            BuildMatcher::Anonymous { literal, field } => {
536                // For anonymous nodes, we use the literal as a synthetic kind ID
537                // In practice, this would be resolved differently
538                let kind_id = self.resolver.resolve_kind(literal).unwrap_or(0);
539                let field_id = match field {
540                    Some(f) => self.resolver.resolve_field(f),
541                    None => None,
542                };
543                Matcher::Anonymous {
544                    kind: kind_id,
545                    field: field_id,
546                    negated_fields: Slice::empty(),
547                }
548            }
549            BuildMatcher::Wildcard { field } => {
550                // Wildcard doesn't use field in IR representation
551                let _ = field;
552                Matcher::Wildcard
553            }
554        })
555    }
556
557    fn convert_ref_marker(&self, marker: &RefMarker) -> RefTransition {
558        match marker {
559            RefMarker::None => RefTransition::None,
560            RefMarker::Enter { ref_id } => RefTransition::Enter(*ref_id as RefId),
561            RefMarker::Exit { ref_id } => RefTransition::Exit(*ref_id as RefId),
562        }
563    }
564
565    fn emit_successors(&self, base: *mut u8, layout: &LayoutInfo) {
566        if self.ctx.spilled_successors.is_empty() {
567            return;
568        }
569
570        let ptr = unsafe { base.add(layout.successors_offset as usize) } as *mut TransitionId;
571        for (i, &succ) in self.ctx.spilled_successors.iter().enumerate() {
572            unsafe {
573                ptr::write(ptr.add(i), succ);
574            }
575        }
576    }
577
578    fn emit_effects(&self, base: *mut u8, layout: &LayoutInfo) {
579        if self.ctx.effects.is_empty() {
580            return;
581        }
582
583        let ptr = unsafe { base.add(layout.effects_offset as usize) } as *mut EffectOp;
584        for (i, effect) in self.ctx.effects.iter().enumerate() {
585            unsafe {
586                ptr::write(ptr.add(i), *effect);
587            }
588        }
589    }
590
591    fn emit_negated_fields(&self, base: *mut u8, layout: &LayoutInfo) {
592        if self.ctx.negated_fields.is_empty() {
593            return;
594        }
595
596        let ptr = unsafe { base.add(layout.negated_fields_offset as usize) } as *mut NodeFieldId;
597        for (i, &field) in self.ctx.negated_fields.iter().enumerate() {
598            unsafe {
599                ptr::write(ptr.add(i), field);
600            }
601        }
602    }
603
604    fn emit_strings(&self, base: *mut u8, layout: &LayoutInfo) {
605        // Emit string refs
606        let refs_ptr = unsafe { base.add(layout.string_refs_offset as usize) } as *mut StringRef;
607        let bytes_ptr = unsafe { base.add(layout.string_bytes_offset as usize) };
608
609        let mut byte_offset: u32 = 0;
610        for (i, (_, s)) in self.ctx.strings.iter().enumerate() {
611            // Write StringRef
612            let string_ref = StringRef::new(byte_offset, s.len() as u16);
613            unsafe {
614                ptr::write(refs_ptr.add(i), string_ref);
615            }
616
617            // Write string bytes
618            unsafe {
619                ptr::copy_nonoverlapping(s.as_ptr(), bytes_ptr.add(byte_offset as usize), s.len());
620            }
621
622            byte_offset += s.len() as u32;
623        }
624    }
625
626    fn emit_types(&self, base: *mut u8, layout: &LayoutInfo) {
627        let defs_ptr = unsafe { base.add(layout.type_defs_offset as usize) } as *mut TypeDef;
628        let members_ptr =
629            unsafe { base.add(layout.type_members_offset as usize) } as *mut TypeMember;
630
631        let mut member_idx: u32 = 0;
632
633        for (i, type_def) in self.ctx.type_info.type_defs.iter().enumerate() {
634            let name_id = type_def
635                .name
636                .and_then(|n| self.ctx.strings.get(n))
637                .unwrap_or(super::ids::STRING_NONE);
638
639            let ir_def = if let Some(inner) = type_def.inner_type {
640                TypeDef::wrapper(type_def.kind, inner)
641            } else {
642                let members_start = member_idx;
643                let members_len = type_def.members.len() as u16;
644
645                // Emit members
646                for member in &type_def.members {
647                    let member_name_id = self
648                        .ctx
649                        .strings
650                        .get(member.name)
651                        .expect("member name should be interned");
652                    let ir_member = TypeMember::new(member_name_id, member.ty);
653                    unsafe {
654                        ptr::write(members_ptr.add(member_idx as usize), ir_member);
655                    }
656                    member_idx += 1;
657                }
658
659                TypeDef::composite(
660                    type_def.kind,
661                    name_id,
662                    Slice::new(members_start, members_len),
663                )
664            };
665
666            unsafe {
667                ptr::write(defs_ptr.add(i), ir_def);
668            }
669        }
670    }
671
672    fn emit_entrypoints(&self, base: *mut u8, layout: &LayoutInfo) -> EmitResult<()> {
673        let ptr = unsafe { base.add(layout.entrypoints_offset as usize) } as *mut Entrypoint;
674
675        for (i, (name, entry_node)) in self.ctx.graph.definitions().enumerate() {
676            let name_id = self
677                .ctx
678                .strings
679                .get(name)
680                .expect("definition name should be interned");
681
682            // Look up the result type for this definition
683            let result_type = self
684                .ctx
685                .type_info
686                .entrypoint_types
687                .get(name)
688                .copied()
689                .unwrap_or(TYPE_NODE);
690
691            let entrypoint = Entrypoint::new(name_id, entry_node, result_type);
692            unsafe {
693                ptr::write(ptr.add(i), entrypoint);
694            }
695        }
696
697        Ok(())
698    }
699
700    fn emit_trivia_kinds(&self, base: *mut u8, layout: &LayoutInfo) {
701        if self.trivia_kinds.is_empty() {
702            return;
703        }
704
705        let ptr = unsafe { base.add(layout.trivia_kinds_offset as usize) } as *mut NodeTypeId;
706        for (i, &kind) in self.trivia_kinds.iter().enumerate() {
707            unsafe {
708                ptr::write(ptr.add(i), kind);
709            }
710        }
711    }
712}
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717    use crate::query::graph::{BuildEffect, BuildGraph, BuildMatcher, BuildNode};
718    use crate::query::infer::TypeInferenceResult;
719    use std::num::NonZeroU16;
720
721    fn make_resolver() -> MapResolver {
722        let mut r = MapResolver::new();
723        r.add_kind("identifier", 1);
724        r.add_kind("function_declaration", 2);
725        r.add_field("name", NonZeroU16::new(1).unwrap());
726        r.add_field("body", NonZeroU16::new(2).unwrap());
727        r
728    }
729
730    #[test]
731    fn emit_simple_query() {
732        let mut graph = BuildGraph::new();
733
734        // Create a simple: (identifier) @id
735        let node = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
736        graph.node_mut(node).add_effect(BuildEffect::CaptureNode);
737        graph.add_definition("Main", node);
738
739        let type_info = TypeInferenceResult::default();
740        let resolver = make_resolver();
741
742        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
743        let compiled = emitter.emit().expect("emit should succeed");
744
745        assert_eq!(compiled.transition_count(), 1);
746        assert_eq!(compiled.entrypoint_count(), 1);
747
748        let t = compiled.transition(0);
749        assert!(matches!(t.matcher, Matcher::Node { kind: 1, .. }));
750    }
751
752    #[test]
753    fn emit_with_effects() {
754        let mut graph = BuildGraph::new();
755
756        let node = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
757        graph.node_mut(node).add_effect(BuildEffect::CaptureNode);
758        graph.node_mut(node).add_effect(BuildEffect::Field {
759            name: "name",
760            span: Default::default(),
761        });
762        graph.add_definition("Main", node);
763
764        let type_info = TypeInferenceResult::default();
765        let resolver = make_resolver();
766
767        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
768        let compiled = emitter.emit().expect("emit should succeed");
769
770        let view = compiled.transition_view(0);
771        let effects = view.effects();
772        assert_eq!(effects.len(), 2);
773        assert!(matches!(effects[0], EffectOp::CaptureNode));
774        assert!(matches!(effects[1], EffectOp::Field(_)));
775
776        // Verify string was interned
777        if let EffectOp::Field(id) = effects[1] {
778            assert_eq!(compiled.string(id), "name");
779        }
780    }
781
782    #[test]
783    fn emit_with_successors() {
784        let mut graph = BuildGraph::new();
785
786        // Create: entry -> branch -> [a, b]
787        let entry = graph.add_epsilon();
788        let a = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
789        let b = graph.add_node(BuildNode::with_matcher(BuildMatcher::node(
790            "function_declaration",
791        )));
792        graph.connect(entry, a);
793        graph.connect(entry, b);
794        graph.add_definition("Main", entry);
795
796        let type_info = TypeInferenceResult::default();
797        let resolver = make_resolver();
798
799        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
800        let compiled = emitter.emit().expect("emit should succeed");
801
802        assert_eq!(compiled.transition_count(), 3);
803
804        let view = compiled.transition_view(0);
805        let successors = view.successors();
806        assert_eq!(successors.len(), 2);
807        assert_eq!(successors[0], 1);
808        assert_eq!(successors[1], 2);
809    }
810
811    #[test]
812    fn emit_many_successors_spills() {
813        let mut graph = BuildGraph::new();
814
815        // Create entry with 10 successors (exceeds MAX_INLINE_SUCCESSORS)
816        let entry = graph.add_epsilon();
817        for _ in 0..10 {
818            let node = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
819            graph.connect(entry, node);
820        }
821        graph.add_definition("Main", entry);
822
823        let type_info = TypeInferenceResult::default();
824        let resolver = make_resolver();
825
826        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
827        let compiled = emitter.emit().expect("emit should succeed");
828
829        let t = compiled.transition(0);
830        assert!(!t.has_inline_successors());
831        assert_eq!(t.successor_count, 10);
832
833        let view = compiled.transition_view(0);
834        let successors = view.successors();
835        assert_eq!(successors.len(), 10);
836    }
837
838    #[test]
839    fn string_interning_deduplicates() {
840        let mut graph = BuildGraph::new();
841
842        // Two fields with same name
843        let n1 = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
844        graph.node_mut(n1).add_effect(BuildEffect::Field {
845            name: "value",
846            span: Default::default(),
847        });
848
849        let n2 = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
850        graph.node_mut(n2).add_effect(BuildEffect::Field {
851            name: "value",
852            span: Default::default(),
853        });
854        graph.connect(n1, n2);
855
856        graph.add_definition("Main", n1);
857
858        let type_info = TypeInferenceResult::default();
859        let resolver = make_resolver();
860
861        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
862        let compiled = emitter.emit().expect("emit should succeed");
863
864        // Both should reference the same string ID
865        let e1 = compiled.transition_view(0).effects();
866        let e2 = compiled.transition_view(1).effects();
867
868        let id1 = match e1[0] {
869            EffectOp::Field(id) => id,
870            _ => panic!(),
871        };
872        let id2 = match e2[0] {
873            EffectOp::Field(id) => id,
874            _ => panic!(),
875        };
876
877        assert_eq!(id1, id2);
878        assert_eq!(compiled.string(id1), "value");
879    }
880
881    #[test]
882    fn unknown_node_kind_errors() {
883        let mut graph = BuildGraph::new();
884        let node = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("unknown_kind")));
885        graph.add_definition("Main", node);
886
887        let type_info = TypeInferenceResult::default();
888        let resolver = make_resolver();
889
890        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
891        let result = emitter.emit();
892
893        assert!(matches!(result, Err(EmitError::UnknownNodeKind(_))));
894    }
895
896    #[test]
897    fn serialize_deserialize_roundtrip() {
898        let mut graph = BuildGraph::new();
899
900        // Build a small graph with effects
901        let n1 = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
902        graph.node_mut(n1).add_effect(BuildEffect::CaptureNode);
903        graph.node_mut(n1).add_effect(BuildEffect::Field {
904            name: "id",
905            span: Default::default(),
906        });
907
908        let n2 = graph.add_node(BuildNode::with_matcher(BuildMatcher::node(
909            "function_declaration",
910        )));
911        graph.node_mut(n2).add_effect(BuildEffect::CaptureNode);
912        graph.connect(n1, n2);
913
914        graph.add_definition("Main", n1);
915
916        let type_info = TypeInferenceResult::default();
917        let resolver = make_resolver();
918
919        // Emit
920        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
921        let compiled = emitter.emit().expect("emit should succeed");
922
923        // Serialize
924        let bytes = crate::ir::to_bytes(&compiled).expect("serialize should succeed");
925
926        // Deserialize
927        let restored = crate::ir::from_bytes(&bytes).expect("deserialize should succeed");
928
929        // Verify counts
930        assert_eq!(restored.transition_count(), compiled.transition_count());
931        assert_eq!(restored.entrypoint_count(), compiled.entrypoint_count());
932
933        // Check transitions match
934        for i in 0..compiled.transition_count() {
935            let orig = compiled.transition_view(i);
936            let rest = restored.transition_view(i);
937
938            assert_eq!(orig.successors(), rest.successors());
939            assert_eq!(orig.effects().len(), rest.effects().len());
940        }
941
942        // Check strings match
943        let ep = restored.entrypoints()[0];
944        assert_eq!(restored.string(ep.name_id()), "Main");
945    }
946
947    #[test]
948    fn dump_produces_output() {
949        let mut graph = BuildGraph::new();
950        let node = graph.add_node(BuildNode::with_matcher(BuildMatcher::node("identifier")));
951        graph.node_mut(node).add_effect(BuildEffect::CaptureNode);
952        graph.add_definition("Test", node);
953
954        let type_info = TypeInferenceResult::default();
955        let resolver = make_resolver();
956
957        let emitter = QueryEmitter::new(&graph, &type_info, resolver);
958        let compiled = emitter.emit().expect("emit should succeed");
959
960        let dump = compiled.dump();
961
962        assert!(dump.contains("CompiledQuery"));
963        assert!(dump.contains("Test"));
964        assert!(dump.contains("Capture"));
965        assert!(dump.contains("Node(1)"));
966    }
967}