1use super::{
4 ast::{AttributeValue, TemplateNode},
5 tokenizer::{Token, Tokenizer},
6};
7use std::collections::HashMap;
8
9pub struct TemplateParser<'a> {
11 tokenizer: Tokenizer<'a>,
12}
13
14impl<'a> TemplateParser<'a> {
15 pub fn new(input: &'a str) -> Self {
17 Self {
18 tokenizer: Tokenizer::new(input),
19 }
20 }
21
22 pub fn parse(&mut self) -> Result<TemplateNode, String> {
24 match self.tokenizer.next_token() {
25 Token::OpenTag(tag) => self.parse_element(tag),
26 token => Err(format!("Expected opening tag, got {token:?}")),
27 }
28 }
29
30 fn parse_element(&mut self, tag: String) -> Result<TemplateNode, String> {
32 let mut attributes = HashMap::new();
33 let mut events = HashMap::new();
34 let mut children = Vec::new();
35
36 loop {
37 match self.tokenizer.next_token() {
38 Token::AttrName(name) => match self.tokenizer.next_token() {
39 Token::Equal => match self.tokenizer.next_token() {
40 Token::String(value) => {
41 if name.starts_with('@') {
43 let event_name = name.trim_start_matches('@');
44 events.insert(event_name.to_string(), value);
45 } else {
46 attributes.insert(name, AttributeValue::Static(value));
47 }
48 }
49 Token::ExprStart => {
50 let expr = self.parse_expression()?;
51 attributes.insert(name, AttributeValue::Dynamic(expr));
52 }
53 token => return Err(format!("Expected attribute value, got {token:?}")),
54 },
55 token => return Err(format!("Expected =, got {token:?}")),
56 },
57 Token::CloseTag(close_tag) => {
58 if close_tag != tag {
59 return Err(format!("Mismatched tags: {tag} and {close_tag}"));
60 }
61 break;
62 }
63 Token::SelfClosingTag(_) => break,
64 Token::Text(text) => {
65 if !text.trim().is_empty() {
67 children.push(TemplateNode::Text(text));
68 }
69 }
70 Token::Identifier(text) => {
71 children.push(TemplateNode::Text(text));
72 }
73 Token::Number(text) => {
74 children.push(TemplateNode::Text(text));
75 }
76 Token::Plus => {
77 children.push(TemplateNode::Text("+".to_string()));
78 }
79 Token::Minus => {
80 children.push(TemplateNode::Text("-".to_string()));
81 }
82 Token::Star => {
83 children.push(TemplateNode::Text("*".to_string()));
84 }
85 Token::Slash => {
86 children.push(TemplateNode::Text("/".to_string()));
87 }
88 Token::Comma => {
89 children.push(TemplateNode::Text(",".to_string()));
90 }
91 Token::ExprStart => {
92 let expr = self.parse_expression()?;
93 children.push(TemplateNode::Expression(expr));
94 }
95 Token::OpenTag(child_tag) => {
96 children.push(self.parse_element(child_tag)?);
97 }
98 Token::Eof => return Err("Unexpected end of template".to_string()),
99 token => return Err(format!("Unexpected token: {token:?}")),
100 }
101 }
102
103 Ok(TemplateNode::Element {
104 tag,
105 attributes,
106 events,
107 children,
108 })
109 }
110 fn parse_expression(&mut self) -> Result<String, String> {
112 let mut expr = String::new();
113 let mut prev_was_operator = false;
114 let mut prev_was_identifier = false;
115
116 loop {
117 match self.tokenizer.next_token() {
118 Token::ExprEnd => break,
119 Token::Identifier(ident) => {
120 if prev_was_identifier {
121 expr.push(' ');
122 }
123 expr.push_str(&ident);
124 prev_was_identifier = true;
125 prev_was_operator = false;
126 }
127 Token::Dot => {
128 expr.push('.');
129 prev_was_identifier = false;
130 prev_was_operator = false;
131 }
132 Token::Number(num) => {
133 if prev_was_identifier {
134 expr.push(' ');
135 }
136 expr.push_str(&num);
137 prev_was_identifier = true;
138 prev_was_operator = false;
139 }
140 Token::String(str) => {
141 expr.push_str(&format!("\"{str}\""));
142 prev_was_identifier = true;
143 prev_was_operator = false;
144 }
145 Token::Plus => {
146 if !prev_was_operator && !expr.is_empty() {
147 expr.push(' ');
148 }
149 expr.push('+');
150 expr.push(' ');
151 prev_was_identifier = false;
152 prev_was_operator = true;
153 }
154 Token::Minus => {
155 if !prev_was_operator && !expr.is_empty() {
156 expr.push(' ');
157 }
158 expr.push('-');
159 expr.push(' ');
160 prev_was_identifier = false;
161 prev_was_operator = true;
162 }
163 Token::Star => {
164 if !prev_was_operator && !expr.is_empty() {
165 expr.push(' ');
166 }
167 expr.push('*');
168 expr.push(' ');
169 prev_was_identifier = false;
170 prev_was_operator = true;
171 }
172 Token::Slash => {
173 if !prev_was_operator && !expr.is_empty() {
174 expr.push(' ');
175 }
176 expr.push('/');
177 expr.push(' ');
178 prev_was_identifier = false;
179 prev_was_operator = true;
180 }
181 Token::OpenParen => {
182 expr.push('(');
183 prev_was_identifier = false;
184 prev_was_operator = false;
185 }
186 Token::CloseParen => {
187 expr.push(')');
188 prev_was_identifier = true;
189 prev_was_operator = false;
190 }
191 Token::Comma => {
192 expr.push(',');
193 expr.push(' ');
194 prev_was_identifier = false;
195 prev_was_operator = false;
196 }
197 Token::Eof => return Err("Unclosed expression".to_string()),
198 token => return Err(format!("Unexpected token in expression: {token:?}")),
199 }
200 }
201
202 Ok(expr)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_parse_simple_element() {
212 let input = r#"<div class="greeting">Hello</div>"#;
213 let mut parser = TemplateParser::new(input);
214 let node = parser.parse().unwrap();
215
216 match node {
217 TemplateNode::Element {
218 tag,
219 attributes,
220 events,
221 children,
222 } => {
223 assert_eq!(tag, "div");
224 assert_eq!(attributes.len(), 1);
225 assert_eq!(events.len(), 0);
226 assert_eq!(children.len(), 1);
227
228 match &attributes.get("class").unwrap() {
229 AttributeValue::Static(value) => assert_eq!(value, "greeting"),
230 _ => panic!("Expected static attribute"),
231 }
232
233 match &children[0] {
234 TemplateNode::Text(text) => assert_eq!(text, "Hello"),
235 _ => panic!("Expected text node"),
236 }
237 }
238 _ => panic!("Expected element node"),
239 }
240 }
241
242 #[test]
243 fn test_parse_expression() {
244 let input = r#"<div>{{ count + 1 }}</div>"#;
245 let mut parser = TemplateParser::new(input);
246 let node = parser.parse().unwrap();
247
248 match node {
249 TemplateNode::Element {
250 tag,
251 attributes,
252 events,
253 children,
254 } => {
255 assert_eq!(tag, "div");
256 assert_eq!(attributes.len(), 0);
257 assert_eq!(events.len(), 0);
258 assert_eq!(children.len(), 1);
259
260 match &children[0] {
261 TemplateNode::Expression(expr) => assert_eq!(expr, "count + 1"),
262 _ => panic!("Expected expression node"),
263 }
264 }
265 _ => panic!("Expected element node"),
266 }
267 }
268
269 #[test]
270 fn test_parse_event_handler() {
271 let input = r#"<button @click="increment">+</button>"#;
272 let mut parser = TemplateParser::new(input);
273 let node = parser.parse().unwrap();
274
275 match node {
276 TemplateNode::Element {
277 tag,
278 attributes,
279 events,
280 children,
281 } => {
282 assert_eq!(tag, "button");
283 assert_eq!(attributes.len(), 0);
284 assert_eq!(events.len(), 1);
285 assert_eq!(children.len(), 1);
286
287 assert_eq!(events.get("click").unwrap(), "increment");
288
289 match &children[0] {
290 TemplateNode::Text(text) => assert_eq!(text, "+"),
291 _ => panic!("Expected text node"),
292 }
293 }
294 _ => panic!("Expected element node"),
295 }
296 }
297}