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::goto_check::GotoCheck;
8use crate::frontend::lexer::{Lexed, Lexer};
9use crate::frontend::span::Span;
10use crate::frontend::token::{Near, Token, TokenInfo, near_text};
11use crate::version::LuaVersion;
12
13/// PUC `LUAI_MAXCCALLS` — the parser's nesting cap. PUC sets it to 200 and
14/// increments once per `subexpr`/`funcargs`/`simpleexp`/`block`/`statement`
15/// call; luna's `enter()` fires on roughly the same surfaces (statement +
16/// sub_expr + suffixedexp + block), so the same 200 budget keeps
17/// errors.lua's `testrep` baseline — 190 levels compile, 201 hits the wall.
18const MAX_DEPTH: u32 = 200;
19
20/// PUC `MAXVARS`: active locals per function.
21const MAXVARS: u32 = 200;
22
23/// `(collective attrib, declared names, initializer exprs)` of a declaration.
24type DeclList = (Option<Attrib>, Vec<AttribName>, Vec<ExprId>);
25
26/// Binary operator priorities from lparser.c (left, right); right < left
27/// means right-associative.
28fn bin_priority(op: BinOp) -> (u8, u8) {
29    match op {
30        BinOp::Or => (1, 1),
31        BinOp::And => (2, 2),
32        BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge | BinOp::Ne | BinOp::Eq => (3, 3),
33        BinOp::BOr => (4, 4),
34        BinOp::BXor => (5, 5),
35        BinOp::BAnd => (6, 6),
36        BinOp::Shl | BinOp::Shr => (7, 7),
37        BinOp::Concat => (9, 8),
38        BinOp::Add | BinOp::Sub => (10, 10),
39        BinOp::Mul | BinOp::Div | BinOp::IDiv | BinOp::Mod => (11, 11),
40        BinOp::Pow => (14, 13),
41    }
42}
43
44const UNARY_PRIORITY: u8 = 12;
45
46fn bin_op_of(tok: &Token) -> Option<BinOp> {
47    Some(match tok {
48        Token::Plus => BinOp::Add,
49        Token::Minus => BinOp::Sub,
50        Token::Star => BinOp::Mul,
51        Token::Slash => BinOp::Div,
52        Token::DSlash => BinOp::IDiv,
53        Token::Percent => BinOp::Mod,
54        Token::Caret => BinOp::Pow,
55        Token::Concat => BinOp::Concat,
56        Token::Eq => BinOp::Eq,
57        Token::Ne => BinOp::Ne,
58        Token::Lt => BinOp::Lt,
59        Token::Le => BinOp::Le,
60        Token::Gt => BinOp::Gt,
61        Token::Ge => BinOp::Ge,
62        Token::And => BinOp::And,
63        Token::Or => BinOp::Or,
64        Token::Amp => BinOp::BAnd,
65        Token::Pipe => BinOp::BOr,
66        Token::Tilde => BinOp::BXor,
67        Token::Shl => BinOp::Shl,
68        Token::Shr => BinOp::Shr,
69        _ => return None,
70    })
71}
72
73fn un_op_of(tok: &Token) -> Option<UnOp> {
74    Some(match tok {
75        Token::Minus => UnOp::Neg,
76        Token::Not => UnOp::Not,
77        Token::Hash => UnOp::Len,
78        Token::Tilde => UnOp::BNot,
79        _ => return None,
80    })
81}
82
83/// Token feed for the recursive-descent parser. Either a live [`Lexer`]
84/// (the default `parse(src, version)` path) or a pre-materialized token
85/// vector (the [`parse_tokens`] entry point used by the MacroLua expander
86/// pre-pass — see `frontend::macro_expander`). Both arms support
87/// `next_token` and `src()`.
88pub(crate) enum TokenSource<'s> {
89    /// Streaming lexer over raw source bytes.
90    Lexer(Lexer<'s>),
91    /// Pre-materialized token stream + a back-pointer to the original
92    /// source bytes so `Token::describe` can still slice spans for
93    /// `... near 'tok'` error reporting.
94    PreExpanded {
95        tokens: Vec<TokenInfo>,
96        cursor: usize,
97        src: &'s [u8],
98    },
99}
100
101/// A token as the parser holds it. `char` is set when the lexer handed back
102/// a byte no token starts with; `info.tok` is then a placeholder that no
103/// grammar rule accepts ([`Token::At`], which only MacroLua lexes, and
104/// MacroLua never parses from a live lexer).
105struct Cur {
106    info: TokenInfo,
107    char: Option<u8>,
108}
109
110impl<'s> TokenSource<'s> {
111    fn next_token(&mut self) -> Result<Cur, SyntaxError> {
112        match self {
113            TokenSource::Lexer(l) => Ok(match l.next_lexed()? {
114                Lexed::Tok(info) => Cur { info, char: None },
115                Lexed::Char(c, mut info) => {
116                    info.tok = Token::At;
117                    Cur {
118                        info,
119                        char: Some(c),
120                    }
121                }
122            }),
123            TokenSource::PreExpanded {
124                tokens,
125                cursor,
126                src,
127            } => {
128                if *cursor >= tokens.len() {
129                    let line = tokens.last().map(|t| t.line).unwrap_or(1);
130                    let _ = src;
131                    Ok(Cur {
132                        info: TokenInfo {
133                            tok: Token::Eof,
134                            span: Span::new(0, 0),
135                            line,
136                        },
137                        char: None,
138                    })
139                } else {
140                    let t = tokens[*cursor].clone();
141                    *cursor += 1;
142                    Ok(Cur {
143                        info: t,
144                        char: None,
145                    })
146                }
147            }
148        }
149    }
150
151    fn src(&self) -> &'s [u8] {
152        match self {
153            TokenSource::Lexer(l) => l.src(),
154            TokenSource::PreExpanded { src, .. } => src,
155        }
156    }
157
158    /// PUC `ls->linenumber`: where the scanner stands, which is where every
159    /// syntax error is reported.
160    fn line(&self) -> u32 {
161        match self {
162            TokenSource::Lexer(l) => l.line(),
163            TokenSource::PreExpanded { tokens, cursor, .. } => tokens
164                .get(cursor.saturating_sub(1))
165                .or(tokens.last())
166                .map_or(1, |t| t.line),
167        }
168    }
169}
170
171/// Parse a Lua source chunk for the given dialect into an arena AST
172/// ([`Chunk`]).
173///
174/// `LuaVersion::MacroLua` sources are **not** routed through the macro
175/// expander here — this entry point is dialect-agnostic and only sees
176/// the raw token stream. The Vm's `eval` path runs the expander
177/// transparently for MacroLua; direct callers feed expanded tokens via
178/// [`parse_tokens`].
179pub fn parse(src: &[u8], version: LuaVersion) -> Result<Chunk, SyntaxError> {
180    parse_at_depth(src, version, 0).map(|p| p.chunk)
181}
182
183/// A parsed chunk with what the public [`Chunk`] has no place for.
184pub(crate) struct Parsed {
185    pub(crate) chunk: Chunk,
186    /// the line of the closing `end` of each `while` / `for` statement, by
187    /// `StatId` (0 for other statements): PUC attributes the code it emits
188    /// after reading that `end` to its line
189    pub(crate) end_lines: Vec<u32>,
190}
191
192/// [`parse`] run by a VM that is `c_depth` C calls deep. PUC's parser
193/// counts its nesting on the running thread's `nCcalls`, so a chunk loaded
194/// near the C-call limit fails to *parse* (and `require` reports it as an
195/// error loading the module) before the call that would run it overflows.
196pub(crate) fn parse_at_depth(
197    src: &[u8],
198    version: LuaVersion,
199    c_depth: u32,
200) -> Result<Parsed, SyntaxError> {
201    let lex = Lexer::new(src, version);
202    parse_from_source(TokenSource::Lexer(lex), version, c_depth)
203}
204
205/// Parse a **pre-materialized** token stream. Used by the MacroLua
206/// expander pre-pass — it walks the lexer output once, expands
207/// `@name(...)` invocations against the per-Vm macro registry, and
208/// feeds the resulting `Vec<TokenInfo>` here.
209pub fn parse_tokens(
210    tokens: Vec<TokenInfo>,
211    src: &[u8],
212    version: LuaVersion,
213) -> Result<Chunk, SyntaxError> {
214    parse_tokens_at_depth(tokens, src, version, 0).map(|p| p.chunk)
215}
216
217/// [`parse_tokens`] at a C depth (see [`parse_at_depth`]).
218pub(crate) fn parse_tokens_at_depth(
219    tokens: Vec<TokenInfo>,
220    src: &[u8],
221    version: LuaVersion,
222    c_depth: u32,
223) -> Result<Parsed, SyntaxError> {
224    parse_from_source(
225        TokenSource::PreExpanded {
226            tokens,
227            cursor: 0,
228            src,
229        },
230        version,
231        c_depth,
232    )
233}
234
235fn parse_from_source<'s>(
236    mut lex: TokenSource<'s>,
237    version: LuaVersion,
238    c_depth: u32,
239) -> Result<Parsed, SyntaxError> {
240    let cur = lex.next_token()?;
241    let mut p = Parser {
242        lex,
243        tok: cur.info,
244        tok_char: cur.char,
245        peeked: None,
246        prev_line: 1,
247        exprs: Vec::new(),
248        stats: Vec::new(),
249        stat_lines: Vec::new(),
250        end_lines: Vec::new(),
251        depth: c_depth,
252        version,
253        // the main chunk is the bottom-most function context (line 0 → main)
254        func_local_count: vec![(0, 0, 0)],
255        funcs: vec![FnFlow {
256            vararg: true,
257            loops: 0,
258        }],
259        gotos: GotoCheck::new(version),
260        last_line: 1,
261        upval_chain_51: if version <= LuaVersion::Lua51 {
262            vec![FnUvSlot {
263                line_defined: 0,
264                ..Default::default()
265            }]
266        } else {
267            Vec::new()
268        },
269    };
270    if let Some(g) = p.gotos.as_mut() {
271        g.enter_function();
272    }
273    let block = p.block()?;
274    if p.tok.tok != Token::Eof {
275        return Err(p.error_expected("<eof>"));
276    }
277    p.close_function()?;
278    let end_line = p.prev_line;
279    Ok(Parsed {
280        chunk: Chunk {
281            exprs: p.exprs,
282            stats: p.stats,
283            stat_lines: p.stat_lines,
284            block,
285            end_line,
286        },
287        end_lines: p.end_lines,
288    })
289}
290
291struct Parser<'s> {
292    lex: TokenSource<'s>,
293    tok: TokenInfo,
294    /// The byte behind a placeholder `tok` (see [`Cur`]).
295    tok_char: Option<u8>,
296    peeked: Option<Cur>,
297    /// Per open function (main chunk first): what `...` and `break` are
298    /// checked against while parsing, as PUC does.
299    funcs: Vec<FnFlow>,
300    /// Gotos (and 5.2-5.4 `break`) are resolved while parsing; see
301    /// [`GotoCheck`].
302    gotos: Option<GotoCheck>,
303    /// PUC `ls->lastline`: where the scanner stood before reading the
304    /// current token, i.e. the line the last consumed token ended on.
305    last_line: u32,
306    /// line of the previously consumed token (for the 5.1 ambiguity check)
307    prev_line: u32,
308    exprs: Vec<Expr>,
309    stats: Vec<Stat>,
310    /// starting source line of each statement (by StatId), for precise per-
311    /// instruction line info in the compiler
312    stat_lines: Vec<u32>,
313    /// see [`Parsed::end_lines`]
314    end_lines: Vec<u32>,
315    depth: u32,
316    version: LuaVersion,
317    /// One entry per function context (main chunk + nested functions): the
318    /// running active-local count (PUC `nactvar`), the function's defining
319    /// line so the limit error can render "in function at line N", and the
320    /// locals declared but not yet in scope (`local a, b` before its `=`
321    /// list is parsed). Pushed by
322    /// `func_body`, popped on exit. Without parse-time tracking, errors.lua
323    /// :775 would race a later structural error (a missing `end`) and lose.
324    func_local_count: Vec<(u32, u32, u32)>,
325    /// Parse-time upvalue accounting for PUC 5.1 (errors.lua :238). PUC 5.1's
326    /// `singlevaraux` resolves each identifier as it parses and stops at
327    /// `MAXUPVAL=60`; luna defers name resolution to the compiler so a stack
328    /// of 61 nested `function`s with no `end`s reaches `<eof>` first and the
329    /// missing-`end` error wins. Tracking declared locals + accumulated
330    /// upvalue names per nested function here lets the same 60-deep chain
331    /// trip while we are still inside `foo61`'s body, with the offending
332    /// function's defining line on the error. Only populated for 5.1 — 5.2+
333    /// goes through `_ENV` (which would itself be an upvalue) and 5.5
334    /// tolerates a wider cap.
335    upval_chain_51: Vec<FnUvSlot>,
336}
337
338struct FnFlow {
339    vararg: bool,
340    /// Loops enclosing the current position inside this function.
341    loops: u32,
342}
343
344#[derive(Default)]
345struct FnUvSlot {
346    locals: Vec<Box<str>>,
347    upvalues: std::collections::HashSet<Box<str>>,
348    line_defined: u32,
349}
350
351impl<'s> Parser<'s> {
352    // ---- token plumbing ----
353
354    fn advance(&mut self) -> Result<TokenInfo, SyntaxError> {
355        self.last_line = self.lex.line();
356        let next = match self.peeked.take() {
357            Some(t) => t,
358            None => self.lex.next_token()?,
359        };
360        self.prev_line = self.tok.line;
361        self.tok_char = next.char;
362        Ok(std::mem::replace(&mut self.tok, next.info))
363    }
364
365    fn peek(&mut self) -> Result<&Token, SyntaxError> {
366        if self.peeked.is_none() {
367            self.peeked = Some(self.lex.next_token()?);
368        }
369        Ok(&self.peeked.as_ref().unwrap().info.tok)
370    }
371
372    fn near(&self) -> Vec<u8> {
373        match self.tok_char {
374            Some(c) => near_text(self.version, Near::Char(c)),
375            None => self
376                .tok
377                .tok
378                .near_bytes(self.lex.src(), self.tok.span, self.version),
379        }
380    }
381
382    /// PUC `luaX_syntaxerror`: `msg near <current token>`.
383    fn error(&self, msg: impl AsRef<str>) -> SyntaxError {
384        // a NUL byte comes back from PUC's scanner as token 0, which
385        // `lexerror` reads as "no near-token"
386        if self.tok_char == Some(0) {
387            return self.plain_error(msg.as_ref());
388        }
389        let mut bytes = msg.as_ref().as_bytes().to_vec();
390        bytes.extend_from_slice(b" near ");
391        bytes.extend_from_slice(&self.near());
392        SyntaxError {
393            line: self.lex.line(),
394            msg: bytes,
395        }
396    }
397
398    /// PUC `luaK_semerror` / `luaX_lexerror(.., 0)`: no near-token.
399    fn plain_error(&self, msg: impl Into<Vec<u8>>) -> SyntaxError {
400        SyntaxError {
401            line: self.lex.line(),
402            msg: msg.into(),
403        }
404    }
405
406    /// PUC `error_expected`. `what` is a token as `luaX_token2str` spells
407    /// it; the `<name>`-style pseudo-tokens are quoted only by 5.1.
408    fn error_expected(&self, what: &str) -> SyntaxError {
409        if what.starts_with('<') && self.version >= LuaVersion::Lua52 {
410            self.error(format!("{what} expected"))
411        } else {
412            self.error(format!("'{what}' expected"))
413        }
414    }
415
416    fn accept(&mut self, tok: Token) -> Result<bool, SyntaxError> {
417        if self.tok.tok == tok {
418            self.advance()?;
419            Ok(true)
420        } else {
421            Ok(false)
422        }
423    }
424
425    fn expect(&mut self, tok: Token, what: &str) -> Result<(), SyntaxError> {
426        if !self.accept(tok)? {
427            return Err(self.error_expected(what));
428        }
429        Ok(())
430    }
431
432    /// Like PUC check_match: closing token with a pointer back to the opener.
433    fn expect_match(
434        &mut self,
435        tok: Token,
436        what: &str,
437        who: &str,
438        who_line: u32,
439    ) -> Result<(), SyntaxError> {
440        if !self.accept(tok)? {
441            if who_line == self.lex.line() {
442                return Err(self.error_expected(what));
443            }
444            return Err(self.error(format!(
445                "'{what}' expected (to close '{who}' at line {who_line})"
446            )));
447        }
448        Ok(())
449    }
450
451    fn expect_name(&mut self) -> Result<Name, SyntaxError> {
452        if !matches!(self.tok.tok, Token::Name(_)) {
453            return Err(self.error_expected("<name>"));
454        }
455        let info = self.advance()?;
456        let Token::Name(text) = info.tok else {
457            unreachable!()
458        };
459        Ok(Name {
460            text,
461            line: info.line,
462        })
463    }
464
465    fn enter(&mut self) -> Result<(), SyntaxError> {
466        self.depth += 1;
467        // 5.1-5.3 `enterlevel` fails past the limit; 5.4+ `luaE_incCstack`
468        // at it
469        let limit = if self.version >= LuaVersion::Lua54 {
470            MAX_DEPTH - 1
471        } else {
472            MAX_DEPTH
473        };
474        if self.depth > limit {
475            return Err(self.levels_error());
476        }
477        Ok(())
478    }
479
480    /// PUC's nesting-limit error: 5.1 `enterlevel` has its own wording and
481    /// no near-token; 5.2/5.3 run it through `errorlimit` ("C levels", in
482    /// the function being parsed); 5.4+ count parser levels on the C stack,
483    /// and `luaE_checkcstack` raises a runtime error that carries no
484    /// position because the running function is `load`, not Lua code.
485    fn levels_error(&self) -> SyntaxError {
486        match self.version {
487            LuaVersion::Lua51 => self.plain_error("chunk has too many syntax levels"),
488            LuaVersion::Lua52 | LuaVersion::Lua53 => self.error(format!(
489                "too many C levels (limit is 200) in {}",
490                self.where_()
491            )),
492            _ => SyntaxError::unpositioned("C stack overflow"),
493        }
494    }
495
496    /// PUC `errorlimit`'s `where`: the function being parsed.
497    fn where_(&self) -> String {
498        let &(_, line_defined, _) = self.func_local_count.last().expect("func ctx");
499        if self.func_local_count.len() == 1 {
500            "main function".to_string()
501        } else {
502            format!("function at line {line_defined}")
503        }
504    }
505
506    fn leave(&mut self) {
507        self.depth -= 1;
508    }
509
510    fn push_expr(&mut self, e: Expr) -> ExprId {
511        self.exprs.push(e);
512        ExprId((self.exprs.len() - 1) as u32)
513    }
514
515    fn push_stat(&mut self, s: Stat) -> StatId {
516        self.stats.push(s);
517        StatId((self.stats.len() - 1) as u32)
518    }
519
520    /// Push a statement that ended with the `end` just read.
521    fn push_ended_stat(&mut self, s: Stat) -> StatId {
522        let id = self.push_stat(s);
523        let idx = id.0 as usize;
524        self.end_lines.resize(idx + 1, 0);
525        self.end_lines[idx] = self.prev_line;
526        id
527    }
528
529    // ---- blocks & statements ----
530
531    fn block_follow(&self) -> bool {
532        matches!(
533            self.tok.tok,
534            Token::Eof | Token::End | Token::Else | Token::Elseif | Token::Until
535        )
536    }
537
538    fn block(&mut self) -> Result<Block, SyntaxError> {
539        self.enter()?;
540        // PUC `leaveblock` restores `nactvar` to the count at block entry, so
541        // a block's locals fall out of scope when it ends. Snapshot the count
542        // here so the limit check tracks ACTIVE locals (locals.lua opens many
543        // short blocks; without this the cap fires spuriously).
544        let local_snapshot = self.func_local_count.last().expect("func ctx").0;
545        let locals_51_snap = self.snap_locals_51();
546        self.goto_step(|g| {
547            g.enter_block(false);
548            Ok(())
549        })?;
550        let mut stats = Vec::new();
551        loop {
552            // labels wait for the no-op statements that follow them
553            if self.gotos.as_ref().is_some_and(GotoCheck::has_open_labels)
554                && !matches!(self.tok.tok, Token::Semi | Token::DColon)
555            {
556                let last = matches!(
557                    self.tok.tok,
558                    Token::Else | Token::Elseif | Token::End | Token::Eof
559                );
560                self.goto_step(|g| g.finish_labels(last))?;
561            }
562            if self.block_follow() {
563                break;
564            }
565            if self.tok.tok == Token::Return {
566                stats.push(self.return_stat()?);
567                break;
568            }
569            if self.tok.tok == Token::Break && self.version.break_is_last_statement() {
570                let line = self.tok.line;
571                self.break_stat()?;
572                stats.push(self.push_stat(Stat::Break { line }));
573                self.accept(Token::Semi)?;
574                break;
575            }
576            if let Some(s) = self.statement()? {
577                stats.push(s);
578            }
579            if !self.version.has_empty_statement() {
580                // 5.1: ';' is a separator after a statement, not a statement
581                self.accept(Token::Semi)?;
582            }
583        }
584        self.goto_step(GotoCheck::leave_block)?;
585        self.leave();
586        self.func_local_count.last_mut().expect("func ctx").0 = local_snapshot;
587        self.restore_locals_51(locals_51_snap);
588        Ok(Block { stats })
589    }
590
591    fn return_stat(&mut self) -> Result<StatId, SyntaxError> {
592        let line = self.tok.line;
593        self.advance()?;
594        let exprs = if self.block_follow() || self.tok.tok == Token::Semi {
595            Vec::new()
596        } else {
597            self.exprlist()?
598        };
599        self.accept(Token::Semi)?;
600        Ok(self.push_stat(Stat::Return { exprs, line }))
601    }
602
603    fn statement(&mut self) -> Result<Option<StatId>, SyntaxError> {
604        // PUC's `statement` does not bump `nCcalls` itself — the surrounding
605        // `block` does, and nested forms (do/while/if/function/...) each
606        // recurse through `block` again. Counting both would double the cost
607        // per `do … end` nesting; errors.lua's `testrep("do ", "", " end")`
608        // expects 190 levels to compile and 201 to fail at the same wall as
609        // the other shapes.
610        let start_line = self.tok.line;
611        // 5.5 `global` is a contextual keyword: a declaration only when it
612        // leads a statement and the next token starts one (name / '*' /
613        // function / attribute '<'). Otherwise it is an ordinary identifier
614        // (e.g. `global = 1`, `global()`, `return global`).
615        if self.version.has_global_decl()
616            && matches!(&self.tok.tok, Token::Name(n) if &**n == "global")
617            && matches!(
618                self.peek()?,
619                Token::Name(_) | Token::Star | Token::Function | Token::Lt
620            )
621        {
622            let stat = self.global_stat()?;
623            self.set_stat_line(stat, start_line);
624            return Ok(Some(stat));
625        }
626        let stat = match self.tok.tok {
627            Token::Semi => {
628                if !self.version.has_empty_statement() {
629                    return Err(self.error("unexpected symbol"));
630                }
631                self.advance()?;
632                None
633            }
634            Token::If => Some(self.if_stat()?),
635            Token::While => Some(self.while_stat()?),
636            Token::Do => {
637                let line = self.tok.line;
638                self.advance()?;
639                let body = self.block()?;
640                self.expect_match(Token::End, "end", "do", line)?;
641                Some(self.push_stat(Stat::Do(body)))
642            }
643            Token::For => Some(self.for_stat()?),
644            Token::Repeat => Some(self.repeat_stat()?),
645            Token::Function => Some(self.function_stat()?),
646            Token::Local => Some(self.local_stat()?),
647            Token::DColon => {
648                self.advance()?;
649                let name = self.expect_name()?;
650                let text = name.text.clone();
651                self.goto_step(|g| g.label_before_close(&text, start_line))?;
652                self.expect(Token::DColon, "::")?;
653                Some(self.push_stat(Stat::Label(name)))
654            }
655            Token::Break => {
656                let line = self.tok.line;
657                self.break_stat()?;
658                Some(self.push_stat(Stat::Break { line }))
659            }
660            Token::Goto => {
661                // 5.4 reads the goto's line after skipping the keyword, 5.5
662                // takes the statement's
663                let mut line = self.lex.line();
664                self.advance()?;
665                if self.version >= LuaVersion::Lua55 {
666                    line = start_line;
667                } else if self.version >= LuaVersion::Lua54 {
668                    line = self.lex.line();
669                }
670                let name = self.expect_name()?;
671                let text = name.text.clone();
672                self.goto_step(|g| g.goto_stat(&text, line))?;
673                Some(self.push_stat(Stat::Goto(name)))
674            }
675            _ => Some(self.expr_stat()?),
676        };
677        if let Some(sid) = stat {
678            self.set_stat_line(sid, start_line);
679        }
680        Ok(stat)
681    }
682
683    fn set_stat_line(&mut self, sid: StatId, line: u32) {
684        let idx = sid.0 as usize;
685        if self.stat_lines.len() <= idx {
686            self.stat_lines.resize(idx + 1, 0);
687        }
688        self.stat_lines[idx] = line;
689    }
690
691    /// Consume `break`, checking it the way the dialect does: 5.1 and 5.5
692    /// on the spot (5.1 after skipping the keyword, 5.5 before); 5.2–5.4
693    /// treat it as a goto to the loop's end, so a break outside a loop is
694    /// an unresolved goto when the function closes.
695    fn break_stat(&mut self) -> Result<(), SyntaxError> {
696        let line = self.lex.line();
697        let in_loop = self.funcs.last().expect("func ctx").loops > 0;
698        if !in_loop && self.version >= LuaVersion::Lua55 {
699            return Err(self.error("break outside loop"));
700        }
701        self.advance()?;
702        if !in_loop && self.version <= LuaVersion::Lua51 {
703            return Err(self.error("no loop to break"));
704        }
705        if self.version >= LuaVersion::Lua55 {
706            return Ok(());
707        }
708        self.goto_step(|g| g.goto_stat("break", line))
709    }
710
711    /// A loop body with the loop's own variables (`vars`) in scope: PUC's
712    /// loop block, which places the "break" label, around a block for the
713    /// declared variables.
714    fn loop_block(&mut self, vars: &[Name]) -> Result<Block, SyntaxError> {
715        self.funcs.last_mut().expect("func ctx").loops += 1;
716        self.goto_step(|g| {
717            g.enter_block(true);
718            g.enter_block(false);
719            for v in vars {
720                g.declare(&v.text);
721            }
722            Ok(())
723        })?;
724        let body = self.block()?;
725        self.goto_step(|g| {
726            g.leave_block()?;
727            g.leave_block()
728        })?;
729        self.funcs.last_mut().expect("func ctx").loops -= 1;
730        Ok(body)
731    }
732
733    /// PUC `close_func` → `leaveblock` of the function's outer block, which
734    /// runs after the closing token has been consumed: a goto (or 5.2-5.4
735    /// `break`) that found no label is reported there, at the scanner's
736    /// line.
737    fn close_function(&mut self) -> Result<(), SyntaxError> {
738        let _ = self.funcs.pop().expect("func ctx");
739        self.goto_step(GotoCheck::leave_block)
740    }
741
742    /// Run a step of the goto check (dialects that have one), turning its
743    /// error into a syntax error without a near-token (PUC `semerror`),
744    /// which 5.5 reports at the line of the last token consumed.
745    fn goto_step(
746        &mut self,
747        step: impl FnOnce(&mut GotoCheck) -> Result<(), String>,
748    ) -> Result<(), SyntaxError> {
749        match self.gotos.as_mut().map(step) {
750            Some(Err(msg)) if self.version >= LuaVersion::Lua55 => Err(SyntaxError {
751                line: self.last_line,
752                msg: msg.into_bytes(),
753            }),
754            Some(Err(msg)) => Err(self.plain_error(msg)),
755            _ => Ok(()),
756        }
757    }
758
759    fn if_stat(&mut self) -> Result<StatId, SyntaxError> {
760        let line = self.tok.line;
761        self.advance()?;
762        let mut arms = Vec::new();
763        let cond = self.expr()?;
764        let then_line = self.tok.line;
765        self.expect(Token::Then, "then")?;
766        arms.push((cond, then_line, self.block()?));
767        while self.tok.tok == Token::Elseif {
768            self.advance()?;
769            let cond = self.expr()?;
770            let then_line = self.tok.line;
771            self.expect(Token::Then, "then")?;
772            arms.push((cond, then_line, self.block()?));
773        }
774        let else_body = if self.accept(Token::Else)? {
775            Some(self.block()?)
776        } else {
777            None
778        };
779        self.expect_match(Token::End, "end", "if", line)?;
780        Ok(self.push_stat(Stat::If { arms, else_body }))
781    }
782
783    fn while_stat(&mut self) -> Result<StatId, SyntaxError> {
784        let line = self.tok.line;
785        self.advance()?;
786        let cond = self.expr()?;
787        self.expect(Token::Do, "do")?;
788        let body = self.loop_block(&[])?;
789        self.expect_match(Token::End, "end", "while", line)?;
790        Ok(self.push_ended_stat(Stat::While { cond, body }))
791    }
792
793    fn repeat_stat(&mut self) -> Result<StatId, SyntaxError> {
794        let line = self.tok.line;
795        self.advance()?;
796        let body = self.loop_block(&[])?;
797        self.expect_match(Token::Until, "until", "repeat", line)?;
798        let cond = self.expr()?;
799        Ok(self.push_stat(Stat::Repeat { body, cond }))
800    }
801
802    fn for_stat(&mut self) -> Result<StatId, SyntaxError> {
803        let line = self.tok.line;
804        self.advance()?;
805        let first = self.expect_name()?;
806        match self.tok.tok {
807            Token::Assign => {
808                self.advance()?;
809                let start = self.expr()?;
810                self.expect(Token::Comma, ",")?;
811                let limit = self.expr()?;
812                let step = if self.accept(Token::Comma)? {
813                    Some(self.expr()?)
814                } else {
815                    None
816                };
817                self.expect(Token::Do, "do")?;
818                self.add_local_51(&first.text);
819                let body = self.loop_block(std::slice::from_ref(&first))?;
820                self.expect_match(Token::End, "end", "for", line)?;
821                Ok(self.push_ended_stat(Stat::NumericFor {
822                    var: first,
823                    start,
824                    limit,
825                    step,
826                    body,
827                }))
828            }
829            Token::Comma | Token::In => {
830                let mut vars = vec![first];
831                while self.accept(Token::Comma)? {
832                    vars.push(self.expect_name()?);
833                }
834                self.expect(Token::In, "in")?;
835                let expr_line = self.tok.line;
836                let exprs = self.exprlist()?;
837                self.expect(Token::Do, "do")?;
838                for v in &vars {
839                    self.add_local_51(&v.text);
840                }
841                let body = self.loop_block(&vars)?;
842                self.expect_match(Token::End, "end", "for", line)?;
843                Ok(self.push_ended_stat(Stat::GenericFor {
844                    vars,
845                    exprs,
846                    body,
847                    expr_line,
848                }))
849            }
850            _ => Err(self.error("'=' or 'in' expected")),
851        }
852    }
853
854    fn function_stat(&mut self) -> Result<StatId, SyntaxError> {
855        let line = self.tok.line;
856        self.advance()?;
857        let base = self.expect_name()?;
858        let mut path = Vec::new();
859        while self.accept(Token::Dot)? {
860            path.push(self.expect_name()?);
861        }
862        let method = if self.accept(Token::Colon)? {
863            Some(self.expect_name()?)
864        } else {
865            None
866        };
867        let body = self.func_body(line)?;
868        Ok(self.push_stat(Stat::Function {
869            name: FuncName { base, path, method },
870            body,
871        }))
872    }
873
874    fn attrib(&mut self) -> Result<Option<Attrib>, SyntaxError> {
875        if !(self.version.has_attribs() && self.tok.tok == Token::Lt) {
876            return Ok(None);
877        }
878        self.advance()?;
879        let name = self.expect_name()?;
880        let attrib = match &*name.text {
881            "const" => Attrib::Const,
882            "close" => Attrib::Close,
883            other => {
884                return Err(SyntaxError {
885                    line: name.line,
886                    msg: format!("unknown attribute '{other}'").into_bytes(),
887                });
888            }
889        };
890        self.expect(Token::Gt, ">")?;
891        Ok(Some(attrib))
892    }
893
894    /// `[attrib] Name [attrib] {',' Name [attrib]} ['=' explist]` — shared by
895    /// `local` and `global` declarations.
896    fn attnamelist(&mut self) -> Result<DeclList, SyntaxError> {
897        let collective = if self.version.has_collective_attrib() {
898            self.attrib()?
899        } else {
900            None
901        };
902        let mut names = Vec::new();
903        loop {
904            let name = self.expect_name()?;
905            self.new_local()?;
906            let attrib = self.attrib()?;
907            names.push(AttribName { name, attrib });
908            if !self.accept(Token::Comma)? {
909                break;
910            }
911        }
912        let exprs = if self.accept(Token::Assign)? {
913            self.exprlist()?
914        } else {
915            Vec::new()
916        };
917        Ok((collective, names, exprs))
918    }
919
920    fn local_stat(&mut self) -> Result<StatId, SyntaxError> {
921        self.advance()?;
922        if self.accept(Token::Function)? {
923            let line = self.prev_line;
924            let name = self.expect_name()?;
925            // `local function f` declares `f` in the enclosing function before
926            // the body is parsed (PUC `localfunc`'s pre-declare); count it.
927            self.new_local()?;
928            self.activate_locals()?;
929            self.add_local_51(&name.text);
930            let text = name.text.clone();
931            self.goto_step(|g| {
932                g.declare(&text);
933                Ok(())
934            })?;
935            let body = self.func_body(line)?;
936            return Ok(self.push_stat(Stat::LocalFunction { name, body }));
937        }
938        let (collective, names, exprs) = self.attnamelist()?;
939        self.activate_locals()?;
940        self.goto_step(|g| {
941            for an in &names {
942                g.declare(&an.name.text);
943            }
944            Ok(())
945        })?;
946        for an in &names {
947            self.add_local_51(&an.name.text);
948        }
949        Ok(self.push_stat(Stat::Local {
950            collective,
951            names,
952            exprs,
953        }))
954    }
955
956    fn global_stat(&mut self) -> Result<StatId, SyntaxError> {
957        self.advance()?;
958        if self.accept(Token::Function)? {
959            let line = self.prev_line;
960            let name = self.expect_name()?;
961            let text = name.text.clone();
962            self.goto_step(|g| {
963                g.declare(&text);
964                Ok(())
965            })?;
966            let body = self.func_body(line)?;
967            return Ok(self.push_stat(Stat::GlobalFunction { name, body }));
968        }
969        // `global [attrib] '*'`
970        let leading = self.attrib()?;
971        if self.accept(Token::Star)? {
972            self.goto_step(|g| {
973                g.declare("*");
974                Ok(())
975            })?;
976            return Ok(self.push_stat(Stat::GlobalAll { attrib: leading }));
977        }
978        let mut names = Vec::new();
979        loop {
980            let name = self.expect_name()?;
981            let attrib = self.attrib()?;
982            names.push(AttribName { name, attrib });
983            if !self.accept(Token::Comma)? {
984                break;
985            }
986        }
987        let exprs = if self.accept(Token::Assign)? {
988            self.exprlist()?
989        } else {
990            Vec::new()
991        };
992        // the declared names come into scope after their initializers
993        self.goto_step(|g| {
994            for an in &names {
995                g.declare(&an.name.text);
996            }
997            Ok(())
998        })?;
999        Ok(self.push_stat(Stat::Global {
1000            collective: leading,
1001            names,
1002            exprs,
1003        }))
1004    }
1005
1006    fn expr_stat(&mut self) -> Result<StatId, SyntaxError> {
1007        let first = self.suffixed_expr()?;
1008        let is_call = matches!(
1009            self.exprs[first.0 as usize],
1010            Expr::Call { .. } | Expr::MethodCall { .. }
1011        );
1012        // 5.1 `exprstat` takes anything that is not a call as the start of
1013        // an assignment (so a lone `x` wants an '='); 5.2+ look for '=' or
1014        // ',' first and otherwise demand a call.
1015        let assign = if self.version <= LuaVersion::Lua51 {
1016            !is_call
1017        } else {
1018            matches!(self.tok.tok, Token::Assign | Token::Comma)
1019        };
1020        if !assign {
1021            if !is_call {
1022                return Err(self.error("syntax error"));
1023            }
1024            return Ok(self.push_stat(Stat::Call(first)));
1025        }
1026        // PUC `assignment`/`restassign` check each target as soon as it is
1027        // parsed, so the near-token is the one following that target.
1028        let mut targets = vec![first];
1029        let mut entered = 0;
1030        loop {
1031            let last = *targets.last().expect("one target");
1032            if !matches!(
1033                self.exprs[last.0 as usize],
1034                Expr::Name(_) | Expr::Index { .. }
1035            ) {
1036                return Err(self.error("syntax error"));
1037            }
1038            if !self.accept(Token::Comma)? {
1039                break;
1040            }
1041            // PUC recurses once per extra target and bounds that against
1042            // the C-call budget (errors.lua :650 expects the error for 500
1043            // targets), after reading the target: 5.1 as a count of
1044            // "variables in assignment", 5.2/5.3 as C levels, 5.4+ by
1045            // entering a level that stays entered until the statement ends.
1046            let nvars = targets.len() as u32;
1047            targets.push(self.suffixed_expr()?);
1048            match self.version {
1049                LuaVersion::Lua51 => {
1050                    let limit = MAX_DEPTH.saturating_sub(self.depth);
1051                    if nvars > limit {
1052                        return Err(self.plain_error(format!(
1053                            "{} has more than {limit} variables in assignment",
1054                            self.where_()
1055                        )));
1056                    }
1057                }
1058                LuaVersion::Lua52 | LuaVersion::Lua53 => {
1059                    if nvars + self.depth > MAX_DEPTH {
1060                        return Err(self.levels_error());
1061                    }
1062                }
1063                _ => {
1064                    self.enter()?;
1065                    entered += 1;
1066                }
1067            }
1068        }
1069        self.expect(Token::Assign, "=")?;
1070        let exprs = self.exprlist()?;
1071        self.depth -= entered;
1072        Ok(self.push_stat(Stat::Assign { targets, exprs }))
1073    }
1074
1075    // ---- expressions ----
1076
1077    fn exprlist(&mut self) -> Result<Vec<ExprId>, SyntaxError> {
1078        let mut list = vec![self.expr()?];
1079        while self.accept(Token::Comma)? {
1080            list.push(self.expr()?);
1081        }
1082        Ok(list)
1083    }
1084
1085    fn expr(&mut self) -> Result<ExprId, SyntaxError> {
1086        self.sub_expr(0)
1087    }
1088
1089    fn sub_expr(&mut self, limit: u8) -> Result<ExprId, SyntaxError> {
1090        self.enter()?;
1091        let mut left = if let Some(op) = un_op_of(&self.tok.tok) {
1092            let line = self.tok.line;
1093            self.advance()?;
1094            let operand = self.sub_expr(UNARY_PRIORITY)?;
1095            self.push_expr(Expr::UnOp { op, operand, line })
1096        } else {
1097            self.simple_expr()?
1098        };
1099        while let Some(op) = bin_op_of(&self.tok.tok) {
1100            let (lp, rp) = bin_priority(op);
1101            if lp <= limit {
1102                break;
1103            }
1104            let line = self.tok.line;
1105            self.advance()?;
1106            let rhs = self.sub_expr(rp)?;
1107            left = self.push_expr(Expr::BinOp {
1108                op,
1109                lhs: left,
1110                rhs,
1111                line,
1112            });
1113        }
1114        self.leave();
1115        Ok(left)
1116    }
1117
1118    fn simple_expr(&mut self) -> Result<ExprId, SyntaxError> {
1119        let e = match &self.tok.tok {
1120            Token::Nil => {
1121                self.advance()?;
1122                Expr::Nil
1123            }
1124            Token::True => {
1125                self.advance()?;
1126                Expr::True
1127            }
1128            Token::False => {
1129                self.advance()?;
1130                Expr::False
1131            }
1132            Token::Ellipsis => {
1133                if !self.funcs.last().expect("func ctx").vararg {
1134                    return Err(self.error("cannot use '...' outside a vararg function"));
1135                }
1136                self.advance()?;
1137                Expr::Vararg
1138            }
1139            Token::Int(_) => {
1140                let Token::Int(v) = self.advance()?.tok else {
1141                    unreachable!()
1142                };
1143                Expr::Int(v)
1144            }
1145            Token::Float(_) => {
1146                let Token::Float(v) = self.advance()?.tok else {
1147                    unreachable!()
1148                };
1149                Expr::Float(v)
1150            }
1151            Token::Str(_) => {
1152                let Token::Str(s) = self.advance()?.tok else {
1153                    unreachable!()
1154                };
1155                Expr::Str(s)
1156            }
1157            Token::LBrace => return self.table_constructor(),
1158            Token::Function => {
1159                let line = self.tok.line;
1160                self.advance()?;
1161                Expr::Function(self.func_body(line)?)
1162            }
1163            _ => return self.suffixed_expr(),
1164        };
1165        Ok(self.push_expr(e))
1166    }
1167
1168    fn primary_expr(&mut self) -> Result<ExprId, SyntaxError> {
1169        match &self.tok.tok {
1170            Token::Name(_) => {
1171                let name = self.expect_name()?;
1172                self.ident_lookup_51(&name.text)?;
1173                Ok(self.push_expr(Expr::Name(name)))
1174            }
1175            Token::LParen => {
1176                let line = self.tok.line;
1177                self.advance()?;
1178                let inner = self.expr()?;
1179                self.expect_match(Token::RParen, ")", "(", line)?;
1180                Ok(self.push_expr(Expr::Paren(inner)))
1181            }
1182            _ => Err(self.error("unexpected symbol")),
1183        }
1184    }
1185
1186    fn suffixed_expr(&mut self) -> Result<ExprId, SyntaxError> {
1187        // PUC's `suffixedexp` does *not* bump `nCcalls` on its own — only its
1188        // callers do (subexpr, funcargs, …). Doing so here too would
1189        // double-count `(` nesting, since `simpleexp`'s default branch dives
1190        // through `suffixed_expr` → `primary_expr` → `expr()` → `sub_expr`
1191        // (which already enters). Keeping the entry out lets errors.lua's
1192        // `testrep("(")` (paren-only nesting) hit the same 200-level wall as
1193        // `{`/`,` nesting.
1194        // PUC 5.1–5.3 `suffixedexp` captured the line of the *primary*
1195        // expression once and pinned every chained call/method to it
1196        // (`a\n(...)` → error reports on `a`'s line); 5.4 switched to
1197        // tracking the current line at each suffix and reports on the `(`
1198        // line instead. errors.lua's `lineerror` covers both:
1199        //   - 5.3:  `lineerror([[a\n(\n23)]], 1)` — expects `a`'s line
1200        //   - 5.4+: `lineerror([[a\n(...)\n23)]], 2)` — expects `(`'s line
1201        let primary_line = self.tok.line;
1202        let mut e = self.primary_expr()?;
1203        loop {
1204            match &self.tok.tok {
1205                Token::Dot => {
1206                    self.advance()?;
1207                    let name = self.expect_name()?;
1208                    let key = self.push_expr(Expr::Str(name.text.into_boxed_bytes().into_vec()));
1209                    e = self.push_expr(Expr::Index { obj: e, key });
1210                }
1211                Token::LBracket => {
1212                    self.advance()?;
1213                    let key = self.expr()?;
1214                    self.expect(Token::RBracket, "]")?;
1215                    e = self.push_expr(Expr::Index { obj: e, key });
1216                }
1217                Token::Colon => {
1218                    self.advance()?;
1219                    let method = self.expect_name()?;
1220                    let line = if self.version <= LuaVersion::Lua53 {
1221                        primary_line
1222                    } else {
1223                        self.tok.line
1224                    };
1225                    let args = self.call_args()?;
1226                    e = self.push_expr(Expr::MethodCall {
1227                        obj: e,
1228                        method,
1229                        args,
1230                        line,
1231                    });
1232                }
1233                Token::LParen | Token::Str(_) | Token::LBrace => {
1234                    let line = if self.version <= LuaVersion::Lua53 {
1235                        primary_line
1236                    } else {
1237                        self.tok.line
1238                    };
1239                    let args = self.call_args()?;
1240                    e = self.push_expr(Expr::Call {
1241                        func: e,
1242                        args,
1243                        line,
1244                    });
1245                }
1246                _ => break,
1247            }
1248        }
1249        Ok(e)
1250    }
1251
1252    fn call_args(&mut self) -> Result<Vec<ExprId>, SyntaxError> {
1253        match &self.tok.tok {
1254            Token::LParen => {
1255                // 5.1 rejects a call paren on a new line (removed in 5.2)
1256                if self.version == LuaVersion::Lua51 && self.tok.line != self.prev_line {
1257                    return Err(self.error("ambiguous syntax (function call x new statement)"));
1258                }
1259                let line = self.tok.line;
1260                self.advance()?;
1261                let args = if self.tok.tok == Token::RParen {
1262                    Vec::new()
1263                } else {
1264                    self.exprlist()?
1265                };
1266                self.expect_match(Token::RParen, ")", "(", line)?;
1267                Ok(args)
1268            }
1269            Token::Str(_) => {
1270                let Token::Str(s) = self.advance()?.tok else {
1271                    unreachable!()
1272                };
1273                Ok(vec![self.push_expr(Expr::Str(s))])
1274            }
1275            Token::LBrace => Ok(vec![self.table_constructor()?]),
1276            _ => Err(self.error("function arguments expected")),
1277        }
1278    }
1279
1280    fn table_constructor(&mut self) -> Result<ExprId, SyntaxError> {
1281        let line = self.tok.line;
1282        self.expect(Token::LBrace, "{")?;
1283        let mut fields = Vec::new();
1284        loop {
1285            if self.tok.tok == Token::RBrace {
1286                break;
1287            }
1288            if self.tok.tok == Token::LBracket {
1289                self.advance()?;
1290                let key = self.expr()?;
1291                self.expect(Token::RBracket, "]")?;
1292                self.expect(Token::Assign, "=")?;
1293                let value = self.expr()?;
1294                fields.push(TableField::Keyed(key, value));
1295            } else if matches!(self.tok.tok, Token::Name(_)) && *self.peek()? == Token::Assign {
1296                let name = self.expect_name()?;
1297                self.advance()?; // '='
1298                let value = self.expr()?;
1299                fields.push(TableField::Named(name, value));
1300            } else {
1301                fields.push(TableField::Item(self.expr()?));
1302            }
1303            if !(self.accept(Token::Comma)? || self.accept(Token::Semi)?) {
1304                break;
1305            }
1306        }
1307        self.expect_match(Token::RBrace, "}", "{", line)?;
1308        Ok(self.push_expr(Expr::Table { fields, line }))
1309    }
1310
1311    // ---- functions ----
1312
1313    fn func_body(&mut self, line: u32) -> Result<FuncBody, SyntaxError> {
1314        self.expect(Token::LParen, "(")?;
1315        self.func_local_count.push((0, line, 0));
1316        self.enter_fn_51(line);
1317        let mut params = Vec::new();
1318        let mut vararg = Vararg::None;
1319        if self.tok.tok != Token::RParen {
1320            loop {
1321                match &self.tok.tok {
1322                    Token::Ellipsis => {
1323                        self.advance()?;
1324                        vararg = if self.version.has_named_vararg()
1325                            && matches!(self.tok.tok, Token::Name(_))
1326                        {
1327                            Vararg::Named(self.expect_name()?)
1328                        } else {
1329                            Vararg::Anonymous
1330                        };
1331                        if let Vararg::Named(ref n) = vararg {
1332                            self.new_local()?;
1333                            self.add_local_51(&n.text);
1334                        }
1335                        break;
1336                    }
1337                    Token::Name(_) => {
1338                        let p = self.expect_name()?;
1339                        self.new_local()?;
1340                        self.add_local_51(&p.text);
1341                        params.push(p);
1342                    }
1343                    _ => return Err(self.error("<name> or '...' expected")),
1344                }
1345                if !self.accept(Token::Comma)? {
1346                    break;
1347                }
1348            }
1349        }
1350        self.activate_locals()?;
1351        self.goto_step(|g| {
1352            g.enter_function();
1353            for p in &params {
1354                g.declare(&p.text);
1355            }
1356            Ok(())
1357        })?;
1358        self.expect(Token::RParen, ")")?;
1359        self.funcs.push(FnFlow {
1360            vararg: !matches!(vararg, Vararg::None),
1361            loops: 0,
1362        });
1363        let block = self.block()?;
1364        let end_line = self.tok.line; // the `end` token's line, before consuming
1365        self.expect_match(Token::End, "end", "function", line)?;
1366        self.close_function()?;
1367        self.func_local_count.pop();
1368        self.leave_fn_51();
1369        Ok(FuncBody {
1370            params,
1371            vararg,
1372            block,
1373            line,
1374            end_line,
1375        })
1376    }
1377
1378    fn track_uv_51(&self) -> bool {
1379        !self.upval_chain_51.is_empty()
1380    }
1381
1382    fn add_local_51(&mut self, name: &str) {
1383        if self.track_uv_51() {
1384            self.upval_chain_51
1385                .last_mut()
1386                .expect("fn ctx")
1387                .locals
1388                .push(name.into());
1389        }
1390    }
1391
1392    fn snap_locals_51(&self) -> usize {
1393        if self.track_uv_51() {
1394            self.upval_chain_51.last().expect("fn ctx").locals.len()
1395        } else {
1396            0
1397        }
1398    }
1399
1400    fn restore_locals_51(&mut self, snap: usize) {
1401        if self.track_uv_51() {
1402            self.upval_chain_51
1403                .last_mut()
1404                .expect("fn ctx")
1405                .locals
1406                .truncate(snap);
1407        }
1408    }
1409
1410    fn enter_fn_51(&mut self, line_defined: u32) {
1411        if self.track_uv_51() {
1412            self.upval_chain_51.push(FnUvSlot {
1413                line_defined,
1414                ..Default::default()
1415            });
1416        }
1417    }
1418
1419    fn leave_fn_51(&mut self) {
1420        if self.track_uv_51() {
1421            self.upval_chain_51.pop();
1422        }
1423    }
1424
1425    /// PUC 5.1 `singlevaraux`-equivalent: resolve `name` against the current
1426    /// nested-function stack of declared locals, accumulating an upvalue entry
1427    /// in every intermediate function between the referencing site and the
1428    /// owning scope. Returns PUC 5.1's "has more than 60 upvalues" error the
1429    /// moment a link's upvalue set crosses 60. No-op for non-5.1 dialects.
1430    fn ident_lookup_51(&mut self, name: &str) -> Result<(), SyntaxError> {
1431        if !self.track_uv_51() {
1432            return Ok(());
1433        }
1434        const MAXUPVAL: usize = 60;
1435        let n = self.upval_chain_51.len();
1436        let mut owner: Option<usize> = None;
1437        for k in (0..n).rev() {
1438            if self.upval_chain_51[k]
1439                .locals
1440                .iter()
1441                .any(|s| s.as_ref() == name)
1442            {
1443                owner = Some(k);
1444                break;
1445            }
1446        }
1447        let Some(owner_idx) = owner else {
1448            return Ok(());
1449        };
1450        if owner_idx + 1 == n {
1451            return Ok(());
1452        }
1453        for k in (owner_idx + 1)..n {
1454            let inserted = self.upval_chain_51[k].upvalues.insert(name.into());
1455            if inserted && self.upval_chain_51[k].upvalues.len() > MAXUPVAL {
1456                let line_defined = self.upval_chain_51[k].line_defined;
1457                let where_ = if k == 0 {
1458                    "main function".to_string()
1459                } else {
1460                    format!("function at line {line_defined}")
1461                };
1462                // 5.1 `errorlimit`: "<where> has more than <limit> <what>"
1463                return Err(SyntaxError {
1464                    line: self.tok.line,
1465                    msg: format!("{where_} has more than {MAXUPVAL} upvalues").into_bytes(),
1466                });
1467            }
1468        }
1469        Ok(())
1470    }
1471
1472    /// PUC `new_localvar`, right after a local's name is read: up to 5.4
1473    /// the local cap counts declared-but-pending names too, and the error
1474    /// points at the token after the name (5.1 words it differently and
1475    /// shows no token).
1476    fn new_local(&mut self) -> Result<(), SyntaxError> {
1477        let &(active, line_defined, pending) = self.func_local_count.last().expect("func ctx");
1478        if self.version <= LuaVersion::Lua54 && active + pending + 1 > MAXVARS {
1479            return Err(if self.version <= LuaVersion::Lua51 {
1480                let what = if self.func_local_count.len() == 1 {
1481                    "main function".to_string()
1482                } else {
1483                    format!("function at line {line_defined}")
1484                };
1485                self.plain_error(format!("{what} has more than {MAXVARS} local variables"))
1486            } else {
1487                self.local_limit_error()
1488            });
1489        }
1490        self.func_local_count.last_mut().expect("func ctx").2 += 1;
1491        Ok(())
1492    }
1493
1494    /// PUC `adjustlocalvars`: the pending locals come into scope. 5.5 checks
1495    /// the cap here instead of at declaration.
1496    fn activate_locals(&mut self) -> Result<(), SyntaxError> {
1497        let (active, _, pending) = *self.func_local_count.last().expect("func ctx");
1498        if self.version >= LuaVersion::Lua55 && active + pending > MAXVARS {
1499            return Err(self.local_limit_error());
1500        }
1501        let slot = self.func_local_count.last_mut().expect("func ctx");
1502        slot.0 += pending;
1503        slot.2 = 0;
1504        Ok(())
1505    }
1506
1507    fn local_limit_error(&self) -> SyntaxError {
1508        self.error(format!(
1509            "too many local variables (limit is {MAXVARS}) in {}",
1510            self.where_()
1511        ))
1512    }
1513}