1use crate::{
2 node_kind::{NonTerminalKind, TerminalKind},
3 tree::{ConcreteSyntaxTree, CstNodeData, CstNodeId, NonTerminalData, TerminalData},
4};
5
6#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct BuilderNodeId(usize);
31
32impl CstBuilder {
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 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 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 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 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 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 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 builder.apply_to_tree(tree)
112 }
113 };
114 command_roots.push(root_id);
115 }
116
117 *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 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 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 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 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 let value_children: Vec<_> = tree.children(root_id).collect();
173 assert_eq!(value_children.len(), 1, "Value should have one child");
174
175 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 let boolean_children: Vec<_> = tree.children(boolean_id).collect();
190 assert_eq!(boolean_children.len(), 1, "Boolean should have one child");
191
192 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 if let TerminalData::Dynamic(token_id) = data {
203 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 assert_eq!(inner_bool, BuilderNodeId(1));
214 assert_eq!(nested_id, BuilderNodeId(0));
216 assert_eq!(wrapper, BuilderNodeId(1));
218 }
219}