Skip to main content

eure_document/document/
source_constructor.rs

1//! Source-aware document constructor.
2//!
3//! This module provides [`SourceConstructor`], which builds an [`EureDocument`]
4//! while tracking source layout information for round-trip formatting.
5//!
6//! # Architecture
7//!
8//! `SourceConstructor` builds both the semantic document and an AST representation
9//! of the source structure. The 6 patterns from the Eure grammar are:
10//!
11//! | # | Pattern | API calls |
12//! |---|---------|-----------|
13//! | 1 | `path = value` | `begin_binding` → navigate → `bind_*` → `end_binding_value` |
14//! | 2 | `path { eure }` | `begin_binding` → navigate → `begin_eure_block` → ... → `end_eure_block` → `end_binding_block` |
15//! | 3 | `path { = value eure }` | `begin_binding` → navigate → `begin_eure_block` → `bind_*` → `set_block_value` → ... → `end_eure_block` → `end_binding_block` |
16//! | 4 | `@ section` (items) | `begin_section` → navigate → `begin_section_items` → ... → `end_section_items` |
17//! | 5 | `@ section { eure }` | `begin_section` → navigate → `begin_eure_block` → ... → `end_eure_block` → `end_section_block` |
18//! | 6 | `@ section { = value eure }` | `begin_section` → navigate → `begin_eure_block` → `bind_*` → `set_block_value` → ... → `end_eure_block` → `end_section_block` |
19
20use crate::document::constructor::{DocumentConstructor, Scope as InnerScope};
21use crate::document::interpreter_sink::InterpreterSink;
22use crate::document::{ConstructorError, InsertError, NodeId};
23use crate::path::PathSegment;
24use crate::prelude_internal::*;
25use crate::source::{
26    BindingSource, Comment, EureSource, SectionSource, SourceDocument, SourceId, SourceKey,
27    SourcePath, SourcePathSegment, Trivia,
28};
29
30/// Builder context for tracking nested structures.
31#[derive(Debug)]
32enum BuilderContext {
33    /// Building an EureSource block (for `{ eure }` patterns)
34    EureBlock {
35        /// The SourceId for this block in the arena
36        source_id: SourceId,
37        /// Saved pending path from the enclosing binding/section
38        saved_path: SourcePath,
39        /// Saved pending trivia from the enclosing context
40        saved_trivia: Vec<Trivia>,
41    },
42    /// Building section items (for `@ section` pattern #4)
43    SectionItems {
44        /// Trivia before this section header
45        trivia_before: Vec<Trivia>,
46        /// Path for the section header
47        path: SourcePath,
48        /// Optional initial value binding
49        value: Option<NodeId>,
50        /// Bindings collected so far
51        bindings: Vec<BindingSource>,
52    },
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56enum PendingPathContext {
57    Binding,
58    Section,
59}
60
61/// A document constructor that tracks source layout for round-trip formatting.
62///
63/// `SourceConstructor` wraps [`DocumentConstructor`] and records source structure
64/// (sections, bindings, comments) as an AST. This enables converting from other
65/// formats (like TOML) while preserving their structure.
66///
67/// # Example
68///
69/// ```ignore
70/// let mut constructor = SourceConstructor::new();
71///
72/// // Build: name = "Alice" (pattern #1)
73/// constructor.begin_binding();
74/// let scope = constructor.begin_scope();
75/// constructor.navigate(PathSegment::Ident("name".parse()?))?;
76/// constructor.bind_primitive("Alice".into())?;
77/// constructor.end_scope(scope)?;
78/// constructor.end_binding_value().unwrap();
79///
80/// // Build: user { name = "Bob" } (pattern #2)
81/// constructor.begin_binding();
82/// let scope = constructor.begin_scope();
83/// constructor.navigate(PathSegment::Ident("user".parse()?))?;
84/// constructor.begin_eure_block();
85///   constructor.begin_binding();
86///   let inner_scope = constructor.begin_scope();
87///   constructor.navigate(PathSegment::Ident("name".parse()?))?;
88///   constructor.bind_primitive("Bob".into())?;
89///   constructor.end_scope(inner_scope)?;
90///   constructor.end_binding_value().unwrap();
91/// constructor.end_eure_block().unwrap();
92/// constructor.end_scope(scope)?;
93/// constructor.end_binding_block().unwrap();
94///
95/// let source_doc = constructor.finish();
96/// ```
97pub struct SourceConstructor {
98    /// The underlying document constructor
99    inner: DocumentConstructor,
100
101    /// Arena of EureSource blocks
102    sources: Vec<EureSource>,
103
104    /// Stack of builder contexts for nested structures
105    builder_stack: Vec<BuilderContext>,
106
107    /// Pending path segments for the current binding/section
108    pending_path: Vec<SourcePathSegment>,
109
110    /// Pending trivia (comments/blank lines) to attach to the next item
111    pending_trivia: Vec<Trivia>,
112
113    /// Node ID of the last bound value (for end_binding_value and set_block_value)
114    last_bound_node: Option<NodeId>,
115
116    /// SourceId of the last completed EureSource block (for end_binding_block/end_section_block)
117    last_block_id: Option<SourceId>,
118
119    /// The next scope opened after `begin_binding`/`begin_section` should keep
120    /// the accumulated source path instead of restoring a snapshot on exit.
121    skip_path_restore_for_next_scope: bool,
122
123    /// Inline container traversal should not mutate the pending binding path.
124    suspended_path_tracking: usize,
125
126    /// Whether the pending path belongs to a binding or a section header.
127    pending_path_context: Option<PendingPathContext>,
128}
129
130/// Scope handle for [`SourceConstructor`].
131///
132/// In addition to the semantic constructor scope, this snapshots the source
133/// path so nested value traversal does not leak into the enclosing binding or
134/// section path.
135#[derive(Debug, Clone)]
136pub struct Scope {
137    inner: InnerScope,
138    pending_path: SourcePath,
139    restore_pending_path: bool,
140}
141
142impl Default for SourceConstructor {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl SourceConstructor {
149    /// Create a new source constructor.
150    #[must_use]
151    pub fn new() -> Self {
152        // Create root EureSource (index 0)
153        let sources = vec![EureSource::default()];
154
155        Self {
156            inner: DocumentConstructor::new(),
157            sources,
158            builder_stack: vec![BuilderContext::EureBlock {
159                source_id: SourceId(0),
160                saved_path: Vec::new(),
161                saved_trivia: Vec::new(),
162            }],
163            pending_path: Vec::new(),
164            pending_trivia: Vec::new(),
165            last_bound_node: None,
166            last_block_id: None,
167            skip_path_restore_for_next_scope: false,
168            suspended_path_tracking: 0,
169            pending_path_context: None,
170        }
171    }
172
173    /// Finish building and return the [`SourceDocument`].
174    #[must_use]
175    pub fn finish(mut self) -> SourceDocument {
176        // Any remaining pending trivia becomes trailing trivia of the root source
177        if !self.pending_trivia.is_empty() {
178            self.sources[0].trailing_trivia = std::mem::take(&mut self.pending_trivia);
179        }
180        SourceDocument::new(self.inner.finish(), self.sources)
181    }
182
183    /// Get mutable reference to the current EureSource being built.
184    ///
185    /// Finds the nearest EureBlock context in the builder stack.
186    fn current_source_mut(&mut self) -> &mut EureSource {
187        for ctx in self.builder_stack.iter().rev() {
188            if let BuilderContext::EureBlock { source_id, .. } = ctx {
189                return &mut self.sources[source_id.0];
190            }
191        }
192        // Root EureBlock should always be present
193        &mut self.sources[0]
194    }
195
196    // ========================================================================
197    // Inherent methods (mirror InterpreterSink trait for macro compatibility)
198    //
199    // These methods allow the eure! macro to work without importing the
200    // InterpreterSink trait.
201    // ========================================================================
202
203    /// Begin a new scope. Returns a handle that must be passed to `end_scope`.
204    pub fn begin_scope(&mut self) -> Scope {
205        InterpreterSink::begin_scope(self)
206    }
207
208    /// End a scope, restoring to the state when `begin_scope` was called.
209    pub fn end_scope(&mut self, scope: Scope) -> Result<(), InsertError> {
210        InterpreterSink::end_scope(self, scope)
211    }
212
213    /// Navigate to a child node by path segment.
214    pub fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, InsertError> {
215        InterpreterSink::navigate(self, segment)
216    }
217
218    /// Navigate to a partial-map entry by key.
219    pub fn navigate_partial_map_entry(
220        &mut self,
221        key: crate::value::PartialObjectKey,
222    ) -> Result<NodeId, InsertError> {
223        self.pending_path.push(SourcePathSegment {
224            key: Self::partial_object_key_to_source_key(&key),
225            array: None,
226        });
227        self.inner.navigate_partial_map_entry(key)
228    }
229
230    /// Assert that the current node is unbound (a hole).
231    pub fn require_hole(&self) -> Result<(), InsertError> {
232        InterpreterSink::require_hole(self)
233    }
234
235    /// Bind a primitive value to the current node.
236    pub fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), InsertError> {
237        InterpreterSink::bind_primitive(self, value)
238    }
239
240    /// Bind a hole (with optional label) to the current node.
241    pub fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), InsertError> {
242        InterpreterSink::bind_hole(self, label)
243    }
244
245    /// Bind an empty map to the current node.
246    pub fn bind_empty_map(&mut self) -> Result<(), InsertError> {
247        InterpreterSink::bind_empty_map(self)
248    }
249
250    /// Bind an empty partial map to the current node.
251    pub fn bind_empty_partial_map(&mut self) -> Result<(), InsertError> {
252        self.last_bound_node = Some(self.inner.current_node_id());
253        self.inner.bind_empty_partial_map()
254    }
255
256    /// Bind an empty array to the current node.
257    pub fn bind_empty_array(&mut self) -> Result<(), InsertError> {
258        InterpreterSink::bind_empty_array(self)
259    }
260
261    /// Bind an empty tuple to the current node.
262    pub fn bind_empty_tuple(&mut self) -> Result<(), InsertError> {
263        InterpreterSink::bind_empty_tuple(self)
264    }
265
266    /// Bind a value using `Into<PrimitiveValue>`.
267    pub fn bind_from(&mut self, value: impl Into<PrimitiveValue>) -> Result<(), InsertError> {
268        InterpreterSink::bind_from(self, value)
269    }
270
271    /// Get the current node ID.
272    pub fn current_node_id(&self) -> NodeId {
273        InterpreterSink::current_node_id(self)
274    }
275
276    /// Get the current path from root.
277    pub fn current_path(&self) -> &[PathSegment] {
278        InterpreterSink::current_path(self)
279    }
280
281    /// Get the current node.
282    pub fn current_node(&self) -> &crate::document::node::Node {
283        self.inner.current_node()
284    }
285
286    /// Get the current node mutably.
287    pub fn current_node_mut(&mut self) -> &mut crate::document::node::Node {
288        self.inner.current_node_mut()
289    }
290
291    /// Mark a node as the last bound value for the current binding/section.
292    pub fn set_last_bound_node(&mut self, node_id: NodeId) {
293        self.last_bound_node = Some(node_id);
294    }
295
296    /// Clone the pending source path for the current binding/section.
297    pub fn clone_pending_path(&self) -> SourcePath {
298        self.pending_path.clone()
299    }
300
301    /// Restore the pending source path for the current binding/section.
302    pub fn set_pending_path(&mut self, path: SourcePath) {
303        self.pending_path = path;
304    }
305
306    /// Temporarily suspend source-path tracking for inline container traversal.
307    pub fn suspend_path_tracking(&mut self) {
308        self.suspended_path_tracking += 1;
309    }
310
311    /// Resume source-path tracking after inline container traversal.
312    pub fn resume_path_tracking(&mut self) {
313        self.suspended_path_tracking = self.suspended_path_tracking.saturating_sub(1);
314    }
315
316    /// Get a reference to the document being built.
317    pub fn document(&self) -> &EureDocument {
318        InterpreterSink::document(self)
319    }
320
321    /// Get a mutable reference to the document being built.
322    pub fn document_mut(&mut self) -> &mut EureDocument {
323        InterpreterSink::document_mut(self)
324    }
325
326    // =========================================================================
327    // Source Layout Markers (inherent methods for macro compatibility)
328    // =========================================================================
329
330    /// Enter a new EureSource block (for `{ eure }` patterns).
331    pub fn begin_eure_block(&mut self) {
332        InterpreterSink::begin_eure_block(self)
333    }
334
335    /// Set the value binding for current block (for `{ = value ... }` patterns).
336    pub fn set_block_value(&mut self) -> Result<(), InsertError> {
337        InterpreterSink::set_block_value(self)
338    }
339
340    /// End current EureSource block.
341    pub fn end_eure_block(&mut self) -> Result<(), InsertError> {
342        InterpreterSink::end_eure_block(self)
343    }
344
345    /// Start a binding statement.
346    pub fn begin_binding(&mut self) {
347        InterpreterSink::begin_binding(self)
348    }
349
350    /// End binding #1: `path = value`.
351    pub fn end_binding_value(&mut self) -> Result<(), InsertError> {
352        InterpreterSink::end_binding_value(self)
353    }
354
355    /// End binding #2/#3: `path { eure }`.
356    pub fn end_binding_block(&mut self) -> Result<(), InsertError> {
357        InterpreterSink::end_binding_block(self)
358    }
359
360    /// Start a section header.
361    pub fn begin_section(&mut self) {
362        InterpreterSink::begin_section(self)
363    }
364
365    /// Begin section #4: `@ section` (items follow).
366    pub fn begin_section_items(&mut self) {
367        InterpreterSink::begin_section_items(self)
368    }
369
370    /// End section #4: finalize section with items body.
371    pub fn end_section_items(&mut self) -> Result<(), InsertError> {
372        InterpreterSink::end_section_items(self)
373    }
374
375    /// End section #5/#6: `@ section { eure }`.
376    pub fn end_section_block(&mut self) -> Result<(), InsertError> {
377        InterpreterSink::end_section_block(self)
378    }
379
380    /// Add a comment to the pending trivia.
381    pub fn comment(&mut self, comment: Comment) {
382        InterpreterSink::comment(self, comment)
383    }
384
385    /// Add a blank line to the pending trivia.
386    pub fn blank_line(&mut self) {
387        InterpreterSink::blank_line(self)
388    }
389
390    /// Add trivia (comment or blank line) to the pending trivia.
391    pub fn add_trivia(&mut self, trivia: Trivia) {
392        self.pending_trivia.push(trivia);
393    }
394
395    // =========================================================================
396    // Helper methods
397    // =========================================================================
398
399    /// Convert a PathSegment to a SourcePathSegment.
400    fn path_segment_to_source(segment: &PathSegment) -> SourcePathSegment {
401        match segment {
402            PathSegment::Ident(id) => SourcePathSegment::ident(id.clone()),
403            PathSegment::Extension(id) => SourcePathSegment::extension(id.clone()),
404            PathSegment::PartialValue(key) => SourcePathSegment {
405                key: Self::partial_object_key_to_source_key(key),
406                array: None,
407            },
408            PathSegment::HoleKey(label) => SourcePathSegment {
409                key: SourceKey::hole(label.clone()),
410                array: None,
411            },
412            PathSegment::Value(key) => SourcePathSegment {
413                key: Self::object_key_to_source_key(key),
414                array: None,
415            },
416            PathSegment::TupleIndex(idx) => SourcePathSegment {
417                key: SourceKey::TupleIndex(*idx),
418                array: None,
419            },
420            PathSegment::ArrayIndex(_) => {
421                // Array index should always be merged with the previous segment in navigate().
422                // This conversion should never be called directly.
423                unreachable!(
424                    "ArrayIndex should be merged with previous segment, not converted directly"
425                )
426            }
427        }
428    }
429
430    fn partial_object_key_to_source_key(key: &crate::value::PartialObjectKey) -> SourceKey {
431        match key {
432            crate::value::PartialObjectKey::String(s) => {
433                if let Ok(id) = s.parse::<Identifier>() {
434                    SourceKey::Ident(id)
435                } else {
436                    SourceKey::quoted(s.clone())
437                }
438            }
439            crate::value::PartialObjectKey::Number(n) => {
440                if let Ok(n64) = i64::try_from(n) {
441                    SourceKey::Integer(n64)
442                } else {
443                    SourceKey::quoted(n.to_string())
444                }
445            }
446            crate::value::PartialObjectKey::Hole(label) => SourceKey::hole(label.clone()),
447            crate::value::PartialObjectKey::Tuple(keys) => SourceKey::Tuple(
448                keys.iter()
449                    .map(Self::partial_object_key_to_source_key)
450                    .collect(),
451            ),
452        }
453    }
454
455    /// Convert an ObjectKey to a SourceKey.
456    fn object_key_to_source_key(key: &ObjectKey) -> SourceKey {
457        match key {
458            ObjectKey::String(s) => {
459                // Try to parse as identifier, otherwise use string
460                if let Ok(id) = s.parse::<Identifier>() {
461                    SourceKey::Ident(id)
462                } else {
463                    SourceKey::quoted(s.clone())
464                }
465            }
466            ObjectKey::Number(n) => {
467                // Try to convert BigInt to i64, fallback to string representation
468                if let Ok(n64) = i64::try_from(n) {
469                    SourceKey::Integer(n64)
470                } else {
471                    SourceKey::quoted(n.to_string())
472                }
473            }
474            ObjectKey::Tuple(keys) => {
475                SourceKey::Tuple(keys.iter().map(Self::object_key_to_source_key).collect())
476            }
477        }
478    }
479
480    /// Add a binding to the current context with pending trivia attached.
481    fn push_binding(&mut self, mut binding: BindingSource) {
482        // Attach pending trivia to this binding
483        binding.trivia_before = std::mem::take(&mut self.pending_trivia);
484
485        match self.builder_stack.last_mut() {
486            Some(BuilderContext::SectionItems { bindings, .. }) => {
487                bindings.push(binding);
488            }
489            Some(BuilderContext::EureBlock { source_id, .. }) => {
490                self.sources[source_id.0].bindings.push(binding);
491            }
492            None => {
493                // Should never happen - root context is always present
494                self.sources[0].bindings.push(binding);
495            }
496        }
497    }
498
499    /// Add a section to the current EureSource with trivia attached.
500    fn push_section(&mut self, mut section: SectionSource, trivia: Vec<Trivia>) {
501        // Attach trivia to this section
502        section.trivia_before = trivia;
503        self.current_source_mut().sections.push(section);
504    }
505}
506
507impl InterpreterSink for SourceConstructor {
508    type Error = InsertError;
509    type Scope = Scope;
510
511    fn begin_scope(&mut self) -> Self::Scope {
512        let restore_pending_path = !self.skip_path_restore_for_next_scope;
513        self.skip_path_restore_for_next_scope = false;
514        Scope {
515            inner: self.inner.begin_scope(),
516            pending_path: self.pending_path.clone(),
517            restore_pending_path,
518        }
519    }
520
521    fn end_scope(&mut self, scope: Self::Scope) -> Result<(), Self::Error> {
522        if scope.restore_pending_path {
523            self.pending_path = scope.pending_path;
524        }
525        InterpreterSink::end_scope(&mut self.inner, scope.inner)
526    }
527
528    fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, Self::Error> {
529        if self.suspended_path_tracking == 0 {
530            // Handle array markers: merge with previous segment
531            if let PathSegment::ArrayIndex(idx) = &segment {
532                if let Some(last) = self.pending_path.last_mut() {
533                    last.array = Some(*idx);
534                } else {
535                    let in_section_items = matches!(
536                        self.builder_stack.last(),
537                        Some(BuilderContext::SectionItems { .. })
538                    );
539                    let in_section_header =
540                        self.pending_path_context == Some(PendingPathContext::Section);
541                    if in_section_items || in_section_header {
542                        self.pending_path.push(SourcePathSegment::root_array(*idx));
543                    } else {
544                        return Err(InsertError {
545                            kind: ConstructorError::StandaloneArrayIndex.into(),
546                            path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
547                        });
548                    }
549                }
550            } else {
551                let source_segment = Self::path_segment_to_source(&segment);
552                self.pending_path.push(source_segment);
553            }
554        }
555
556        InterpreterSink::navigate(&mut self.inner, segment)
557    }
558
559    fn require_hole(&self) -> Result<(), Self::Error> {
560        self.inner.require_hole()
561    }
562
563    fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), Self::Error> {
564        self.last_bound_node = Some(self.inner.current_node_id());
565        InterpreterSink::bind_primitive(&mut self.inner, value)
566    }
567
568    fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), Self::Error> {
569        self.last_bound_node = Some(self.inner.current_node_id());
570        InterpreterSink::bind_hole(&mut self.inner, label)
571    }
572
573    fn bind_empty_map(&mut self) -> Result<(), Self::Error> {
574        self.last_bound_node = Some(self.inner.current_node_id());
575        InterpreterSink::bind_empty_map(&mut self.inner)
576    }
577
578    fn bind_empty_array(&mut self) -> Result<(), Self::Error> {
579        self.last_bound_node = Some(self.inner.current_node_id());
580        InterpreterSink::bind_empty_array(&mut self.inner)
581    }
582
583    fn bind_empty_tuple(&mut self) -> Result<(), Self::Error> {
584        self.last_bound_node = Some(self.inner.current_node_id());
585        InterpreterSink::bind_empty_tuple(&mut self.inner)
586    }
587
588    fn current_node_id(&self) -> NodeId {
589        self.inner.current_node_id()
590    }
591
592    fn current_path(&self) -> &[PathSegment] {
593        self.inner.current_path()
594    }
595
596    fn document(&self) -> &EureDocument {
597        self.inner.document()
598    }
599
600    fn document_mut(&mut self) -> &mut EureDocument {
601        self.inner.document_mut()
602    }
603
604    // =========================================================================
605    // Source Layout Markers (overrides with actual implementations)
606    // =========================================================================
607
608    fn begin_eure_block(&mut self) {
609        // Create a new EureSource in the arena
610        let source_id = SourceId(self.sources.len());
611        self.sources.push(EureSource::default());
612
613        // Save the pending path and trivia, clear them for the inner block
614        let saved_path = std::mem::take(&mut self.pending_path);
615        let saved_trivia = std::mem::take(&mut self.pending_trivia);
616
617        // Push context
618        self.builder_stack.push(BuilderContext::EureBlock {
619            source_id,
620            saved_path,
621            saved_trivia,
622        });
623    }
624
625    fn set_block_value(&mut self) -> Result<(), Self::Error> {
626        // Set the value field of the current EureSource
627        let node_id = self.last_bound_node.take().ok_or_else(|| InsertError {
628            kind: ConstructorError::MissingBindBeforeSetBlockValue.into(),
629            path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
630        })?;
631        self.current_source_mut().value = Some(node_id);
632        Ok(())
633    }
634
635    fn end_eure_block(&mut self) -> Result<(), Self::Error> {
636        // Any remaining pending trivia becomes trailing trivia of this block
637        if !self.pending_trivia.is_empty() {
638            let source_id = match self.builder_stack.last() {
639                Some(BuilderContext::EureBlock { source_id, .. }) => *source_id,
640                _ => {
641                    return Err(InsertError {
642                        kind: ConstructorError::InvalidBuilderStackForEndEureBlock.into(),
643                        path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
644                    });
645                }
646            };
647            self.sources[source_id.0].trailing_trivia = std::mem::take(&mut self.pending_trivia);
648        }
649
650        // Pop the EureBlock context and record its SourceId
651        match self.builder_stack.pop() {
652            Some(BuilderContext::EureBlock {
653                source_id,
654                saved_path,
655                saved_trivia,
656            }) => {
657                self.last_block_id = Some(source_id);
658                // Restore the saved path and trivia for the enclosing binding/section
659                self.pending_path = saved_path;
660                self.pending_trivia = saved_trivia;
661                Ok(())
662            }
663            _ => Err(InsertError {
664                kind: ConstructorError::InvalidBuilderStackForEndEureBlock.into(),
665                path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
666            }),
667        }
668    }
669
670    fn begin_binding(&mut self) {
671        self.pending_path.clear();
672        self.pending_path_context = Some(PendingPathContext::Binding);
673        self.skip_path_restore_for_next_scope = true;
674    }
675
676    fn end_binding_value(&mut self) -> Result<(), Self::Error> {
677        // Pattern #1: path = value
678        let path = std::mem::take(&mut self.pending_path);
679        let node_id = self.last_bound_node.take().ok_or_else(|| InsertError {
680            kind: ConstructorError::MissingBindBeforeEndBindingValue.into(),
681            path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
682        })?;
683
684        let binding = BindingSource::value(path, node_id);
685        self.push_binding(binding);
686        self.pending_path_context = None;
687        Ok(())
688    }
689
690    fn end_binding_block(&mut self) -> Result<(), Self::Error> {
691        // Pattern #2/#3: path { eure }
692        let path = std::mem::take(&mut self.pending_path);
693        let source_id = self.last_block_id.take().ok_or_else(|| InsertError {
694            kind: ConstructorError::MissingEndEureBlockBeforeEndBindingBlock.into(),
695            path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
696        })?;
697
698        let binding = BindingSource::block(path, source_id);
699        self.push_binding(binding);
700        self.pending_path_context = None;
701        Ok(())
702    }
703
704    fn begin_section(&mut self) {
705        self.pending_path.clear();
706        self.pending_path_context = Some(PendingPathContext::Section);
707        self.skip_path_restore_for_next_scope = true;
708    }
709
710    fn begin_section_items(&mut self) {
711        // Pattern #4: @ section (items follow)
712        let path = std::mem::take(&mut self.pending_path);
713        let trivia_before = std::mem::take(&mut self.pending_trivia);
714
715        // Check if there was a value binding before this
716        let value = self.last_bound_node.take();
717
718        self.builder_stack.push(BuilderContext::SectionItems {
719            trivia_before,
720            path,
721            value,
722            bindings: Vec::new(),
723        });
724        self.pending_path_context = None;
725    }
726
727    fn end_section_items(&mut self) -> Result<(), Self::Error> {
728        // Finalize pattern #4
729        match self.builder_stack.pop() {
730            Some(BuilderContext::SectionItems {
731                trivia_before,
732                path,
733                value,
734                bindings,
735            }) => {
736                let section = SectionSource::items(path, value, bindings);
737                self.push_section(section, trivia_before);
738                Ok(())
739            }
740            _ => Err(InsertError {
741                kind: ConstructorError::InvalidBuilderStackForEndSectionItems.into(),
742                path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
743            }),
744        }
745    }
746
747    fn end_section_block(&mut self) -> Result<(), Self::Error> {
748        // Pattern #5/#6: @ section { eure }
749        let path = std::mem::take(&mut self.pending_path);
750        let trivia_before = std::mem::take(&mut self.pending_trivia);
751        let source_id = self.last_block_id.take().ok_or_else(|| InsertError {
752            kind: ConstructorError::MissingEndEureBlockBeforeEndSectionBlock.into(),
753            path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
754        })?;
755
756        let section = SectionSource::block(path, source_id);
757        self.push_section(section, trivia_before);
758        self.pending_path_context = None;
759        Ok(())
760    }
761
762    fn comment(&mut self, comment: Comment) {
763        self.pending_trivia.push(Trivia::Comment(comment));
764    }
765
766    fn blank_line(&mut self) {
767        self.pending_trivia.push(Trivia::BlankLine);
768    }
769}
770
771#[cfg(test)]
772mod tests {
773    use super::*;
774    use crate::document::InsertErrorKind;
775    use crate::path::ArrayIndexKind;
776    use crate::source::{BindSource, SectionBody};
777
778    fn ident(s: &str) -> Identifier {
779        s.parse().unwrap()
780    }
781
782    // =========================================================================
783    // Pattern #1: path = value
784    // =========================================================================
785
786    #[test]
787    fn test_pattern1_simple_binding() {
788        let mut constructor = SourceConstructor::new();
789
790        // Build: name = "Alice"
791        constructor.begin_binding();
792        let scope = constructor.begin_scope();
793        constructor
794            .navigate(PathSegment::Ident(ident("name")))
795            .unwrap();
796        constructor
797            .bind_primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
798            .unwrap();
799        constructor.end_scope(scope).unwrap();
800        constructor.end_binding_value().unwrap();
801
802        let source_doc = constructor.finish();
803
804        // Check source structure
805        let root = source_doc.root_source();
806        assert_eq!(root.bindings.len(), 1);
807        assert!(root.sections.is_empty());
808        assert!(root.value.is_none());
809
810        let binding = &root.bindings[0];
811        assert_eq!(binding.path.len(), 1);
812        assert_eq!(binding.path[0].key, SourceKey::Ident(ident("name")));
813        match &binding.bind {
814            BindSource::Value(node_id) => {
815                assert!(node_id.0 > 0); // Not root
816            }
817            _ => panic!("Expected BindSource::Value"),
818        }
819    }
820
821    #[test]
822    fn test_pattern1_nested_path() {
823        let mut constructor = SourceConstructor::new();
824
825        // Build: a.b.c = 42
826        constructor.begin_binding();
827        let scope = constructor.begin_scope();
828        constructor
829            .navigate(PathSegment::Ident(ident("a")))
830            .unwrap();
831        constructor
832            .navigate(PathSegment::Ident(ident("b")))
833            .unwrap();
834        constructor
835            .navigate(PathSegment::Ident(ident("c")))
836            .unwrap();
837        constructor
838            .bind_primitive(PrimitiveValue::Integer(42.into()))
839            .unwrap();
840        constructor.end_scope(scope).unwrap();
841        constructor.end_binding_value().unwrap();
842
843        let source_doc = constructor.finish();
844
845        let root = source_doc.root_source();
846        assert_eq!(root.bindings.len(), 1);
847
848        let binding = &root.bindings[0];
849        assert_eq!(binding.path.len(), 3);
850        assert_eq!(binding.path[0].key, SourceKey::Ident(ident("a")));
851        assert_eq!(binding.path[1].key, SourceKey::Ident(ident("b")));
852        assert_eq!(binding.path[2].key, SourceKey::Ident(ident("c")));
853    }
854
855    // =========================================================================
856    // Pattern #2: path { eure }
857    // =========================================================================
858
859    #[test]
860    fn test_pattern2_binding_block() {
861        let mut constructor = SourceConstructor::new();
862
863        // Build: user { name = "Bob" }
864        constructor.begin_binding();
865        let scope = constructor.begin_scope();
866        constructor
867            .navigate(PathSegment::Ident(ident("user")))
868            .unwrap();
869        constructor.begin_eure_block();
870
871        // Inner binding: name = "Bob"
872        constructor.begin_binding();
873        let inner_scope = constructor.begin_scope();
874        constructor
875            .navigate(PathSegment::Ident(ident("name")))
876            .unwrap();
877        constructor
878            .bind_primitive(PrimitiveValue::Text(Text::plaintext("Bob")))
879            .unwrap();
880        constructor.end_scope(inner_scope).unwrap();
881        constructor.end_binding_value().unwrap();
882
883        constructor.end_eure_block().unwrap();
884        constructor.end_scope(scope).unwrap();
885        constructor.end_binding_block().unwrap();
886
887        let source_doc = constructor.finish();
888
889        // Check root
890        let root = source_doc.root_source();
891        assert_eq!(root.bindings.len(), 1);
892
893        let binding = &root.bindings[0];
894        assert_eq!(binding.path.len(), 1);
895        assert_eq!(binding.path[0].key, SourceKey::Ident(ident("user")));
896
897        match &binding.bind {
898            BindSource::Block(source_id) => {
899                let inner_source = source_doc.source(*source_id);
900                assert!(inner_source.value.is_none());
901                assert_eq!(inner_source.bindings.len(), 1);
902                assert_eq!(
903                    inner_source.bindings[0].path[0].key,
904                    SourceKey::Ident(ident("name"))
905                );
906            }
907            _ => panic!("Expected BindSource::Block"),
908        }
909    }
910
911    // =========================================================================
912    // Pattern #3: path { = value eure }
913    // =========================================================================
914
915    #[test]
916    fn test_pattern3_binding_value_block() {
917        let mut constructor = SourceConstructor::new();
918
919        // Build: data { = [] $schema = "array" }
920        constructor.begin_binding();
921        let scope = constructor.begin_scope();
922        constructor
923            .navigate(PathSegment::Ident(ident("data")))
924            .unwrap();
925        constructor.begin_eure_block();
926
927        // Value: = []
928        constructor.bind_empty_array().unwrap();
929        constructor.set_block_value().unwrap();
930
931        // Inner binding: $schema = "array"
932        constructor.begin_binding();
933        let inner_scope = constructor.begin_scope();
934        constructor
935            .navigate(PathSegment::Extension(ident("schema")))
936            .unwrap();
937        constructor
938            .bind_primitive(PrimitiveValue::Text(Text::plaintext("array")))
939            .unwrap();
940        constructor.end_scope(inner_scope).unwrap();
941        constructor.end_binding_value().unwrap();
942
943        constructor.end_eure_block().unwrap();
944        constructor.end_scope(scope).unwrap();
945        constructor.end_binding_block().unwrap();
946
947        let source_doc = constructor.finish();
948
949        let root = source_doc.root_source();
950        assert_eq!(root.bindings.len(), 1);
951
952        let binding = &root.bindings[0];
953        match &binding.bind {
954            BindSource::Block(source_id) => {
955                let inner_source = source_doc.source(*source_id);
956                // Should have a value
957                assert!(inner_source.value.is_some());
958                // And one binding
959                assert_eq!(inner_source.bindings.len(), 1);
960            }
961            _ => panic!("Expected BindSource::Block"),
962        }
963    }
964
965    // =========================================================================
966    // Pattern #4: @ section (items follow)
967    // =========================================================================
968
969    #[test]
970    fn test_pattern4_section_items() {
971        let mut constructor = SourceConstructor::new();
972
973        // Build:
974        // @ server
975        // host = "localhost"
976        // port = 8080
977
978        constructor.begin_section();
979        let scope = constructor.begin_scope();
980        constructor
981            .navigate(PathSegment::Ident(ident("server")))
982            .unwrap();
983        constructor.begin_section_items();
984
985        // Binding 1: host = "localhost"
986        constructor.begin_binding();
987        let inner_scope1 = constructor.begin_scope();
988        constructor
989            .navigate(PathSegment::Ident(ident("host")))
990            .unwrap();
991        constructor
992            .bind_primitive(PrimitiveValue::Text(Text::plaintext("localhost")))
993            .unwrap();
994        constructor.end_scope(inner_scope1).unwrap();
995        constructor.end_binding_value().unwrap();
996
997        // Binding 2: port = 8080
998        constructor.begin_binding();
999        let inner_scope2 = constructor.begin_scope();
1000        constructor
1001            .navigate(PathSegment::Ident(ident("port")))
1002            .unwrap();
1003        constructor
1004            .bind_primitive(PrimitiveValue::Integer(8080.into()))
1005            .unwrap();
1006        constructor.end_scope(inner_scope2).unwrap();
1007        constructor.end_binding_value().unwrap();
1008
1009        constructor.end_section_items().unwrap();
1010        constructor.end_scope(scope).unwrap();
1011
1012        let source_doc = constructor.finish();
1013
1014        let root = source_doc.root_source();
1015        assert!(root.bindings.is_empty());
1016        assert_eq!(root.sections.len(), 1);
1017
1018        let section = &root.sections[0];
1019        assert_eq!(section.path.len(), 1);
1020        assert_eq!(section.path[0].key, SourceKey::Ident(ident("server")));
1021
1022        match &section.body {
1023            SectionBody::Items { value, bindings } => {
1024                assert!(value.is_none());
1025                assert_eq!(bindings.len(), 2);
1026                assert_eq!(bindings[0].path[0].key, SourceKey::Ident(ident("host")));
1027                assert_eq!(bindings[1].path[0].key, SourceKey::Ident(ident("port")));
1028            }
1029            _ => panic!("Expected SectionBody::Items"),
1030        }
1031    }
1032
1033    #[test]
1034    fn test_pattern4_root_array_section_items() {
1035        let mut constructor = SourceConstructor::new();
1036
1037        // Build:
1038        // @[]
1039        // a = 1
1040
1041        constructor.begin_section();
1042        let scope = constructor.begin_scope();
1043        constructor
1044            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1045            .unwrap();
1046        constructor.begin_section_items();
1047
1048        constructor.begin_binding();
1049        let inner_scope = constructor.begin_scope();
1050        constructor
1051            .navigate(PathSegment::Ident(ident("a")))
1052            .unwrap();
1053        constructor
1054            .bind_primitive(PrimitiveValue::Integer(1.into()))
1055            .unwrap();
1056        constructor.end_scope(inner_scope).unwrap();
1057        constructor.end_binding_value().unwrap();
1058
1059        constructor.end_section_items().unwrap();
1060        constructor.end_scope(scope).unwrap();
1061
1062        let source_doc = constructor.finish();
1063        let root = source_doc.root_source();
1064        assert_eq!(root.sections.len(), 1);
1065
1066        let section = &root.sections[0];
1067        assert_eq!(section.path.len(), 1);
1068        assert_eq!(section.path[0].key, SourceKey::Root);
1069        assert_eq!(section.path[0].array, Some(ArrayIndexKind::Push));
1070    }
1071
1072    // =========================================================================
1073    // Pattern #5: @ section { eure }
1074    // =========================================================================
1075
1076    #[test]
1077    fn test_pattern5_section_block() {
1078        let mut constructor = SourceConstructor::new();
1079
1080        // Build: @ server { host = "localhost" }
1081        constructor.begin_section();
1082        let scope = constructor.begin_scope();
1083        constructor
1084            .navigate(PathSegment::Ident(ident("server")))
1085            .unwrap();
1086        constructor.begin_eure_block();
1087
1088        // Inner binding: host = "localhost"
1089        constructor.begin_binding();
1090        let inner_scope = constructor.begin_scope();
1091        constructor
1092            .navigate(PathSegment::Ident(ident("host")))
1093            .unwrap();
1094        constructor
1095            .bind_primitive(PrimitiveValue::Text(Text::plaintext("localhost")))
1096            .unwrap();
1097        constructor.end_scope(inner_scope).unwrap();
1098        constructor.end_binding_value().unwrap();
1099
1100        constructor.end_eure_block().unwrap();
1101        constructor.end_scope(scope).unwrap();
1102        constructor.end_section_block().unwrap();
1103
1104        let source_doc = constructor.finish();
1105
1106        let root = source_doc.root_source();
1107        assert!(root.bindings.is_empty());
1108        assert_eq!(root.sections.len(), 1);
1109
1110        let section = &root.sections[0];
1111        match &section.body {
1112            SectionBody::Block(source_id) => {
1113                let inner_source = source_doc.source(*source_id);
1114                assert!(inner_source.value.is_none());
1115                assert_eq!(inner_source.bindings.len(), 1);
1116            }
1117            _ => panic!("Expected SectionBody::Block"),
1118        }
1119    }
1120
1121    // =========================================================================
1122    // Pattern #6: @ section { = value eure }
1123    // =========================================================================
1124
1125    #[test]
1126    fn test_pattern6_section_value_block() {
1127        let mut constructor = SourceConstructor::new();
1128
1129        // Build: @ data { = [] $schema = "array" }
1130        constructor.begin_section();
1131        let scope = constructor.begin_scope();
1132        constructor
1133            .navigate(PathSegment::Ident(ident("data")))
1134            .unwrap();
1135        constructor.begin_eure_block();
1136
1137        // Value: = []
1138        constructor.bind_empty_array().unwrap();
1139        constructor.set_block_value().unwrap();
1140
1141        // Inner binding: $schema = "array"
1142        constructor.begin_binding();
1143        let inner_scope = constructor.begin_scope();
1144        constructor
1145            .navigate(PathSegment::Extension(ident("schema")))
1146            .unwrap();
1147        constructor
1148            .bind_primitive(PrimitiveValue::Text(Text::plaintext("array")))
1149            .unwrap();
1150        constructor.end_scope(inner_scope).unwrap();
1151        constructor.end_binding_value().unwrap();
1152
1153        constructor.end_eure_block().unwrap();
1154        constructor.end_scope(scope).unwrap();
1155        constructor.end_section_block().unwrap();
1156
1157        let source_doc = constructor.finish();
1158
1159        let root = source_doc.root_source();
1160        assert_eq!(root.sections.len(), 1);
1161
1162        let section = &root.sections[0];
1163        match &section.body {
1164            SectionBody::Block(source_id) => {
1165                let inner_source = source_doc.source(*source_id);
1166                // Should have a value
1167                assert!(inner_source.value.is_some());
1168                // And one binding
1169                assert_eq!(inner_source.bindings.len(), 1);
1170            }
1171            _ => panic!("Expected SectionBody::Block"),
1172        }
1173    }
1174
1175    // =========================================================================
1176    // Array index tests
1177    // =========================================================================
1178
1179    #[test]
1180    fn test_array_index_with_key() {
1181        // Build: items[0] = "first"
1182        let mut constructor = SourceConstructor::new();
1183
1184        constructor.begin_binding();
1185        let scope = constructor.begin_scope();
1186        constructor
1187            .navigate(PathSegment::Ident(ident("items")))
1188            .unwrap();
1189        constructor
1190            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Specific(0)))
1191            .unwrap();
1192        constructor
1193            .bind_primitive(PrimitiveValue::Text(Text::plaintext("first")))
1194            .unwrap();
1195        constructor.end_scope(scope).unwrap();
1196        constructor.end_binding_value().unwrap();
1197
1198        let source_doc = constructor.finish();
1199
1200        let root = source_doc.root_source();
1201        assert_eq!(root.bindings.len(), 1);
1202
1203        let binding = &root.bindings[0];
1204        // Path should have one segment with array marker
1205        assert_eq!(binding.path.len(), 1);
1206        assert_eq!(binding.path[0].key, SourceKey::Ident(ident("items")));
1207        assert_eq!(binding.path[0].array, Some(ArrayIndexKind::Specific(0)));
1208    }
1209
1210    #[test]
1211    fn test_array_push_marker() {
1212        // Build: items[] = "new"
1213        let mut constructor = SourceConstructor::new();
1214
1215        constructor.begin_binding();
1216        let scope = constructor.begin_scope();
1217        constructor
1218            .navigate(PathSegment::Ident(ident("items")))
1219            .unwrap();
1220        constructor
1221            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1222            .unwrap();
1223        constructor
1224            .bind_primitive(PrimitiveValue::Text(Text::plaintext("new")))
1225            .unwrap();
1226        constructor.end_scope(scope).unwrap();
1227        constructor.end_binding_value().unwrap();
1228
1229        let source_doc = constructor.finish();
1230
1231        let root = source_doc.root_source();
1232        let binding = &root.bindings[0];
1233        assert_eq!(binding.path.len(), 1);
1234        assert_eq!(binding.path[0].key, SourceKey::Ident(ident("items")));
1235        // Push means no index specified (`[]`)
1236        assert_eq!(binding.path[0].array, Some(ArrayIndexKind::Push));
1237    }
1238
1239    #[test]
1240    fn test_standalone_array_index_returns_error() {
1241        // Standalone [] is not valid in Eure syntax
1242        let mut constructor = SourceConstructor::new();
1243
1244        constructor.begin_binding();
1245        let _scope = constructor.begin_scope();
1246        // This should return an error - ArrayIndex without a preceding key segment
1247        let result = constructor.navigate(PathSegment::ArrayIndex(ArrayIndexKind::Specific(0)));
1248        assert!(matches!(
1249            result,
1250            Err(InsertError {
1251                kind: InsertErrorKind::ConstructorError(ConstructorError::StandaloneArrayIndex),
1252                ..
1253            })
1254        ));
1255    }
1256
1257    // =========================================================================
1258    // Error case tests
1259    // =========================================================================
1260
1261    #[test]
1262    fn test_end_binding_value_without_bind_returns_error() {
1263        let mut constructor = SourceConstructor::new();
1264
1265        constructor.begin_binding();
1266        let scope = constructor.begin_scope();
1267        constructor
1268            .navigate(PathSegment::Ident(ident("name")))
1269            .unwrap();
1270        // Missing: bind operation here
1271        constructor.end_scope(scope).unwrap();
1272        let result = constructor.end_binding_value();
1273        assert!(matches!(
1274            result,
1275            Err(InsertError {
1276                kind: InsertErrorKind::ConstructorError(
1277                    ConstructorError::MissingBindBeforeEndBindingValue
1278                ),
1279                ..
1280            })
1281        ));
1282    }
1283
1284    #[test]
1285    fn test_set_block_value_without_bind_returns_error() {
1286        let mut constructor = SourceConstructor::new();
1287
1288        constructor.begin_binding();
1289        let _scope = constructor.begin_scope();
1290        constructor
1291            .navigate(PathSegment::Ident(ident("data")))
1292            .unwrap();
1293        constructor.begin_eure_block();
1294        // Missing: bind operation here
1295        let result = constructor.set_block_value();
1296        assert!(matches!(
1297            result,
1298            Err(InsertError {
1299                kind: InsertErrorKind::ConstructorError(
1300                    ConstructorError::MissingBindBeforeSetBlockValue
1301                ),
1302                ..
1303            })
1304        ));
1305    }
1306
1307    #[test]
1308    fn test_end_binding_block_without_end_eure_block_returns_error() {
1309        let mut constructor = SourceConstructor::new();
1310
1311        constructor.begin_binding();
1312        let scope = constructor.begin_scope();
1313        constructor
1314            .navigate(PathSegment::Ident(ident("data")))
1315            .unwrap();
1316        // Missing: begin_eure_block, end_eure_block
1317        constructor.end_scope(scope).unwrap();
1318        let result = constructor.end_binding_block();
1319        assert!(matches!(
1320            result,
1321            Err(InsertError {
1322                kind: InsertErrorKind::ConstructorError(
1323                    ConstructorError::MissingEndEureBlockBeforeEndBindingBlock
1324                ),
1325                ..
1326            })
1327        ));
1328    }
1329
1330    // =========================================================================
1331    // Complex nested structure tests
1332    // =========================================================================
1333
1334    #[test]
1335    fn test_multiple_bindings() {
1336        let mut constructor = SourceConstructor::new();
1337
1338        // Build: a = 1, b = 2
1339        for (name, value) in [("a", 1), ("b", 2)] {
1340            constructor.begin_binding();
1341            let scope = constructor.begin_scope();
1342            constructor
1343                .navigate(PathSegment::Ident(ident(name)))
1344                .unwrap();
1345            constructor
1346                .bind_primitive(PrimitiveValue::Integer(value.into()))
1347                .unwrap();
1348            constructor.end_scope(scope).unwrap();
1349            constructor.end_binding_value().unwrap();
1350        }
1351
1352        let source_doc = constructor.finish();
1353
1354        let root = source_doc.root_source();
1355        assert_eq!(root.bindings.len(), 2);
1356        assert_eq!(root.bindings[0].path[0].key, SourceKey::Ident(ident("a")));
1357        assert_eq!(root.bindings[1].path[0].key, SourceKey::Ident(ident("b")));
1358    }
1359
1360    #[test]
1361    fn test_nested_blocks() {
1362        let mut constructor = SourceConstructor::new();
1363
1364        // Build: outer { inner { value = 1 } }
1365        constructor.begin_binding();
1366        let scope1 = constructor.begin_scope();
1367        constructor
1368            .navigate(PathSegment::Ident(ident("outer")))
1369            .unwrap();
1370        constructor.begin_eure_block();
1371
1372        constructor.begin_binding();
1373        let scope2 = constructor.begin_scope();
1374        constructor
1375            .navigate(PathSegment::Ident(ident("inner")))
1376            .unwrap();
1377        constructor.begin_eure_block();
1378
1379        constructor.begin_binding();
1380        let scope3 = constructor.begin_scope();
1381        constructor
1382            .navigate(PathSegment::Ident(ident("value")))
1383            .unwrap();
1384        constructor
1385            .bind_primitive(PrimitiveValue::Integer(1.into()))
1386            .unwrap();
1387        constructor.end_scope(scope3).unwrap();
1388        constructor.end_binding_value().unwrap();
1389
1390        constructor.end_eure_block().unwrap();
1391        constructor.end_scope(scope2).unwrap();
1392        constructor.end_binding_block().unwrap();
1393
1394        constructor.end_eure_block().unwrap();
1395        constructor.end_scope(scope1).unwrap();
1396        constructor.end_binding_block().unwrap();
1397
1398        let source_doc = constructor.finish();
1399
1400        // Verify structure: root -> outer -> inner -> value
1401        let root = source_doc.root_source();
1402        assert_eq!(root.bindings.len(), 1);
1403
1404        if let BindSource::Block(outer_id) = &root.bindings[0].bind {
1405            let outer = source_doc.source(*outer_id);
1406            assert_eq!(outer.bindings.len(), 1);
1407
1408            if let BindSource::Block(inner_id) = &outer.bindings[0].bind {
1409                let inner = source_doc.source(*inner_id);
1410                assert_eq!(inner.bindings.len(), 1);
1411                assert!(matches!(inner.bindings[0].bind, BindSource::Value(_)));
1412            } else {
1413                panic!("Expected inner block");
1414            }
1415        } else {
1416            panic!("Expected outer block");
1417        }
1418    }
1419
1420    // =========================================================================
1421    // Trivia (comments and blank lines) tests
1422    // =========================================================================
1423
1424    #[test]
1425    fn test_trivia_before_binding() {
1426        let mut constructor = SourceConstructor::new();
1427
1428        // Add comment and blank line before first binding
1429        constructor.comment(Comment::Line("This is a comment".to_string()));
1430        constructor.blank_line();
1431
1432        // Build: name = "Alice"
1433        constructor.begin_binding();
1434        let scope = constructor.begin_scope();
1435        constructor
1436            .navigate(PathSegment::Ident(ident("name")))
1437            .unwrap();
1438        constructor
1439            .bind_primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
1440            .unwrap();
1441        constructor.end_scope(scope).unwrap();
1442        constructor.end_binding_value().unwrap();
1443
1444        let source_doc = constructor.finish();
1445
1446        let root = source_doc.root_source();
1447        assert_eq!(root.bindings.len(), 1);
1448
1449        // Check trivia attached to the binding
1450        let binding = &root.bindings[0];
1451        assert_eq!(binding.trivia_before.len(), 2);
1452        assert!(matches!(
1453            &binding.trivia_before[0],
1454            Trivia::Comment(Comment::Line(s)) if s == "This is a comment"
1455        ));
1456        assert!(matches!(&binding.trivia_before[1], Trivia::BlankLine));
1457    }
1458
1459    #[test]
1460    fn test_trivia_before_section() {
1461        let mut constructor = SourceConstructor::new();
1462
1463        // Add blank line before section
1464        constructor.blank_line();
1465
1466        // Build: @ server
1467        constructor.begin_section();
1468        let scope = constructor.begin_scope();
1469        constructor
1470            .navigate(PathSegment::Ident(ident("server")))
1471            .unwrap();
1472        constructor.begin_section_items();
1473
1474        // Binding inside section
1475        constructor.begin_binding();
1476        let inner_scope = constructor.begin_scope();
1477        constructor
1478            .navigate(PathSegment::Ident(ident("host")))
1479            .unwrap();
1480        constructor
1481            .bind_primitive(PrimitiveValue::Text(Text::plaintext("localhost")))
1482            .unwrap();
1483        constructor.end_scope(inner_scope).unwrap();
1484        constructor.end_binding_value().unwrap();
1485
1486        constructor.end_section_items().unwrap();
1487        constructor.end_scope(scope).unwrap();
1488
1489        let source_doc = constructor.finish();
1490
1491        let root = source_doc.root_source();
1492        assert_eq!(root.sections.len(), 1);
1493
1494        // Check trivia attached to the section
1495        let section = &root.sections[0];
1496        assert_eq!(section.trivia_before.len(), 1);
1497        assert!(matches!(&section.trivia_before[0], Trivia::BlankLine));
1498    }
1499
1500    #[test]
1501    fn test_trailing_trivia() {
1502        let mut constructor = SourceConstructor::new();
1503
1504        // Build: name = "Alice"
1505        constructor.begin_binding();
1506        let scope = constructor.begin_scope();
1507        constructor
1508            .navigate(PathSegment::Ident(ident("name")))
1509            .unwrap();
1510        constructor
1511            .bind_primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
1512            .unwrap();
1513        constructor.end_scope(scope).unwrap();
1514        constructor.end_binding_value().unwrap();
1515
1516        // Add trailing comment/blank line after all items
1517        constructor.blank_line();
1518        constructor.comment(Comment::Line("end of file".to_string()));
1519
1520        let source_doc = constructor.finish();
1521
1522        let root = source_doc.root_source();
1523        assert_eq!(root.trailing_trivia.len(), 2);
1524        assert!(matches!(&root.trailing_trivia[0], Trivia::BlankLine));
1525        assert!(matches!(
1526            &root.trailing_trivia[1],
1527            Trivia::Comment(Comment::Line(s)) if s == "end of file"
1528        ));
1529    }
1530
1531    #[test]
1532    fn test_trivia_between_bindings() {
1533        let mut constructor = SourceConstructor::new();
1534
1535        // Build: a = 1
1536        constructor.begin_binding();
1537        let scope1 = constructor.begin_scope();
1538        constructor
1539            .navigate(PathSegment::Ident(ident("a")))
1540            .unwrap();
1541        constructor
1542            .bind_primitive(PrimitiveValue::Integer(1.into()))
1543            .unwrap();
1544        constructor.end_scope(scope1).unwrap();
1545        constructor.end_binding_value().unwrap();
1546
1547        // Add blank line and comment between bindings
1548        constructor.blank_line();
1549        constructor.comment(Comment::Line("Second binding".to_string()));
1550
1551        // Build: b = 2
1552        constructor.begin_binding();
1553        let scope2 = constructor.begin_scope();
1554        constructor
1555            .navigate(PathSegment::Ident(ident("b")))
1556            .unwrap();
1557        constructor
1558            .bind_primitive(PrimitiveValue::Integer(2.into()))
1559            .unwrap();
1560        constructor.end_scope(scope2).unwrap();
1561        constructor.end_binding_value().unwrap();
1562
1563        let source_doc = constructor.finish();
1564
1565        let root = source_doc.root_source();
1566        assert_eq!(root.bindings.len(), 2);
1567
1568        // First binding should have no trivia
1569        assert!(root.bindings[0].trivia_before.is_empty());
1570
1571        // Second binding should have the trivia
1572        assert_eq!(root.bindings[1].trivia_before.len(), 2);
1573        assert!(matches!(
1574            &root.bindings[1].trivia_before[0],
1575            Trivia::BlankLine
1576        ));
1577        assert!(matches!(
1578            &root.bindings[1].trivia_before[1],
1579            Trivia::Comment(Comment::Line(s)) if s == "Second binding"
1580        ));
1581    }
1582}