Skip to main content

eure_document/document/
constructor.rs

1use indexmap::IndexSet;
2
3use crate::document::interpreter_sink::InterpreterSink;
4use crate::map::PartialNodeMap;
5use crate::prelude_internal::*;
6use crate::value::PartialObjectKey;
7
8/// Represents a scope in the document constructor.
9/// Must be passed to `end_scope` to restore the constructor to the state when the scope was created.
10/// Scopes must be ended in LIFO order (most recent first).
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Scope {
13    id: usize,
14    stack_depth: usize,
15    path_depth: usize,
16}
17
18#[derive(Debug, PartialEq, thiserror::Error, Clone)]
19pub enum ScopeError {
20    #[error("Cannot end scope at root")]
21    CannotEndAtRoot,
22    #[error("Scope must be ended in LIFO order (most recent first)")]
23    NotMostRecentScope,
24}
25
26pub struct DocumentConstructor {
27    document: EureDocument,
28    /// The path from the root to the current node.
29    path: Vec<PathSegment>,
30    /// Stack of NodeIds from root to current position.
31    stack: Vec<NodeId>,
32    /// Counter for generating unique scope IDs.
33    scope_counter: usize,
34    /// Stack of outstanding scope IDs for LIFO enforcement.
35    outstanding_scopes: Vec<usize>,
36    /// Whether hole has been bound to the node
37    hole_bound: Vec<bool>,
38    /// IDs of nodes that are unbound.
39    unbound_nodes: IndexSet<NodeId>,
40}
41
42impl Default for DocumentConstructor {
43    fn default() -> Self {
44        let document = EureDocument::default();
45        let root = document.get_root_id();
46        Self {
47            document,
48            path: vec![],
49            stack: vec![root],
50            hole_bound: vec![false],
51            scope_counter: 0,
52            outstanding_scopes: vec![],
53            unbound_nodes: IndexSet::new(),
54        }
55    }
56}
57
58impl DocumentConstructor {
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    pub fn current_node_id(&self) -> NodeId {
64        *self.stack.last().expect("Stack should never be empty")
65    }
66
67    pub fn current_node(&self) -> &Node {
68        self.document.node(self.current_node_id())
69    }
70
71    pub fn current_node_mut(&mut self) -> &mut Node {
72        self.document.node_mut(self.current_node_id())
73    }
74
75    pub fn current_path(&self) -> &[PathSegment] {
76        &self.path
77    }
78
79    pub fn document(&self) -> &EureDocument {
80        &self.document
81    }
82
83    pub fn document_mut(&mut self) -> &mut EureDocument {
84        &mut self.document
85    }
86
87    pub fn finish(mut self) -> EureDocument {
88        for node_id in self.unbound_nodes {
89            let node = self.document.node_mut(node_id);
90            if node.content.is_hole() {
91                node.content = NodeValue::Map(Default::default());
92            }
93        }
94        // If the root node is Hole, empty map
95        let root_id = self.document.get_root_id();
96        let root_node = self.document.node_mut(root_id);
97        if root_node.content.is_hole() && !self.hole_bound[0] {
98            root_node.content = NodeValue::Map(Default::default());
99        }
100        self.document
101    }
102}
103
104impl DocumentConstructor {
105    /// Begin a new scope. Returns a scope handle that must be passed to `end_scope`.
106    /// Scopes must be ended in LIFO order (most recent first).
107    pub fn begin_scope(&mut self) -> Scope {
108        let id = self.scope_counter;
109        self.scope_counter += 1;
110        self.outstanding_scopes.push(id);
111        Scope {
112            id,
113            stack_depth: self.stack.len(),
114            path_depth: self.path.len(),
115        }
116    }
117
118    /// End a scope, restoring the constructor to the state when the scope was created.
119    /// Returns an error if the scope is not the most recent outstanding scope.
120    pub fn end_scope(&mut self, scope: Scope) -> Result<(), ScopeError> {
121        // LIFO enforcement: scope must be the most recent outstanding scope
122        if self.outstanding_scopes.last() != Some(&scope.id) {
123            return Err(ScopeError::NotMostRecentScope);
124        }
125        if scope.stack_depth < 1 {
126            return Err(ScopeError::CannotEndAtRoot);
127        }
128        self.outstanding_scopes.pop();
129        for i in scope.stack_depth..self.stack.len() {
130            let hole_bound = self.hole_bound[i];
131            if !hole_bound && self.document.node(self.stack[i]).content.is_hole() {
132                self.unbound_nodes.insert(self.stack[i]);
133            }
134        }
135        self.stack.truncate(scope.stack_depth);
136        self.hole_bound.truncate(scope.stack_depth);
137        self.path.truncate(scope.path_depth);
138        Ok(())
139    }
140
141    /// Navigate to a child node by path segment.
142    /// Creates the node if it doesn't exist.
143    pub fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, InsertError> {
144        let current = self.current_node_id();
145        let node_mut = self
146            .document
147            .resolve_child_by_segment(segment.clone(), current)
148            .map_err(|e| InsertError {
149                kind: e,
150                path: EurePath::from_iter(self.path.iter().cloned()),
151            })?;
152        let node_id = node_mut.node_id;
153        self.stack.push(node_id);
154        self.hole_bound.push(false);
155        self.path.push(segment);
156        Ok(node_id)
157    }
158
159    /// Navigate into a PartialMap entry.
160    ///
161    /// Find-or-create semantics:
162    /// - labeled holes and resolved keys reuse an existing entry
163    /// - anonymous holes (`Hole(None)`) always create a fresh entry
164    pub fn navigate_partial_map_entry(
165        &mut self,
166        key: PartialObjectKey,
167    ) -> Result<NodeId, InsertError> {
168        let current = self.current_node_id();
169        let existing = self
170            .document
171            .node(current)
172            .as_partial_map()
173            .and_then(|pm| pm.find(&key))
174            .copied();
175
176        let node_id = if let Some(node_id) = existing {
177            node_id
178        } else {
179            self.document
180                .add_partial_map_child(key.clone(), current)
181                .map_err(|kind| InsertError {
182                    kind,
183                    path: EurePath::from_iter(self.path.iter().cloned()),
184                })?
185                .node_id
186        };
187
188        let segment = PathSegment::from_partial_object_key(key);
189
190        self.stack.push(node_id);
191        self.hole_bound.push(false);
192        self.path.push(segment);
193        Ok(node_id)
194    }
195
196    /// Validate that the current node is a Hole (unbound).
197    /// Use this before binding a value to ensure the node hasn't already been assigned.
198    pub fn require_hole(&self) -> Result<(), InsertError> {
199        let node = self.current_node();
200        if !node.content.is_hole() {
201            return Err(InsertError {
202                kind: InsertErrorKind::BindingTargetHasValue,
203                path: EurePath::from_iter(self.path.iter().cloned()),
204            });
205        }
206        Ok(())
207    }
208
209    /// Bind a hole (optionally labeled) to the current node.
210    pub fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), InsertError> {
211        if !self.current_node().content.is_hole() {
212            return Err(InsertError {
213                kind: InsertErrorKind::BindingTargetHasValue,
214                path: EurePath::from_iter(self.current_path().iter().cloned()),
215            });
216        }
217        self.hole_bound[self.stack.len() - 1] = true;
218        self.unbound_nodes.swap_remove(&self.current_node_id());
219        self.current_node_mut().content = NodeValue::Hole(label);
220        Ok(())
221    }
222
223    /// Bind a primitive value to the current node. Error if already bound.
224    pub fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), InsertError> {
225        let node = self.current_node_mut();
226        if !node.content.is_hole() {
227            return Err(InsertError {
228                kind: InsertErrorKind::BindingTargetHasValue,
229                path: EurePath::from_iter(self.current_path().iter().cloned()),
230            });
231        }
232        node.content = NodeValue::Primitive(value);
233        Ok(())
234    }
235
236    /// Bind a value to the current node using `Into<PrimitiveValue>`.
237    ///
238    /// This is a convenience method for use with the `eure!` macro.
239    /// It accepts any type that implements `Into<PrimitiveValue>`.
240    pub fn bind_from(&mut self, value: impl Into<PrimitiveValue>) -> Result<(), InsertError> {
241        self.bind_primitive(value.into())
242    }
243
244    /// Bind an empty map to the current node. Error if already bound.
245    pub fn bind_empty_map(&mut self) -> Result<(), InsertError> {
246        let node = self.current_node_mut();
247        if !node.content.is_hole() {
248            return Err(InsertError {
249                kind: InsertErrorKind::BindingTargetHasValue,
250                path: EurePath::from_iter(self.current_path().iter().cloned()),
251            });
252        }
253        node.content = NodeValue::Map(Default::default());
254        Ok(())
255    }
256
257    /// Bind an empty PartialMap to the current node. Error if already bound.
258    pub fn bind_empty_partial_map(&mut self) -> Result<(), InsertError> {
259        let node = self.current_node_mut();
260        if !node.content.is_hole() {
261            return Err(InsertError {
262                kind: InsertErrorKind::BindingTargetHasValue,
263                path: EurePath::from_iter(self.current_path().iter().cloned()),
264            });
265        }
266        node.content = NodeValue::PartialMap(PartialNodeMap::new());
267        Ok(())
268    }
269
270    /// Bind an empty array to the current node. Error if already bound.
271    pub fn bind_empty_array(&mut self) -> Result<(), InsertError> {
272        let node = self.current_node_mut();
273        if !node.content.is_hole() {
274            return Err(InsertError {
275                kind: InsertErrorKind::BindingTargetHasValue,
276                path: EurePath::from_iter(self.current_path().iter().cloned()),
277            });
278        }
279        node.content = NodeValue::Array(Default::default());
280        Ok(())
281    }
282
283    /// Bind an empty tuple to the current node. Error if already bound.
284    pub fn bind_empty_tuple(&mut self) -> Result<(), InsertError> {
285        let node = self.current_node_mut();
286        if !node.content.is_hole() {
287            return Err(InsertError {
288                kind: InsertErrorKind::BindingTargetHasValue,
289                path: EurePath::from_iter(self.current_path().iter().cloned()),
290            });
291        }
292        node.content = NodeValue::Tuple(Default::default());
293        Ok(())
294    }
295
296    // =========================================================================
297    // Source Layout Markers (no-op for DocumentConstructor)
298    //
299    // These methods allow the eure! macro to call them without importing the
300    // InterpreterSink trait. They use the trait's default no-op implementations.
301    // =========================================================================
302
303    /// Enter a new EureSource block. No-op for DocumentConstructor.
304    pub fn begin_eure_block(&mut self) {}
305
306    /// Set the value binding for current block. No-op for DocumentConstructor.
307    pub fn set_block_value(&mut self) -> Result<(), InsertError> {
308        Ok(())
309    }
310
311    /// End current EureSource block. No-op for DocumentConstructor.
312    pub fn end_eure_block(&mut self) -> Result<(), InsertError> {
313        Ok(())
314    }
315
316    /// Mark the start of a binding statement. No-op for DocumentConstructor.
317    pub fn begin_binding(&mut self) {}
318
319    /// End binding #1: path = value. No-op for DocumentConstructor.
320    pub fn end_binding_value(&mut self) -> Result<(), InsertError> {
321        Ok(())
322    }
323
324    /// End binding #2/#3: path { eure }. No-op for DocumentConstructor.
325    pub fn end_binding_block(&mut self) -> Result<(), InsertError> {
326        Ok(())
327    }
328
329    /// Start a section header. No-op for DocumentConstructor.
330    pub fn begin_section(&mut self) {}
331
332    /// Begin section #4: items follow. No-op for DocumentConstructor.
333    pub fn begin_section_items(&mut self) {}
334
335    /// End section #4: finalize section with items. No-op for DocumentConstructor.
336    pub fn end_section_items(&mut self) -> Result<(), InsertError> {
337        Ok(())
338    }
339
340    /// End section #5/#6: block. No-op for DocumentConstructor.
341    pub fn end_section_block(&mut self) -> Result<(), InsertError> {
342        Ok(())
343    }
344}
345
346impl InterpreterSink for DocumentConstructor {
347    type Error = InsertError;
348    type Scope = Scope;
349
350    fn begin_scope(&mut self) -> Self::Scope {
351        DocumentConstructor::begin_scope(self)
352    }
353
354    fn end_scope(&mut self, scope: Self::Scope) -> Result<(), Self::Error> {
355        DocumentConstructor::end_scope(self, scope).map_err(|e| InsertError {
356            kind: InsertErrorKind::ScopeError(e),
357            path: EurePath::from_iter(self.current_path().iter().cloned()),
358        })
359    }
360
361    fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, Self::Error> {
362        DocumentConstructor::navigate(self, segment)
363    }
364
365    fn require_hole(&self) -> Result<(), Self::Error> {
366        DocumentConstructor::require_hole(self)
367    }
368
369    fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), Self::Error> {
370        DocumentConstructor::bind_primitive(self, value)
371    }
372
373    fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), Self::Error> {
374        DocumentConstructor::bind_hole(self, label)
375    }
376
377    fn bind_empty_map(&mut self) -> Result<(), Self::Error> {
378        DocumentConstructor::bind_empty_map(self)
379    }
380
381    fn bind_empty_array(&mut self) -> Result<(), Self::Error> {
382        DocumentConstructor::bind_empty_array(self)
383    }
384
385    fn bind_empty_tuple(&mut self) -> Result<(), Self::Error> {
386        DocumentConstructor::bind_empty_tuple(self)
387    }
388
389    fn current_node_id(&self) -> NodeId {
390        DocumentConstructor::current_node_id(self)
391    }
392
393    fn current_path(&self) -> &[PathSegment] {
394        DocumentConstructor::current_path(self)
395    }
396
397    fn document(&self) -> &EureDocument {
398        DocumentConstructor::document(self)
399    }
400
401    fn document_mut(&mut self) -> &mut EureDocument {
402        DocumentConstructor::document_mut(self)
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409    use crate::identifier::IdentifierParser;
410    use crate::value::{PartialObjectKey, Tuple};
411
412    fn create_identifier(s: &str) -> Identifier {
413        let parser = IdentifierParser::init();
414        parser.parse(s).unwrap()
415    }
416
417    #[test]
418    fn test_new_initializes_at_root() {
419        let constructor = DocumentConstructor::new();
420        let root_id = constructor.document().get_root_id();
421
422        assert_eq!(constructor.current_node_id(), root_id);
423        assert_eq!(constructor.current_path(), &[]);
424    }
425
426    #[test]
427    fn test_current_node_returns_root_initially() {
428        let constructor = DocumentConstructor::new();
429
430        let node = constructor.current_node();
431        assert!(node.content.is_hole());
432    }
433
434    #[test]
435    fn test_navigate_single_ident() {
436        let mut constructor = DocumentConstructor::new();
437
438        let identifier = create_identifier("field");
439        let segment = PathSegment::Ident(identifier.clone());
440
441        let node_id = constructor
442            .navigate(segment.clone())
443            .expect("Failed to navigate");
444
445        assert_eq!(constructor.current_node_id(), node_id);
446        assert_eq!(constructor.current_path(), &[segment]);
447    }
448
449    #[test]
450    fn test_navigate_multiple_times() {
451        let mut constructor = DocumentConstructor::new();
452
453        let id1 = create_identifier("field1");
454        let id2 = create_identifier("field2");
455
456        constructor
457            .navigate(PathSegment::Ident(id1.clone()))
458            .expect("Failed to navigate first");
459
460        let node_id2 = constructor
461            .navigate(PathSegment::Extension(id2.clone()))
462            .expect("Failed to navigate second");
463
464        assert_eq!(constructor.current_node_id(), node_id2);
465        assert_eq!(
466            constructor.current_path(),
467            &[PathSegment::Ident(id1), PathSegment::Extension(id2)]
468        );
469    }
470
471    #[test]
472    fn test_navigate_error_propagates() {
473        // Try to add tuple index to primitive node (should fail)
474        let mut constructor = DocumentConstructor::new();
475        // First navigate to the field node
476        let identifier = create_identifier("field");
477        constructor
478            .navigate(PathSegment::Ident(identifier))
479            .expect("Failed to navigate");
480        // Set it to Primitive
481        let node_id = constructor.current_node_id();
482        constructor.document_mut().node_mut(node_id).content =
483            NodeValue::Primitive(PrimitiveValue::Null);
484
485        let result = constructor.navigate(PathSegment::TupleIndex(0));
486
487        assert_eq!(
488            result.map_err(|e| e.kind),
489            Err(InsertErrorKind::ExpectedTuple)
490        );
491    }
492
493    #[test]
494    fn test_scope_success() {
495        let mut constructor = DocumentConstructor::new();
496        let root_id = constructor.document().get_root_id();
497
498        let identifier = create_identifier("field");
499        let token = constructor.begin_scope();
500        let _node_id = constructor
501            .navigate(PathSegment::Ident(identifier.clone()))
502            .expect("Failed to navigate");
503
504        // End scope
505        let result = constructor.end_scope(token);
506        assert_eq!(result, Ok(()));
507
508        // After end_scope, should be back at root
509        assert_eq!(constructor.current_node_id(), root_id);
510        assert_eq!(constructor.current_path(), &[]);
511    }
512
513    #[test]
514    fn test_scope_lifo_enforcement() {
515        let mut constructor = DocumentConstructor::new();
516
517        let id1 = create_identifier("field1");
518        let id2 = create_identifier("field2");
519
520        let token1 = constructor.begin_scope();
521        constructor
522            .navigate(PathSegment::Ident(id1))
523            .expect("Failed to navigate");
524
525        let token2 = constructor.begin_scope();
526        constructor
527            .navigate(PathSegment::Extension(id2))
528            .expect("Failed to navigate");
529
530        // Try to end token1 before token2 (should fail)
531        let result = constructor.end_scope(token1);
532        assert_eq!(result, Err(ScopeError::NotMostRecentScope));
533
534        // End in correct order
535        constructor
536            .end_scope(token2)
537            .expect("Failed to end scope 2");
538        constructor
539            .end_scope(token1)
540            .expect("Failed to end scope 1");
541    }
542
543    #[test]
544    fn test_scope_with_multiple_navigations() {
545        let mut constructor = DocumentConstructor::new();
546        let root_id = constructor.document().get_root_id();
547
548        let id1 = create_identifier("level1");
549        let id2 = create_identifier("level2");
550        let id3 = create_identifier("level3");
551
552        let token = constructor.begin_scope();
553
554        // Navigate three levels
555        let node_id1 = constructor
556            .navigate(PathSegment::Ident(id1.clone()))
557            .expect("Failed to navigate level1");
558
559        let node_id2 = constructor
560            .navigate(PathSegment::Extension(id2.clone()))
561            .expect("Failed to navigate level2");
562
563        let node_id3 = constructor
564            .navigate(PathSegment::Extension(id3.clone()))
565            .expect("Failed to navigate level3");
566
567        // Verify at deepest level
568        assert_eq!(constructor.current_node_id(), node_id3);
569        assert_eq!(
570            constructor.current_path(),
571            &[
572                PathSegment::Ident(id1.clone()),
573                PathSegment::Extension(id2.clone()),
574                PathSegment::Extension(id3)
575            ]
576        );
577
578        // End scope - should restore to root
579        constructor.end_scope(token).expect("Failed to end scope");
580        assert_eq!(constructor.current_node_id(), root_id);
581        assert_eq!(constructor.current_path(), &[]);
582
583        // Verify nodes still exist in document (node() panics if not found)
584        let _ = constructor.document().node(node_id1);
585        let _ = constructor.document().node(node_id2);
586        let _ = constructor.document().node(node_id3);
587    }
588
589    #[test]
590    fn test_nested_scopes() {
591        let mut constructor = DocumentConstructor::new();
592        let root_id = constructor.document().get_root_id();
593
594        let id1 = create_identifier("a");
595        let id2 = create_identifier("b");
596        let id3 = create_identifier("c");
597
598        // Outer scope: navigate to a
599        let token_outer = constructor.begin_scope();
600        let node_a = constructor
601            .navigate(PathSegment::Ident(id1.clone()))
602            .expect("Failed to navigate a");
603
604        // Inner scope: navigate to b.c
605        let token_inner = constructor.begin_scope();
606        let _node_b = constructor
607            .navigate(PathSegment::Extension(id2.clone()))
608            .expect("Failed to navigate b");
609        let _node_c = constructor
610            .navigate(PathSegment::Extension(id3.clone()))
611            .expect("Failed to navigate c");
612
613        // End inner scope - should be back at a
614        constructor
615            .end_scope(token_inner)
616            .expect("Failed to end inner scope");
617        assert_eq!(constructor.current_node_id(), node_a);
618        assert_eq!(constructor.current_path(), &[PathSegment::Ident(id1)]);
619
620        // End outer scope - should be back at root
621        constructor
622            .end_scope(token_outer)
623            .expect("Failed to end outer scope");
624        assert_eq!(constructor.current_node_id(), root_id);
625        assert_eq!(constructor.current_path(), &[]);
626    }
627
628    #[test]
629    fn test_require_hole_success() {
630        let mut constructor = DocumentConstructor::new();
631
632        let identifier = create_identifier("field");
633        constructor
634            .navigate(PathSegment::Ident(identifier))
635            .expect("Failed to navigate");
636
637        // New node should be a Hole
638        let result = constructor.require_hole();
639        assert_eq!(result, Ok(()));
640    }
641
642    #[test]
643    fn test_require_hole_fails_when_bound() {
644        let mut constructor = DocumentConstructor::new();
645
646        let identifier = create_identifier("field");
647        let node_id = constructor
648            .navigate(PathSegment::Ident(identifier))
649            .expect("Failed to navigate");
650
651        // Set the node to have a value
652        constructor.document_mut().node_mut(node_id).content =
653            NodeValue::Primitive(PrimitiveValue::Bool(true));
654
655        // require_hole should fail
656        let result = constructor.require_hole();
657        assert_eq!(
658            result.unwrap_err().kind,
659            InsertErrorKind::BindingTargetHasValue
660        );
661    }
662
663    #[test]
664    fn test_bind_primitive_success() {
665        let mut constructor = DocumentConstructor::new();
666        let identifier = create_identifier("field");
667
668        // Navigate to a field node
669        let node_id = constructor
670            .navigate(PathSegment::Ident(identifier))
671            .expect("Failed to navigate");
672
673        // Bind a primitive value to the node
674        let result = constructor.bind_primitive(PrimitiveValue::Bool(true));
675        assert_eq!(result, Ok(()));
676
677        // Verify the node content is set to Primitive
678        let node = constructor.document().node(node_id);
679        assert!(matches!(
680            node.content,
681            NodeValue::Primitive(PrimitiveValue::Bool(true))
682        ));
683    }
684
685    #[test]
686    fn test_bind_primitive_already_bound() {
687        let mut constructor = DocumentConstructor::new();
688        let identifier = create_identifier("field");
689
690        // Navigate to a field node
691        let node_id = constructor
692            .navigate(PathSegment::Ident(identifier.clone()))
693            .expect("Failed to navigate");
694
695        // Set the node to already have a value
696        constructor.document_mut().node_mut(node_id).content =
697            NodeValue::Primitive(PrimitiveValue::Null);
698
699        // Try to bind a primitive value (should fail)
700        let result = constructor.bind_primitive(PrimitiveValue::Bool(false));
701
702        assert_eq!(
703            result.unwrap_err().kind,
704            InsertErrorKind::BindingTargetHasValue
705        );
706
707        // Verify the node content remains unchanged
708        let node = constructor.document().node(node_id);
709        assert!(matches!(
710            node.content,
711            NodeValue::Primitive(PrimitiveValue::Null)
712        ));
713    }
714
715    #[test]
716    fn test_finish_replaces_uninitialized_root_with_null() {
717        let constructor = DocumentConstructor::new();
718
719        // Root should be Hole before finish
720        let root_id = constructor.document().get_root_id();
721        assert!(constructor.document().node(root_id).content.is_hole());
722
723        // After finish, root should be empty map
724        let document = constructor.finish();
725        let root_node = document.node(document.get_root_id());
726        assert_eq!(root_node.content, NodeValue::Map(Default::default()));
727    }
728
729    #[test]
730    fn test_finish_preserves_initialized_root() {
731        let mut constructor = DocumentConstructor::new();
732
733        // Bind a value to the root
734        constructor
735            .bind_primitive(PrimitiveValue::Bool(true))
736            .expect("Failed to bind");
737
738        // After finish, root should still have the bound value
739        let document = constructor.finish();
740        let root_node = document.node(document.get_root_id());
741        assert!(matches!(
742            root_node.content,
743            NodeValue::Primitive(PrimitiveValue::Bool(true))
744        ));
745    }
746
747    #[test]
748    fn test_finish_preserves_partial_map_root() {
749        let mut constructor = DocumentConstructor::new();
750
751        constructor
752            .navigate_partial_map_entry(PartialObjectKey::Hole(Some(create_identifier("x"))))
753            .unwrap();
754        constructor
755            .bind_primitive(PrimitiveValue::Integer(1.into()))
756            .unwrap();
757
758        let document = constructor.finish();
759        assert!(matches!(
760            document.node(document.get_root_id()).content,
761            NodeValue::PartialMap(_)
762        ));
763    }
764
765    #[test]
766    fn test_navigate_partial_map_entry_does_not_reuse_tuple_with_anonymous_hole() {
767        let mut constructor = DocumentConstructor::new();
768        let scope = constructor.begin_scope();
769
770        let key = PartialObjectKey::Tuple(Tuple(vec![
771            PartialObjectKey::Number(1.into()),
772            PartialObjectKey::Hole(None),
773        ]));
774
775        let first = constructor.navigate_partial_map_entry(key.clone()).unwrap();
776        constructor.end_scope(scope).unwrap();
777
778        let second_scope = constructor.begin_scope();
779        let second = constructor.navigate_partial_map_entry(key).unwrap();
780
781        assert_ne!(first, second);
782        constructor.end_scope(second_scope).unwrap();
783    }
784
785    #[test]
786    fn test_navigate_reuses_labeled_hole_key_segment() {
787        let mut constructor = DocumentConstructor::new();
788        let label = create_identifier("x");
789
790        let scope = constructor.begin_scope();
791        let first = constructor
792            .navigate(PathSegment::HoleKey(Some(label.clone())))
793            .unwrap();
794        constructor.end_scope(scope).unwrap();
795
796        let scope = constructor.begin_scope();
797        let second = constructor
798            .navigate(PathSegment::HoleKey(Some(label)))
799            .unwrap();
800
801        assert_eq!(first, second);
802        constructor.end_scope(scope).unwrap();
803    }
804
805    #[test]
806    fn test_typical_binding_pattern() {
807        // Test the typical pattern: a.b.c = true
808        let mut constructor = DocumentConstructor::new();
809
810        let id_a = create_identifier("a");
811        let id_b = create_identifier("b");
812        let id_c = create_identifier("c");
813
814        let token = constructor.begin_scope();
815        constructor
816            .navigate(PathSegment::Ident(id_a.clone()))
817            .unwrap();
818        constructor
819            .navigate(PathSegment::Extension(id_b.clone()))
820            .unwrap();
821        let node_c = constructor
822            .navigate(PathSegment::Extension(id_c.clone()))
823            .unwrap();
824        constructor.require_hole().unwrap();
825        constructor
826            .bind_primitive(PrimitiveValue::Bool(true))
827            .unwrap();
828        constructor.end_scope(token).unwrap();
829
830        // Verify the value was bound
831        let node = constructor.document().node(node_c);
832        assert!(matches!(
833            node.content,
834            NodeValue::Primitive(PrimitiveValue::Bool(true))
835        ));
836    }
837}