1use proc_macro2::TokenStream;
2
3use crate::ast;
4
5pub fn raw(tokens: &TokenStream) -> String {
6 let raw = tokens.to_string();
7 let cleaned = raw
8 .replace(
9 ":: zyn :: proc_macro2 :: TokenStream :: new ()",
10 "TokenStream::new()",
11 )
12 .replace(":: zyn :: proc_macro2 :: TokenStream", "TokenStream")
13 .replace(":: zyn :: quote :: quote !", "quote!")
14 .replace(
15 ":: zyn :: quote :: ToTokens :: to_tokens",
16 "ToTokens::to_tokens",
17 )
18 .replace(":: zyn :: Pipe :: pipe", "Pipe::pipe")
19 .replace(":: zyn :: Render :: render", "Render::render")
20 .replace(":: core :: compile_error !", "compile_error!")
21 .replace(":: core :: result :: Result :: Ok", "Ok")
22 .replace(":: core :: result :: Result :: Err", "Err")
23 .replace("__zyn_ts_0", "output")
24 .replace("__zyn_ts_1", "inner_1")
25 .replace("__zyn_ts_2", "inner_2")
26 .replace("__zyn_ts_3", "inner_3")
27 .replace("__zyn_ts_4", "inner_4")
28 .replace("__zyn_val", "value")
29 .replace("__zyn_rendered", "rendered")
30 .replace("__zyn_expand_result", "result");
31
32 fmt(&cleaned)
33}
34
35pub fn ast(element: &ast::Element) -> String {
36 let mut lines = Vec::new();
37 lines.push("Element [".to_string());
38
39 for node in &element.nodes {
40 let desc = match node {
41 ast::Node::Tokens(t) => format!(" Tokens({:?})", t.stream.to_string()),
42 ast::Node::Interp(_) => " Interp { ... }".to_string(),
43 ast::Node::At(at) => {
44 let kind = if at.is_if() {
45 "If"
46 } else if at.is_for() {
47 "For"
48 } else if at.is_match() {
49 "Match"
50 } else if at.is_element() {
51 "Element"
52 } else {
53 "Unknown"
54 };
55 format!(" At({})", kind)
56 }
57 ast::Node::Group(_) => " Group { ... }".to_string(),
58 };
59 lines.push(desc);
60 }
61
62 lines.push("]".to_string());
63 lines.join("\n")
64}
65
66pub fn print(tokens: &TokenStream) {
67 let formatted = fmt(&tokens.to_string());
68 eprintln!("zyn::expand! ─── pretty\n{}", formatted);
69}
70
71pub fn fmt(input: &str) -> String {
72 let mut result = String::new();
73 let mut depth: usize = 0;
74 let mut chars = input.chars().peekable();
75
76 while let Some(ch) = chars.next() {
77 match ch {
78 '{' => {
79 result.push('{');
80 result.push('\n');
81 depth += 1;
82 push_indent(&mut result, depth);
83 }
84 '}' => {
85 depth = depth.saturating_sub(1);
86 if result.ends_with(" ") || result.ends_with('\n') {
87 result.truncate(result.trim_end().len());
88 result.push('\n');
89 }
90 push_indent(&mut result, depth);
91 result.push('}');
92
93 if chars
94 .peek()
95 .is_some_and(|c| *c != ',' && *c != ';' && *c != ')')
96 {
97 result.push('\n');
98 push_indent(&mut result, depth);
99 }
100 }
101 ';' => {
102 result.push(';');
103 result.push('\n');
104 push_indent(&mut result, depth);
105 }
106 ' ' if result.ends_with('\n') || result.ends_with(" ") => {}
107 _ => {
108 result.push(ch);
109 }
110 }
111 }
112
113 result.trim().to_string()
114}
115
116fn push_indent(s: &mut String, depth: usize) {
117 for _ in 0..depth {
118 s.push_str(" ");
119 }
120}