1use crate::parser::{AstNode, Span};
4
5pub fn format_ast(node: &AstNode) -> String {
7 let mut output = String::new();
8 format_ast_impl(node, &mut output, 0);
9 output
10}
11
12fn format_ast_impl(node: &AstNode, output: &mut String, depth: usize) {
13 let indent = " ".repeat(depth);
14
15 match node {
16 AstNode::Program { body, span } => {
17 output.push_str(&format!(
18 "{}Program ({}..{})\n",
19 indent, span.start, span.end
20 ));
21 for child in body {
22 format_ast_impl(child, output, depth + 1);
23 }
24 }
25 AstNode::Text { content, span } => {
26 output.push_str(&format!(
27 "{}Text ({}..{}): {:?}\n",
28 indent, span.start, span.end, content
29 ));
30 }
31 AstNode::FunctionCall {
32 name,
33 args,
34 modifiers,
35 span,
36 ..
37 } => {
38 output.push_str(&format!(
39 "{}FunctionCall ({}..{}): ${}{}{}{}\n",
40 indent,
41 span.start,
42 span.end,
43 name,
44 if modifiers.silent { " [silent]" } else { "" },
45 if modifiers.negated { " [negated]" } else { "" },
46 if let Some(count) = &modifiers.count {
47 format!(" [count: {}]", count)
48 } else {
49 String::new()
50 }
51 ));
52 if let Some(args) = args {
53 for (i, arg) in args.iter().enumerate() {
54 output.push_str(&format!(
55 "{} Arg {} ({}..{}):\n",
56 indent, i, arg.span.start, arg.span.end
57 ));
58 for part in &arg.parts {
59 format_ast_impl(part, output, depth + 2);
60 }
61 }
62 }
63 }
64 AstNode::JavaScript { code, span } => {
65 output.push_str(&format!(
66 "{}JavaScript ({}..{}): {:?}\n",
67 indent, span.start, span.end, code
68 ));
69 }
70 AstNode::Escaped { content, span } => {
71 output.push_str(&format!(
72 "{}Escaped ({}..{}): {:?}\n",
73 indent, span.start, span.end, content
74 ));
75 }
76 }
77}
78
79pub fn extract_function_names(node: &AstNode) -> Vec<String> {
81 let mut names = Vec::new();
82 extract_function_names_impl(node, &mut names);
83 names
84}
85
86fn extract_function_names_impl(node: &AstNode, names: &mut Vec<String>) {
87 match node {
88 AstNode::Program { body, .. } => {
89 for child in body {
90 extract_function_names_impl(child, names);
91 }
92 }
93 AstNode::FunctionCall { name, args, .. } => {
94 names.push(name.clone());
95 if let Some(args) = args {
96 for arg in args {
97 for part in &arg.parts {
98 extract_function_names_impl(part, names);
99 }
100 }
101 }
102 }
103 _ => {}
104 }
105}
106
107pub fn count_nodes(node: &AstNode) -> usize {
109 match node {
110 AstNode::Program { body, .. } => 1 + body.iter().map(count_nodes).sum::<usize>(),
111 AstNode::FunctionCall { args, .. } => {
112 1 + args
113 .as_ref()
114 .map(|args| {
115 args.iter()
116 .map(|arg| arg.parts.iter().map(count_nodes).sum::<usize>())
117 .sum()
118 })
119 .unwrap_or(0)
120 }
121 _ => 1,
122 }
123}
124
125pub fn extract_text_nodes(node: &AstNode) -> Vec<(String, Span)> {
127 let mut texts = Vec::new();
128 extract_text_nodes_impl(node, &mut texts);
129 texts
130}
131
132fn extract_text_nodes_impl(node: &AstNode, texts: &mut Vec<(String, Span)>) {
133 match node {
134 AstNode::Program { body, .. } => {
135 for child in body {
136 extract_text_nodes_impl(child, texts);
137 }
138 }
139 AstNode::Text { content, span } => {
140 texts.push((content.clone(), *span));
141 }
142 AstNode::FunctionCall { args, .. } => {
143 if let Some(args) = args {
144 for arg in args {
145 for part in &arg.parts {
146 extract_text_nodes_impl(part, texts);
147 }
148 }
149 }
150 }
151 _ => {}
152 }
153}
154
155pub fn max_nesting_depth(node: &AstNode) -> usize {
157 max_nesting_depth_impl(node, 0)
158}
159
160fn max_nesting_depth_impl(node: &AstNode, current: usize) -> usize {
161 match node {
162 AstNode::Program { body, .. } => body
163 .iter()
164 .map(|n| max_nesting_depth_impl(n, current))
165 .max()
166 .unwrap_or(current),
167 AstNode::FunctionCall { args, .. } => {
168 let next = current + 1;
169 args.as_ref()
170 .map(|args| {
171 args.iter()
172 .flat_map(|arg| arg.parts.iter())
173 .map(|part| max_nesting_depth_impl(part, next))
174 .max()
175 .unwrap_or(next)
176 })
177 .unwrap_or(next)
178 }
179 _ => current,
180 }
181}
182
183pub fn contains_javascript(node: &AstNode) -> bool {
185 match node {
186 AstNode::Program { body, .. } => body.iter().any(contains_javascript),
187 AstNode::JavaScript { .. } => true,
188 AstNode::FunctionCall { args, .. } => args
189 .as_ref()
190 .map(|args| {
191 args.iter()
192 .flat_map(|arg| arg.parts.iter())
193 .any(contains_javascript)
194 })
195 .unwrap_or(false),
196 _ => false,
197 }
198}
199
200pub fn get_source_slice<'a>(source: &'a str, span: Span) -> &'a str {
202 &source[span.start..span.end.min(source.len())]
203}
204
205#[derive(Debug, Clone)]
207pub struct AstStats {
208 pub total_nodes: usize,
209 pub text_nodes: usize,
210 pub function_calls: usize,
211 pub javascript_nodes: usize,
212 pub escaped_nodes: usize,
213 pub max_depth: usize,
214 pub unique_functions: usize,
215}
216
217pub fn calculate_stats(node: &AstNode) -> AstStats {
218 let mut text_nodes = 0;
219 let mut function_calls = 0;
220 let mut javascript_nodes = 0;
221 let mut escaped_nodes = 0;
222
223 count_node_types(
224 node,
225 &mut text_nodes,
226 &mut function_calls,
227 &mut javascript_nodes,
228 &mut escaped_nodes,
229 );
230
231 let function_names = extract_function_names(node);
232 let mut unique = function_names.clone();
233 unique.sort();
234 unique.dedup();
235
236 AstStats {
237 total_nodes: count_nodes(node),
238 text_nodes,
239 function_calls,
240 javascript_nodes,
241 escaped_nodes,
242 max_depth: max_nesting_depth(node),
243 unique_functions: unique.len(),
244 }
245}
246
247fn count_node_types(
248 node: &AstNode,
249 text: &mut usize,
250 funcs: &mut usize,
251 js: &mut usize,
252 esc: &mut usize,
253) {
254 match node {
255 AstNode::Program { body, .. } => {
256 for child in body {
257 count_node_types(child, text, funcs, js, esc);
258 }
259 }
260 AstNode::Text { .. } => *text += 1,
261 AstNode::FunctionCall { args, .. } => {
262 *funcs += 1;
263 if let Some(args) = args {
264 for arg in args {
265 for part in &arg.parts {
266 count_node_types(part, text, funcs, js, esc);
267 }
268 }
269 }
270 }
271 AstNode::JavaScript { .. } => *js += 1,
272 AstNode::Escaped { .. } => *esc += 1,
273 }
274}
275
276pub fn flatten_ast(node: &AstNode) -> Vec<AstNode> {
278 let mut nodes = Vec::new();
279 flatten_ast_impl(node, &mut nodes);
280 nodes
281}
282
283fn flatten_ast_impl(node: &AstNode, nodes: &mut Vec<AstNode>) {
284 nodes.push(node.clone());
285 match node {
286 AstNode::Program { body, .. } => {
287 for child in body {
288 flatten_ast_impl(child, nodes);
289 }
290 }
291 AstNode::FunctionCall { args, .. } => {
292 if let Some(args) = args {
293 for arg in args {
294 for part in &arg.parts {
295 flatten_ast_impl(part, nodes);
296 }
297 }
298 }
299 }
300 _ => {}
301 }
302}