Skip to main content

lux/
parser.rs

1//! The parser: tokens in, an ast out.
2//!
3//! This is a hand-written recursive-descent parser. Each grammar rule is one
4//! method, and expression precedence is a ladder of methods from loosest
5//! (`||`) to tightest (unary `!`/`-`). Hand-rolling keeps the whole frontend
6//! legible — you can read exactly how lux understands a program, which is the
7//! point of a language built to be learned from.
8
9use crate::ast::*;
10use crate::diagnostic::{LuxError, Span};
11use crate::lexer::{Tok, Token};
12
13struct Parser {
14    tokens: Vec<Token>,
15    pos: usize,
16}
17
18/// Parse a full program into a list of statements.
19pub fn parse(tokens: Vec<Token>) -> Result<Vec<Stmt>, LuxError> {
20    let mut p = Parser { tokens, pos: 0 };
21    let mut stmts = Vec::new();
22    while !p.at_eof() {
23        stmts.push(p.statement()?);
24    }
25    Ok(stmts)
26}
27
28impl Parser {
29    fn peek(&self) -> &Token {
30        &self.tokens[self.pos]
31    }
32
33    fn peek_tok(&self) -> &Tok {
34        &self.tokens[self.pos].tok
35    }
36
37    fn next_tok(&self) -> &Tok {
38        let j = (self.pos + 1).min(self.tokens.len() - 1);
39        &self.tokens[j].tok
40    }
41
42    fn span(&self) -> Span {
43        self.peek().span
44    }
45
46    fn at_eof(&self) -> bool {
47        matches!(self.peek_tok(), Tok::Eof)
48    }
49
50    fn advance(&mut self) -> Token {
51        let t = self.tokens[self.pos].clone();
52        if self.pos < self.tokens.len() - 1 {
53            self.pos += 1;
54        }
55        t
56    }
57
58    /// Consume a token of the given kind or produce a helpful error.
59    fn expect(&mut self, want: &Tok, what: &str) -> Result<Token, LuxError> {
60        if std::mem::discriminant(self.peek_tok()) == std::mem::discriminant(want) {
61            Ok(self.advance())
62        } else {
63            Err(LuxError::new(format!("expected {}", what), self.span()))
64        }
65    }
66
67    fn ident(&mut self, what: &str) -> Result<(String, Span), LuxError> {
68        let t = self.peek().clone();
69        if let Tok::Ident(name) = t.tok {
70            self.advance();
71            Ok((name, t.span))
72        } else {
73            Err(LuxError::new(format!("expected {}", what), t.span))
74        }
75    }
76
77    fn optional_type(&mut self) -> Result<Option<TypeAnn>, LuxError> {
78        if matches!(self.peek_tok(), Tok::Colon) {
79            self.advance();
80            Ok(Some(self.parse_type()?))
81        } else {
82            Ok(None)
83        }
84    }
85
86    /// Parse a type: a name like `int`, or an array type like `[int]`.
87    fn parse_type(&mut self) -> Result<TypeAnn, LuxError> {
88        if matches!(self.peek_tok(), Tok::LBracket) {
89            let start = self.span();
90            self.advance();
91            let elem = self.parse_type()?;
92            let close = self.expect(&Tok::RBracket, "']' to close the array type")?;
93            Ok(TypeAnn {
94                kind: TypeKind::Array(Box::new(elem)),
95                span: start.to(close.span),
96            })
97        } else {
98            let (name, span) = self.ident("a type name")?;
99            // `Name<...>` is a parameterized type, like `Option<int>`.
100            if matches!(self.peek_tok(), Tok::Lt) {
101                self.advance(); // <
102                let mut args = Vec::new();
103                loop {
104                    args.push(self.parse_type()?);
105                    if matches!(self.peek_tok(), Tok::Comma) {
106                        self.advance();
107                        // A trailing comma is allowed: stop if the list closed.
108                        if matches!(self.peek_tok(), Tok::Gt) {
109                            break;
110                        }
111                    } else {
112                        break;
113                    }
114                }
115                let close = self.expect(&Tok::Gt, "'>' to close the type parameters")?;
116                Ok(TypeAnn {
117                    kind: TypeKind::Generic(name, args),
118                    span: span.to(close.span),
119                })
120            } else {
121                Ok(TypeAnn {
122                    kind: TypeKind::Named(name),
123                    span,
124                })
125            }
126        }
127    }
128
129    // ----- statements -------------------------------------------------------
130
131    fn statement(&mut self) -> Result<Stmt, LuxError> {
132        match self.peek_tok() {
133            Tok::Let => self.let_stmt(),
134            Tok::Var => self.var_stmt(),
135            Tok::Func => self.func_stmt(),
136            Tok::Return => self.return_stmt(),
137            Tok::Struct => self.struct_stmt(),
138            Tok::Enum => self.enum_stmt(),
139            Tok::If => self.if_stmt(),
140            Tok::While => self.while_stmt(),
141            Tok::For => self.for_stmt(),
142            Tok::Ident(_) if self.assign_ahead() => self.assign_stmt(),
143            _ => Ok(Stmt::Expr(self.expression()?)),
144        }
145    }
146
147    /// True when the current ident is followed by an assignment operator.
148    fn assign_ahead(&self) -> bool {
149        matches!(self.next_tok(), Tok::Eq | Tok::PlusEq | Tok::MinusEq)
150    }
151
152    fn let_stmt(&mut self) -> Result<Stmt, LuxError> {
153        let start = self.span();
154        self.advance(); // let
155        let (name, _) = self.ident("a name after 'let'")?;
156        let ty = self.optional_type()?;
157        self.expect(&Tok::Eq, "'=' and a value ('let' always needs a value)")?;
158        let value = self.expression()?;
159        let span = start.to(value.span());
160        Ok(Stmt::Let {
161            name,
162            ty,
163            value,
164            span,
165        })
166    }
167
168    fn var_stmt(&mut self) -> Result<Stmt, LuxError> {
169        let start = self.span();
170        self.advance(); // var
171        let (name, name_span) = self.ident("a name after 'var'")?;
172        let ty = self.optional_type()?;
173        let (value, end) = if matches!(self.peek_tok(), Tok::Eq) {
174            self.advance();
175            let v = self.expression()?;
176            let sp = v.span();
177            (Some(v), sp)
178        } else {
179            let end = ty.as_ref().map(|t| t.span).unwrap_or(name_span);
180            (None, end)
181        };
182        Ok(Stmt::Var {
183            name,
184            ty,
185            value,
186            span: start.to(end),
187        })
188    }
189
190    fn func_stmt(&mut self) -> Result<Stmt, LuxError> {
191        let start = self.span();
192        self.advance(); // func
193        let (name, _) = self.ident("a function name after 'func'")?;
194        let params = self.params()?;
195        let ret = if matches!(self.peek_tok(), Tok::Arrow) {
196            self.advance();
197            Some(self.parse_type()?)
198        } else {
199            None
200        };
201        let body = self.block()?;
202        Ok(Stmt::Func {
203            name,
204            params,
205            ret,
206            body,
207            span: start,
208        })
209    }
210
211    /// Parse a parenthesized, comma-separated list of `name: type` parameters.
212    fn params(&mut self) -> Result<Vec<Param>, LuxError> {
213        self.expect(&Tok::LParen, "'(' to start the parameter list")?;
214        let mut params = Vec::new();
215        if !matches!(self.peek_tok(), Tok::RParen) {
216            loop {
217                let (name, name_span) = self.ident("a parameter name")?;
218                self.expect(&Tok::Colon, "':' and a type for the parameter")?;
219                let ty = self.parse_type()?;
220                let span = name_span.to(ty.span);
221                params.push(Param { name, ty, span });
222                if matches!(self.peek_tok(), Tok::Comma) {
223                    self.advance();
224                    if matches!(self.peek_tok(), Tok::RParen) {
225                        break;
226                    }
227                } else {
228                    break;
229                }
230            }
231        }
232        self.expect(&Tok::RParen, "')' to close the parameter list")?;
233        Ok(params)
234    }
235
236    fn struct_stmt(&mut self) -> Result<Stmt, LuxError> {
237        let start = self.span();
238        self.advance(); // struct
239        let (name, _) = self.ident("a struct name after 'struct'")?;
240        self.expect(&Tok::LBrace, "'{' to start the struct body")?;
241        let mut fields = Vec::new();
242        while !matches!(self.peek_tok(), Tok::RBrace | Tok::Eof) {
243            fields.push(self.field_def()?);
244            // Fields are separated by newlines; a comma between them is allowed.
245            if matches!(self.peek_tok(), Tok::Comma) {
246                self.advance();
247            }
248        }
249        self.expect(&Tok::RBrace, "'}' to close the struct")?;
250        Ok(Stmt::Struct {
251            name,
252            fields,
253            span: start,
254        })
255    }
256
257    fn enum_stmt(&mut self) -> Result<Stmt, LuxError> {
258        let start = self.span();
259        self.advance(); // enum
260        let (name, _) = self.ident("an enum name after 'enum'")?;
261        self.expect(&Tok::LBrace, "'{' to start the enum body")?;
262        let mut variants = Vec::new();
263        while !matches!(self.peek_tok(), Tok::RBrace | Tok::Eof) {
264            let (vname, vspan) = self.ident("a case name")?;
265            let mut fields = Vec::new();
266            if matches!(self.peek_tok(), Tok::LParen) {
267                self.advance(); // (
268                if !matches!(self.peek_tok(), Tok::RParen) {
269                    loop {
270                        fields.push(self.field_def()?);
271                        if matches!(self.peek_tok(), Tok::Comma) {
272                            self.advance();
273                            if matches!(self.peek_tok(), Tok::RParen) {
274                                break;
275                            }
276                        } else {
277                            break;
278                        }
279                    }
280                }
281                self.expect(&Tok::RParen, "')' to close the case's values")?;
282            }
283            variants.push(VariantDef {
284                name: vname,
285                fields,
286                span: vspan,
287            });
288            if matches!(self.peek_tok(), Tok::Comma) {
289                self.advance();
290            }
291        }
292        self.expect(&Tok::RBrace, "'}' to close the enum")?;
293        Ok(Stmt::Enum {
294            name,
295            variants,
296            span: start,
297        })
298    }
299
300    /// Parse one `name: type` field declaration.
301    fn field_def(&mut self) -> Result<FieldDef, LuxError> {
302        let (name, name_span) = self.ident("a field name")?;
303        self.expect(&Tok::Colon, "':' and a type for the field")?;
304        let ty = self.parse_type()?;
305        let span = name_span.to(ty.span);
306        Ok(FieldDef { name, ty, span })
307    }
308
309    fn return_stmt(&mut self) -> Result<Stmt, LuxError> {
310        let start = self.span();
311        self.advance(); // return
312        // A bare `return` is one that ends its block; anything else is a value.
313        if matches!(self.peek_tok(), Tok::RBrace | Tok::Eof) {
314            Ok(Stmt::Return {
315                value: None,
316                span: start,
317            })
318        } else {
319            let value = self.expression()?;
320            let span = start.to(value.span());
321            Ok(Stmt::Return {
322                value: Some(value),
323                span,
324            })
325        }
326    }
327
328    fn for_stmt(&mut self) -> Result<Stmt, LuxError> {
329        let start = self.span();
330        self.advance(); // for
331        let (var, _) = self.ident("a loop variable after 'for'")?;
332        self.expect(&Tok::In, "'in' after the loop variable")?;
333        let iter = self.expression()?;
334        let body = self.block()?;
335        Ok(Stmt::For {
336            var,
337            iter,
338            body,
339            span: start,
340        })
341    }
342
343    fn assign_stmt(&mut self) -> Result<Stmt, LuxError> {
344        let (name, name_span) = self.ident("a name")?;
345        let op = match self.peek_tok() {
346            Tok::Eq => AssignOp::Set,
347            Tok::PlusEq => AssignOp::Add,
348            Tok::MinusEq => AssignOp::Sub,
349            _ => unreachable!("assign_ahead guaranteed an assignment operator"),
350        };
351        self.advance();
352        let value = self.expression()?;
353        let span = name_span.to(value.span());
354        Ok(Stmt::Assign {
355            name,
356            name_span,
357            op,
358            value,
359            span,
360        })
361    }
362
363    fn if_stmt(&mut self) -> Result<Stmt, LuxError> {
364        let start = self.span();
365        self.advance(); // if
366        let cond = self.expression()?;
367        let then_body = self.block()?;
368        let mut else_body = None;
369        if matches!(self.peek_tok(), Tok::Else) {
370            self.advance();
371            if matches!(self.peek_tok(), Tok::If) {
372                else_body = Some(vec![self.if_stmt()?]);
373            } else {
374                else_body = Some(self.block()?);
375            }
376        }
377        Ok(Stmt::If {
378            cond,
379            then_body,
380            else_body,
381            span: start,
382        })
383    }
384
385    fn while_stmt(&mut self) -> Result<Stmt, LuxError> {
386        let start = self.span();
387        self.advance(); // while
388        let cond = self.expression()?;
389        let body = self.block()?;
390        Ok(Stmt::While {
391            cond,
392            body,
393            span: start,
394        })
395    }
396
397    fn block(&mut self) -> Result<Vec<Stmt>, LuxError> {
398        self.expect(&Tok::LBrace, "'{' to start a block")?;
399        let mut stmts = Vec::new();
400        while !matches!(self.peek_tok(), Tok::RBrace | Tok::Eof) {
401            stmts.push(self.statement()?);
402        }
403        self.expect(&Tok::RBrace, "'}' to close the block")?;
404        Ok(stmts)
405    }
406
407    // ----- expressions (precedence ladder, loosest first) -------------------
408
409    fn expression(&mut self) -> Result<Expr, LuxError> {
410        self.range()
411    }
412
413    /// `a..b` — a half-open range. Loosest precedence, and non-chaining: you
414    /// write one `..`, not `a..b..c`.
415    fn range(&mut self) -> Result<Expr, LuxError> {
416        let lhs = self.or()?;
417        if matches!(self.peek_tok(), Tok::DotDot) {
418            self.advance();
419            let rhs = self.or()?;
420            let span = lhs.span().to(rhs.span());
421            Ok(Expr::Range {
422                start: Box::new(lhs),
423                end: Box::new(rhs),
424                span,
425            })
426        } else {
427            Ok(lhs)
428        }
429    }
430
431    fn or(&mut self) -> Result<Expr, LuxError> {
432        let mut lhs = self.and()?;
433        while matches!(self.peek_tok(), Tok::OrOr) {
434            self.advance();
435            let rhs = self.and()?;
436            lhs = binary(BinOp::Or, lhs, rhs);
437        }
438        Ok(lhs)
439    }
440
441    fn and(&mut self) -> Result<Expr, LuxError> {
442        let mut lhs = self.equality()?;
443        while matches!(self.peek_tok(), Tok::AndAnd) {
444            self.advance();
445            let rhs = self.equality()?;
446            lhs = binary(BinOp::And, lhs, rhs);
447        }
448        Ok(lhs)
449    }
450
451    fn equality(&mut self) -> Result<Expr, LuxError> {
452        let mut lhs = self.comparison()?;
453        loop {
454            let op = match self.peek_tok() {
455                Tok::EqEq => BinOp::Eq,
456                Tok::NotEq => BinOp::Ne,
457                _ => break,
458            };
459            self.advance();
460            let rhs = self.comparison()?;
461            lhs = binary(op, lhs, rhs);
462        }
463        Ok(lhs)
464    }
465
466    fn comparison(&mut self) -> Result<Expr, LuxError> {
467        let mut lhs = self.term()?;
468        loop {
469            let op = match self.peek_tok() {
470                Tok::Lt => BinOp::Lt,
471                Tok::Gt => BinOp::Gt,
472                Tok::Le => BinOp::Le,
473                Tok::Ge => BinOp::Ge,
474                _ => break,
475            };
476            self.advance();
477            let rhs = self.term()?;
478            lhs = binary(op, lhs, rhs);
479        }
480        Ok(lhs)
481    }
482
483    fn term(&mut self) -> Result<Expr, LuxError> {
484        let mut lhs = self.factor()?;
485        loop {
486            let op = match self.peek_tok() {
487                Tok::Plus => BinOp::Add,
488                Tok::Minus => BinOp::Sub,
489                _ => break,
490            };
491            self.advance();
492            let rhs = self.factor()?;
493            lhs = binary(op, lhs, rhs);
494        }
495        Ok(lhs)
496    }
497
498    fn factor(&mut self) -> Result<Expr, LuxError> {
499        let mut lhs = self.unary()?;
500        loop {
501            let op = match self.peek_tok() {
502                Tok::Star => BinOp::Mul,
503                Tok::Slash => BinOp::Div,
504                Tok::Percent => BinOp::Mod,
505                _ => break,
506            };
507            self.advance();
508            let rhs = self.unary()?;
509            lhs = binary(op, lhs, rhs);
510        }
511        Ok(lhs)
512    }
513
514    fn unary(&mut self) -> Result<Expr, LuxError> {
515        let t = self.peek().clone();
516        match t.tok {
517            Tok::Bang => {
518                self.advance();
519                let rhs = self.unary()?;
520                let span = t.span.to(rhs.span());
521                Ok(Expr::Unary {
522                    op: UnOp::Not,
523                    rhs: Box::new(rhs),
524                    span,
525                })
526            }
527            Tok::Minus => {
528                self.advance();
529                let rhs = self.unary()?;
530                let span = t.span.to(rhs.span());
531                Ok(Expr::Unary {
532                    op: UnOp::Neg,
533                    rhs: Box::new(rhs),
534                    span,
535                })
536            }
537            _ => self.postfix(),
538        }
539    }
540
541    /// A primary expression followed by any number of `[index]` lookups and
542    /// `.field` accesses. A `.case(...)` after a bare name is enum construction.
543    fn postfix(&mut self) -> Result<Expr, LuxError> {
544        let mut e = self.primary()?;
545        loop {
546            match self.peek_tok() {
547                Tok::LBracket => {
548                    self.advance();
549                    let index = self.expression()?;
550                    let close = self.expect(&Tok::RBracket, "']' to close the index")?;
551                    let span = e.span().to(close.span);
552                    e = Expr::Index {
553                        base: Box::new(e),
554                        index: Box::new(index),
555                        span,
556                    };
557                }
558                Tok::Dot => {
559                    self.advance();
560                    let (field, field_span) = self.ident("a field or case name after '.'")?;
561                    if matches!(self.peek_tok(), Tok::LParen) {
562                        // `Name.case(label: value, ...)` — enum construction. The
563                        // thing before the dot must be a bare enum name.
564                        let enum_name = match &e {
565                            Expr::Ident(n, _) => n.clone(),
566                            _ => {
567                                return Err(LuxError::new(
568                                    "only an enum name can be used like `Name.case(...)`",
569                                    e.span(),
570                                )
571                                .with_learn(
572                                    "enums",
573                                    "`Shape.circle(...)` builds one case of an enum",
574                                ));
575                            }
576                        };
577                        self.advance(); // (
578                        let fields = self.label_list()?;
579                        let close = self.expect(&Tok::RParen, "')' to close the case's values")?;
580                        let span = e.span().to(close.span);
581                        e = Expr::EnumLit {
582                            enum_name,
583                            variant: field,
584                            fields,
585                            span,
586                        };
587                    } else {
588                        let span = e.span().to(field_span);
589                        e = Expr::Field {
590                            base: Box::new(e),
591                            field,
592                            span,
593                        };
594                    }
595                }
596                _ => break,
597            }
598        }
599        Ok(e)
600    }
601
602    /// Parse a parenthesized list of `name: value` pairs, with the opening `(`
603    /// already consumed and the closing `)` left for the caller. Shared by
604    /// struct literals and enum construction.
605    fn label_list(&mut self) -> Result<Vec<(String, Expr)>, LuxError> {
606        let mut fields = Vec::new();
607        if !matches!(self.peek_tok(), Tok::RParen) {
608            loop {
609                let (name, _) = self.ident("a field name")?;
610                self.expect(&Tok::Colon, "':' after the field name")?;
611                let value = self.expression()?;
612                fields.push((name, value));
613                if matches!(self.peek_tok(), Tok::Comma) {
614                    self.advance();
615                    if matches!(self.peek_tok(), Tok::RParen) {
616                        break;
617                    }
618                } else {
619                    break;
620                }
621            }
622        }
623        Ok(fields)
624    }
625
626    /// `match scrutinee { pattern => expr ... }`.
627    fn match_expr(&mut self) -> Result<Expr, LuxError> {
628        let start = self.span();
629        self.advance(); // match
630        let scrutinee = self.expression()?;
631        self.expect(&Tok::LBrace, "'{' to start the match arms")?;
632        let mut arms = Vec::new();
633        while !matches!(self.peek_tok(), Tok::RBrace | Tok::Eof) {
634            let pattern = self.pattern()?;
635            self.expect(&Tok::FatArrow, "'=>' after the pattern")?;
636            let body = self.expression()?;
637            let span = pattern.span().to(body.span());
638            arms.push(MatchArm {
639                pattern,
640                body,
641                span,
642            });
643        }
644        let close = self.expect(&Tok::RBrace, "'}' to close the match")?;
645        Ok(Expr::Match {
646            scrutinee: Box::new(scrutinee),
647            arms,
648            span: start.to(close.span),
649        })
650    }
651
652    /// Parse one match pattern: a literal, an enum case (with optional captured
653    /// names), or `_`.
654    fn pattern(&mut self) -> Result<Pattern, LuxError> {
655        let t = self.peek().clone();
656        match t.tok {
657            Tok::Int(v) => {
658                self.advance();
659                Ok(Pattern::Int(v, t.span))
660            }
661            Tok::Minus => {
662                self.advance();
663                let nt = self.peek().clone();
664                if let Tok::Int(v) = nt.tok {
665                    self.advance();
666                    Ok(Pattern::Int(-v, t.span.to(nt.span)))
667                } else {
668                    Err(LuxError::new(
669                        "expected a number after '-' in a pattern",
670                        nt.span,
671                    ))
672                }
673            }
674            Tok::Str(s) => {
675                self.advance();
676                Ok(Pattern::Str(s, t.span))
677            }
678            Tok::True => {
679                self.advance();
680                Ok(Pattern::Bool(true, t.span))
681            }
682            Tok::False => {
683                self.advance();
684                Ok(Pattern::Bool(false, t.span))
685            }
686            Tok::Ident(name) => {
687                self.advance();
688                if name == "_" {
689                    Ok(Pattern::Wildcard(t.span))
690                } else if matches!(self.peek_tok(), Tok::LParen) {
691                    self.advance(); // (
692                    let mut bindings = Vec::new();
693                    if !matches!(self.peek_tok(), Tok::RParen) {
694                        loop {
695                            self.expect(
696                                &Tok::Let,
697                                "'let' before each captured name, like circle(let r)",
698                            )?;
699                            let (b, _) = self.ident("a name to bind")?;
700                            bindings.push(b);
701                            if matches!(self.peek_tok(), Tok::Comma) {
702                                self.advance();
703                                if matches!(self.peek_tok(), Tok::RParen) {
704                                    break;
705                                }
706                            } else {
707                                break;
708                            }
709                        }
710                    }
711                    let close = self.expect(&Tok::RParen, "')' to close the pattern")?;
712                    Ok(Pattern::Variant {
713                        name,
714                        bindings,
715                        span: t.span.to(close.span),
716                    })
717                } else {
718                    Ok(Pattern::Variant {
719                        name,
720                        bindings: Vec::new(),
721                        span: t.span,
722                    })
723                }
724            }
725            _ => Err(LuxError::new("expected a pattern", t.span)
726                .with_note("a pattern is a case name, a literal (int, string, true/false), or `_`")
727                .with_learn(
728                    "match",
729                    "each arm starts with a pattern — what it's looking for",
730                )),
731        }
732    }
733
734    fn primary(&mut self) -> Result<Expr, LuxError> {
735        let t = self.peek().clone();
736        match t.tok {
737            Tok::Int(v) => {
738                self.advance();
739                Ok(Expr::Int(v, t.span))
740            }
741            Tok::Float(v) => {
742                self.advance();
743                Ok(Expr::Float(v, t.span))
744            }
745            Tok::Str(s) => {
746                self.advance();
747                Ok(Expr::Str(s, t.span))
748            }
749            Tok::True => {
750                self.advance();
751                Ok(Expr::Bool(true, t.span))
752            }
753            Tok::False => {
754                self.advance();
755                Ok(Expr::Bool(false, t.span))
756            }
757            Tok::LParen => {
758                self.advance();
759                let e = self.expression()?;
760                self.expect(&Tok::RParen, "')' to close the group")?;
761                Ok(e)
762            }
763            Tok::LBracket => {
764                self.advance();
765                let mut elems = Vec::new();
766                if !matches!(self.peek_tok(), Tok::RBracket) {
767                    loop {
768                        elems.push(self.expression()?);
769                        if matches!(self.peek_tok(), Tok::Comma) {
770                            self.advance();
771                            // A trailing comma is allowed: stop if the list closed.
772                            if matches!(self.peek_tok(), Tok::RBracket) {
773                                break;
774                            }
775                        } else {
776                            break;
777                        }
778                    }
779                }
780                let close = self.expect(&Tok::RBracket, "']' to close the array")?;
781                Ok(Expr::Array(elems, t.span.to(close.span)))
782            }
783            Tok::Match => self.match_expr(),
784            Tok::Ident(name) => {
785                self.advance();
786                if matches!(self.peek_tok(), Tok::LParen) {
787                    self.advance(); // (
788                    // `Name(field: ...)` is a struct literal; `Name(value, ...)`
789                    // is a function call. Labelled fields have a `:` after the
790                    // first name, which never appears in a positional argument.
791                    if matches!(self.peek_tok(), Tok::Ident(_))
792                        && matches!(self.next_tok(), Tok::Colon)
793                    {
794                        let fields = self.label_list()?;
795                        let close = self.expect(&Tok::RParen, "')' to close the struct fields")?;
796                        Ok(Expr::StructLit {
797                            name,
798                            fields,
799                            span: t.span.to(close.span),
800                        })
801                    } else {
802                        let mut args = Vec::new();
803                        if !matches!(self.peek_tok(), Tok::RParen) {
804                            loop {
805                                args.push(self.expression()?);
806                                if matches!(self.peek_tok(), Tok::Comma) {
807                                    self.advance();
808                                    if matches!(self.peek_tok(), Tok::RParen) {
809                                        break;
810                                    }
811                                } else {
812                                    break;
813                                }
814                            }
815                        }
816                        let close = self.expect(&Tok::RParen, "')' to close the call")?;
817                        Ok(Expr::Call {
818                            name,
819                            args,
820                            span: t.span.to(close.span),
821                        })
822                    }
823                } else {
824                    Ok(Expr::Ident(name, t.span))
825                }
826            }
827            _ => Err(LuxError::new("expected a value", t.span)),
828        }
829    }
830}
831
832/// Build a binary expression spanning both sides.
833fn binary(op: BinOp, lhs: Expr, rhs: Expr) -> Expr {
834    let span = lhs.span().to(rhs.span());
835    Expr::Binary {
836        op,
837        lhs: Box::new(lhs),
838        rhs: Box::new(rhs),
839        span,
840    }
841}