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 Uninitialized, replace it with Null
78        let root_id = self.document.get_root_id();
79        let root_node = self.document.node_mut(root_id);
80        if matches!(root_node.content, NodeValue::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 !matches!(node.content, NodeValue::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    pub fn bind_hole(&mut self) -> Result<(), InsertError> {
148        let node = self.current_node_mut();
149        if !matches!(node.content, NodeValue::Hole) {
150            return Err(InsertError {
151                kind: InsertErrorKind::BindingTargetHasValue,
152                path: EurePath::from_iter(self.current_path().iter().cloned()),
153            });
154        }
155        // Already hole
156        Ok(())
157    }
158
159    /// Bind a primitive value to the current node. Error if already bound.
160    pub fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), InsertError> {
161        let node = self.current_node_mut();
162        if !matches!(node.content, NodeValue::Hole) {
163            return Err(InsertError {
164                kind: InsertErrorKind::BindingTargetHasValue,
165                path: EurePath::from_iter(self.current_path().iter().cloned()),
166            });
167        }
168        node.content = NodeValue::Primitive(value);
169        Ok(())
170    }
171
172    /// Bind a value to the current node using `Into<PrimitiveValue>`.
173    ///
174    /// This is a convenience method for use with the `eure!` macro.
175    /// It accepts any type that implements `Into<PrimitiveValue>`.
176    pub fn bind_from(&mut self, value: impl Into<PrimitiveValue>) -> Result<(), InsertError> {
177        self.bind_primitive(value.into())
178    }
179
180    /// Bind an empty map to the current node. Error if already bound.
181    pub fn bind_empty_map(&mut self) -> Result<(), InsertError> {
182        let node = self.current_node_mut();
183        if !matches!(node.content, NodeValue::Hole) {
184            return Err(InsertError {
185                kind: InsertErrorKind::BindingTargetHasValue,
186                path: EurePath::from_iter(self.current_path().iter().cloned()),
187            });
188        }
189        node.content = NodeValue::Map(Default::default());
190        Ok(())
191    }
192
193    /// Bind an empty array to the current node. Error if already bound.
194    pub fn bind_empty_array(&mut self) -> Result<(), InsertError> {
195        let node = self.current_node_mut();
196        if !matches!(node.content, NodeValue::Hole) {
197            return Err(InsertError {
198                kind: InsertErrorKind::BindingTargetHasValue,
199                path: EurePath::from_iter(self.current_path().iter().cloned()),
200            });
201        }
202        node.content = NodeValue::Array(Default::default());
203        Ok(())
204    }
205
206    /// Bind an empty tuple to the current node. Error if already bound.
207    pub fn bind_empty_tuple(&mut self) -> Result<(), InsertError> {
208        let node = self.current_node_mut();
209        if !matches!(node.content, NodeValue::Hole) {
210            return Err(InsertError {
211                kind: InsertErrorKind::BindingTargetHasValue,
212                path: EurePath::from_iter(self.current_path().iter().cloned()),
213            });
214        }
215        node.content = NodeValue::Tuple(Default::default());
216        Ok(())
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use crate::identifier::IdentifierParser;
224
225    fn create_identifier(s: &str) -> Identifier {
226        let parser = IdentifierParser::init();
227        parser.parse(s).unwrap()
228    }
229
230    #[test]
231    fn test_new_initializes_at_root() {
232        let constructor = DocumentConstructor::new();
233        let root_id = constructor.document().get_root_id();
234
235        assert_eq!(constructor.current_node_id(), root_id);
236        assert_eq!(constructor.current_path(), &[]);
237    }
238
239    #[test]
240    fn test_current_node_returns_root_initially() {
241        let constructor = DocumentConstructor::new();
242
243        let node = constructor.current_node();
244        assert!(matches!(node.content, NodeValue::Hole));
245    }
246
247    #[test]
248    fn test_navigate_single_ident() {
249        let mut constructor = DocumentConstructor::new();
250
251        let identifier = create_identifier("field");
252        let segment = PathSegment::Ident(identifier.clone());
253
254        let node_id = constructor
255            .navigate(segment.clone())
256            .expect("Failed to navigate");
257
258        assert_eq!(constructor.current_node_id(), node_id);
259        assert_eq!(constructor.current_path(), &[segment]);
260    }
261
262    #[test]
263    fn test_navigate_multiple_times() {
264        let mut constructor = DocumentConstructor::new();
265
266        let id1 = create_identifier("field1");
267        let id2 = create_identifier("field2");
268
269        constructor
270            .navigate(PathSegment::Ident(id1.clone()))
271            .expect("Failed to navigate first");
272
273        let node_id2 = constructor
274            .navigate(PathSegment::Extension(id2.clone()))
275            .expect("Failed to navigate second");
276
277        assert_eq!(constructor.current_node_id(), node_id2);
278        assert_eq!(
279            constructor.current_path(),
280            &[PathSegment::Ident(id1), PathSegment::Extension(id2)]
281        );
282    }
283
284    #[test]
285    fn test_navigate_error_propagates() {
286        // Try to add tuple index to primitive node (should fail)
287        let mut constructor = DocumentConstructor::new();
288        // First navigate to the field node
289        let identifier = create_identifier("field");
290        constructor
291            .navigate(PathSegment::Ident(identifier))
292            .expect("Failed to navigate");
293        // Set it to Primitive
294        let node_id = constructor.current_node_id();
295        constructor.document_mut().node_mut(node_id).content =
296            NodeValue::Primitive(PrimitiveValue::Null);
297
298        let result = constructor.navigate(PathSegment::TupleIndex(0));
299
300        assert_eq!(
301            result.map_err(|e| e.kind),
302            Err(InsertErrorKind::ExpectedTuple)
303        );
304    }
305
306    #[test]
307    fn test_scope_success() {
308        let mut constructor = DocumentConstructor::new();
309        let root_id = constructor.document().get_root_id();
310
311        let identifier = create_identifier("field");
312        let token = constructor.begin_scope();
313        let _node_id = constructor
314            .navigate(PathSegment::Ident(identifier.clone()))
315            .expect("Failed to navigate");
316
317        // End scope
318        let result = constructor.end_scope(token);
319        assert_eq!(result, Ok(()));
320
321        // After end_scope, should be back at root
322        assert_eq!(constructor.current_node_id(), root_id);
323        assert_eq!(constructor.current_path(), &[]);
324    }
325
326    #[test]
327    fn test_scope_lifo_enforcement() {
328        let mut constructor = DocumentConstructor::new();
329
330        let id1 = create_identifier("field1");
331        let id2 = create_identifier("field2");
332
333        let token1 = constructor.begin_scope();
334        constructor
335            .navigate(PathSegment::Ident(id1))
336            .expect("Failed to navigate");
337
338        let token2 = constructor.begin_scope();
339        constructor
340            .navigate(PathSegment::Extension(id2))
341            .expect("Failed to navigate");
342
343        // Try to end token1 before token2 (should fail)
344        let result = constructor.end_scope(token1);
345        assert_eq!(result, Err(ScopeError::NotMostRecentScope));
346
347        // End in correct order
348        constructor
349            .end_scope(token2)
350            .expect("Failed to end scope 2");
351        constructor
352            .end_scope(token1)
353            .expect("Failed to end scope 1");
354    }
355
356    #[test]
357    fn test_scope_with_multiple_navigations() {
358        let mut constructor = DocumentConstructor::new();
359        let root_id = constructor.document().get_root_id();
360
361        let id1 = create_identifier("level1");
362        let id2 = create_identifier("level2");
363        let id3 = create_identifier("level3");
364
365        let token = constructor.begin_scope();
366
367        // Navigate three levels
368        let node_id1 = constructor
369            .navigate(PathSegment::Ident(id1.clone()))
370            .expect("Failed to navigate level1");
371
372        let node_id2 = constructor
373            .navigate(PathSegment::Extension(id2.clone()))
374            .expect("Failed to navigate level2");
375
376        let node_id3 = constructor
377            .navigate(PathSegment::Extension(id3.clone()))
378            .expect("Failed to navigate level3");
379
380        // Verify at deepest level
381        assert_eq!(constructor.current_node_id(), node_id3);
382        assert_eq!(
383            constructor.current_path(),
384            &[
385                PathSegment::Ident(id1.clone()),
386                PathSegment::Extension(id2.clone()),
387                PathSegment::Extension(id3)
388            ]
389        );
390
391        // End scope - should restore to root
392        constructor.end_scope(token).expect("Failed to end scope");
393        assert_eq!(constructor.current_node_id(), root_id);
394        assert_eq!(constructor.current_path(), &[]);
395
396        // Verify nodes still exist in document (node() panics if not found)
397        let _ = constructor.document().node(node_id1);
398        let _ = constructor.document().node(node_id2);
399        let _ = constructor.document().node(node_id3);
400    }
401
402    #[test]
403    fn test_nested_scopes() {
404        let mut constructor = DocumentConstructor::new();
405        let root_id = constructor.document().get_root_id();
406
407        let id1 = create_identifier("a");
408        let id2 = create_identifier("b");
409        let id3 = create_identifier("c");
410
411        // Outer scope: navigate to a
412        let token_outer = constructor.begin_scope();
413        let node_a = constructor
414            .navigate(PathSegment::Ident(id1.clone()))
415            .expect("Failed to navigate a");
416
417        // Inner scope: navigate to b.c
418        let token_inner = constructor.begin_scope();
419        let _node_b = constructor
420            .navigate(PathSegment::Extension(id2.clone()))
421            .expect("Failed to navigate b");
422        let _node_c = constructor
423            .navigate(PathSegment::Extension(id3.clone()))
424            .expect("Failed to navigate c");
425
426        // End inner scope - should be back at a
427        constructor
428            .end_scope(token_inner)
429            .expect("Failed to end inner scope");
430        assert_eq!(constructor.current_node_id(), node_a);
431        assert_eq!(constructor.current_path(), &[PathSegment::Ident(id1)]);
432
433        // End outer scope - should be back at root
434        constructor
435            .end_scope(token_outer)
436            .expect("Failed to end outer scope");
437        assert_eq!(constructor.current_node_id(), root_id);
438        assert_eq!(constructor.current_path(), &[]);
439    }
440
441    #[test]
442    fn test_require_hole_success() {
443        let mut constructor = DocumentConstructor::new();
444
445        let identifier = create_identifier("field");
446        constructor
447            .navigate(PathSegment::Ident(identifier))
448            .expect("Failed to navigate");
449
450        // New node should be a Hole
451        let result = constructor.require_hole();
452        assert_eq!(result, Ok(()));
453    }
454
455    #[test]
456    fn test_require_hole_fails_when_bound() {
457        let mut constructor = DocumentConstructor::new();
458
459        let identifier = create_identifier("field");
460        let node_id = constructor
461            .navigate(PathSegment::Ident(identifier))
462            .expect("Failed to navigate");
463
464        // Set the node to have a value
465        constructor.document_mut().node_mut(node_id).content =
466            NodeValue::Primitive(PrimitiveValue::Bool(true));
467
468        // require_hole should fail
469        let result = constructor.require_hole();
470        assert_eq!(
471            result.unwrap_err().kind,
472            InsertErrorKind::BindingTargetHasValue
473        );
474    }
475
476    #[test]
477    fn test_bind_primitive_success() {
478        let mut constructor = DocumentConstructor::new();
479        let identifier = create_identifier("field");
480
481        // Navigate to a field node
482        let node_id = constructor
483            .navigate(PathSegment::Ident(identifier))
484            .expect("Failed to navigate");
485
486        // Bind a primitive value to the node
487        let result = constructor.bind_primitive(PrimitiveValue::Bool(true));
488        assert_eq!(result, Ok(()));
489
490        // Verify the node content is set to Primitive
491        let node = constructor.document().node(node_id);
492        assert!(matches!(
493            node.content,
494            NodeValue::Primitive(PrimitiveValue::Bool(true))
495        ));
496    }
497
498    #[test]
499    fn test_bind_primitive_already_bound() {
500        let mut constructor = DocumentConstructor::new();
501        let identifier = create_identifier("field");
502
503        // Navigate to a field node
504        let node_id = constructor
505            .navigate(PathSegment::Ident(identifier.clone()))
506            .expect("Failed to navigate");
507
508        // Set the node to already have a value
509        constructor.document_mut().node_mut(node_id).content =
510            NodeValue::Primitive(PrimitiveValue::Null);
511
512        // Try to bind a primitive value (should fail)
513        let result = constructor.bind_primitive(PrimitiveValue::Bool(false));
514
515        assert_eq!(
516            result.unwrap_err().kind,
517            InsertErrorKind::BindingTargetHasValue
518        );
519
520        // Verify the node content remains unchanged
521        let node = constructor.document().node(node_id);
522        assert!(matches!(
523            node.content,
524            NodeValue::Primitive(PrimitiveValue::Null)
525        ));
526    }
527
528    #[test]
529    fn test_finish_replaces_uninitialized_root_with_null() {
530        let constructor = DocumentConstructor::new();
531
532        // Root should be Uninitialized before finish
533        let root_id = constructor.document().get_root_id();
534        assert!(matches!(
535            constructor.document().node(root_id).content,
536            NodeValue::Hole
537        ));
538
539        // After finish, root should be Null
540        let document = constructor.finish();
541        let root_node = document.node(document.get_root_id());
542        assert_eq!(root_node.content, NodeValue::Map(Default::default()));
543    }
544
545    #[test]
546    fn test_finish_preserves_initialized_root() {
547        let mut constructor = DocumentConstructor::new();
548
549        // Bind a value to the root
550        constructor
551            .bind_primitive(PrimitiveValue::Bool(true))
552            .expect("Failed to bind");
553
554        // After finish, root should still have the bound value
555        let document = constructor.finish();
556        let root_node = document.node(document.get_root_id());
557        assert!(matches!(
558            root_node.content,
559            NodeValue::Primitive(PrimitiveValue::Bool(true))
560        ));
561    }
562
563    #[test]
564    fn test_typical_binding_pattern() {
565        // Test the typical pattern: a.b.c = true
566        let mut constructor = DocumentConstructor::new();
567
568        let id_a = create_identifier("a");
569        let id_b = create_identifier("b");
570        let id_c = create_identifier("c");
571
572        let token = constructor.begin_scope();
573        constructor
574            .navigate(PathSegment::Ident(id_a.clone()))
575            .unwrap();
576        constructor
577            .navigate(PathSegment::Extension(id_b.clone()))
578            .unwrap();
579        let node_c = constructor
580            .navigate(PathSegment::Extension(id_c.clone()))
581            .unwrap();
582        constructor.require_hole().unwrap();
583        constructor
584            .bind_primitive(PrimitiveValue::Bool(true))
585            .unwrap();
586        constructor.end_scope(token).unwrap();
587
588        // Verify the value was bound
589        let node = constructor.document().node(node_c);
590        assert!(matches!(
591            node.content,
592            NodeValue::Primitive(PrimitiveValue::Bool(true))
593        ));
594    }
595}