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