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