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