Skip to main content

eure_document/document/
constructor.rs

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