1use crate::ast::*;
2
3pub struct Parser<'a> {
4 src: &'a [u8],
5 pos: usize,
6 pub module: ModuleSoA,
7}
8
9impl<'a> Parser<'a> {
10 pub fn new(src: &'a str) -> Self {
11 Self {
12 src: src.as_bytes(),
13 pos: 0,
14 module: ModuleSoA::new(),
15 }
16 }
17
18 #[inline]
19 fn peek(&self) -> Option<u8> {
20 self.src.get(self.pos).copied()
21 }
22
23 #[inline]
24 fn advance(&mut self) {
25 self.pos += 1;
26 }
27
28 #[inline]
29 fn consume_if(&mut self, expected: u8) -> bool {
30 if self.peek() == Some(expected) {
31 self.pos += 1;
32 true
33 } else {
34 false
35 }
36 }
37
38 fn skip_whitespace(&mut self) {
39 while let Some(c) = self.peek() {
40 if c.is_ascii_whitespace() {
41 self.advance();
42 } else if c == b'/' && self.src.get(self.pos + 1) == Some(&b'/') {
43 while let Some(cc) = self.peek() {
44 if cc == b'\n' {
45 break;
46 }
47 self.advance();
48 }
49 } else {
50 break;
51 }
52 }
53 }
54
55 fn parse_ident(&mut self) -> String {
56 let start = self.pos;
57 while let Some(c) = self.peek() {
58 if c.is_ascii_alphanumeric() || c == b'-' || c == b'_' || c == b'.' {
59 self.advance();
60 } else {
61 break;
62 }
63 }
64 std::str::from_utf8(&self.src[start..self.pos])
65 .unwrap()
66 .to_string()
67 }
68
69 fn parse_rhei_expr(&mut self) -> Result<String, String> {
70 self.skip_whitespace();
71 if self.consume_if(b'{') {
72 let start = self.pos;
73 let mut depth = 1;
74 while depth > 0 {
75 let c = self.peek().ok_or("Unexpected EOF in Rhei block")?;
76 self.advance();
77 if c == b'{' {
78 depth += 1;
79 }
80 if c == b'}' {
81 depth -= 1;
82 }
83 }
84 let expr = std::str::from_utf8(&self.src[start..self.pos - 1])
85 .unwrap()
86 .trim()
87 .to_string();
88 Ok(expr)
89 } else {
90 let start = self.pos;
91 let mut parens = 0;
92 let mut in_str = false;
93 let mut escape = false;
94 while let Some(c) = self.peek() {
95 if escape {
96 escape = false;
97 self.advance();
98 continue;
99 }
100 if c == b'\\' {
101 escape = true;
102 self.advance();
103 continue;
104 }
105 if c == b'"' {
106 in_str = !in_str;
107 }
108 if !in_str {
109 if c == b'(' {
110 parens += 1;
111 }
112 if c == b')' {
113 if parens == 0 {
114 break;
115 }
116 parens -= 1;
117 }
118 if parens == 0 && (c == b',' || c == b'\n' || c == b'}' || c == b'{') {
119 break;
120 }
121 }
122 self.advance();
123 }
124 let expr = std::str::from_utf8(&self.src[start..self.pos])
125 .unwrap()
126 .trim()
127 .to_string();
128 Ok(expr)
129 }
130 }
131
132 fn parse_value(&mut self) -> Result<Value, String> {
133 self.skip_whitespace();
134 let c = self.peek().ok_or("Expected value, found EOF")?;
135 match c {
136 b'"' => {
137 self.advance();
138 let start = self.pos;
139 while self.peek() != Some(b'"') {
140 if self.peek().is_none() {
141 return Err("Unexpected EOF inside string literal".into());
142 }
143 self.advance();
144 }
145 let val = std::str::from_utf8(&self.src[start..self.pos])
146 .unwrap()
147 .to_string();
148 self.advance();
149 Ok(Value::String(val))
150 }
151 b'#' => {
152 self.advance();
153 let mut color = String::from("#");
154 color.push_str(&self.parse_ident());
155 Ok(Value::Color(color))
156 }
157 b'$' => {
158 self.advance();
159 Ok(Value::Variable(self.parse_ident()))
160 }
161 b'!' => {
162 self.advance();
163 let ident = self.parse_ident();
164 if ident != "rhei" {
165 return Err("Expected !rhei".into());
166 }
167 self.consume_if(b':');
168 Ok(Value::Rhei(self.parse_rhei_expr()?))
169 }
170 b'0'..=b'9' | b'-' => {
171 let s = self.parse_ident();
172 if s.contains('.') {
173 Ok(Value::Float(s.parse().unwrap()))
174 } else {
175 Ok(Value::Int(s.parse().unwrap()))
176 }
177 }
178 _ => {
179 let s = self.parse_ident();
180 match s.as_str() {
181 "true" => Ok(Value::Bool(true)),
182 "false" => Ok(Value::Bool(false)),
183 "fs" => {
184 self.consume_if(b':');
185 let start = self.pos;
186 while let Some(cc) = self.peek() {
187 if cc.is_ascii_whitespace() || cc == b',' || cc == b')' || cc == b'}' {
188 break;
189 }
190 self.advance();
191 }
192 let path = std::str::from_utf8(&self.src[start..self.pos])
193 .unwrap()
194 .to_string();
195 Ok(Value::FsPath(path))
196 }
197 _ => Err(format!("Unknown value token: {}", s)),
198 }
199 }
200 }
201 }
202
203 fn parse_properties(&mut self) -> Result<(u32, u32), String> {
204 let start_idx = self.module.prop_keys.len() as u32;
205 self.advance();
206 loop {
207 self.skip_whitespace();
208 if self.consume_if(b')') {
209 break;
210 }
211 let key = self.parse_ident();
212 if key.is_empty() {
213 return Err(format!(
214 "Expected property identifier, found {:?}",
215 self.peek().map(|b| b as char)
216 ));
217 }
218 self.skip_whitespace();
219 if self.consume_if(b'=') {
220 let val = self.parse_value()?;
221 self.module.prop_keys.push(key);
222 self.module.prop_values.push(val);
223 }
224 self.skip_whitespace();
225 self.consume_if(b',');
226 }
227 let len = (self.module.prop_keys.len() as u32) - start_idx;
228 Ok((start_idx, len))
229 }
230
231 fn parse_block(&mut self) -> Result<(u32, u32), String> {
232 self.skip_whitespace();
233 if !self.consume_if(b'{') {
234 return Ok((self.module.hierarchy.len() as u32, 0));
235 }
236
237 let mut direct_children = Vec::new();
238 loop {
239 self.skip_whitespace();
240 if self.consume_if(b'}') {
241 break;
242 }
243 let node = self.parse_node()?;
244 direct_children.push(node);
245 }
246
247 let start_idx = self.module.hierarchy.len() as u32;
248 let len = direct_children.len() as u32;
249 self.module.hierarchy.extend(direct_children);
250 Ok((start_idx, len))
251 }
252
253 fn parse_directive(&mut self) -> Result<NodeId, String> {
254 self.advance();
255 let name = self.parse_ident();
256 let dir = match name.as_str() {
257 "version" => {
258 self.skip_whitespace();
259 let v = self.parse_ident().parse().unwrap();
260 Directive::Version(v)
261 }
262 "style" => Directive::Style(match self.parse_value()? {
263 Value::String(s) => s,
264 _ => return Err("Style must be a string".into()),
265 }),
266 "global" => {
267 self.skip_whitespace();
268 if !self.consume_if(b'$') {
269 return Err("Expected $ var".into());
270 }
271 let var_name = self.parse_ident();
272 self.skip_whitespace();
273 self.consume_if(b'=');
274 let val = self.parse_value()?;
275 Directive::Global {
276 name: var_name,
277 value: val,
278 }
279 }
280 "singleton" => {
281 self.skip_whitespace();
282 let sname = self.parse_ident();
283 self.skip_whitespace();
284 let start_idx = self.module.prop_keys.len() as u32;
285 if self.consume_if(b'{') {
286 loop {
287 self.skip_whitespace();
288 if self.consume_if(b'}') {
289 break;
290 }
291
292 let key = self.parse_ident();
293 if key.is_empty() {
294 return Err(format!(
295 "Expected singleton property identifier, found {:?}",
296 self.peek().map(|b| b as char)
297 ));
298 }
299 self.skip_whitespace();
300 self.consume_if(b'=');
301 let val = self.parse_value()?;
302 self.module.prop_keys.push(key);
303 self.module.prop_values.push(val);
304 }
305 }
306 let len = (self.module.prop_keys.len() as u32) - start_idx;
307 Directive::Singleton {
308 name: sname,
309 prop_span: (start_idx, len),
310 }
311 }
312 "component" => {
313 self.skip_whitespace();
314 let cname = self.parse_ident();
315 let mut params = Vec::new();
316 self.skip_whitespace();
317 if self.consume_if(b'(') {
318 loop {
319 self.skip_whitespace();
320 if self.consume_if(b')') {
321 break;
322 }
323 let pname = self.parse_ident();
324 self.skip_whitespace();
325 self.consume_if(b':');
326 self.skip_whitespace();
327 let ptype = self.parse_ident();
328 params.push((pname, ptype));
329 self.skip_whitespace();
330 self.consume_if(b',');
331 }
332 }
333 let child_span = self.parse_block()?;
334 Directive::Component {
335 name: cname,
336 params,
337 child_span,
338 }
339 }
340 "let" => {
341 self.skip_whitespace();
342 self.consume_if(b'$');
343 let var_name = self.parse_ident();
344 self.skip_whitespace();
345 self.consume_if(b'=');
346 let val = self.parse_value()?;
347 Directive::Let {
348 name: var_name,
349 value: val,
350 }
351 }
352 "if" => {
353 let condition = self.parse_value()?;
354 let child_span = self.parse_block()?;
355 let mut else_span = None;
356 self.skip_whitespace();
357
358 let backup = self.pos;
359 if self.consume_if(b'@') {
360 if self.parse_ident() == "else" {
361 else_span = Some(self.parse_block()?);
362 } else {
363 self.pos = backup;
364 }
365 }
366 Directive::If {
367 condition,
368 child_span,
369 else_span,
370 }
371 }
372 "each" => {
373 self.skip_whitespace();
374 self.consume_if(b'$');
375 let item = self.parse_ident();
376 self.skip_whitespace();
377 let in_kw = self.parse_ident();
378 if in_kw != "in" {
379 return Err("Expected 'in' in @each".into());
380 }
381 let collection = self.parse_value()?;
382 let child_span = self.parse_block()?;
383 Directive::Each {
384 item,
385 collection,
386 child_span,
387 }
388 }
389 "on" => {
390 self.skip_whitespace();
391 let event = self.parse_ident();
392 let mut args = Vec::new();
393 self.skip_whitespace();
394 if self.consume_if(b'(') {
395 loop {
396 self.skip_whitespace();
397 if self.consume_if(b')') {
398 break;
399 }
400 let k = self.parse_ident();
401 self.skip_whitespace();
402 self.consume_if(b'=');
403 let v = self.parse_value()?;
404 args.push((k, v));
405 self.skip_whitespace();
406 self.consume_if(b',');
407 }
408 }
409 let child_span = self.parse_block()?;
410 Directive::On {
411 event,
412 args,
413 child_span,
414 }
415 }
416 _ => return Err(format!("Unknown directive: @{}", name)),
417 };
418 Ok(NodeId::Directive(self.module.push_directive(dir)))
419 }
420
421 fn parse_element(&mut self) -> Result<NodeId, String> {
422 let name = self.parse_ident();
423 let el_id = self.module.push_element(name);
424
425 self.skip_whitespace();
426 if self.peek() == Some(b'(') {
427 let span = self.parse_properties()?;
428 self.module.elem_prop_spans[el_id as usize] = span;
429 }
430
431 self.skip_whitespace();
432 let p = self.peek();
433 if p == Some(b'{') {
434 let span = self.parse_block()?;
435 self.module.elem_child_spans[el_id as usize] = span;
436 } else if p == Some(b'"') || p == Some(b'$') {
437 let val = self.parse_value()?;
438 self.module.elem_content[el_id as usize] = Some(val);
439 }
440
441 Ok(NodeId::Element(el_id))
442 }
443
444 pub fn parse_node(&mut self) -> Result<NodeId, String> {
445 self.skip_whitespace();
446 let c = self.peek().ok_or("EOF reached")?;
447
448 match c {
449 b'@' => self.parse_directive(),
450 b'!' => {
451 self.advance();
452 let i = self.parse_ident();
453 if i != "rhei" {
454 return Err("Expected rhei".into());
455 }
456 self.consume_if(b':');
457 let expr = self.parse_rhei_expr()?;
458 Ok(NodeId::Directive(
459 self.module.push_directive(Directive::RheiBlock(expr)),
460 ))
461 }
462 b'A'..=b'Z' => self.parse_element(),
463 b'"' | b'$' => {
464 let val = self.parse_value()?;
465 let el_id = self.module.push_element("#text".to_string());
466 self.module.elem_content[el_id as usize] = Some(val);
467 Ok(NodeId::Element(el_id))
468 }
469 _ => Err(format!("Unexpected token: {}", c as char)),
470 }
471 }
472
473 pub fn parse_all(&mut self) -> Result<(), String> {
474 self.skip_whitespace();
475 while self.peek().is_some() {
476 let node = self.parse_node()?;
477 self.module.hierarchy.push(node);
478 self.skip_whitespace();
479 }
480 Ok(())
481 }
482}