Skip to main content

luna_core/frontend/
parser.rs

1//! Recursive-descent parser; grammar and operator priorities follow PUC
2//! lparser.c. Statement/expression nesting is depth-limited like PUC's
3//! C-stack guard.
4
5use crate::frontend::ast::*;
6use crate::frontend::error::SyntaxError;
7use crate::frontend::lexer::Lexer;
8use crate::frontend::span::Span;
9use crate::frontend::token::{Token, TokenInfo};
10use crate::version::LuaVersion;
11
12/// PUC `LUAI_MAXCCALLS` — the parser's nesting cap. PUC sets it to 200 and
13/// increments once per `subexpr`/`funcargs`/`simpleexp`/`block`/`statement`
14/// call; luna's `enter()` fires on roughly the same surfaces (statement +
15/// sub_expr + suffixedexp + block), so the same 200 budget keeps
16/// errors.lua's `testrep` baseline — 190 levels compile, 201 hits the wall.
17const MAX_DEPTH: u32 = 200;
18
19/// `(collective attrib, declared names, initializer exprs)` of a declaration.
20type DeclList = (Option<Attrib>, Vec<AttribName>, Vec<ExprId>);
21
22/// Binary operator priorities from lparser.c (left, right); right < left
23/// means right-associative.
24fn bin_priority(op: BinOp) -> (u8, u8) {
25    match op {
26        BinOp::Or => (1, 1),
27        BinOp::And => (2, 2),
28        BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge | BinOp::Ne | BinOp::Eq => (3, 3),
29        BinOp::BOr => (4, 4),
30        BinOp::BXor => (5, 5),
31        BinOp::BAnd => (6, 6),
32        BinOp::Shl | BinOp::Shr => (7, 7),
33        BinOp::Concat => (9, 8),
34        BinOp::Add | BinOp::Sub => (10, 10),
35        BinOp::Mul | BinOp::Div | BinOp::IDiv | BinOp::Mod => (11, 11),
36        BinOp::Pow => (14, 13),
37    }
38}
39
40const UNARY_PRIORITY: u8 = 12;
41
42fn bin_op_of(tok: &Token) -> Option<BinOp> {
43    Some(match tok {
44        Token::Plus => BinOp::Add,
45        Token::Minus => BinOp::Sub,
46        Token::Star => BinOp::Mul,
47        Token::Slash => BinOp::Div,
48        Token::DSlash => BinOp::IDiv,
49        Token::Percent => BinOp::Mod,
50        Token::Caret => BinOp::Pow,
51        Token::Concat => BinOp::Concat,
52        Token::Eq => BinOp::Eq,
53        Token::Ne => BinOp::Ne,
54        Token::Lt => BinOp::Lt,
55        Token::Le => BinOp::Le,
56        Token::Gt => BinOp::Gt,
57        Token::Ge => BinOp::Ge,
58        Token::And => BinOp::And,
59        Token::Or => BinOp::Or,
60        Token::Amp => BinOp::BAnd,
61        Token::Pipe => BinOp::BOr,
62        Token::Tilde => BinOp::BXor,
63        Token::Shl => BinOp::Shl,
64        Token::Shr => BinOp::Shr,
65        _ => return None,
66    })
67}
68
69fn un_op_of(tok: &Token) -> Option<UnOp> {
70    Some(match tok {
71        Token::Minus => UnOp::Neg,
72        Token::Not => UnOp::Not,
73        Token::Hash => UnOp::Len,
74        Token::Tilde => UnOp::BNot,
75        _ => return None,
76    })
77}
78
79/// Token feed for the recursive-descent parser. Either a live [`Lexer`]
80/// (the default `parse(src, version)` path) or a pre-materialized token
81/// vector (the [`parse_tokens`] entry point used by the MacroLua expander
82/// pre-pass — see `frontend::macro_expander`). Both arms support
83/// `next_token` and `src()`.
84pub(crate) enum TokenSource<'s> {
85    /// Streaming lexer over raw source bytes.
86    Lexer(Lexer<'s>),
87    /// Pre-materialized token stream + a back-pointer to the original
88    /// source bytes so `Token::describe` can still slice spans for
89    /// `... near 'tok'` error reporting.
90    PreExpanded {
91        tokens: Vec<TokenInfo>,
92        cursor: usize,
93        src: &'s [u8],
94    },
95}
96
97impl<'s> TokenSource<'s> {
98    fn next_token(&mut self) -> Result<TokenInfo, SyntaxError> {
99        match self {
100            TokenSource::Lexer(l) => l.next_token(),
101            TokenSource::PreExpanded {
102                tokens,
103                cursor,
104                src,
105            } => {
106                if *cursor >= tokens.len() {
107                    let line = tokens.last().map(|t| t.line).unwrap_or(1);
108                    let _ = src;
109                    Ok(TokenInfo {
110                        tok: Token::Eof,
111                        span: Span::new(0, 0),
112                        line,
113                    })
114                } else {
115                    let t = tokens[*cursor].clone();
116                    *cursor += 1;
117                    Ok(t)
118                }
119            }
120        }
121    }
122
123    fn src(&self) -> &'s [u8] {
124        match self {
125            TokenSource::Lexer(l) => l.src(),
126            TokenSource::PreExpanded { src, .. } => src,
127        }
128    }
129}
130
131/// Parse a Lua source chunk for the given dialect into an arena AST
132/// ([`Chunk`]).
133///
134/// `LuaVersion::MacroLua` sources are **not** routed through the macro
135/// expander here — this entry point is dialect-agnostic and only sees
136/// the raw token stream. The Vm's `eval` path runs the expander
137/// transparently for MacroLua; direct callers feed expanded tokens via
138/// [`parse_tokens`].
139pub fn parse(src: &[u8], version: LuaVersion) -> Result<Chunk, SyntaxError> {
140    let lex = Lexer::new(src, version);
141    parse_from_source(TokenSource::Lexer(lex), version)
142}
143
144/// Parse a **pre-materialized** token stream. Used by the MacroLua
145/// expander pre-pass — it walks the lexer output once, expands
146/// `@name(...)` invocations against the per-Vm macro registry, and
147/// feeds the resulting `Vec<TokenInfo>` here.
148pub fn parse_tokens(
149    tokens: Vec<TokenInfo>,
150    src: &[u8],
151    version: LuaVersion,
152) -> Result<Chunk, SyntaxError> {
153    parse_from_source(
154        TokenSource::PreExpanded {
155            tokens,
156            cursor: 0,
157            src,
158        },
159        version,
160    )
161}
162
163fn parse_from_source<'s>(
164    mut lex: TokenSource<'s>,
165    version: LuaVersion,
166) -> Result<Chunk, SyntaxError> {
167    let tok = lex.next_token()?;
168    let mut p = Parser {
169        lex,
170        tok,
171        peeked: None,
172        prev_line: 1,
173        exprs: Vec::new(),
174        stats: Vec::new(),
175        stat_lines: Vec::new(),
176        depth: 0,
177        version,
178        // the main chunk is the bottom-most function context (line 0 → main)
179        func_local_count: vec![(0, 0)],
180        upval_chain_51: if version <= LuaVersion::Lua51 {
181            vec![FnUvSlot {
182                line_defined: 0,
183                ..Default::default()
184            }]
185        } else {
186            Vec::new()
187        },
188    };
189    let block = p.block()?;
190    if p.tok.tok != Token::Eof {
191        return Err(p.error("'<eof>' expected"));
192    }
193    let end_line = p.prev_line;
194    Ok(Chunk {
195        exprs: p.exprs,
196        stats: p.stats,
197        stat_lines: p.stat_lines,
198        block,
199        end_line,
200    })
201}
202
203struct Parser<'s> {
204    lex: TokenSource<'s>,
205    tok: TokenInfo,
206    peeked: Option<TokenInfo>,
207    /// line of the previously consumed token (for the 5.1 ambiguity check)
208    prev_line: u32,
209    exprs: Vec<Expr>,
210    stats: Vec<Stat>,
211    /// starting source line of each statement (by StatId), for precise per-
212    /// instruction line info in the compiler
213    stat_lines: Vec<u32>,
214    depth: u32,
215    version: LuaVersion,
216    /// One entry per function context (main chunk + nested functions): the
217    /// running active-local count (PUC `nactvar`) and the function's defining
218    /// line so the limit error can render "in function at line N". Pushed by
219    /// `func_body`, popped on exit. Without parse-time tracking, errors.lua
220    /// :775 would race a later structural error (a missing `end`) and lose.
221    func_local_count: Vec<(u32, u32)>,
222    /// Parse-time upvalue accounting for PUC 5.1 (errors.lua :238). PUC 5.1's
223    /// `singlevaraux` resolves each identifier as it parses and stops at
224    /// `MAXUPVAL=60`; luna defers name resolution to the compiler so a stack
225    /// of 61 nested `function`s with no `end`s reaches `<eof>` first and the
226    /// missing-`end` error wins. Tracking declared locals + accumulated
227    /// upvalue names per nested function here lets the same 60-deep chain
228    /// trip while we are still inside `foo61`'s body, with the offending
229    /// function's defining line on the error. Only populated for 5.1 — 5.2+
230    /// goes through `_ENV` (which would itself be an upvalue) and 5.5
231    /// tolerates a wider cap.
232    upval_chain_51: Vec<FnUvSlot>,
233}
234
235#[derive(Default)]
236struct FnUvSlot {
237    locals: Vec<Box<str>>,
238    upvalues: std::collections::HashSet<Box<str>>,
239    line_defined: u32,
240}
241
242impl<'s> Parser<'s> {
243    // ---- token plumbing ----
244
245    fn advance(&mut self) -> Result<TokenInfo, SyntaxError> {
246        let next = match self.peeked.take() {
247            Some(t) => t,
248            None => self.lex.next_token()?,
249        };
250        self.prev_line = self.tok.line;
251        Ok(std::mem::replace(&mut self.tok, next))
252    }
253
254    fn peek(&mut self) -> Result<&Token, SyntaxError> {
255        if self.peeked.is_none() {
256            self.peeked = Some(self.lex.next_token()?);
257        }
258        Ok(&self.peeked.as_ref().unwrap().tok)
259    }
260
261    fn near(&self) -> String {
262        self.tok
263            .tok
264            .describe(self.lex.src(), self.tok.span, self.version)
265    }
266
267    fn error(&self, msg: impl AsRef<str>) -> SyntaxError {
268        let mut bytes = msg.as_ref().as_bytes().to_vec();
269        bytes.extend_from_slice(b" near ");
270        bytes.extend_from_slice(self.near().as_bytes());
271        SyntaxError {
272            line: self.tok.line,
273            msg: bytes,
274        }
275    }
276
277    fn accept(&mut self, tok: Token) -> Result<bool, SyntaxError> {
278        if self.tok.tok == tok {
279            self.advance()?;
280            Ok(true)
281        } else {
282            Ok(false)
283        }
284    }
285
286    fn expect(&mut self, tok: Token, what: &str) -> Result<(), SyntaxError> {
287        if !self.accept(tok)? {
288            return Err(self.error(format!("'{what}' expected")));
289        }
290        Ok(())
291    }
292
293    /// Like PUC check_match: closing token with a pointer back to the opener.
294    fn expect_match(
295        &mut self,
296        tok: Token,
297        what: &str,
298        who: &str,
299        who_line: u32,
300    ) -> Result<(), SyntaxError> {
301        if !self.accept(tok)? {
302            if who_line == self.tok.line {
303                return Err(self.error(format!("'{what}' expected")));
304            }
305            return Err(self.error(format!(
306                "'{what}' expected (to close '{who}' at line {who_line})"
307            )));
308        }
309        Ok(())
310    }
311
312    fn expect_name(&mut self) -> Result<Name, SyntaxError> {
313        if !matches!(self.tok.tok, Token::Name(_)) {
314            return Err(self.error("<name> expected"));
315        }
316        let info = self.advance()?;
317        let Token::Name(text) = info.tok else {
318            unreachable!()
319        };
320        Ok(Name {
321            text,
322            line: info.line,
323        })
324    }
325
326    fn enter(&mut self) -> Result<(), SyntaxError> {
327        self.depth += 1;
328        if self.depth > MAX_DEPTH {
329            // PUC 5.1 `enterlevel`: "chunk has too many syntax levels".
330            // 5.2+ `LUAI_MAXCCALLS` overflow: "too many C levels (limit is
331            // N) in main function near <token>". errors.lua 5.1 :214 vs
332            // 5.4 :650 baseline on each spelling.
333            let msg: &[u8] = if self.version <= LuaVersion::Lua51 {
334                b"chunk has too many syntax levels"
335            } else {
336                b"too many C levels (limit is 200) in main function"
337            };
338            return Err(SyntaxError {
339                line: self.tok.line,
340                msg: msg.to_vec(),
341            });
342        }
343        Ok(())
344    }
345
346    fn leave(&mut self) {
347        self.depth -= 1;
348    }
349
350    fn push_expr(&mut self, e: Expr) -> ExprId {
351        self.exprs.push(e);
352        ExprId((self.exprs.len() - 1) as u32)
353    }
354
355    fn push_stat(&mut self, s: Stat) -> StatId {
356        self.stats.push(s);
357        StatId((self.stats.len() - 1) as u32)
358    }
359
360    // ---- blocks & statements ----
361
362    fn block_follow(&self) -> bool {
363        matches!(
364            self.tok.tok,
365            Token::Eof | Token::End | Token::Else | Token::Elseif | Token::Until
366        )
367    }
368
369    fn block(&mut self) -> Result<Block, SyntaxError> {
370        self.enter()?;
371        // PUC `leaveblock` restores `nactvar` to the count at block entry, so
372        // a block's locals fall out of scope when it ends. Snapshot the count
373        // here so the limit check tracks ACTIVE locals (locals.lua opens many
374        // short blocks; without this the cap fires spuriously).
375        let local_snapshot = self.func_local_count.last().expect("func ctx").0;
376        let locals_51_snap = self.snap_locals_51();
377        let mut stats = Vec::new();
378        loop {
379            if self.block_follow() {
380                break;
381            }
382            if self.tok.tok == Token::Return {
383                stats.push(self.return_stat()?);
384                break;
385            }
386            if self.tok.tok == Token::Break && self.version.break_is_last_statement() {
387                let line = self.tok.line;
388                self.advance()?;
389                stats.push(self.push_stat(Stat::Break { line }));
390                self.accept(Token::Semi)?;
391                break;
392            }
393            if let Some(s) = self.statement()? {
394                stats.push(s);
395            }
396            if !self.version.has_empty_statement() {
397                // 5.1: ';' is a separator after a statement, not a statement
398                self.accept(Token::Semi)?;
399            }
400        }
401        self.leave();
402        self.func_local_count.last_mut().expect("func ctx").0 = local_snapshot;
403        self.restore_locals_51(locals_51_snap);
404        Ok(Block { stats })
405    }
406
407    fn return_stat(&mut self) -> Result<StatId, SyntaxError> {
408        let line = self.tok.line;
409        self.advance()?;
410        let exprs = if self.block_follow() || self.tok.tok == Token::Semi {
411            Vec::new()
412        } else {
413            self.exprlist()?
414        };
415        self.accept(Token::Semi)?;
416        Ok(self.push_stat(Stat::Return { exprs, line }))
417    }
418
419    fn statement(&mut self) -> Result<Option<StatId>, SyntaxError> {
420        // PUC's `statement` does not bump `nCcalls` itself — the surrounding
421        // `block` does, and nested forms (do/while/if/function/...) each
422        // recurse through `block` again. Counting both would double the cost
423        // per `do … end` nesting; errors.lua's `testrep("do ", "", " end")`
424        // expects 190 levels to compile and 201 to fail at the same wall as
425        // the other shapes.
426        let start_line = self.tok.line;
427        // 5.5 `global` is a contextual keyword: a declaration only when it
428        // leads a statement and the next token starts one (name / '*' /
429        // function / attribute '<'). Otherwise it is an ordinary identifier
430        // (e.g. `global = 1`, `global()`, `return global`).
431        if self.version.has_global_decl()
432            && matches!(&self.tok.tok, Token::Name(n) if &**n == "global")
433            && matches!(
434                self.peek()?,
435                Token::Name(_) | Token::Star | Token::Function | Token::Lt
436            )
437        {
438            let stat = self.global_stat()?;
439            return Ok(Some(stat));
440        }
441        let stat = match self.tok.tok {
442            Token::Semi => {
443                if !self.version.has_empty_statement() {
444                    return Err(self.error("unexpected symbol"));
445                }
446                self.advance()?;
447                None
448            }
449            Token::If => Some(self.if_stat()?),
450            Token::While => Some(self.while_stat()?),
451            Token::Do => {
452                let line = self.tok.line;
453                self.advance()?;
454                let body = self.block()?;
455                self.expect_match(Token::End, "end", "do", line)?;
456                Some(self.push_stat(Stat::Do(body)))
457            }
458            Token::For => Some(self.for_stat()?),
459            Token::Repeat => Some(self.repeat_stat()?),
460            Token::Function => Some(self.function_stat()?),
461            Token::Local => Some(self.local_stat()?),
462            Token::DColon => {
463                self.advance()?;
464                let name = self.expect_name()?;
465                self.expect(Token::DColon, "::")?;
466                Some(self.push_stat(Stat::Label(name)))
467            }
468            Token::Break => {
469                let line = self.tok.line;
470                self.advance()?;
471                Some(self.push_stat(Stat::Break { line }))
472            }
473            Token::Goto => {
474                self.advance()?;
475                let name = self.expect_name()?;
476                Some(self.push_stat(Stat::Goto(name)))
477            }
478            _ => Some(self.expr_stat()?),
479        };
480        if let Some(sid) = stat {
481            let idx = sid.0 as usize;
482            if self.stat_lines.len() <= idx {
483                self.stat_lines.resize(idx + 1, 0);
484            }
485            self.stat_lines[idx] = start_line;
486        }
487        Ok(stat)
488    }
489
490    fn if_stat(&mut self) -> Result<StatId, SyntaxError> {
491        let line = self.tok.line;
492        self.advance()?;
493        let mut arms = Vec::new();
494        let cond = self.expr()?;
495        let then_line = self.tok.line;
496        self.expect(Token::Then, "then")?;
497        arms.push((cond, then_line, self.block()?));
498        while self.tok.tok == Token::Elseif {
499            self.advance()?;
500            let cond = self.expr()?;
501            let then_line = self.tok.line;
502            self.expect(Token::Then, "then")?;
503            arms.push((cond, then_line, self.block()?));
504        }
505        let else_body = if self.accept(Token::Else)? {
506            Some(self.block()?)
507        } else {
508            None
509        };
510        self.expect_match(Token::End, "end", "if", line)?;
511        Ok(self.push_stat(Stat::If { arms, else_body }))
512    }
513
514    fn while_stat(&mut self) -> Result<StatId, SyntaxError> {
515        let line = self.tok.line;
516        self.advance()?;
517        let cond = self.expr()?;
518        self.expect(Token::Do, "do")?;
519        let body = self.block()?;
520        self.expect_match(Token::End, "end", "while", line)?;
521        Ok(self.push_stat(Stat::While { cond, body }))
522    }
523
524    fn repeat_stat(&mut self) -> Result<StatId, SyntaxError> {
525        let line = self.tok.line;
526        self.advance()?;
527        let body = self.block()?;
528        self.expect_match(Token::Until, "until", "repeat", line)?;
529        let cond = self.expr()?;
530        Ok(self.push_stat(Stat::Repeat { body, cond }))
531    }
532
533    fn for_stat(&mut self) -> Result<StatId, SyntaxError> {
534        let line = self.tok.line;
535        self.advance()?;
536        let first = self.expect_name()?;
537        match self.tok.tok {
538            Token::Assign => {
539                self.advance()?;
540                let start = self.expr()?;
541                self.expect(Token::Comma, ",")?;
542                let limit = self.expr()?;
543                let step = if self.accept(Token::Comma)? {
544                    Some(self.expr()?)
545                } else {
546                    None
547                };
548                self.expect(Token::Do, "do")?;
549                self.add_local_51(&first.text);
550                let body = self.block()?;
551                self.expect_match(Token::End, "end", "for", line)?;
552                Ok(self.push_stat(Stat::NumericFor {
553                    var: first,
554                    start,
555                    limit,
556                    step,
557                    body,
558                }))
559            }
560            Token::Comma | Token::In => {
561                let mut vars = vec![first];
562                while self.accept(Token::Comma)? {
563                    vars.push(self.expect_name()?);
564                }
565                self.expect(Token::In, "in")?;
566                let expr_line = self.tok.line;
567                let exprs = self.exprlist()?;
568                self.expect(Token::Do, "do")?;
569                for v in &vars {
570                    self.add_local_51(&v.text);
571                }
572                let body = self.block()?;
573                self.expect_match(Token::End, "end", "for", line)?;
574                Ok(self.push_stat(Stat::GenericFor {
575                    vars,
576                    exprs,
577                    body,
578                    expr_line,
579                }))
580            }
581            _ => Err(self.error("'=' or 'in' expected")),
582        }
583    }
584
585    fn function_stat(&mut self) -> Result<StatId, SyntaxError> {
586        let line = self.tok.line;
587        self.advance()?;
588        let base = self.expect_name()?;
589        let mut path = Vec::new();
590        while self.accept(Token::Dot)? {
591            path.push(self.expect_name()?);
592        }
593        let method = if self.accept(Token::Colon)? {
594            Some(self.expect_name()?)
595        } else {
596            None
597        };
598        let body = self.func_body(line)?;
599        Ok(self.push_stat(Stat::Function {
600            name: FuncName { base, path, method },
601            body,
602        }))
603    }
604
605    fn attrib(&mut self) -> Result<Option<Attrib>, SyntaxError> {
606        if !(self.version.has_attribs() && self.tok.tok == Token::Lt) {
607            return Ok(None);
608        }
609        self.advance()?;
610        let name = self.expect_name()?;
611        let attrib = match &*name.text {
612            "const" => Attrib::Const,
613            "close" => Attrib::Close,
614            other => {
615                return Err(SyntaxError {
616                    line: name.line,
617                    msg: format!("unknown attribute '{other}'").into_bytes(),
618                });
619            }
620        };
621        self.expect(Token::Gt, ">")?;
622        Ok(Some(attrib))
623    }
624
625    /// `[attrib] Name [attrib] {',' Name [attrib]} ['=' explist]` — shared by
626    /// `local` and `global` declarations.
627    fn attnamelist(&mut self) -> Result<DeclList, SyntaxError> {
628        let collective = if self.version.has_collective_attrib() {
629            self.attrib()?
630        } else {
631            None
632        };
633        let mut names = Vec::new();
634        loop {
635            let name = self.expect_name()?;
636            let attrib = self.attrib()?;
637            names.push(AttribName { name, attrib });
638            if !self.accept(Token::Comma)? {
639                break;
640            }
641        }
642        let exprs = if self.accept(Token::Assign)? {
643            self.exprlist()?
644        } else {
645            Vec::new()
646        };
647        Ok((collective, names, exprs))
648    }
649
650    fn local_stat(&mut self) -> Result<StatId, SyntaxError> {
651        self.advance()?;
652        if self.accept(Token::Function)? {
653            let line = self.prev_line;
654            let name = self.expect_name()?;
655            // `local function f` declares `f` in the enclosing function before
656            // the body is parsed (PUC `localfunc`'s pre-declare); count it.
657            self.bump_locals(1)?;
658            self.add_local_51(&name.text);
659            let body = self.func_body(line)?;
660            return Ok(self.push_stat(Stat::LocalFunction { name, body }));
661        }
662        let (collective, names, exprs) = self.attnamelist()?;
663        self.bump_locals(names.len() as u32)?;
664        for an in &names {
665            self.add_local_51(&an.name.text);
666        }
667        Ok(self.push_stat(Stat::Local {
668            collective,
669            names,
670            exprs,
671        }))
672    }
673
674    fn global_stat(&mut self) -> Result<StatId, SyntaxError> {
675        self.advance()?;
676        if self.accept(Token::Function)? {
677            let line = self.prev_line;
678            let name = self.expect_name()?;
679            let body = self.func_body(line)?;
680            return Ok(self.push_stat(Stat::GlobalFunction { name, body }));
681        }
682        // `global [attrib] '*'`
683        let leading = self.attrib()?;
684        if self.accept(Token::Star)? {
685            return Ok(self.push_stat(Stat::GlobalAll { attrib: leading }));
686        }
687        let mut names = Vec::new();
688        loop {
689            let name = self.expect_name()?;
690            let attrib = self.attrib()?;
691            names.push(AttribName { name, attrib });
692            if !self.accept(Token::Comma)? {
693                break;
694            }
695        }
696        let exprs = if self.accept(Token::Assign)? {
697            self.exprlist()?
698        } else {
699            Vec::new()
700        };
701        Ok(self.push_stat(Stat::Global {
702            collective: leading,
703            names,
704            exprs,
705        }))
706    }
707
708    fn expr_stat(&mut self) -> Result<StatId, SyntaxError> {
709        let first = self.suffixed_expr()?;
710        if matches!(self.tok.tok, Token::Assign | Token::Comma) {
711            let mut targets = vec![first];
712            while self.accept(Token::Comma)? {
713                // PUC's `restassign` enforces `nvars + nCcalls < LUAI_MAXCCALLS`
714                // (200) at each comma; otherwise a runaway multi-assign would
715                // exhaust the C stack. errors.lua :650 builds a 500-target list
716                // and expects the limit error.
717                if targets.len() >= 200 {
718                    let msg: &[u8] = if self.version <= LuaVersion::Lua51 {
719                        b"chunk has too many syntax levels"
720                    } else {
721                        b"too many C levels (limit is 200) in main function"
722                    };
723                    return Err(SyntaxError {
724                        line: self.tok.line,
725                        msg: msg.to_vec(),
726                    });
727                }
728                targets.push(self.suffixed_expr()?);
729            }
730            self.expect(Token::Assign, "=")?;
731            for &t in &targets {
732                if !matches!(self.exprs[t.0 as usize], Expr::Name(_) | Expr::Index { .. }) {
733                    return Err(self.error("syntax error"));
734                }
735            }
736            let exprs = self.exprlist()?;
737            return Ok(self.push_stat(Stat::Assign { targets, exprs }));
738        }
739        if !matches!(
740            self.exprs[first.0 as usize],
741            Expr::Call { .. } | Expr::MethodCall { .. }
742        ) {
743            return Err(self.error("syntax error"));
744        }
745        Ok(self.push_stat(Stat::Call(first)))
746    }
747
748    // ---- expressions ----
749
750    fn exprlist(&mut self) -> Result<Vec<ExprId>, SyntaxError> {
751        let mut list = vec![self.expr()?];
752        while self.accept(Token::Comma)? {
753            list.push(self.expr()?);
754        }
755        Ok(list)
756    }
757
758    fn expr(&mut self) -> Result<ExprId, SyntaxError> {
759        self.sub_expr(0)
760    }
761
762    fn sub_expr(&mut self, limit: u8) -> Result<ExprId, SyntaxError> {
763        self.enter()?;
764        let mut left = if let Some(op) = un_op_of(&self.tok.tok) {
765            let line = self.tok.line;
766            self.advance()?;
767            let operand = self.sub_expr(UNARY_PRIORITY)?;
768            self.push_expr(Expr::UnOp { op, operand, line })
769        } else {
770            self.simple_expr()?
771        };
772        while let Some(op) = bin_op_of(&self.tok.tok) {
773            let (lp, rp) = bin_priority(op);
774            if lp <= limit {
775                break;
776            }
777            let line = self.tok.line;
778            self.advance()?;
779            let rhs = self.sub_expr(rp)?;
780            left = self.push_expr(Expr::BinOp {
781                op,
782                lhs: left,
783                rhs,
784                line,
785            });
786        }
787        self.leave();
788        Ok(left)
789    }
790
791    fn simple_expr(&mut self) -> Result<ExprId, SyntaxError> {
792        let e = match &self.tok.tok {
793            Token::Nil => {
794                self.advance()?;
795                Expr::Nil
796            }
797            Token::True => {
798                self.advance()?;
799                Expr::True
800            }
801            Token::False => {
802                self.advance()?;
803                Expr::False
804            }
805            Token::Ellipsis => {
806                self.advance()?;
807                Expr::Vararg
808            }
809            Token::Int(_) => {
810                let Token::Int(v) = self.advance()?.tok else {
811                    unreachable!()
812                };
813                Expr::Int(v)
814            }
815            Token::Float(_) => {
816                let Token::Float(v) = self.advance()?.tok else {
817                    unreachable!()
818                };
819                Expr::Float(v)
820            }
821            Token::Str(_) => {
822                let Token::Str(s) = self.advance()?.tok else {
823                    unreachable!()
824                };
825                Expr::Str(s)
826            }
827            Token::LBrace => return self.table_constructor(),
828            Token::Function => {
829                let line = self.tok.line;
830                self.advance()?;
831                Expr::Function(self.func_body(line)?)
832            }
833            _ => return self.suffixed_expr(),
834        };
835        Ok(self.push_expr(e))
836    }
837
838    fn primary_expr(&mut self) -> Result<ExprId, SyntaxError> {
839        match &self.tok.tok {
840            Token::Name(_) => {
841                let name = self.expect_name()?;
842                self.ident_lookup_51(&name.text)?;
843                Ok(self.push_expr(Expr::Name(name)))
844            }
845            Token::LParen => {
846                let line = self.tok.line;
847                self.advance()?;
848                let inner = self.expr()?;
849                self.expect_match(Token::RParen, ")", "(", line)?;
850                Ok(self.push_expr(Expr::Paren(inner)))
851            }
852            _ => Err(self.error("unexpected symbol")),
853        }
854    }
855
856    fn suffixed_expr(&mut self) -> Result<ExprId, SyntaxError> {
857        // PUC's `suffixedexp` does *not* bump `nCcalls` on its own — only its
858        // callers do (subexpr, funcargs, …). Doing so here too would
859        // double-count `(` nesting, since `simpleexp`'s default branch dives
860        // through `suffixed_expr` → `primary_expr` → `expr()` → `sub_expr`
861        // (which already enters). Keeping the entry out lets errors.lua's
862        // `testrep("(")` (paren-only nesting) hit the same 200-level wall as
863        // `{`/`,` nesting.
864        // PUC 5.1–5.3 `suffixedexp` captured the line of the *primary*
865        // expression once and pinned every chained call/method to it
866        // (`a\n(...)` → error reports on `a`'s line); 5.4 switched to
867        // tracking the current line at each suffix and reports on the `(`
868        // line instead. errors.lua's `lineerror` covers both:
869        //   - 5.3:  `lineerror([[a\n(\n23)]], 1)` — expects `a`'s line
870        //   - 5.4+: `lineerror([[a\n(...)\n23)]], 2)` — expects `(`'s line
871        let primary_line = self.tok.line;
872        let mut e = self.primary_expr()?;
873        loop {
874            match &self.tok.tok {
875                Token::Dot => {
876                    self.advance()?;
877                    let name = self.expect_name()?;
878                    let key = self.push_expr(Expr::Str(name.text.into_boxed_bytes().into_vec()));
879                    e = self.push_expr(Expr::Index { obj: e, key });
880                }
881                Token::LBracket => {
882                    self.advance()?;
883                    let key = self.expr()?;
884                    self.expect(Token::RBracket, "]")?;
885                    e = self.push_expr(Expr::Index { obj: e, key });
886                }
887                Token::Colon => {
888                    self.advance()?;
889                    let method = self.expect_name()?;
890                    let line = if self.version <= LuaVersion::Lua53 {
891                        primary_line
892                    } else {
893                        self.tok.line
894                    };
895                    let args = self.call_args()?;
896                    e = self.push_expr(Expr::MethodCall {
897                        obj: e,
898                        method,
899                        args,
900                        line,
901                    });
902                }
903                Token::LParen | Token::Str(_) | Token::LBrace => {
904                    let line = if self.version <= LuaVersion::Lua53 {
905                        primary_line
906                    } else {
907                        self.tok.line
908                    };
909                    let args = self.call_args()?;
910                    e = self.push_expr(Expr::Call {
911                        func: e,
912                        args,
913                        line,
914                    });
915                }
916                _ => break,
917            }
918        }
919        Ok(e)
920    }
921
922    fn call_args(&mut self) -> Result<Vec<ExprId>, SyntaxError> {
923        match &self.tok.tok {
924            Token::LParen => {
925                // 5.1 rejects a call paren on a new line (removed in 5.2)
926                if self.version == LuaVersion::Lua51 && self.tok.line != self.prev_line {
927                    return Err(self.error("ambiguous syntax (function call x new statement)"));
928                }
929                let line = self.tok.line;
930                self.advance()?;
931                let args = if self.tok.tok == Token::RParen {
932                    Vec::new()
933                } else {
934                    self.exprlist()?
935                };
936                self.expect_match(Token::RParen, ")", "(", line)?;
937                Ok(args)
938            }
939            Token::Str(_) => {
940                let Token::Str(s) = self.advance()?.tok else {
941                    unreachable!()
942                };
943                Ok(vec![self.push_expr(Expr::Str(s))])
944            }
945            Token::LBrace => Ok(vec![self.table_constructor()?]),
946            _ => Err(self.error("function arguments expected")),
947        }
948    }
949
950    fn table_constructor(&mut self) -> Result<ExprId, SyntaxError> {
951        let line = self.tok.line;
952        self.expect(Token::LBrace, "{")?;
953        let mut fields = Vec::new();
954        loop {
955            if self.tok.tok == Token::RBrace {
956                break;
957            }
958            if self.tok.tok == Token::LBracket {
959                self.advance()?;
960                let key = self.expr()?;
961                self.expect(Token::RBracket, "]")?;
962                self.expect(Token::Assign, "=")?;
963                let value = self.expr()?;
964                fields.push(TableField::Keyed(key, value));
965            } else if matches!(self.tok.tok, Token::Name(_)) && *self.peek()? == Token::Assign {
966                let name = self.expect_name()?;
967                self.advance()?; // '='
968                let value = self.expr()?;
969                fields.push(TableField::Named(name, value));
970            } else {
971                fields.push(TableField::Item(self.expr()?));
972            }
973            if !(self.accept(Token::Comma)? || self.accept(Token::Semi)?) {
974                break;
975            }
976        }
977        self.expect_match(Token::RBrace, "}", "{", line)?;
978        Ok(self.push_expr(Expr::Table { fields, line }))
979    }
980
981    // ---- functions ----
982
983    fn func_body(&mut self, line: u32) -> Result<FuncBody, SyntaxError> {
984        self.expect(Token::LParen, "(")?;
985        self.func_local_count.push((0, line));
986        self.enter_fn_51(line);
987        let mut params = Vec::new();
988        let mut vararg = Vararg::None;
989        if self.tok.tok != Token::RParen {
990            loop {
991                match &self.tok.tok {
992                    Token::Ellipsis => {
993                        self.advance()?;
994                        vararg = if self.version.has_named_vararg()
995                            && matches!(self.tok.tok, Token::Name(_))
996                        {
997                            Vararg::Named(self.expect_name()?)
998                        } else {
999                            Vararg::Anonymous
1000                        };
1001                        if let Vararg::Named(ref n) = vararg {
1002                            self.add_local_51(&n.text);
1003                        }
1004                        break;
1005                    }
1006                    Token::Name(_) => {
1007                        let p = self.expect_name()?;
1008                        self.add_local_51(&p.text);
1009                        params.push(p);
1010                    }
1011                    _ => return Err(self.error("<name> expected")),
1012                }
1013                if !self.accept(Token::Comma)? {
1014                    break;
1015                }
1016            }
1017        }
1018        self.expect(Token::RParen, ")")?;
1019        // params count against the function's local cap (PUC `new_localvar`
1020        // for parameters); errors raised at this point still attribute to
1021        // the function's defining line.
1022        let nparams = params.len() as u32 + matches!(vararg, Vararg::Named(_)) as u32;
1023        self.bump_locals(nparams)?;
1024        let block = self.block()?;
1025        let end_line = self.tok.line; // the `end` token's line, before consuming
1026        self.expect_match(Token::End, "end", "function", line)?;
1027        self.func_local_count.pop();
1028        self.leave_fn_51();
1029        Ok(FuncBody {
1030            params,
1031            vararg,
1032            block,
1033            line,
1034            end_line,
1035        })
1036    }
1037
1038    fn track_uv_51(&self) -> bool {
1039        !self.upval_chain_51.is_empty()
1040    }
1041
1042    fn add_local_51(&mut self, name: &str) {
1043        if self.track_uv_51() {
1044            self.upval_chain_51
1045                .last_mut()
1046                .expect("fn ctx")
1047                .locals
1048                .push(name.into());
1049        }
1050    }
1051
1052    fn snap_locals_51(&self) -> usize {
1053        if self.track_uv_51() {
1054            self.upval_chain_51.last().expect("fn ctx").locals.len()
1055        } else {
1056            0
1057        }
1058    }
1059
1060    fn restore_locals_51(&mut self, snap: usize) {
1061        if self.track_uv_51() {
1062            self.upval_chain_51
1063                .last_mut()
1064                .expect("fn ctx")
1065                .locals
1066                .truncate(snap);
1067        }
1068    }
1069
1070    fn enter_fn_51(&mut self, line_defined: u32) {
1071        if self.track_uv_51() {
1072            self.upval_chain_51.push(FnUvSlot {
1073                line_defined,
1074                ..Default::default()
1075            });
1076        }
1077    }
1078
1079    fn leave_fn_51(&mut self) {
1080        if self.track_uv_51() {
1081            self.upval_chain_51.pop();
1082        }
1083    }
1084
1085    /// PUC 5.1 `singlevaraux`-equivalent: resolve `name` against the current
1086    /// nested-function stack of declared locals, accumulating an upvalue entry
1087    /// in every intermediate function between the referencing site and the
1088    /// owning scope. Returns the PUC "too many upvalues" error the moment a
1089    /// link's upvalue set crosses 60. No-op for non-5.1 dialects.
1090    fn ident_lookup_51(&mut self, name: &str) -> Result<(), SyntaxError> {
1091        if !self.track_uv_51() {
1092            return Ok(());
1093        }
1094        const MAXUPVAL: usize = 60;
1095        let n = self.upval_chain_51.len();
1096        let mut owner: Option<usize> = None;
1097        for k in (0..n).rev() {
1098            if self.upval_chain_51[k]
1099                .locals
1100                .iter()
1101                .any(|s| s.as_ref() == name)
1102            {
1103                owner = Some(k);
1104                break;
1105            }
1106        }
1107        let Some(owner_idx) = owner else {
1108            return Ok(());
1109        };
1110        if owner_idx + 1 == n {
1111            return Ok(());
1112        }
1113        for k in (owner_idx + 1)..n {
1114            let inserted = self.upval_chain_51[k].upvalues.insert(name.into());
1115            if inserted && self.upval_chain_51[k].upvalues.len() > MAXUPVAL {
1116                let line_defined = self.upval_chain_51[k].line_defined;
1117                let where_ = if k == 0 {
1118                    "main function".to_string()
1119                } else {
1120                    format!("function at line {line_defined}")
1121                };
1122                return Err(SyntaxError {
1123                    line: self.tok.line,
1124                    msg: format!("too many upvalues (limit is {MAXUPVAL}) in {where_}")
1125                        .into_bytes(),
1126                });
1127            }
1128        }
1129        Ok(())
1130    }
1131
1132    /// Increment the active-local count of the function we are currently
1133    /// parsing and raise PUC's "too many local variables" error if the cap
1134    /// is exceeded. The "in function at line N" suffix uses the function's
1135    /// defining line that `func_body` stashed alongside the counter.
1136    fn bump_locals(&mut self, n: u32) -> Result<(), SyntaxError> {
1137        const MAXVARS: u32 = 200;
1138        let depth = self.func_local_count.len();
1139        let &(cur, line_defined) = self.func_local_count.last().expect("func ctx pushed");
1140        let new = cur.saturating_add(n);
1141        if new > MAXVARS {
1142            let where_ = if depth == 1 {
1143                "main function".to_string()
1144            } else {
1145                format!("function at line {line_defined}")
1146            };
1147            return Err(SyntaxError {
1148                line: self.tok.line,
1149                msg: format!("too many local variables (limit is {MAXVARS}) in {where_}")
1150                    .into_bytes(),
1151            });
1152        }
1153        self.func_local_count.last_mut().unwrap().0 = new;
1154        Ok(())
1155    }
1156}