Skip to main content

eure_document/document/
constructor.rs

1use alloc::collections::BTreeMap;
2use indexmap::IndexSet;
3
4use crate::document::interpreter_sink::InterpreterSink;
5use crate::map::PartialNodeMap;
6use crate::prelude_internal::*;
7use crate::value::PartialObjectKey;
8
9/// Tracks, for the current block scope, which child was most recently pushed into
10/// each array encountered in that scope. Used to resolve `[^]` back to that child.
11///
12/// Entries are keyed by the array's `NodeId` (the node whose value is `NodeValue::Array`).
13#[derive(Debug, Default, Clone)]
14struct BlockScope {
15    last_pushes: BTreeMap<NodeId, NodeId>,
16}
17
18/// Represents a scope in the document constructor.
19/// Must be passed to `end_scope` to restore the constructor to the state when the scope was created.
20/// Scopes must be ended in LIFO order (most recent first).
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Scope {
23    id: usize,
24    stack_depth: usize,
25    path_depth: usize,
26}
27
28#[derive(Debug, PartialEq, thiserror::Error, Clone)]
29pub enum ScopeError {
30    #[error("Cannot end scope at root")]
31    CannotEndAtRoot,
32    #[error("Scope must be ended in LIFO order (most recent first)")]
33    NotMostRecentScope,
34}
35
36pub struct DocumentConstructor {
37    document: EureDocument,
38    /// The path from the root to the current node.
39    path: Vec<PathSegment>,
40    /// Stack of NodeIds from root to current position.
41    stack: Vec<NodeId>,
42    /// Counter for generating unique scope IDs.
43    scope_counter: usize,
44    /// Stack of outstanding scope IDs for LIFO enforcement.
45    outstanding_scopes: Vec<usize>,
46    /// Whether hole has been bound to the node
47    hole_bound: Vec<bool>,
48    /// IDs of nodes that are unbound.
49    unbound_nodes: IndexSet<NodeId>,
50    /// Stack of block scopes used to resolve `[^]` (current-index) segments. The topmost
51    /// scope records the most recent push into each array observed within the current
52    /// block/section body. Always non-empty: the first entry represents the document root.
53    block_scope_stack: Vec<BlockScope>,
54}
55
56impl Default for DocumentConstructor {
57    fn default() -> Self {
58        let document = EureDocument::default();
59        let root = document.get_root_id();
60        Self {
61            document,
62            path: vec![],
63            stack: vec![root],
64            hole_bound: vec![false],
65            scope_counter: 0,
66            outstanding_scopes: vec![],
67            unbound_nodes: IndexSet::new(),
68            block_scope_stack: vec![BlockScope::default()],
69        }
70    }
71}
72
73impl DocumentConstructor {
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    pub fn current_node_id(&self) -> NodeId {
79        *self.stack.last().expect("Stack should never be empty")
80    }
81
82    pub fn current_node(&self) -> &Node {
83        self.document.node(self.current_node_id())
84    }
85
86    pub fn current_node_mut(&mut self) -> &mut Node {
87        self.document.node_mut(self.current_node_id())
88    }
89
90    pub fn current_path(&self) -> &[PathSegment] {
91        &self.path
92    }
93
94    pub fn document(&self) -> &EureDocument {
95        &self.document
96    }
97
98    pub fn document_mut(&mut self) -> &mut EureDocument {
99        &mut self.document
100    }
101
102    pub fn finish(mut self) -> EureDocument {
103        for node_id in self.unbound_nodes {
104            let node = self.document.node_mut(node_id);
105            if node.content.is_hole() {
106                node.content = NodeValue::Map(Default::default());
107            }
108        }
109        // If the root node is Hole, empty map
110        let root_id = self.document.get_root_id();
111        let root_node = self.document.node_mut(root_id);
112        if root_node.content.is_hole() && !self.hole_bound[0] {
113            root_node.content = NodeValue::Map(Default::default());
114        }
115        self.document
116    }
117}
118
119impl DocumentConstructor {
120    /// Begin a new scope. Returns a scope handle that must be passed to `end_scope`.
121    /// Scopes must be ended in LIFO order (most recent first).
122    pub fn begin_scope(&mut self) -> Scope {
123        let id = self.scope_counter;
124        self.scope_counter += 1;
125        self.outstanding_scopes.push(id);
126        Scope {
127            id,
128            stack_depth: self.stack.len(),
129            path_depth: self.path.len(),
130        }
131    }
132
133    /// End a scope, restoring the constructor to the state when the scope was created.
134    /// Returns an error if the scope is not the most recent outstanding scope.
135    pub fn end_scope(&mut self, scope: Scope) -> Result<(), ScopeError> {
136        // LIFO enforcement: scope must be the most recent outstanding scope
137        if self.outstanding_scopes.last() != Some(&scope.id) {
138            return Err(ScopeError::NotMostRecentScope);
139        }
140        if scope.stack_depth < 1 {
141            return Err(ScopeError::CannotEndAtRoot);
142        }
143        self.outstanding_scopes.pop();
144        for i in scope.stack_depth..self.stack.len() {
145            let hole_bound = self.hole_bound[i];
146            if !hole_bound && self.document.node(self.stack[i]).content.is_hole() {
147                self.unbound_nodes.insert(self.stack[i]);
148            }
149        }
150        self.stack.truncate(scope.stack_depth);
151        self.hole_bound.truncate(scope.stack_depth);
152        self.path.truncate(scope.path_depth);
153        Ok(())
154    }
155
156    /// Navigate to a child node by path segment.
157    /// Creates the node if it doesn't exist.
158    ///
159    /// Array-index semantics:
160    /// - `ArrayIndexKind::Push` — always pushes a new array element, and records that
161    ///   push in the current block scope so a later `[^]` can find it.
162    /// - `ArrayIndexKind::Current` — resolves to the element most recently pushed into
163    ///   the target array within the current block scope. Errors with
164    ///   [`InsertErrorKind::ArrayCurrentOutOfScope`] if no such push exists. This never
165    ///   creates a new element.
166    /// - `ArrayIndexKind::Specific(n)` — reuses or creates the element at index `n`.
167    pub fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, InsertError> {
168        let current = self.current_node_id();
169
170        // `[^]` must resolve to a prior push within the current block scope.
171        if let PathSegment::ArrayIndex(ArrayIndexKind::Current) = &segment {
172            // Ensure the target is actually an array so we surface ExpectedArray first.
173            self.document
174                .node(current)
175                .as_array()
176                .ok_or_else(|| InsertError {
177                    kind: InsertErrorKind::ExpectedArray,
178                    path: EurePath::from_iter(self.path.iter().cloned()),
179                })?;
180            let child_id = self
181                .block_scope_stack
182                .last()
183                .expect("block scope stack is never empty")
184                .last_pushes
185                .get(&current)
186                .copied()
187                .ok_or_else(|| InsertError {
188                    kind: InsertErrorKind::ArrayCurrentOutOfScope {
189                        array_node_id: current,
190                    },
191                    path: EurePath::from_iter(self.path.iter().cloned()),
192                })?;
193            self.stack.push(child_id);
194            self.hole_bound.push(false);
195            self.path.push(segment);
196            return Ok(child_id);
197        }
198
199        let node_mut = self
200            .document
201            .resolve_child_by_segment(segment.clone(), current)
202            .map_err(|e| InsertError {
203                kind: e,
204                path: EurePath::from_iter(self.path.iter().cloned()),
205            })?;
206        let node_id = node_mut.node_id;
207
208        // Record pushes so later `[^]` segments in the same block scope can find them.
209        if let PathSegment::ArrayIndex(ArrayIndexKind::Push) = &segment {
210            self.block_scope_stack
211                .last_mut()
212                .expect("block scope stack is never empty")
213                .last_pushes
214                .insert(current, node_id);
215        }
216
217        self.stack.push(node_id);
218        self.hole_bound.push(false);
219        self.path.push(segment);
220        Ok(node_id)
221    }
222
223    /// Navigate into a PartialMap entry.
224    ///
225    /// Find-or-create semantics:
226    /// - labeled holes and resolved keys reuse an existing entry
227    /// - anonymous holes (`Hole(None)`) always create a fresh entry
228    pub fn navigate_partial_map_entry(
229        &mut self,
230        key: PartialObjectKey,
231    ) -> Result<NodeId, InsertError> {
232        let current = self.current_node_id();
233        let existing = self
234            .document
235            .node(current)
236            .as_partial_map()
237            .and_then(|pm| pm.find(&key))
238            .copied();
239
240        let node_id = if let Some(node_id) = existing {
241            node_id
242        } else {
243            self.document
244                .add_partial_map_child(key.clone(), current)
245                .map_err(|kind| InsertError {
246                    kind,
247                    path: EurePath::from_iter(self.path.iter().cloned()),
248                })?
249                .node_id
250        };
251
252        let segment = PathSegment::from_partial_object_key(key);
253
254        self.stack.push(node_id);
255        self.hole_bound.push(false);
256        self.path.push(segment);
257        Ok(node_id)
258    }
259
260    /// Validate that the current node is a Hole (unbound).
261    /// Use this before binding a value to ensure the node hasn't already been assigned.
262    pub fn require_hole(&self) -> Result<(), InsertError> {
263        let node = self.current_node();
264        if !node.content.is_hole() {
265            return Err(InsertError {
266                kind: InsertErrorKind::BindingTargetHasValue,
267                path: EurePath::from_iter(self.path.iter().cloned()),
268            });
269        }
270        Ok(())
271    }
272
273    /// Bind a hole (optionally labeled) to the current node.
274    pub fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), InsertError> {
275        if !self.current_node().content.is_hole() {
276            return Err(InsertError {
277                kind: InsertErrorKind::BindingTargetHasValue,
278                path: EurePath::from_iter(self.current_path().iter().cloned()),
279            });
280        }
281        self.hole_bound[self.stack.len() - 1] = true;
282        self.unbound_nodes.swap_remove(&self.current_node_id());
283        self.current_node_mut().content = NodeValue::Hole(label);
284        Ok(())
285    }
286
287    /// Bind a primitive value to the current node. Error if already bound.
288    pub fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), InsertError> {
289        let node = self.current_node_mut();
290        if !node.content.is_hole() {
291            return Err(InsertError {
292                kind: InsertErrorKind::BindingTargetHasValue,
293                path: EurePath::from_iter(self.current_path().iter().cloned()),
294            });
295        }
296        node.content = NodeValue::Primitive(value);
297        Ok(())
298    }
299
300    /// Bind a value to the current node using `Into<PrimitiveValue>`.
301    ///
302    /// This is a convenience method for use with the `eure!` macro.
303    /// It accepts any type that implements `Into<PrimitiveValue>`.
304    pub fn bind_from(&mut self, value: impl Into<PrimitiveValue>) -> Result<(), InsertError> {
305        self.bind_primitive(value.into())
306    }
307
308    /// Bind an empty map to the current node. Error if already bound.
309    pub fn bind_empty_map(&mut self) -> Result<(), InsertError> {
310        let node = self.current_node_mut();
311        if !node.content.is_hole() {
312            return Err(InsertError {
313                kind: InsertErrorKind::BindingTargetHasValue,
314                path: EurePath::from_iter(self.current_path().iter().cloned()),
315            });
316        }
317        node.content = NodeValue::Map(Default::default());
318        Ok(())
319    }
320
321    /// Bind an empty PartialMap to the current node. Error if already bound.
322    pub fn bind_empty_partial_map(&mut self) -> Result<(), InsertError> {
323        let node = self.current_node_mut();
324        if !node.content.is_hole() {
325            return Err(InsertError {
326                kind: InsertErrorKind::BindingTargetHasValue,
327                path: EurePath::from_iter(self.current_path().iter().cloned()),
328            });
329        }
330        node.content = NodeValue::PartialMap(PartialNodeMap::new());
331        Ok(())
332    }
333
334    /// Bind an empty array to the current node. Error if already bound.
335    pub fn bind_empty_array(&mut self) -> Result<(), InsertError> {
336        let node = self.current_node_mut();
337        if !node.content.is_hole() {
338            return Err(InsertError {
339                kind: InsertErrorKind::BindingTargetHasValue,
340                path: EurePath::from_iter(self.current_path().iter().cloned()),
341            });
342        }
343        node.content = NodeValue::Array(Default::default());
344        Ok(())
345    }
346
347    /// Bind an empty tuple to the current node. Error if already bound.
348    pub fn bind_empty_tuple(&mut self) -> Result<(), InsertError> {
349        let node = self.current_node_mut();
350        if !node.content.is_hole() {
351            return Err(InsertError {
352                kind: InsertErrorKind::BindingTargetHasValue,
353                path: EurePath::from_iter(self.current_path().iter().cloned()),
354            });
355        }
356        node.content = NodeValue::Tuple(Default::default());
357        Ok(())
358    }
359
360    /// Push a fresh block scope used to track `[^]` resolution.
361    fn begin_block_scope(&mut self) {
362        self.block_scope_stack.push(BlockScope::default());
363    }
364
365    /// Pop the topmost block scope. Safe to call as long as at least two scopes are on
366    /// the stack; the root scope is preserved.
367    fn end_block_scope(&mut self) {
368        debug_assert!(
369            self.block_scope_stack.len() > 1,
370            "attempted to pop the root block scope"
371        );
372        if self.block_scope_stack.len() > 1 {
373            self.block_scope_stack.pop();
374        }
375    }
376
377    // =========================================================================
378    // Source Layout Markers
379    //
380    // These methods allow the eure! macro and interpreter to delimit block scopes so
381    // `[^]` can be resolved against the correct scope.
382    // =========================================================================
383
384    /// Enter a new EureSource block (e.g. `{ ... }` body of a binding or section).
385    pub fn begin_eure_block(&mut self) {
386        self.begin_block_scope();
387    }
388
389    /// Set the value binding for current block. No-op for DocumentConstructor.
390    pub fn set_block_value(&mut self) -> Result<(), InsertError> {
391        Ok(())
392    }
393
394    /// End current EureSource block.
395    pub fn end_eure_block(&mut self) -> Result<(), InsertError> {
396        self.end_block_scope();
397        Ok(())
398    }
399
400    /// Mark the start of a binding statement. No-op for DocumentConstructor.
401    pub fn begin_binding(&mut self) {}
402
403    /// End binding #1: path = value. No-op for DocumentConstructor.
404    pub fn end_binding_value(&mut self) -> Result<(), InsertError> {
405        Ok(())
406    }
407
408    /// End binding #2/#3: path { eure }. No-op for DocumentConstructor.
409    pub fn end_binding_block(&mut self) -> Result<(), InsertError> {
410        Ok(())
411    }
412
413    /// Start a section header. No-op for DocumentConstructor.
414    pub fn begin_section(&mut self) {}
415
416    /// Begin section #4: items follow (flat section body).
417    pub fn begin_section_items(&mut self) {
418        self.begin_block_scope();
419    }
420
421    /// End section #4: finalize section with items.
422    pub fn end_section_items(&mut self) -> Result<(), InsertError> {
423        self.end_block_scope();
424        Ok(())
425    }
426
427    /// End section #5/#6: block. No-op for DocumentConstructor.
428    pub fn end_section_block(&mut self) -> Result<(), InsertError> {
429        Ok(())
430    }
431}
432
433impl InterpreterSink for DocumentConstructor {
434    type Error = InsertError;
435    type Scope = Scope;
436
437    fn begin_scope(&mut self) -> Self::Scope {
438        DocumentConstructor::begin_scope(self)
439    }
440
441    fn end_scope(&mut self, scope: Self::Scope) -> Result<(), Self::Error> {
442        DocumentConstructor::end_scope(self, scope).map_err(|e| InsertError {
443            kind: InsertErrorKind::ScopeError(e),
444            path: EurePath::from_iter(self.current_path().iter().cloned()),
445        })
446    }
447
448    fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, Self::Error> {
449        DocumentConstructor::navigate(self, segment)
450    }
451
452    fn require_hole(&self) -> Result<(), Self::Error> {
453        DocumentConstructor::require_hole(self)
454    }
455
456    fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), Self::Error> {
457        DocumentConstructor::bind_primitive(self, value)
458    }
459
460    fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), Self::Error> {
461        DocumentConstructor::bind_hole(self, label)
462    }
463
464    fn bind_empty_map(&mut self) -> Result<(), Self::Error> {
465        DocumentConstructor::bind_empty_map(self)
466    }
467
468    fn bind_empty_array(&mut self) -> Result<(), Self::Error> {
469        DocumentConstructor::bind_empty_array(self)
470    }
471
472    fn bind_empty_tuple(&mut self) -> Result<(), Self::Error> {
473        DocumentConstructor::bind_empty_tuple(self)
474    }
475
476    fn current_node_id(&self) -> NodeId {
477        DocumentConstructor::current_node_id(self)
478    }
479
480    fn current_path(&self) -> &[PathSegment] {
481        DocumentConstructor::current_path(self)
482    }
483
484    fn document(&self) -> &EureDocument {
485        DocumentConstructor::document(self)
486    }
487
488    fn document_mut(&mut self) -> &mut EureDocument {
489        DocumentConstructor::document_mut(self)
490    }
491
492    fn begin_eure_block(&mut self) {
493        DocumentConstructor::begin_eure_block(self)
494    }
495
496    fn end_eure_block(&mut self) -> Result<(), Self::Error> {
497        DocumentConstructor::end_eure_block(self)
498    }
499
500    fn begin_section_items(&mut self) {
501        DocumentConstructor::begin_section_items(self)
502    }
503
504    fn end_section_items(&mut self) -> Result<(), Self::Error> {
505        DocumentConstructor::end_section_items(self)
506    }
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512    use crate::identifier::IdentifierParser;
513    use crate::value::{PartialObjectKey, Tuple};
514
515    fn create_identifier(s: &str) -> Identifier {
516        let parser = IdentifierParser::init();
517        parser.parse(s).unwrap()
518    }
519
520    #[test]
521    fn test_new_initializes_at_root() {
522        let constructor = DocumentConstructor::new();
523        let root_id = constructor.document().get_root_id();
524
525        assert_eq!(constructor.current_node_id(), root_id);
526        assert_eq!(constructor.current_path(), &[]);
527    }
528
529    #[test]
530    fn test_current_node_returns_root_initially() {
531        let constructor = DocumentConstructor::new();
532
533        let node = constructor.current_node();
534        assert!(node.content.is_hole());
535    }
536
537    #[test]
538    fn test_navigate_single_ident() {
539        let mut constructor = DocumentConstructor::new();
540
541        let identifier = create_identifier("field");
542        let segment = PathSegment::Ident(identifier.clone());
543
544        let node_id = constructor
545            .navigate(segment.clone())
546            .expect("Failed to navigate");
547
548        assert_eq!(constructor.current_node_id(), node_id);
549        assert_eq!(constructor.current_path(), &[segment]);
550    }
551
552    #[test]
553    fn test_navigate_multiple_times() {
554        let mut constructor = DocumentConstructor::new();
555
556        let id1 = create_identifier("field1");
557        let id2 = create_identifier("field2");
558
559        constructor
560            .navigate(PathSegment::Ident(id1.clone()))
561            .expect("Failed to navigate first");
562
563        let node_id2 = constructor
564            .navigate(PathSegment::Extension(id2.clone()))
565            .expect("Failed to navigate second");
566
567        assert_eq!(constructor.current_node_id(), node_id2);
568        assert_eq!(
569            constructor.current_path(),
570            &[PathSegment::Ident(id1), PathSegment::Extension(id2)]
571        );
572    }
573
574    #[test]
575    fn test_navigate_error_propagates() {
576        // Try to add tuple index to primitive node (should fail)
577        let mut constructor = DocumentConstructor::new();
578        // First navigate to the field node
579        let identifier = create_identifier("field");
580        constructor
581            .navigate(PathSegment::Ident(identifier))
582            .expect("Failed to navigate");
583        // Set it to Primitive
584        let node_id = constructor.current_node_id();
585        constructor.document_mut().node_mut(node_id).content =
586            NodeValue::Primitive(PrimitiveValue::Null);
587
588        let result = constructor.navigate(PathSegment::TupleIndex(0));
589
590        assert_eq!(
591            result.map_err(|e| e.kind),
592            Err(InsertErrorKind::ExpectedTuple)
593        );
594    }
595
596    #[test]
597    fn test_scope_success() {
598        let mut constructor = DocumentConstructor::new();
599        let root_id = constructor.document().get_root_id();
600
601        let identifier = create_identifier("field");
602        let token = constructor.begin_scope();
603        let _node_id = constructor
604            .navigate(PathSegment::Ident(identifier.clone()))
605            .expect("Failed to navigate");
606
607        // End scope
608        let result = constructor.end_scope(token);
609        assert_eq!(result, Ok(()));
610
611        // After end_scope, should be back at root
612        assert_eq!(constructor.current_node_id(), root_id);
613        assert_eq!(constructor.current_path(), &[]);
614    }
615
616    #[test]
617    fn test_scope_lifo_enforcement() {
618        let mut constructor = DocumentConstructor::new();
619
620        let id1 = create_identifier("field1");
621        let id2 = create_identifier("field2");
622
623        let token1 = constructor.begin_scope();
624        constructor
625            .navigate(PathSegment::Ident(id1))
626            .expect("Failed to navigate");
627
628        let token2 = constructor.begin_scope();
629        constructor
630            .navigate(PathSegment::Extension(id2))
631            .expect("Failed to navigate");
632
633        // Try to end token1 before token2 (should fail)
634        let result = constructor.end_scope(token1);
635        assert_eq!(result, Err(ScopeError::NotMostRecentScope));
636
637        // End in correct order
638        constructor
639            .end_scope(token2)
640            .expect("Failed to end scope 2");
641        constructor
642            .end_scope(token1)
643            .expect("Failed to end scope 1");
644    }
645
646    #[test]
647    fn test_scope_with_multiple_navigations() {
648        let mut constructor = DocumentConstructor::new();
649        let root_id = constructor.document().get_root_id();
650
651        let id1 = create_identifier("level1");
652        let id2 = create_identifier("level2");
653        let id3 = create_identifier("level3");
654
655        let token = constructor.begin_scope();
656
657        // Navigate three levels
658        let node_id1 = constructor
659            .navigate(PathSegment::Ident(id1.clone()))
660            .expect("Failed to navigate level1");
661
662        let node_id2 = constructor
663            .navigate(PathSegment::Extension(id2.clone()))
664            .expect("Failed to navigate level2");
665
666        let node_id3 = constructor
667            .navigate(PathSegment::Extension(id3.clone()))
668            .expect("Failed to navigate level3");
669
670        // Verify at deepest level
671        assert_eq!(constructor.current_node_id(), node_id3);
672        assert_eq!(
673            constructor.current_path(),
674            &[
675                PathSegment::Ident(id1.clone()),
676                PathSegment::Extension(id2.clone()),
677                PathSegment::Extension(id3)
678            ]
679        );
680
681        // End scope - should restore to root
682        constructor.end_scope(token).expect("Failed to end scope");
683        assert_eq!(constructor.current_node_id(), root_id);
684        assert_eq!(constructor.current_path(), &[]);
685
686        // Verify nodes still exist in document (node() panics if not found)
687        let _ = constructor.document().node(node_id1);
688        let _ = constructor.document().node(node_id2);
689        let _ = constructor.document().node(node_id3);
690    }
691
692    #[test]
693    fn test_nested_scopes() {
694        let mut constructor = DocumentConstructor::new();
695        let root_id = constructor.document().get_root_id();
696
697        let id1 = create_identifier("a");
698        let id2 = create_identifier("b");
699        let id3 = create_identifier("c");
700
701        // Outer scope: navigate to a
702        let token_outer = constructor.begin_scope();
703        let node_a = constructor
704            .navigate(PathSegment::Ident(id1.clone()))
705            .expect("Failed to navigate a");
706
707        // Inner scope: navigate to b.c
708        let token_inner = constructor.begin_scope();
709        let _node_b = constructor
710            .navigate(PathSegment::Extension(id2.clone()))
711            .expect("Failed to navigate b");
712        let _node_c = constructor
713            .navigate(PathSegment::Extension(id3.clone()))
714            .expect("Failed to navigate c");
715
716        // End inner scope - should be back at a
717        constructor
718            .end_scope(token_inner)
719            .expect("Failed to end inner scope");
720        assert_eq!(constructor.current_node_id(), node_a);
721        assert_eq!(constructor.current_path(), &[PathSegment::Ident(id1)]);
722
723        // End outer scope - should be back at root
724        constructor
725            .end_scope(token_outer)
726            .expect("Failed to end outer scope");
727        assert_eq!(constructor.current_node_id(), root_id);
728        assert_eq!(constructor.current_path(), &[]);
729    }
730
731    #[test]
732    fn test_require_hole_success() {
733        let mut constructor = DocumentConstructor::new();
734
735        let identifier = create_identifier("field");
736        constructor
737            .navigate(PathSegment::Ident(identifier))
738            .expect("Failed to navigate");
739
740        // New node should be a Hole
741        let result = constructor.require_hole();
742        assert_eq!(result, Ok(()));
743    }
744
745    #[test]
746    fn test_require_hole_fails_when_bound() {
747        let mut constructor = DocumentConstructor::new();
748
749        let identifier = create_identifier("field");
750        let node_id = constructor
751            .navigate(PathSegment::Ident(identifier))
752            .expect("Failed to navigate");
753
754        // Set the node to have a value
755        constructor.document_mut().node_mut(node_id).content =
756            NodeValue::Primitive(PrimitiveValue::Bool(true));
757
758        // require_hole should fail
759        let result = constructor.require_hole();
760        assert_eq!(
761            result.unwrap_err().kind,
762            InsertErrorKind::BindingTargetHasValue
763        );
764    }
765
766    #[test]
767    fn test_bind_primitive_success() {
768        let mut constructor = DocumentConstructor::new();
769        let identifier = create_identifier("field");
770
771        // Navigate to a field node
772        let node_id = constructor
773            .navigate(PathSegment::Ident(identifier))
774            .expect("Failed to navigate");
775
776        // Bind a primitive value to the node
777        let result = constructor.bind_primitive(PrimitiveValue::Bool(true));
778        assert_eq!(result, Ok(()));
779
780        // Verify the node content is set to Primitive
781        let node = constructor.document().node(node_id);
782        assert!(matches!(
783            node.content,
784            NodeValue::Primitive(PrimitiveValue::Bool(true))
785        ));
786    }
787
788    #[test]
789    fn test_bind_primitive_already_bound() {
790        let mut constructor = DocumentConstructor::new();
791        let identifier = create_identifier("field");
792
793        // Navigate to a field node
794        let node_id = constructor
795            .navigate(PathSegment::Ident(identifier.clone()))
796            .expect("Failed to navigate");
797
798        // Set the node to already have a value
799        constructor.document_mut().node_mut(node_id).content =
800            NodeValue::Primitive(PrimitiveValue::Null);
801
802        // Try to bind a primitive value (should fail)
803        let result = constructor.bind_primitive(PrimitiveValue::Bool(false));
804
805        assert_eq!(
806            result.unwrap_err().kind,
807            InsertErrorKind::BindingTargetHasValue
808        );
809
810        // Verify the node content remains unchanged
811        let node = constructor.document().node(node_id);
812        assert!(matches!(
813            node.content,
814            NodeValue::Primitive(PrimitiveValue::Null)
815        ));
816    }
817
818    #[test]
819    fn test_finish_replaces_uninitialized_root_with_null() {
820        let constructor = DocumentConstructor::new();
821
822        // Root should be Hole before finish
823        let root_id = constructor.document().get_root_id();
824        assert!(constructor.document().node(root_id).content.is_hole());
825
826        // After finish, root should be empty map
827        let document = constructor.finish();
828        let root_node = document.node(document.get_root_id());
829        assert_eq!(root_node.content, NodeValue::Map(Default::default()));
830    }
831
832    #[test]
833    fn test_finish_preserves_initialized_root() {
834        let mut constructor = DocumentConstructor::new();
835
836        // Bind a value to the root
837        constructor
838            .bind_primitive(PrimitiveValue::Bool(true))
839            .expect("Failed to bind");
840
841        // After finish, root should still have the bound value
842        let document = constructor.finish();
843        let root_node = document.node(document.get_root_id());
844        assert!(matches!(
845            root_node.content,
846            NodeValue::Primitive(PrimitiveValue::Bool(true))
847        ));
848    }
849
850    #[test]
851    fn test_finish_preserves_partial_map_root() {
852        let mut constructor = DocumentConstructor::new();
853
854        constructor
855            .navigate_partial_map_entry(PartialObjectKey::Hole(Some(create_identifier("x"))))
856            .unwrap();
857        constructor
858            .bind_primitive(PrimitiveValue::Integer(1.into()))
859            .unwrap();
860
861        let document = constructor.finish();
862        assert!(matches!(
863            document.node(document.get_root_id()).content,
864            NodeValue::PartialMap(_)
865        ));
866    }
867
868    #[test]
869    fn test_navigate_partial_map_entry_does_not_reuse_tuple_with_anonymous_hole() {
870        let mut constructor = DocumentConstructor::new();
871        let scope = constructor.begin_scope();
872
873        let key = PartialObjectKey::Tuple(Tuple(vec![
874            PartialObjectKey::Number(1.into()),
875            PartialObjectKey::Hole(None),
876        ]));
877
878        let first = constructor.navigate_partial_map_entry(key.clone()).unwrap();
879        constructor.end_scope(scope).unwrap();
880
881        let second_scope = constructor.begin_scope();
882        let second = constructor.navigate_partial_map_entry(key).unwrap();
883
884        assert_ne!(first, second);
885        constructor.end_scope(second_scope).unwrap();
886    }
887
888    #[test]
889    fn test_navigate_reuses_labeled_hole_key_segment() {
890        let mut constructor = DocumentConstructor::new();
891        let label = create_identifier("x");
892
893        let scope = constructor.begin_scope();
894        let first = constructor
895            .navigate(PathSegment::HoleKey(Some(label.clone())))
896            .unwrap();
897        constructor.end_scope(scope).unwrap();
898
899        let scope = constructor.begin_scope();
900        let second = constructor
901            .navigate(PathSegment::HoleKey(Some(label)))
902            .unwrap();
903
904        assert_eq!(first, second);
905        constructor.end_scope(scope).unwrap();
906    }
907
908    #[test]
909    fn test_typical_binding_pattern() {
910        // Test the typical pattern: a.b.c = true
911        let mut constructor = DocumentConstructor::new();
912
913        let id_a = create_identifier("a");
914        let id_b = create_identifier("b");
915        let id_c = create_identifier("c");
916
917        let token = constructor.begin_scope();
918        constructor
919            .navigate(PathSegment::Ident(id_a.clone()))
920            .unwrap();
921        constructor
922            .navigate(PathSegment::Extension(id_b.clone()))
923            .unwrap();
924        let node_c = constructor
925            .navigate(PathSegment::Extension(id_c.clone()))
926            .unwrap();
927        constructor.require_hole().unwrap();
928        constructor
929            .bind_primitive(PrimitiveValue::Bool(true))
930            .unwrap();
931        constructor.end_scope(token).unwrap();
932
933        // Verify the value was bound
934        let node = constructor.document().node(node_c);
935        assert!(matches!(
936            node.content,
937            NodeValue::Primitive(PrimitiveValue::Bool(true))
938        ));
939    }
940
941    // ==========================================================================
942    // `[^]` (ArrayIndexKind::Current) semantics
943    //
944    // Each of these tests mirrors one of the cases outlined in the design doc:
945    // - Push records the target element in the active block scope.
946    // - Current resolves to that element, merging further navigation into it.
947    // - A Current with no matching prior Push in the same block scope errors.
948    // - Block scope boundaries (begin_eure_block / begin_section_items) isolate
949    //   pushes so that `[^]` cannot reach across into a parent scope.
950    // - Current respects the usual type checks: if the target is not an array,
951    //   the ExpectedArray error is surfaced.
952    // ==========================================================================
953
954    #[test]
955    fn test_array_current_merges_with_preceding_push_root_scope() {
956        // users[].x = 1; users[^].y = 2 → one element with both fields.
957        let mut constructor = DocumentConstructor::new();
958        let users = create_identifier("users");
959        let x = create_identifier("x");
960        let y = create_identifier("y");
961
962        // users[].x = 1
963        let t1 = constructor.begin_scope();
964        constructor
965            .navigate(PathSegment::Ident(users.clone()))
966            .unwrap();
967        let pushed = constructor
968            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
969            .unwrap();
970        let x_node = constructor.navigate(PathSegment::Ident(x.clone())).unwrap();
971        constructor.require_hole().unwrap();
972        constructor
973            .bind_primitive(PrimitiveValue::Integer(1.into()))
974            .unwrap();
975        constructor.end_scope(t1).unwrap();
976
977        // users[^].y = 2
978        let t2 = constructor.begin_scope();
979        constructor
980            .navigate(PathSegment::Ident(users.clone()))
981            .unwrap();
982        let current = constructor
983            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
984            .unwrap();
985        let y_node = constructor.navigate(PathSegment::Ident(y.clone())).unwrap();
986        constructor.require_hole().unwrap();
987        constructor
988            .bind_primitive(PrimitiveValue::Integer(2.into()))
989            .unwrap();
990        constructor.end_scope(t2).unwrap();
991
992        assert_eq!(pushed, current, "[^] must resolve to the last [] push");
993
994        let element = constructor.document().node(pushed);
995        let map = element.as_map().expect("element should be a map");
996        let resolved_x = map
997            .get_node_id(&ObjectKey::String(x.to_string()))
998            .expect("x should exist");
999        let resolved_y = map
1000            .get_node_id(&ObjectKey::String(y.to_string()))
1001            .expect("y should exist");
1002        assert_eq!(resolved_x, x_node);
1003        assert_eq!(resolved_y, y_node);
1004    }
1005
1006    #[test]
1007    fn test_array_current_merges_inside_block_scope() {
1008        // posts[].title = "a"; posts[^].body = "b" inside a single block scope.
1009        let mut constructor = DocumentConstructor::new();
1010        let posts = create_identifier("posts");
1011        let title = create_identifier("title");
1012        let body = create_identifier("body");
1013
1014        // Simulate `{ posts[].title = "a"; posts[^].body = "b" }` by wrapping in
1015        // a block scope.
1016        constructor.begin_eure_block();
1017
1018        let t1 = constructor.begin_scope();
1019        constructor
1020            .navigate(PathSegment::Ident(posts.clone()))
1021            .unwrap();
1022        let pushed = constructor
1023            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1024            .unwrap();
1025        constructor
1026            .navigate(PathSegment::Ident(title.clone()))
1027            .unwrap();
1028        constructor.require_hole().unwrap();
1029        constructor
1030            .bind_primitive(PrimitiveValue::Text(Text::plaintext("a")))
1031            .unwrap();
1032        constructor.end_scope(t1).unwrap();
1033
1034        let t2 = constructor.begin_scope();
1035        constructor
1036            .navigate(PathSegment::Ident(posts.clone()))
1037            .unwrap();
1038        let current = constructor
1039            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1040            .unwrap();
1041        constructor
1042            .navigate(PathSegment::Ident(body.clone()))
1043            .unwrap();
1044        constructor.require_hole().unwrap();
1045        constructor
1046            .bind_primitive(PrimitiveValue::Text(Text::plaintext("b")))
1047            .unwrap();
1048        constructor.end_scope(t2).unwrap();
1049
1050        constructor.end_eure_block().unwrap();
1051
1052        assert_eq!(pushed, current);
1053        let map = constructor.document().node(pushed).as_map().unwrap();
1054        assert!(
1055            map.get_node_id(&ObjectKey::String(title.to_string()))
1056                .is_some()
1057        );
1058        assert!(
1059            map.get_node_id(&ObjectKey::String(body.to_string()))
1060                .is_some()
1061        );
1062    }
1063
1064    #[test]
1065    fn test_array_current_cannot_reach_across_block_scope() {
1066        // users[] at root; inside a nested block, users[^] must error — the inner
1067        // scope saw no push of its own.
1068        let mut constructor = DocumentConstructor::new();
1069        let users = create_identifier("users");
1070        let x = create_identifier("x");
1071
1072        // Root: users[].x = 1
1073        let t1 = constructor.begin_scope();
1074        constructor
1075            .navigate(PathSegment::Ident(users.clone()))
1076            .unwrap();
1077        constructor
1078            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1079            .unwrap();
1080        constructor.navigate(PathSegment::Ident(x.clone())).unwrap();
1081        constructor.require_hole().unwrap();
1082        constructor
1083            .bind_primitive(PrimitiveValue::Integer(1.into()))
1084            .unwrap();
1085        constructor.end_scope(t1).unwrap();
1086
1087        // Inside a nested block scope: users[^].x must fail.
1088        constructor.begin_eure_block();
1089        let t2 = constructor.begin_scope();
1090        constructor
1091            .navigate(PathSegment::Ident(users.clone()))
1092            .unwrap();
1093        let err = constructor
1094            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1095            .unwrap_err();
1096        assert!(matches!(
1097            err.kind,
1098            InsertErrorKind::ArrayCurrentOutOfScope { .. }
1099        ));
1100        // Rewind the failed scope so we can finish the test cleanly.
1101        constructor.end_scope(t2).unwrap();
1102        constructor.end_eure_block().unwrap();
1103    }
1104
1105    #[test]
1106    fn test_array_current_after_inner_block_refers_to_outer_last_push() {
1107        // Two root-level sibling pushes; a nested block inside the first must
1108        // not leak into the outer scope's `last_pushes`. After both, root-level
1109        // users[^] should resolve to the second root-level push.
1110        let mut constructor = DocumentConstructor::new();
1111        let users = create_identifier("users");
1112        let nested = create_identifier("nested");
1113        let x = create_identifier("x");
1114        let y = create_identifier("y");
1115
1116        // users[].x = 1 with a nested-block-local array touched in between.
1117        let t1 = constructor.begin_scope();
1118        constructor
1119            .navigate(PathSegment::Ident(users.clone()))
1120            .unwrap();
1121        let first = constructor
1122            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1123            .unwrap();
1124        constructor.navigate(PathSegment::Ident(x.clone())).unwrap();
1125        constructor.require_hole().unwrap();
1126        constructor
1127            .bind_primitive(PrimitiveValue::Integer(1.into()))
1128            .unwrap();
1129        constructor.end_scope(t1).unwrap();
1130
1131        // A nested block at root that happens to push into `nested` — this
1132        // must not be visible to the outer scope.
1133        constructor.begin_eure_block();
1134        let inner_scope = constructor.begin_scope();
1135        constructor
1136            .navigate(PathSegment::Ident(nested.clone()))
1137            .unwrap();
1138        constructor
1139            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1140            .unwrap();
1141        constructor.require_hole().unwrap();
1142        constructor.bind_primitive(PrimitiveValue::Null).unwrap();
1143        constructor.end_scope(inner_scope).unwrap();
1144        constructor.end_eure_block().unwrap();
1145
1146        // A second root-level push.
1147        let t2 = constructor.begin_scope();
1148        constructor
1149            .navigate(PathSegment::Ident(users.clone()))
1150            .unwrap();
1151        let second = constructor
1152            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1153            .unwrap();
1154        constructor.navigate(PathSegment::Ident(x.clone())).unwrap();
1155        constructor.require_hole().unwrap();
1156        constructor
1157            .bind_primitive(PrimitiveValue::Integer(2.into()))
1158            .unwrap();
1159        constructor.end_scope(t2).unwrap();
1160
1161        // users[^].y at root should attach to `second`, not `first`.
1162        let t3 = constructor.begin_scope();
1163        constructor
1164            .navigate(PathSegment::Ident(users.clone()))
1165            .unwrap();
1166        let current = constructor
1167            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1168            .unwrap();
1169        constructor.navigate(PathSegment::Ident(y.clone())).unwrap();
1170        constructor.require_hole().unwrap();
1171        constructor
1172            .bind_primitive(PrimitiveValue::Integer(3.into()))
1173            .unwrap();
1174        constructor.end_scope(t3).unwrap();
1175
1176        assert_ne!(first, second);
1177        assert_eq!(
1178            current, second,
1179            "[^] must refer to the second root-level push"
1180        );
1181    }
1182
1183    #[test]
1184    fn test_array_current_without_prior_push_errors() {
1185        // An array seeded by an explicit index (no Push recorded) should also
1186        // surface ArrayCurrentOutOfScope when followed by `[^]` in the same
1187        // scope.
1188        let mut constructor = DocumentConstructor::new();
1189        let users = create_identifier("users");
1190        let x = create_identifier("x");
1191
1192        // Seed: users[0].x = 1 — this makes `users` an array without recording
1193        // a push in the block scope.
1194        let t1 = constructor.begin_scope();
1195        constructor
1196            .navigate(PathSegment::Ident(users.clone()))
1197            .unwrap();
1198        constructor
1199            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Specific(0)))
1200            .unwrap();
1201        constructor.navigate(PathSegment::Ident(x.clone())).unwrap();
1202        constructor.require_hole().unwrap();
1203        constructor
1204            .bind_primitive(PrimitiveValue::Integer(1.into()))
1205            .unwrap();
1206        constructor.end_scope(t1).unwrap();
1207
1208        // users[^] should now error — no Push has been recorded for this array.
1209        let t2 = constructor.begin_scope();
1210        constructor
1211            .navigate(PathSegment::Ident(users.clone()))
1212            .unwrap();
1213        let err = constructor
1214            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1215            .unwrap_err();
1216        assert!(
1217            matches!(err.kind, InsertErrorKind::ArrayCurrentOutOfScope { .. }),
1218            "expected ArrayCurrentOutOfScope, got {:?}",
1219            err.kind
1220        );
1221        constructor.end_scope(t2).unwrap();
1222    }
1223
1224    #[test]
1225    fn test_array_current_nested_arrays() {
1226        // orgs[].teams[].members[].name = ...; orgs[^].teams[^].members[].age = ...
1227        // Inner-inner `[]` creates a second member in the same team.
1228        let mut constructor = DocumentConstructor::new();
1229        let orgs = create_identifier("orgs");
1230        let teams = create_identifier("teams");
1231        let members = create_identifier("members");
1232        let name = create_identifier("name");
1233        let age = create_identifier("age");
1234
1235        let t1 = constructor.begin_scope();
1236        constructor
1237            .navigate(PathSegment::Ident(orgs.clone()))
1238            .unwrap();
1239        let org = constructor
1240            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1241            .unwrap();
1242        constructor
1243            .navigate(PathSegment::Ident(teams.clone()))
1244            .unwrap();
1245        let team = constructor
1246            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1247            .unwrap();
1248        constructor
1249            .navigate(PathSegment::Ident(members.clone()))
1250            .unwrap();
1251        let member1 = constructor
1252            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1253            .unwrap();
1254        constructor
1255            .navigate(PathSegment::Ident(name.clone()))
1256            .unwrap();
1257        constructor.require_hole().unwrap();
1258        constructor
1259            .bind_primitive(PrimitiveValue::Text(Text::plaintext("Ada")))
1260            .unwrap();
1261        constructor.end_scope(t1).unwrap();
1262
1263        let t2 = constructor.begin_scope();
1264        constructor
1265            .navigate(PathSegment::Ident(orgs.clone()))
1266            .unwrap();
1267        let org_current = constructor
1268            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1269            .unwrap();
1270        constructor
1271            .navigate(PathSegment::Ident(teams.clone()))
1272            .unwrap();
1273        let team_current = constructor
1274            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1275            .unwrap();
1276        constructor
1277            .navigate(PathSegment::Ident(members.clone()))
1278            .unwrap();
1279        let member2 = constructor
1280            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1281            .unwrap();
1282        constructor
1283            .navigate(PathSegment::Ident(age.clone()))
1284            .unwrap();
1285        constructor.require_hole().unwrap();
1286        constructor
1287            .bind_primitive(PrimitiveValue::Integer(30.into()))
1288            .unwrap();
1289        constructor.end_scope(t2).unwrap();
1290
1291        assert_eq!(org_current, org);
1292        assert_eq!(team_current, team);
1293        assert_ne!(member1, member2, "the inner [] should create a new member");
1294
1295        // Team should now have two members.
1296        let members_node = {
1297            let team_map = constructor.document().node(team).as_map().unwrap();
1298            team_map
1299                .get_node_id(&ObjectKey::String(members.to_string()))
1300                .expect("members array")
1301        };
1302        let arr = constructor
1303            .document()
1304            .node(members_node)
1305            .as_array()
1306            .unwrap();
1307        assert_eq!(arr.len(), 2);
1308    }
1309
1310    #[test]
1311    fn test_array_current_followed_by_push_into_child() {
1312        // users[].name = "Ada"; users[^].posts[].title = "hello"
1313        // — reuse the same user, add a new nested post.
1314        let mut constructor = DocumentConstructor::new();
1315        let users = create_identifier("users");
1316        let name = create_identifier("name");
1317        let posts = create_identifier("posts");
1318        let title = create_identifier("title");
1319
1320        let t1 = constructor.begin_scope();
1321        constructor
1322            .navigate(PathSegment::Ident(users.clone()))
1323            .unwrap();
1324        let user = constructor
1325            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1326            .unwrap();
1327        constructor
1328            .navigate(PathSegment::Ident(name.clone()))
1329            .unwrap();
1330        constructor.require_hole().unwrap();
1331        constructor
1332            .bind_primitive(PrimitiveValue::Text(Text::plaintext("Ada")))
1333            .unwrap();
1334        constructor.end_scope(t1).unwrap();
1335
1336        let t2 = constructor.begin_scope();
1337        constructor
1338            .navigate(PathSegment::Ident(users.clone()))
1339            .unwrap();
1340        let same_user = constructor
1341            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1342            .unwrap();
1343        constructor
1344            .navigate(PathSegment::Ident(posts.clone()))
1345            .unwrap();
1346        constructor
1347            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Push))
1348            .unwrap();
1349        constructor
1350            .navigate(PathSegment::Ident(title.clone()))
1351            .unwrap();
1352        constructor.require_hole().unwrap();
1353        constructor
1354            .bind_primitive(PrimitiveValue::Text(Text::plaintext("hello")))
1355            .unwrap();
1356        constructor.end_scope(t2).unwrap();
1357
1358        assert_eq!(same_user, user);
1359    }
1360
1361    #[test]
1362    fn test_array_current_on_non_array_errors_as_expected_array() {
1363        // Set a field to a primitive first, then try `[^]` on it.
1364        // The ExpectedArray error should take precedence over ArrayCurrentOutOfScope.
1365        let mut constructor = DocumentConstructor::new();
1366        let items = create_identifier("items");
1367
1368        let t1 = constructor.begin_scope();
1369        constructor
1370            .navigate(PathSegment::Ident(items.clone()))
1371            .unwrap();
1372        constructor.require_hole().unwrap();
1373        constructor
1374            .bind_primitive(PrimitiveValue::Integer(42.into()))
1375            .unwrap();
1376        constructor.end_scope(t1).unwrap();
1377
1378        let t2 = constructor.begin_scope();
1379        constructor
1380            .navigate(PathSegment::Ident(items.clone()))
1381            .unwrap();
1382        let err = constructor
1383            .navigate(PathSegment::ArrayIndex(ArrayIndexKind::Current))
1384            .unwrap_err();
1385        assert!(
1386            matches!(err.kind, InsertErrorKind::ExpectedArray),
1387            "expected ExpectedArray, got {:?}",
1388            err.kind
1389        );
1390        constructor.end_scope(t2).unwrap();
1391    }
1392}