Skip to main content

glint/
parser.rs

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}