Skip to main content

newter_compiler/
parser.rs

1//! Recursive descent parser for the Newt UI language.
2
3use crate::ast::*;
4use crate::error::{NewtError, Span};
5use crate::lexer::{Lexer, Token, TokenKind};
6
7pub struct Parser<'a> {
8    tokens: Vec<Token>,
9    index: usize,
10    errors: Vec<NewtError>,
11    _marker: std::marker::PhantomData<&'a str>,
12}
13
14impl<'a> Parser<'a> {
15    pub fn new(source: &'a str, path: Option<&'a str>) -> Result<Self, NewtError> {
16        let mut lexer = Lexer::new(source, path);
17        let mut tokens = Vec::new();
18        loop {
19            let t = lexer.next_token()?;
20            let is_eof = matches!(t.kind, TokenKind::Eof);
21            tokens.push(t);
22            if is_eof {
23                break;
24            }
25        }
26        Ok(Self {
27            tokens,
28            index: 0,
29            errors: Vec::new(),
30            _marker: std::marker::PhantomData,
31        })
32    }
33
34    fn current(&self) -> &Token {
35        self.tokens.get(self.index).unwrap_or_else(|| self.tokens.last().unwrap())
36    }
37
38    fn advance(&mut self) -> Token {
39        let t = self.current().clone();
40        if self.index < self.tokens.len() {
41            self.index += 1;
42        }
43        t
44    }
45
46    fn expect(&mut self, kind: TokenKind) -> Result<Token, NewtError> {
47        let cur = self.current();
48        if std::mem::discriminant(&cur.kind) == std::mem::discriminant(&kind) {
49            return Ok(self.advance());
50        }
51        Err(NewtError::parse(
52            cur.span,
53            format!("expected {}, got {}", kind, cur.kind),
54        ))
55    }
56
57    fn expect_ident(&mut self) -> Result<String, NewtError> {
58        let cur = self.current();
59        match &cur.kind {
60            TokenKind::Ident(s) => {
61                let name = s.clone();
62                self.advance();
63                Ok(name)
64            }
65            _ => Err(NewtError::parse(
66                cur.span,
67                format!("expected identifier, got {}", cur.kind),
68            )),
69        }
70    }
71
72    fn at(&self, kind: TokenKind) -> bool {
73        std::mem::discriminant(&self.current().kind) == std::mem::discriminant(&kind)
74    }
75
76    fn at_ident(&self) -> bool {
77        matches!(self.current().kind, TokenKind::Ident(_))
78    }
79
80    fn peek_next(&self) -> Option<&Token> {
81        self.tokens.get(self.index + 1)
82    }
83
84    fn at_ident_then_colon(&self) -> bool {
85        if !self.at_ident() {
86            return false;
87        }
88        self.peek_next()
89            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(&TokenKind::Colon))
90            .unwrap_or(false)
91    }
92
93    pub fn parse(&mut self) -> Result<Program, Vec<NewtError>> {
94        let mut items = Vec::new();
95        while !self.at(TokenKind::Eof) {
96            match self.parse_top_level_item() {
97                Ok(item) => items.push(item),
98                Err(e) => {
99                    self.errors.push(e);
100                    self.synchronize();
101                }
102            }
103        }
104        if self.errors.is_empty() {
105            Ok(Program { items })
106        } else {
107            Err(std::mem::take(&mut self.errors))
108        }
109    }
110
111    fn parse_top_level_item(&mut self) -> Result<ProgramItem, NewtError> {
112        if self.at(TokenKind::Import) {
113            Ok(ProgramItem::Import(self.parse_import()?))
114        } else if self.at(TokenKind::Theme) {
115            Ok(ProgramItem::Theme(self.parse_theme()?))
116        } else if self.at(TokenKind::Use) {
117            Ok(ProgramItem::UseTheme(self.parse_use_theme()?))
118        } else if self.at(TokenKind::Let) {
119            Ok(ProgramItem::Variable(self.parse_variable()?))
120        } else if self.at(TokenKind::Component) {
121            Ok(ProgramItem::Component(self.parse_component()?))
122        } else if self.at(TokenKind::Screen) {
123            Ok(ProgramItem::Screen(self.parse_screen()?))
124        } else if self.at(TokenKind::State) {
125            Ok(ProgramItem::StateDecl(self.parse_state_decl()?))
126        } else {
127            let cur = self.current();
128            let msg = format!(
129                "expected import, theme, use theme, let, state, component, or screen at top level, got {}",
130                cur.kind
131            );
132            let err = if let TokenKind::Ident(ref s) = cur.kind {
133                if let Some(suggestion) = closest_top_level_keyword(s) {
134                    NewtError::parse_with_suggestion(cur.span, msg, format!("did you mean `{}`?", suggestion))
135                } else {
136                    NewtError::parse(cur.span, msg)
137                }
138            } else {
139                NewtError::parse(cur.span, msg)
140            };
141            Err(err)
142        }
143    }
144
145    /// Skip tokens until we reach a safe restart point for top-level parsing.
146    /// Tracks brace depth so we skip entire `{ ... }` blocks.
147    fn synchronize(&mut self) {
148        let mut depth = 0i32;
149        loop {
150            match self.current().kind {
151                TokenKind::Eof => break,
152                TokenKind::LeftBrace => { depth += 1; self.advance(); }
153                TokenKind::RightBrace => {
154                    if depth > 0 {
155                        depth -= 1;
156                        self.advance();
157                        if depth == 0 {
158                            // Finished skipping a balanced block — ready for next top-level item
159                            break;
160                        }
161                    } else {
162                        self.advance();
163                    }
164                }
165                TokenKind::Semicolon if depth == 0 => { self.advance(); break; }
166                TokenKind::Let | TokenKind::Component | TokenKind::Screen
167                | TokenKind::Theme | TokenKind::Import | TokenKind::Use
168                | TokenKind::State if depth == 0 => break,
169                _ => { self.advance(); }
170            }
171        }
172    }
173
174    fn parse_import(&mut self) -> Result<ImportDecl, NewtError> {
175        let span = self.current().span;
176        self.expect(TokenKind::Import)?;
177        let path = match &self.current().kind {
178            TokenKind::String(s) => s.clone(),
179            _ => return Err(NewtError::parse(self.current().span, "import expects a string path")),
180        };
181        self.advance();
182        self.expect(TokenKind::Semicolon)?;
183        Ok(ImportDecl { path, span })
184    }
185
186    fn parse_theme(&mut self) -> Result<ThemeDecl, NewtError> {
187        let span = self.current().span;
188        self.expect(TokenKind::Theme)?;
189        let name = self.expect_ident()?;
190        self.expect(TokenKind::LeftBrace)?;
191        let mut vars = Vec::new();
192        while !self.at(TokenKind::RightBrace) && !self.at(TokenKind::Eof) {
193            if self.at(TokenKind::Let) {
194                vars.push(self.parse_variable()?);
195            } else {
196                return Err(NewtError::parse(
197                    self.current().span,
198                    "theme body may only contain let declarations",
199                ));
200            }
201        }
202        self.expect(TokenKind::RightBrace)?;
203        Ok(ThemeDecl { name, vars, span })
204    }
205
206    fn parse_use_theme(&mut self) -> Result<String, NewtError> {
207        self.expect(TokenKind::Use)?;
208        self.expect(TokenKind::Theme)?;
209        let name = self.expect_ident()?;
210        self.expect(TokenKind::Semicolon)?;
211        Ok(name)
212    }
213
214    fn parse_variable(&mut self) -> Result<VariableDecl, NewtError> {
215        let span = self.current().span;
216        self.expect(TokenKind::Let)?;
217        let name = self.expect_ident()?;
218        self.expect(TokenKind::Eq)?;
219        let value = self.parse_expr()?;
220        self.expect(TokenKind::Semicolon)?;
221        Ok(VariableDecl { name, value, span })
222    }
223
224    fn parse_state_decl(&mut self) -> Result<StateVarDecl, NewtError> {
225        let span = self.current().span;
226        self.expect(TokenKind::State)?;
227        let name = self.expect_ident()?;
228        self.expect(TokenKind::Eq)?;
229        let initial_value = self.parse_expr()?;
230        self.expect(TokenKind::Semicolon)?;
231        Ok(StateVarDecl { name, initial_value, span })
232    }
233
234    fn parse_component(&mut self) -> Result<ComponentDecl, NewtError> {
235        let span = self.current().span;
236        self.expect(TokenKind::Component)?;
237        let name = self.expect_ident()?;
238        let params = if self.at(TokenKind::LeftParen) {
239            self.advance();
240            let mut p = Vec::new();
241            if !self.at(TokenKind::RightParen) {
242                p.push(self.expect_ident()?);
243                while self.at(TokenKind::Comma) {
244                    self.advance();
245                    p.push(self.expect_ident()?);
246                }
247            }
248            self.expect(TokenKind::RightParen)?;
249            p
250        } else {
251            Vec::new()
252        };
253        self.expect(TokenKind::LeftBrace)?;
254        let body = self.parse_expr()?;
255        self.expect(TokenKind::RightBrace)?;
256        Ok(ComponentDecl { name, params, body, span })
257    }
258
259    /// Parse block body (after opening {): exprs until }, return Block. No leading {.
260    fn parse_block_body(&mut self, span: Span) -> Result<Expr, NewtError> {
261        let mut stmts = Vec::new();
262        while !self.at(TokenKind::RightBrace) && !self.at(TokenKind::Eof) {
263            if self.at(TokenKind::Let) {
264                self.advance();
265                let name = self.expect_ident()?;
266                self.expect(TokenKind::Eq)?;
267                let value = self.parse_expr()?;
268                self.expect(TokenKind::Semicolon)?;
269                stmts.push(Stmt::Let {
270                    name,
271                    value,
272                    span: self.current().span,
273                });
274            } else if self.at(TokenKind::State) {
275                let sd = self.parse_state_decl()?;
276                stmts.push(Stmt::StateDecl(sd));
277            } else {
278                stmts.push(Stmt::Expr(self.parse_expr()?));
279                if self.at(TokenKind::Semicolon) {
280                    self.advance();
281                }
282            }
283        }
284        Ok(Expr::Block { stmts, span })
285    }
286
287    fn parse_screen(&mut self) -> Result<ScreenDecl, NewtError> {
288        let span = self.current().span;
289        self.expect(TokenKind::Screen)?;
290        let name = if self.at(TokenKind::LeftParen) {
291            self.advance();
292            let n = self.expect_ident()?;
293            self.expect(TokenKind::RightParen)?;
294            n
295        } else if self.at_ident() {
296            self.expect_ident()?
297        } else {
298            "Main".to_string()
299        };
300        self.expect(TokenKind::LeftBrace)?;
301        let body = self.parse_block_body(span)?;
302        self.expect(TokenKind::RightBrace)?;
303        Ok(ScreenDecl { name, body, span })
304    }
305
306    fn parse_expr(&mut self) -> Result<Expr, NewtError> {
307        self.parse_or()
308    }
309
310    fn parse_or(&mut self) -> Result<Expr, NewtError> {
311        let mut left = self.parse_and()?;
312        while self.at(TokenKind::Or) {
313            let span = self.current().span;
314            self.advance();
315            let right = self.parse_and()?;
316            left = Expr::Binary {
317                left: Box::new(left),
318                op: BinaryOp::Or,
319                right: Box::new(right),
320                span,
321            };
322        }
323        Ok(left)
324    }
325
326    fn parse_and(&mut self) -> Result<Expr, NewtError> {
327        let mut left = self.parse_equality()?;
328        while self.at(TokenKind::And) {
329            let span = self.current().span;
330            self.advance();
331            let right = self.parse_equality()?;
332            left = Expr::Binary {
333                left: Box::new(left),
334                op: BinaryOp::And,
335                right: Box::new(right),
336                span,
337            };
338        }
339        Ok(left)
340    }
341
342    fn parse_equality(&mut self) -> Result<Expr, NewtError> {
343        let mut left = self.parse_comparison()?;
344        loop {
345            let span = self.current().span;
346            if self.at(TokenKind::EqEq) {
347                self.advance();
348                left = Expr::Binary {
349                    left: Box::new(left),
350                    op: BinaryOp::Eq,
351                    right: Box::new(self.parse_comparison()?),
352                    span,
353                };
354            } else if self.at(TokenKind::NotEq) {
355                self.advance();
356                left = Expr::Binary {
357                    left: Box::new(left),
358                    op: BinaryOp::Ne,
359                    right: Box::new(self.parse_comparison()?),
360                    span,
361                };
362            } else {
363                break;
364            }
365        }
366        Ok(left)
367    }
368
369    fn parse_comparison(&mut self) -> Result<Expr, NewtError> {
370        let mut left = self.parse_term()?;
371        loop {
372            let span = self.current().span;
373            let op = if self.at(TokenKind::Lt) {
374                self.advance();
375                BinaryOp::Lt
376            } else if self.at(TokenKind::Le) {
377                self.advance();
378                BinaryOp::Le
379            } else if self.at(TokenKind::Gt) {
380                self.advance();
381                BinaryOp::Gt
382            } else if self.at(TokenKind::Ge) {
383                self.advance();
384                BinaryOp::Ge
385            } else {
386                break;
387            };
388            left = Expr::Binary {
389                left: Box::new(left),
390                op,
391                right: Box::new(self.parse_term()?),
392                span,
393            };
394        }
395        Ok(left)
396    }
397
398    fn parse_term(&mut self) -> Result<Expr, NewtError> {
399        let mut left = self.parse_factor()?;
400        loop {
401            let span = self.current().span;
402            let op = if self.at(TokenKind::Plus) {
403                self.advance();
404                BinaryOp::Add
405            } else if self.at(TokenKind::Minus) {
406                self.advance();
407                BinaryOp::Sub
408            } else {
409                break;
410            };
411            left = Expr::Binary {
412                left: Box::new(left),
413                op,
414                right: Box::new(self.parse_factor()?),
415                span,
416            };
417        }
418        Ok(left)
419    }
420
421    fn parse_factor(&mut self) -> Result<Expr, NewtError> {
422        let mut left = self.parse_unary()?;
423        loop {
424            let span = self.current().span;
425            let op = if self.at(TokenKind::Star) {
426                self.advance();
427                BinaryOp::Mul
428            } else if self.at(TokenKind::Slash) {
429                self.advance();
430                BinaryOp::Div
431            } else if self.at(TokenKind::Percent) {
432                self.advance();
433                BinaryOp::Mod
434            } else {
435                break;
436            };
437            left = Expr::Binary {
438                left: Box::new(left),
439                op,
440                right: Box::new(self.parse_unary()?),
441                span,
442            };
443        }
444        Ok(left)
445    }
446
447    fn parse_unary(&mut self) -> Result<Expr, NewtError> {
448        let span = self.current().span;
449        if self.at(TokenKind::Not) {
450            self.advance();
451            let inner = self.parse_unary()?;
452            return Ok(Expr::Unary {
453                op: UnaryOp::Not,
454                inner: Box::new(inner),
455                span,
456            });
457        }
458        if self.at(TokenKind::Minus) {
459            self.advance();
460            let inner = self.parse_unary()?;
461            return Ok(Expr::Unary {
462                op: UnaryOp::Neg,
463                inner: Box::new(inner),
464                span,
465            });
466        }
467        self.parse_primary()
468    }
469
470    fn parse_primary(&mut self) -> Result<Expr, NewtError> {
471        let span = self.current().span;
472        match &self.current().kind {
473            TokenKind::Number(n) => {
474                let v = *n;
475                self.advance();
476                return Ok(Expr::Literal(Literal::Number(v)));
477            }
478            TokenKind::String(s) => {
479                let v = s.clone();
480                self.advance();
481                return Ok(Expr::Literal(Literal::String(v)));
482            }
483            TokenKind::InterpolatedString(parts) => {
484                let parts = parts.clone();
485                self.advance();
486                return self.build_interp_expr(parts, span);
487            }
488            TokenKind::True => {
489                self.advance();
490                return Ok(Expr::Literal(Literal::Bool(true)));
491            }
492            TokenKind::False => {
493                self.advance();
494                return Ok(Expr::Literal(Literal::Bool(false)));
495            }
496            TokenKind::HexColor(r, g, b, a) => {
497                let (r, g, b, a) = (*r, *g, *b, *a);
498                self.advance();
499                return Ok(Expr::Literal(Literal::Color { r, g, b, a }));
500            }
501            TokenKind::LeftBracket => {
502                self.advance();
503                let mut elems = Vec::new();
504                while !self.at(TokenKind::RightBracket) && !self.at(TokenKind::Eof) {
505                    elems.push(self.parse_expr()?);
506                    if self.at(TokenKind::Comma) {
507                        self.advance();
508                    }
509                }
510                self.expect(TokenKind::RightBracket)?;
511                return Ok(Expr::Literal(Literal::Array(elems)));
512            }
513            TokenKind::LeftBrace => {
514                self.advance();
515                let mut stmts = Vec::new();
516                while !self.at(TokenKind::RightBrace) && !self.at(TokenKind::Eof) {
517                    if self.at(TokenKind::Let) {
518                        self.advance();
519                        let name = self.expect_ident()?;
520                        self.expect(TokenKind::Eq)?;
521                        let value = self.parse_expr()?;
522                        self.expect(TokenKind::Semicolon)?;
523                        stmts.push(Stmt::Let {
524                            name,
525                            value,
526                            span: self.current().span,
527                        });
528                    } else if self.at(TokenKind::State) {
529                        let sd = self.parse_state_decl()?;
530                        stmts.push(Stmt::StateDecl(sd));
531                    } else {
532                        stmts.push(Stmt::Expr(self.parse_expr()?));
533                        if self.at(TokenKind::Semicolon) {
534                            self.advance();
535                        }
536                    }
537                }
538                self.expect(TokenKind::RightBrace)?;
539                return Ok(Expr::Block { stmts, span });
540            }
541            TokenKind::If => {
542                self.advance();
543                let cond = Box::new(self.parse_expr()?);
544                self.expect(TokenKind::LeftBrace)?;
545                let then_branch = Box::new(self.parse_expr()?);
546                self.expect(TokenKind::RightBrace)?;
547                let else_branch = if self.at(TokenKind::Else) {
548                    self.advance();
549                    self.expect(TokenKind::LeftBrace)?;
550                    let e = self.parse_expr()?;
551                    self.expect(TokenKind::RightBrace)?;
552                    Some(Box::new(e))
553                } else {
554                    None
555                };
556                return Ok(Expr::If {
557                    cond,
558                    then_branch,
559                    else_branch,
560                    span,
561                });
562            }
563            TokenKind::For => {
564                self.advance();
565                let var = self.expect_ident()?;
566                self.expect(TokenKind::In)?;
567                let iter = Box::new(self.parse_expr()?);
568                self.expect(TokenKind::LeftBrace)?;
569                let body = Box::new(self.parse_expr()?);
570                self.expect(TokenKind::RightBrace)?;
571                return Ok(Expr::For { var, iter, body, span });
572            }
573            TokenKind::Ident(_) => {
574                let name = self.expect_ident()?;
575                if self.at(TokenKind::LeftParen) {
576                    self.advance();
577                    let (args, slot_args) = if !self.at(TokenKind::RightParen) && self.at_ident_then_colon() {
578                        let mut slot_args = Vec::new();
579                        while !self.at(TokenKind::RightParen) && !self.at(TokenKind::Eof) {
580                            let slot_name = self.expect_ident()?;
581                            self.expect(TokenKind::Colon)?;
582                            let e = self.parse_expr()?;
583                            slot_args.push((slot_name, e));
584                            if self.at(TokenKind::Comma) {
585                                self.advance();
586                            }
587                        }
588                        (Vec::new(), Some(slot_args))
589                    } else {
590                        let mut args = Vec::new();
591                        if !self.at(TokenKind::RightParen) {
592                            args.push(self.parse_expr()?);
593                            while self.at(TokenKind::Comma) {
594                                self.advance();
595                                args.push(self.parse_expr()?);
596                            }
597                        }
598                        (args, None)
599                    };
600                    self.expect(TokenKind::RightParen)?;
601                    return Ok(Expr::Call {
602                        callee: name,
603                        args,
604                        slot_args,
605                        span,
606                    });
607                }
608                if let Some(kind) = ElementKind::from_token_kind(&TokenKind::Ident(name.clone())) {
609                    // We already advanced for ident; element keyword is the ident
610                    return self.parse_element_props_and_children(kind, span);
611                }
612                if self.at(TokenKind::Eq) {
613                    self.advance();
614                    let value = self.parse_expr()?;
615                    return Ok(Expr::Assignment { name, value: Box::new(value), span });
616                }
617                return Ok(Expr::Ident(name, span));
618            }
619            _ => {}
620        }
621
622        // Element keywords: box, text, row, column, ...
623        if let Some(kind) = ElementKind::from_token_kind(&self.current().kind) {
624            let span = self.current().span;
625            self.advance();
626            return self.parse_element_props_and_children(kind, span);
627        }
628
629        Err(NewtError::parse(
630            self.current().span,
631            format!("expected expression, got {}", self.current().kind),
632        ))
633    }
634
635    fn parse_element_props_and_children(&mut self, kind: ElementKind, span: Span) -> Result<Expr, NewtError> {
636        let mut props = Vec::new();
637        let mut children = Vec::new();
638
639        // First group: ( ... ) or { ... }
640        if self.at(TokenKind::LeftParen) {
641            self.advance();
642            if matches!(self.current().kind, TokenKind::String(_))
643                && (kind == ElementKind::Text || kind == ElementKind::Button)
644            {
645                let s = match &self.current().kind {
646                    TokenKind::String(x) => x.clone(),
647                    _ => unreachable!(),
648                };
649                self.advance();
650                props.push(Prop {
651                    name: PropName::Content,
652                    value: PropValue::String(s),
653                    span: self.current().span,
654                });
655                while self.at(TokenKind::Comma) {
656                    self.advance();
657                    self.parse_prop_or_semantic_tokens(&mut props)?;
658                }
659                self.expect(TokenKind::RightParen)?;
660            } else if matches!(self.current().kind, TokenKind::InterpolatedString(_))
661                && (kind == ElementKind::Text || kind == ElementKind::Button)
662            {
663                let (parts, ispan) = match &self.current().kind {
664                    TokenKind::InterpolatedString(p) => (p.clone(), self.current().span),
665                    _ => unreachable!(),
666                };
667                self.advance();
668                let expr = self.build_interp_expr(parts, ispan)?;
669                props.push(Prop {
670                    name: PropName::Content,
671                    value: PropValue::Expr(expr),
672                    span: ispan,
673                });
674                while self.at(TokenKind::Comma) {
675                    self.advance();
676                    self.parse_prop_or_semantic_tokens(&mut props)?;
677                }
678                self.expect(TokenKind::RightParen)?;
679            } else if self.at_ident_then_colon() || self.is_prop_keyword() || self.at_semantic_token() {
680                while !self.at(TokenKind::RightParen) && !self.at(TokenKind::Eof) {
681                    self.parse_prop_or_semantic_tokens(&mut props)?;
682                    if self.at(TokenKind::Comma) {
683                        self.advance();
684                    }
685                }
686                self.expect(TokenKind::RightParen)?;
687            } else {
688                while !self.at(TokenKind::RightParen) && !self.at(TokenKind::Eof) {
689                    children.push(self.parse_expr()?);
690                    if self.at(TokenKind::Comma) {
691                        self.advance();
692                    }
693                }
694                self.expect(TokenKind::RightParen)?;
695            }
696        }
697
698        // Second group: ( children ) if we had ( props ) first
699        if self.at(TokenKind::LeftParen) {
700            self.advance();
701            while !self.at(TokenKind::RightParen) && !self.at(TokenKind::Eof) {
702                children.push(self.parse_expr()?);
703                if self.at(TokenKind::Comma) {
704                    self.advance();
705                }
706            }
707            self.expect(TokenKind::RightParen)?;
708        }
709
710        // Brace group(s): { props and/or children } or first { props } then { children }
711        if self.at(TokenKind::LeftBrace) {
712            self.advance();
713            let mut in_first_brace = true;
714            while !self.at(TokenKind::RightBrace) && !self.at(TokenKind::Eof) {
715                if in_first_brace && (self.at_semantic_token() || self.at_ident_then_colon() || self.is_prop_keyword()) {
716                    self.parse_prop_or_semantic_tokens(&mut props)?;
717                    if self.at(TokenKind::Comma) {
718                        self.advance();
719                    }
720                } else if in_first_brace && self.at_ident() && !self.at_ident_then_colon() {
721                    // Bare identifier that's not a semantic token — treat as child expression
722                    in_first_brace = false;
723                    children.push(self.parse_expr()?);
724                    if self.at(TokenKind::Comma) {
725                        self.advance();
726                    }
727                } else {
728                    in_first_brace = false;
729                    children.push(self.parse_expr()?);
730                    if self.at(TokenKind::Comma) {
731                        self.advance();
732                    }
733                }
734            }
735            self.expect(TokenKind::RightBrace)?;
736            // Second brace group = children only (legacy: column { props } { children })
737            if self.at(TokenKind::LeftBrace) {
738                self.advance();
739                while !self.at(TokenKind::RightBrace) && !self.at(TokenKind::Eof) {
740                    children.push(self.parse_expr()?);
741                    if self.at(TokenKind::Comma) {
742                        self.advance();
743                    }
744                }
745                self.expect(TokenKind::RightBrace)?;
746            }
747        }
748
749        Ok(Expr::Element {
750            kind,
751            props,
752            children,
753            span,
754        })
755    }
756
757    /// Parse interpolated string parts into an `Expr::InterpolatedString`.
758    fn build_interp_expr(
759        &self,
760        parts: Vec<crate::lexer::InterpPart>,
761        span: Span,
762    ) -> Result<Expr, NewtError> {
763        use crate::lexer::InterpPart;
764        let mut segments = Vec::with_capacity(parts.len());
765        for part in parts {
766            match part {
767                InterpPart::Literal(s) => {
768                    segments.push(InterpSegment::Literal(s));
769                }
770                InterpPart::ExprSource(src) => {
771                    let mut sub_parser = Parser::new(&src, None)?;
772                    let expr = sub_parser.parse_expr()?;
773                    segments.push(InterpSegment::Expr(Box::new(expr)));
774                }
775            }
776        }
777        Ok(Expr::InterpolatedString { parts: segments, span })
778    }
779
780    fn is_prop_keyword(&self) -> bool {
781        matches!(
782            self.current().kind,
783            TokenKind::Width
784                | TokenKind::Height
785                | TokenKind::Fill
786                | TokenKind::Stroke
787                | TokenKind::Radius
788                | TokenKind::Padding
789                | TokenKind::Gap
790                | TokenKind::Grow
791                | TokenKind::Shrink
792                | TokenKind::Align
793                | TokenKind::Justify
794                | TokenKind::Direction
795                | TokenKind::FontSize
796                | TokenKind::FontWeight
797                | TokenKind::Shadow
798        )
799    }
800
801    /// Check if the current token is an identifier matching a semantic style token.
802    fn at_semantic_token(&self) -> bool {
803        if let TokenKind::Ident(ref s) = self.current().kind {
804            semantic_token_props(s).is_some()
805        } else {
806            false
807        }
808    }
809
810    /// Parse a prop, or if the current token is a semantic style token (an identifier
811    /// not followed by a colon), expand it into multiple props.
812    fn parse_prop_or_semantic_tokens(&mut self, out: &mut Vec<Prop>) -> Result<(), NewtError> {
813        // Check for semantic token: ident NOT followed by colon
814        if let TokenKind::Ident(ref s) = self.current().kind {
815            if !self.at_ident_then_colon() {
816                if let Some(expanded) = semantic_token_props(s) {
817                    let span = self.current().span;
818                    self.advance();
819                    for (name, value) in expanded {
820                        out.push(Prop { name, value, span });
821                    }
822                    return Ok(());
823                }
824            }
825        }
826        out.push(self.parse_prop()?);
827        Ok(())
828    }
829
830    fn parse_prop(&mut self) -> Result<Prop, NewtError> {
831        let span = self.current().span;
832        let name = match &self.current().kind {
833            TokenKind::Ident(s) => {
834                let n = s.clone();
835                self.advance();
836                PropName::Ident(n)
837            }
838            TokenKind::Width => {
839                self.advance();
840                PropName::Width
841            }
842            TokenKind::Height => {
843                self.advance();
844                PropName::Height
845            }
846            TokenKind::Fill => {
847                self.advance();
848                PropName::Fill
849            }
850            TokenKind::Stroke => {
851                self.advance();
852                PropName::Stroke
853            }
854            TokenKind::Radius => {
855                self.advance();
856                PropName::Radius
857            }
858            TokenKind::Padding => {
859                self.advance();
860                PropName::Padding
861            }
862            TokenKind::Gap => {
863                self.advance();
864                PropName::Gap
865            }
866            TokenKind::Grow => {
867                self.advance();
868                PropName::Grow
869            }
870            TokenKind::Shrink => {
871                self.advance();
872                PropName::Shrink
873            }
874            TokenKind::Align => {
875                self.advance();
876                PropName::Align
877            }
878            TokenKind::Justify => {
879                self.advance();
880                PropName::Justify
881            }
882            TokenKind::Direction => {
883                self.advance();
884                PropName::Direction
885            }
886            TokenKind::FontSize => {
887                self.advance();
888                PropName::FontSize
889            }
890            TokenKind::FontWeight => {
891                self.advance();
892                PropName::FontWeight
893            }
894            TokenKind::Shadow => {
895                self.advance();
896                PropName::Shadow
897            }
898            _ => {
899                return Err(NewtError::parse(
900                    span,
901                    "expected property name",
902                ));
903            }
904        };
905        self.expect(TokenKind::Colon)?;
906        let value = self.parse_prop_value()?;
907        Ok(Prop { name, value, span })
908    }
909
910    fn parse_prop_value(&mut self) -> Result<PropValue, NewtError> {
911        match &self.current().kind {
912            TokenKind::Number(n) => {
913                let v = *n;
914                self.advance();
915                Ok(PropValue::Number(v))
916            }
917            TokenKind::String(s) => {
918                let v = s.clone();
919                self.advance();
920                Ok(PropValue::String(v))
921            }
922            TokenKind::InterpolatedString(parts) => {
923                let parts = parts.clone();
924                let span = self.current().span;
925                self.advance();
926                let expr = self.build_interp_expr(parts, span)?;
927                Ok(PropValue::Expr(expr))
928            }
929            TokenKind::HexColor(r, g, b, a) => {
930                let (r, g, b, a) = (*r, *g, *b, *a);
931                self.advance();
932                Ok(PropValue::Color { r, g, b, a })
933            }
934            _ => {
935                let e = self.parse_expr()?;
936                Ok(PropValue::Expr(e))
937            }
938        }
939    }
940}
941
942/// Top-level keywords that can start a program item.
943const TOP_LEVEL_KEYWORDS: &[&str] = &["import", "theme", "use", "let", "state", "component", "screen"];
944
945fn levenshtein(a: &str, b: &str) -> usize {
946    let a: Vec<char> = a.chars().collect();
947    let b: Vec<char> = b.chars().collect();
948    let mut prev = (0..=b.len()).collect::<Vec<_>>();
949    for (i, ca) in a.iter().enumerate() {
950        let mut curr = vec![i + 1];
951        for (j, cb) in b.iter().enumerate() {
952            let cost = if ca == cb { 0 } else { 1 };
953            curr.push((prev[j] + cost).min(prev[j + 1] + 1).min(curr[j] + 1));
954        }
955        prev = curr;
956    }
957    prev[b.len()]
958}
959
960fn closest_top_level_keyword(ident: &str) -> Option<String> {
961    let ident_lower = ident.to_lowercase();
962    TOP_LEVEL_KEYWORDS
963        .iter()
964        .min_by_key(|kw| levenshtein(&ident_lower, kw))
965        .filter(|kw| levenshtein(&ident_lower, kw) <= 2)
966        .map(|s| (*s).to_string())
967}
968
969/// Expand a semantic style token identifier into a list of (PropName, PropValue) pairs.
970/// Returns `None` if the identifier is not a recognized semantic token.
971fn semantic_token_props(name: &str) -> Option<Vec<(PropName, PropValue)>> {
972    match name {
973        // ── Color presets ──────────────────────────────────────────────
974        "primary" => Some(vec![
975            (PropName::Fill, PropValue::Color { r: 124, g: 58, b: 237, a: 255 }),
976            (PropName::Ident("textColor".into()), PropValue::Color { r: 255, g: 255, b: 255, a: 255 }),
977        ]),
978        "secondary" => Some(vec![
979            (PropName::Fill, PropValue::Color { r: 107, g: 114, b: 128, a: 255 }),
980            (PropName::Ident("textColor".into()), PropValue::Color { r: 255, g: 255, b: 255, a: 255 }),
981        ]),
982        "danger" => Some(vec![
983            (PropName::Fill, PropValue::Color { r: 239, g: 68, b: 68, a: 255 }),
984            (PropName::Ident("textColor".into()), PropValue::Color { r: 255, g: 255, b: 255, a: 255 }),
985        ]),
986        "success" => Some(vec![
987            (PropName::Fill, PropValue::Color { r: 16, g: 185, b: 129, a: 255 }),
988            (PropName::Ident("textColor".into()), PropValue::Color { r: 255, g: 255, b: 255, a: 255 }),
989        ]),
990        "warning" => Some(vec![
991            (PropName::Fill, PropValue::Color { r: 245, g: 158, b: 11, a: 255 }),
992            (PropName::Ident("textColor".into()), PropValue::Color { r: 255, g: 255, b: 255, a: 255 }),
993        ]),
994        "muted" => Some(vec![
995            (PropName::Fill, PropValue::Color { r: 243, g: 244, b: 246, a: 255 }),
996            (PropName::Ident("textColor".into()), PropValue::Color { r: 107, g: 114, b: 128, a: 255 }),
997        ]),
998        "ghost" => Some(vec![
999            (PropName::Fill, PropValue::Color { r: 0, g: 0, b: 0, a: 0 }),
1000            (PropName::Stroke, PropValue::Color { r: 229, g: 231, b: 235, a: 255 }),
1001        ]),
1002
1003        // ── Typography presets ─────────────────────────────────────────
1004        "bold" => Some(vec![
1005            (PropName::FontWeight, PropValue::String("700".into())),
1006        ]),
1007        "semibold" => Some(vec![
1008            (PropName::FontWeight, PropValue::String("600".into())),
1009        ]),
1010        "heading" => Some(vec![
1011            (PropName::FontSize, PropValue::Number(24.0)),
1012            (PropName::FontWeight, PropValue::String("700".into())),
1013        ]),
1014        "subheading" => Some(vec![
1015            (PropName::FontSize, PropValue::Number(18.0)),
1016            (PropName::FontWeight, PropValue::String("600".into())),
1017        ]),
1018        "caption" => Some(vec![
1019            (PropName::FontSize, PropValue::Number(12.0)),
1020        ]),
1021        "small" => Some(vec![
1022            (PropName::FontSize, PropValue::Number(14.0)),
1023        ]),
1024
1025        // ── Size presets ───────────────────────────────────────────────
1026        "compact" => Some(vec![
1027            (PropName::Padding, PropValue::Number(8.0)),
1028            (PropName::Radius, PropValue::Number(4.0)),
1029        ]),
1030        "comfortable" => Some(vec![
1031            (PropName::Padding, PropValue::Number(16.0)),
1032            (PropName::Radius, PropValue::Number(8.0)),
1033        ]),
1034        "spacious" => Some(vec![
1035            (PropName::Padding, PropValue::Number(24.0)),
1036            (PropName::Radius, PropValue::Number(12.0)),
1037        ]),
1038        "rounded" => Some(vec![
1039            (PropName::Radius, PropValue::Number(999.0)),
1040        ]),
1041        "pill" => Some(vec![
1042            (PropName::Radius, PropValue::Number(999.0)),
1043            (PropName::Padding, PropValue::Number(8.0)),
1044        ]),
1045
1046        // ── Shadow presets ─────────────────────────────────────────────
1047        "elevated" => Some(vec![
1048            (PropName::Shadow, PropValue::Number(8.0)),
1049        ]),
1050        "floating" => Some(vec![
1051            (PropName::Shadow, PropValue::Number(16.0)),
1052        ]),
1053
1054        _ => None,
1055    }
1056}