eure_document/document/
constructor.rs

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