Skip to main content

eure_tree/
builder.rs

1use crate::{
2    node_kind::{NonTerminalKind, TerminalKind},
3    tree::{ConcreteSyntaxTree, CstNodeData, CstNodeId, NonTerminalData, TerminalData},
4};
5
6/// A specialized builder for constructing CST nodes.
7/// This is a simpler alternative to CstCommands, designed specifically for tree construction.
8#[derive(Debug, Clone, Default)]
9pub struct CstBuilder {
10    commands: Vec<BuildCommand>,
11}
12
13#[derive(Debug, Clone)]
14pub enum BuildCommand {
15    Terminal {
16        kind: TerminalKind,
17        data: String,
18    },
19    NonTerminal {
20        kind: NonTerminalKind,
21        children: Vec<BuilderNodeId>,
22    },
23    Nested {
24        builder: CstBuilder,
25    },
26}
27
28/// A node ID within a CstBuilder context
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct BuilderNodeId(usize);
31
32impl CstBuilder {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Insert a terminal and return its ID
38    pub fn terminal(&mut self, kind: TerminalKind, data: impl Into<String>) -> BuilderNodeId {
39        let id = BuilderNodeId(self.commands.len());
40        self.commands.push(BuildCommand::Terminal {
41            kind,
42            data: data.into(),
43        });
44        id
45    }
46
47    /// Insert a non-terminal with children and return its ID
48    pub fn non_terminal(
49        &mut self,
50        kind: NonTerminalKind,
51        children: Vec<impl Into<BuilderNodeId>>,
52    ) -> BuilderNodeId {
53        let id = BuilderNodeId(self.commands.len());
54        self.commands.push(BuildCommand::NonTerminal {
55            kind,
56            children: children.into_iter().map(|c| c.into()).collect(),
57        });
58        id
59    }
60
61    /// Embed another builder and return its ID
62    pub fn embed(&mut self, builder: CstBuilder) -> BuilderNodeId {
63        let id = BuilderNodeId(self.commands.len());
64        self.commands.push(BuildCommand::Nested { builder });
65        id
66    }
67
68    /// Apply to tree and return the root node ID
69    pub fn apply<T, NT>(self, tree: &mut ConcreteSyntaxTree<T, NT>) -> CstNodeId
70    where
71        T: From<TerminalKind> + Clone,
72        NT: From<NonTerminalKind> + Clone,
73    {
74        self.apply_to_tree(tree)
75    }
76
77    fn apply_to_tree<T, NT>(self, tree: &mut ConcreteSyntaxTree<T, NT>) -> CstNodeId
78    where
79        T: From<TerminalKind> + Clone,
80        NT: From<NonTerminalKind> + Clone,
81    {
82        // Track the root node for each command
83        let mut command_roots = Vec::with_capacity(self.commands.len());
84
85        for command in self.commands {
86            let root_id = match command {
87                BuildCommand::Terminal { kind, data } => {
88                    let token_id = tree.insert_dynamic_terminal(data);
89                    tree.add_node(CstNodeData::Terminal {
90                        kind: kind.into(),
91                        data: TerminalData::Dynamic(token_id),
92                    })
93                }
94                BuildCommand::NonTerminal { kind, children } => {
95                    let node_id = tree.add_node(CstNodeData::NonTerminal {
96                        kind: kind.into(),
97                        data: NonTerminalData::Dynamic,
98                    });
99
100                    // Look up children from command_roots
101                    let child_ids: Vec<_> = children
102                        .iter()
103                        .map(|&BuilderNodeId(idx)| command_roots[idx])
104                        .collect();
105
106                    tree.update_children(node_id, child_ids);
107                    node_id
108                }
109                BuildCommand::Nested { builder } => {
110                    // The nested builder returns its root node
111                    builder.apply_to_tree(tree)
112                }
113            };
114            command_roots.push(root_id);
115        }
116
117        // Return the root (last node created)
118        *command_roots
119            .last()
120            .expect("Builder must have at least one command")
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::tree::{CstNodeData, DynamicTokenId, TerminalData};
128
129    #[test]
130    fn test_builder_basic() {
131        let mut builder = CstBuilder::new();
132        let term_id = builder.terminal(TerminalKind::Integer, "42");
133        let node_id = builder.non_terminal(NonTerminalKind::Value, vec![term_id]);
134
135        assert_eq!(term_id, BuilderNodeId(0));
136        assert_eq!(node_id, BuilderNodeId(1));
137    }
138
139    #[test]
140    fn test_builder_nested() {
141        // Build inner structure: Boolean(True)
142        let mut inner = CstBuilder::new();
143        let inner_term = inner.terminal(TerminalKind::True, "true");
144        let inner_bool = inner.non_terminal(NonTerminalKind::Boolean, vec![inner_term]);
145
146        // Build outer structure: Value(embedded inner)
147        let mut outer = CstBuilder::new();
148        let nested_id = outer.embed(inner);
149        let wrapper = outer.non_terminal(NonTerminalKind::Value, vec![nested_id]);
150
151        // Apply to tree
152        let mut tree: ConcreteSyntaxTree<TerminalKind, NonTerminalKind> =
153            ConcreteSyntaxTree::new(CstNodeData::Terminal {
154                kind: TerminalKind::Whitespace,
155                data: TerminalData::Dynamic(DynamicTokenId(0)),
156            });
157        tree.insert_dynamic_terminal("");
158
159        let root_id = outer.apply(&mut tree);
160        tree.set_root(root_id);
161
162        // Verify tree structure
163        // Root should be Value node
164        match tree.node_data(root_id) {
165            Some(CstNodeData::NonTerminal { kind, .. }) => {
166                assert_eq!(kind, NonTerminalKind::Value, "Root should be Value node");
167            }
168            _ => panic!("Expected Value non-terminal at root"),
169        }
170
171        // Value should have one child (the embedded Boolean)
172        let value_children: Vec<_> = tree.children(root_id).collect();
173        assert_eq!(value_children.len(), 1, "Value should have one child");
174
175        // The child should be Boolean node
176        let boolean_id = value_children[0];
177        match tree.node_data(boolean_id) {
178            Some(CstNodeData::NonTerminal { kind, .. }) => {
179                assert_eq!(
180                    kind,
181                    NonTerminalKind::Boolean,
182                    "Child should be Boolean node"
183                );
184            }
185            _ => panic!("Expected Boolean non-terminal"),
186        }
187
188        // Boolean should have one child (True terminal)
189        let boolean_children: Vec<_> = tree.children(boolean_id).collect();
190        assert_eq!(boolean_children.len(), 1, "Boolean should have one child");
191
192        // The child should be True terminal
193        let true_id = boolean_children[0];
194        match tree.node_data(true_id) {
195            Some(CstNodeData::Terminal { kind, data }) => {
196                assert_eq!(
197                    kind,
198                    TerminalKind::True,
199                    "Grandchild should be True terminal"
200                );
201                // Verify the data contains "true"
202                if let TerminalData::Dynamic(token_id) = data {
203                    // The tree structure is correct, token content was set during terminal creation
204                    assert_eq!(tree.dynamic_token(token_id).unwrap(), "true");
205                } else {
206                    panic!("Expected dynamic terminal data");
207                }
208            }
209            _ => panic!("Expected True terminal"),
210        }
211
212        // The inner_bool ID in the inner builder should be 1 (after the terminal)
213        assert_eq!(inner_bool, BuilderNodeId(1));
214        // The nested_id in outer builder should be 0 (first command)
215        assert_eq!(nested_id, BuilderNodeId(0));
216        // The wrapper in outer builder should be 1 (second command)
217        assert_eq!(wrapper, BuilderNodeId(1));
218    }
219}