use crate::frontend::ast::*;
use crate::frontend::error::SyntaxError;
use crate::frontend::lexer::Lexer;
use crate::frontend::span::Span;
use crate::frontend::token::{Token, TokenInfo};
use crate::version::LuaVersion;
const MAX_DEPTH: u32 = 200;
type DeclList = (Option<Attrib>, Vec<AttribName>, Vec<ExprId>);
fn bin_priority(op: BinOp) -> (u8, u8) {
match op {
BinOp::Or => (1, 1),
BinOp::And => (2, 2),
BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge | BinOp::Ne | BinOp::Eq => (3, 3),
BinOp::BOr => (4, 4),
BinOp::BXor => (5, 5),
BinOp::BAnd => (6, 6),
BinOp::Shl | BinOp::Shr => (7, 7),
BinOp::Concat => (9, 8),
BinOp::Add | BinOp::Sub => (10, 10),
BinOp::Mul | BinOp::Div | BinOp::IDiv | BinOp::Mod => (11, 11),
BinOp::Pow => (14, 13),
}
}
const UNARY_PRIORITY: u8 = 12;
fn bin_op_of(tok: &Token) -> Option<BinOp> {
Some(match tok {
Token::Plus => BinOp::Add,
Token::Minus => BinOp::Sub,
Token::Star => BinOp::Mul,
Token::Slash => BinOp::Div,
Token::DSlash => BinOp::IDiv,
Token::Percent => BinOp::Mod,
Token::Caret => BinOp::Pow,
Token::Concat => BinOp::Concat,
Token::Eq => BinOp::Eq,
Token::Ne => BinOp::Ne,
Token::Lt => BinOp::Lt,
Token::Le => BinOp::Le,
Token::Gt => BinOp::Gt,
Token::Ge => BinOp::Ge,
Token::And => BinOp::And,
Token::Or => BinOp::Or,
Token::Amp => BinOp::BAnd,
Token::Pipe => BinOp::BOr,
Token::Tilde => BinOp::BXor,
Token::Shl => BinOp::Shl,
Token::Shr => BinOp::Shr,
_ => return None,
})
}
fn un_op_of(tok: &Token) -> Option<UnOp> {
Some(match tok {
Token::Minus => UnOp::Neg,
Token::Not => UnOp::Not,
Token::Hash => UnOp::Len,
Token::Tilde => UnOp::BNot,
_ => return None,
})
}
pub(crate) enum TokenSource<'s> {
Lexer(Lexer<'s>),
PreExpanded {
tokens: Vec<TokenInfo>,
cursor: usize,
src: &'s [u8],
},
}
impl<'s> TokenSource<'s> {
fn next_token(&mut self) -> Result<TokenInfo, SyntaxError> {
match self {
TokenSource::Lexer(l) => l.next_token(),
TokenSource::PreExpanded {
tokens,
cursor,
src,
} => {
if *cursor >= tokens.len() {
let line = tokens.last().map(|t| t.line).unwrap_or(1);
let _ = src;
Ok(TokenInfo {
tok: Token::Eof,
span: Span::new(0, 0),
line,
})
} else {
let t = tokens[*cursor].clone();
*cursor += 1;
Ok(t)
}
}
}
}
fn src(&self) -> &'s [u8] {
match self {
TokenSource::Lexer(l) => l.src(),
TokenSource::PreExpanded { src, .. } => src,
}
}
}
pub fn parse(src: &[u8], version: LuaVersion) -> Result<Chunk, SyntaxError> {
let lex = Lexer::new(src, version);
parse_from_source(TokenSource::Lexer(lex), version)
}
pub fn parse_tokens(
tokens: Vec<TokenInfo>,
src: &[u8],
version: LuaVersion,
) -> Result<Chunk, SyntaxError> {
parse_from_source(
TokenSource::PreExpanded {
tokens,
cursor: 0,
src,
},
version,
)
}
fn parse_from_source<'s>(
mut lex: TokenSource<'s>,
version: LuaVersion,
) -> Result<Chunk, SyntaxError> {
let tok = lex.next_token()?;
let mut p = Parser {
lex,
tok,
peeked: None,
prev_line: 1,
exprs: Vec::new(),
stats: Vec::new(),
stat_lines: Vec::new(),
depth: 0,
version,
func_local_count: vec![(0, 0)],
upval_chain_51: if version <= LuaVersion::Lua51 {
vec![FnUvSlot {
line_defined: 0,
..Default::default()
}]
} else {
Vec::new()
},
};
let block = p.block()?;
if p.tok.tok != Token::Eof {
return Err(p.error("'<eof>' expected"));
}
let end_line = p.prev_line;
Ok(Chunk {
exprs: p.exprs,
stats: p.stats,
stat_lines: p.stat_lines,
block,
end_line,
})
}
struct Parser<'s> {
lex: TokenSource<'s>,
tok: TokenInfo,
peeked: Option<TokenInfo>,
prev_line: u32,
exprs: Vec<Expr>,
stats: Vec<Stat>,
stat_lines: Vec<u32>,
depth: u32,
version: LuaVersion,
func_local_count: Vec<(u32, u32)>,
upval_chain_51: Vec<FnUvSlot>,
}
#[derive(Default)]
struct FnUvSlot {
locals: Vec<Box<str>>,
upvalues: std::collections::HashSet<Box<str>>,
line_defined: u32,
}
impl<'s> Parser<'s> {
fn advance(&mut self) -> Result<TokenInfo, SyntaxError> {
let next = match self.peeked.take() {
Some(t) => t,
None => self.lex.next_token()?,
};
self.prev_line = self.tok.line;
Ok(std::mem::replace(&mut self.tok, next))
}
fn peek(&mut self) -> Result<&Token, SyntaxError> {
if self.peeked.is_none() {
self.peeked = Some(self.lex.next_token()?);
}
Ok(&self.peeked.as_ref().unwrap().tok)
}
fn near(&self) -> String {
self.tok
.tok
.describe(self.lex.src(), self.tok.span, self.version)
}
fn error(&self, msg: impl AsRef<str>) -> SyntaxError {
let mut bytes = msg.as_ref().as_bytes().to_vec();
bytes.extend_from_slice(b" near ");
bytes.extend_from_slice(self.near().as_bytes());
SyntaxError {
line: self.tok.line,
msg: bytes,
}
}
fn accept(&mut self, tok: Token) -> Result<bool, SyntaxError> {
if self.tok.tok == tok {
self.advance()?;
Ok(true)
} else {
Ok(false)
}
}
fn expect(&mut self, tok: Token, what: &str) -> Result<(), SyntaxError> {
if !self.accept(tok)? {
return Err(self.error(format!("'{what}' expected")));
}
Ok(())
}
fn expect_match(
&mut self,
tok: Token,
what: &str,
who: &str,
who_line: u32,
) -> Result<(), SyntaxError> {
if !self.accept(tok)? {
if who_line == self.tok.line {
return Err(self.error(format!("'{what}' expected")));
}
return Err(self.error(format!(
"'{what}' expected (to close '{who}' at line {who_line})"
)));
}
Ok(())
}
fn expect_name(&mut self) -> Result<Name, SyntaxError> {
if !matches!(self.tok.tok, Token::Name(_)) {
return Err(self.error("<name> expected"));
}
let info = self.advance()?;
let Token::Name(text) = info.tok else {
unreachable!()
};
Ok(Name {
text,
line: info.line,
})
}
fn enter(&mut self) -> Result<(), SyntaxError> {
self.depth += 1;
if self.depth > MAX_DEPTH {
let msg: &[u8] = if self.version <= LuaVersion::Lua51 {
b"chunk has too many syntax levels"
} else {
b"too many C levels (limit is 200) in main function"
};
return Err(SyntaxError {
line: self.tok.line,
msg: msg.to_vec(),
});
}
Ok(())
}
fn leave(&mut self) {
self.depth -= 1;
}
fn push_expr(&mut self, e: Expr) -> ExprId {
self.exprs.push(e);
ExprId((self.exprs.len() - 1) as u32)
}
fn push_stat(&mut self, s: Stat) -> StatId {
self.stats.push(s);
StatId((self.stats.len() - 1) as u32)
}
fn block_follow(&self) -> bool {
matches!(
self.tok.tok,
Token::Eof | Token::End | Token::Else | Token::Elseif | Token::Until
)
}
fn block(&mut self) -> Result<Block, SyntaxError> {
self.enter()?;
let local_snapshot = self.func_local_count.last().expect("func ctx").0;
let locals_51_snap = self.snap_locals_51();
let mut stats = Vec::new();
loop {
if self.block_follow() {
break;
}
if self.tok.tok == Token::Return {
stats.push(self.return_stat()?);
break;
}
if self.tok.tok == Token::Break && self.version.break_is_last_statement() {
let line = self.tok.line;
self.advance()?;
stats.push(self.push_stat(Stat::Break { line }));
self.accept(Token::Semi)?;
break;
}
if let Some(s) = self.statement()? {
stats.push(s);
}
if !self.version.has_empty_statement() {
self.accept(Token::Semi)?;
}
}
self.leave();
self.func_local_count.last_mut().expect("func ctx").0 = local_snapshot;
self.restore_locals_51(locals_51_snap);
Ok(Block { stats })
}
fn return_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let exprs = if self.block_follow() || self.tok.tok == Token::Semi {
Vec::new()
} else {
self.exprlist()?
};
self.accept(Token::Semi)?;
Ok(self.push_stat(Stat::Return { exprs, line }))
}
fn statement(&mut self) -> Result<Option<StatId>, SyntaxError> {
let start_line = self.tok.line;
if self.version.has_global_decl()
&& matches!(&self.tok.tok, Token::Name(n) if &**n == "global")
&& matches!(
self.peek()?,
Token::Name(_) | Token::Star | Token::Function | Token::Lt
)
{
let stat = self.global_stat()?;
return Ok(Some(stat));
}
let stat = match self.tok.tok {
Token::Semi => {
if !self.version.has_empty_statement() {
return Err(self.error("unexpected symbol"));
}
self.advance()?;
None
}
Token::If => Some(self.if_stat()?),
Token::While => Some(self.while_stat()?),
Token::Do => {
let line = self.tok.line;
self.advance()?;
let body = self.block()?;
self.expect_match(Token::End, "end", "do", line)?;
Some(self.push_stat(Stat::Do(body)))
}
Token::For => Some(self.for_stat()?),
Token::Repeat => Some(self.repeat_stat()?),
Token::Function => Some(self.function_stat()?),
Token::Local => Some(self.local_stat()?),
Token::DColon => {
self.advance()?;
let name = self.expect_name()?;
self.expect(Token::DColon, "::")?;
Some(self.push_stat(Stat::Label(name)))
}
Token::Break => {
let line = self.tok.line;
self.advance()?;
Some(self.push_stat(Stat::Break { line }))
}
Token::Goto => {
self.advance()?;
let name = self.expect_name()?;
Some(self.push_stat(Stat::Goto(name)))
}
_ => Some(self.expr_stat()?),
};
if let Some(sid) = stat {
let idx = sid.0 as usize;
if self.stat_lines.len() <= idx {
self.stat_lines.resize(idx + 1, 0);
}
self.stat_lines[idx] = start_line;
}
Ok(stat)
}
fn if_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let mut arms = Vec::new();
let cond = self.expr()?;
let then_line = self.tok.line;
self.expect(Token::Then, "then")?;
arms.push((cond, then_line, self.block()?));
while self.tok.tok == Token::Elseif {
self.advance()?;
let cond = self.expr()?;
let then_line = self.tok.line;
self.expect(Token::Then, "then")?;
arms.push((cond, then_line, self.block()?));
}
let else_body = if self.accept(Token::Else)? {
Some(self.block()?)
} else {
None
};
self.expect_match(Token::End, "end", "if", line)?;
Ok(self.push_stat(Stat::If { arms, else_body }))
}
fn while_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let cond = self.expr()?;
self.expect(Token::Do, "do")?;
let body = self.block()?;
self.expect_match(Token::End, "end", "while", line)?;
Ok(self.push_stat(Stat::While { cond, body }))
}
fn repeat_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let body = self.block()?;
self.expect_match(Token::Until, "until", "repeat", line)?;
let cond = self.expr()?;
Ok(self.push_stat(Stat::Repeat { body, cond }))
}
fn for_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let first = self.expect_name()?;
match self.tok.tok {
Token::Assign => {
self.advance()?;
let start = self.expr()?;
self.expect(Token::Comma, ",")?;
let limit = self.expr()?;
let step = if self.accept(Token::Comma)? {
Some(self.expr()?)
} else {
None
};
self.expect(Token::Do, "do")?;
self.add_local_51(&first.text);
let body = self.block()?;
self.expect_match(Token::End, "end", "for", line)?;
Ok(self.push_stat(Stat::NumericFor {
var: first,
start,
limit,
step,
body,
}))
}
Token::Comma | Token::In => {
let mut vars = vec![first];
while self.accept(Token::Comma)? {
vars.push(self.expect_name()?);
}
self.expect(Token::In, "in")?;
let expr_line = self.tok.line;
let exprs = self.exprlist()?;
self.expect(Token::Do, "do")?;
for v in &vars {
self.add_local_51(&v.text);
}
let body = self.block()?;
self.expect_match(Token::End, "end", "for", line)?;
Ok(self.push_stat(Stat::GenericFor {
vars,
exprs,
body,
expr_line,
}))
}
_ => Err(self.error("'=' or 'in' expected")),
}
}
fn function_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let base = self.expect_name()?;
let mut path = Vec::new();
while self.accept(Token::Dot)? {
path.push(self.expect_name()?);
}
let method = if self.accept(Token::Colon)? {
Some(self.expect_name()?)
} else {
None
};
let body = self.func_body(line)?;
Ok(self.push_stat(Stat::Function {
name: FuncName { base, path, method },
body,
}))
}
fn attrib(&mut self) -> Result<Option<Attrib>, SyntaxError> {
if !(self.version.has_attribs() && self.tok.tok == Token::Lt) {
return Ok(None);
}
self.advance()?;
let name = self.expect_name()?;
let attrib = match &*name.text {
"const" => Attrib::Const,
"close" => Attrib::Close,
other => {
return Err(SyntaxError {
line: name.line,
msg: format!("unknown attribute '{other}'").into_bytes(),
});
}
};
self.expect(Token::Gt, ">")?;
Ok(Some(attrib))
}
fn attnamelist(&mut self) -> Result<DeclList, SyntaxError> {
let collective = if self.version.has_collective_attrib() {
self.attrib()?
} else {
None
};
let mut names = Vec::new();
loop {
let name = self.expect_name()?;
let attrib = self.attrib()?;
names.push(AttribName { name, attrib });
if !self.accept(Token::Comma)? {
break;
}
}
let exprs = if self.accept(Token::Assign)? {
self.exprlist()?
} else {
Vec::new()
};
Ok((collective, names, exprs))
}
fn local_stat(&mut self) -> Result<StatId, SyntaxError> {
self.advance()?;
if self.accept(Token::Function)? {
let line = self.prev_line;
let name = self.expect_name()?;
self.bump_locals(1)?;
self.add_local_51(&name.text);
let body = self.func_body(line)?;
return Ok(self.push_stat(Stat::LocalFunction { name, body }));
}
let (collective, names, exprs) = self.attnamelist()?;
self.bump_locals(names.len() as u32)?;
for an in &names {
self.add_local_51(&an.name.text);
}
Ok(self.push_stat(Stat::Local {
collective,
names,
exprs,
}))
}
fn global_stat(&mut self) -> Result<StatId, SyntaxError> {
self.advance()?;
if self.accept(Token::Function)? {
let line = self.prev_line;
let name = self.expect_name()?;
let body = self.func_body(line)?;
return Ok(self.push_stat(Stat::GlobalFunction { name, body }));
}
let leading = self.attrib()?;
if self.accept(Token::Star)? {
return Ok(self.push_stat(Stat::GlobalAll { attrib: leading }));
}
let mut names = Vec::new();
loop {
let name = self.expect_name()?;
let attrib = self.attrib()?;
names.push(AttribName { name, attrib });
if !self.accept(Token::Comma)? {
break;
}
}
let exprs = if self.accept(Token::Assign)? {
self.exprlist()?
} else {
Vec::new()
};
Ok(self.push_stat(Stat::Global {
collective: leading,
names,
exprs,
}))
}
fn expr_stat(&mut self) -> Result<StatId, SyntaxError> {
let first = self.suffixed_expr()?;
if matches!(self.tok.tok, Token::Assign | Token::Comma) {
let mut targets = vec![first];
while self.accept(Token::Comma)? {
if targets.len() >= 200 {
let msg: &[u8] = if self.version <= LuaVersion::Lua51 {
b"chunk has too many syntax levels"
} else {
b"too many C levels (limit is 200) in main function"
};
return Err(SyntaxError {
line: self.tok.line,
msg: msg.to_vec(),
});
}
targets.push(self.suffixed_expr()?);
}
self.expect(Token::Assign, "=")?;
for &t in &targets {
if !matches!(self.exprs[t.0 as usize], Expr::Name(_) | Expr::Index { .. }) {
return Err(self.error("syntax error"));
}
}
let exprs = self.exprlist()?;
return Ok(self.push_stat(Stat::Assign { targets, exprs }));
}
if !matches!(
self.exprs[first.0 as usize],
Expr::Call { .. } | Expr::MethodCall { .. }
) {
return Err(self.error("syntax error"));
}
Ok(self.push_stat(Stat::Call(first)))
}
fn exprlist(&mut self) -> Result<Vec<ExprId>, SyntaxError> {
let mut list = vec![self.expr()?];
while self.accept(Token::Comma)? {
list.push(self.expr()?);
}
Ok(list)
}
fn expr(&mut self) -> Result<ExprId, SyntaxError> {
self.sub_expr(0)
}
fn sub_expr(&mut self, limit: u8) -> Result<ExprId, SyntaxError> {
self.enter()?;
let mut left = if let Some(op) = un_op_of(&self.tok.tok) {
let line = self.tok.line;
self.advance()?;
let operand = self.sub_expr(UNARY_PRIORITY)?;
self.push_expr(Expr::UnOp { op, operand, line })
} else {
self.simple_expr()?
};
while let Some(op) = bin_op_of(&self.tok.tok) {
let (lp, rp) = bin_priority(op);
if lp <= limit {
break;
}
let line = self.tok.line;
self.advance()?;
let rhs = self.sub_expr(rp)?;
left = self.push_expr(Expr::BinOp {
op,
lhs: left,
rhs,
line,
});
}
self.leave();
Ok(left)
}
fn simple_expr(&mut self) -> Result<ExprId, SyntaxError> {
let e = match &self.tok.tok {
Token::Nil => {
self.advance()?;
Expr::Nil
}
Token::True => {
self.advance()?;
Expr::True
}
Token::False => {
self.advance()?;
Expr::False
}
Token::Ellipsis => {
self.advance()?;
Expr::Vararg
}
Token::Int(_) => {
let Token::Int(v) = self.advance()?.tok else {
unreachable!()
};
Expr::Int(v)
}
Token::Float(_) => {
let Token::Float(v) = self.advance()?.tok else {
unreachable!()
};
Expr::Float(v)
}
Token::Str(_) => {
let Token::Str(s) = self.advance()?.tok else {
unreachable!()
};
Expr::Str(s)
}
Token::LBrace => return self.table_constructor(),
Token::Function => {
let line = self.tok.line;
self.advance()?;
Expr::Function(self.func_body(line)?)
}
_ => return self.suffixed_expr(),
};
Ok(self.push_expr(e))
}
fn primary_expr(&mut self) -> Result<ExprId, SyntaxError> {
match &self.tok.tok {
Token::Name(_) => {
let name = self.expect_name()?;
self.ident_lookup_51(&name.text)?;
Ok(self.push_expr(Expr::Name(name)))
}
Token::LParen => {
let line = self.tok.line;
self.advance()?;
let inner = self.expr()?;
self.expect_match(Token::RParen, ")", "(", line)?;
Ok(self.push_expr(Expr::Paren(inner)))
}
_ => Err(self.error("unexpected symbol")),
}
}
fn suffixed_expr(&mut self) -> Result<ExprId, SyntaxError> {
let primary_line = self.tok.line;
let mut e = self.primary_expr()?;
loop {
match &self.tok.tok {
Token::Dot => {
self.advance()?;
let name = self.expect_name()?;
let key = self.push_expr(Expr::Str(name.text.into_boxed_bytes().into_vec()));
e = self.push_expr(Expr::Index { obj: e, key });
}
Token::LBracket => {
self.advance()?;
let key = self.expr()?;
self.expect(Token::RBracket, "]")?;
e = self.push_expr(Expr::Index { obj: e, key });
}
Token::Colon => {
self.advance()?;
let method = self.expect_name()?;
let line = if self.version <= LuaVersion::Lua53 {
primary_line
} else {
self.tok.line
};
let args = self.call_args()?;
e = self.push_expr(Expr::MethodCall {
obj: e,
method,
args,
line,
});
}
Token::LParen | Token::Str(_) | Token::LBrace => {
let line = if self.version <= LuaVersion::Lua53 {
primary_line
} else {
self.tok.line
};
let args = self.call_args()?;
e = self.push_expr(Expr::Call {
func: e,
args,
line,
});
}
_ => break,
}
}
Ok(e)
}
fn call_args(&mut self) -> Result<Vec<ExprId>, SyntaxError> {
match &self.tok.tok {
Token::LParen => {
if self.version == LuaVersion::Lua51 && self.tok.line != self.prev_line {
return Err(self.error("ambiguous syntax (function call x new statement)"));
}
let line = self.tok.line;
self.advance()?;
let args = if self.tok.tok == Token::RParen {
Vec::new()
} else {
self.exprlist()?
};
self.expect_match(Token::RParen, ")", "(", line)?;
Ok(args)
}
Token::Str(_) => {
let Token::Str(s) = self.advance()?.tok else {
unreachable!()
};
Ok(vec![self.push_expr(Expr::Str(s))])
}
Token::LBrace => Ok(vec![self.table_constructor()?]),
_ => Err(self.error("function arguments expected")),
}
}
fn table_constructor(&mut self) -> Result<ExprId, SyntaxError> {
let line = self.tok.line;
self.expect(Token::LBrace, "{")?;
let mut fields = Vec::new();
loop {
if self.tok.tok == Token::RBrace {
break;
}
if self.tok.tok == Token::LBracket {
self.advance()?;
let key = self.expr()?;
self.expect(Token::RBracket, "]")?;
self.expect(Token::Assign, "=")?;
let value = self.expr()?;
fields.push(TableField::Keyed(key, value));
} else if matches!(self.tok.tok, Token::Name(_)) && *self.peek()? == Token::Assign {
let name = self.expect_name()?;
self.advance()?; let value = self.expr()?;
fields.push(TableField::Named(name, value));
} else {
fields.push(TableField::Item(self.expr()?));
}
if !(self.accept(Token::Comma)? || self.accept(Token::Semi)?) {
break;
}
}
self.expect_match(Token::RBrace, "}", "{", line)?;
Ok(self.push_expr(Expr::Table { fields, line }))
}
fn func_body(&mut self, line: u32) -> Result<FuncBody, SyntaxError> {
self.expect(Token::LParen, "(")?;
self.func_local_count.push((0, line));
self.enter_fn_51(line);
let mut params = Vec::new();
let mut vararg = Vararg::None;
if self.tok.tok != Token::RParen {
loop {
match &self.tok.tok {
Token::Ellipsis => {
self.advance()?;
vararg = if self.version.has_named_vararg()
&& matches!(self.tok.tok, Token::Name(_))
{
Vararg::Named(self.expect_name()?)
} else {
Vararg::Anonymous
};
if let Vararg::Named(ref n) = vararg {
self.add_local_51(&n.text);
}
break;
}
Token::Name(_) => {
let p = self.expect_name()?;
self.add_local_51(&p.text);
params.push(p);
}
_ => return Err(self.error("<name> expected")),
}
if !self.accept(Token::Comma)? {
break;
}
}
}
self.expect(Token::RParen, ")")?;
let nparams = params.len() as u32 + matches!(vararg, Vararg::Named(_)) as u32;
self.bump_locals(nparams)?;
let block = self.block()?;
let end_line = self.tok.line; self.expect_match(Token::End, "end", "function", line)?;
self.func_local_count.pop();
self.leave_fn_51();
Ok(FuncBody {
params,
vararg,
block,
line,
end_line,
})
}
fn track_uv_51(&self) -> bool {
!self.upval_chain_51.is_empty()
}
fn add_local_51(&mut self, name: &str) {
if self.track_uv_51() {
self.upval_chain_51
.last_mut()
.expect("fn ctx")
.locals
.push(name.into());
}
}
fn snap_locals_51(&self) -> usize {
if self.track_uv_51() {
self.upval_chain_51.last().expect("fn ctx").locals.len()
} else {
0
}
}
fn restore_locals_51(&mut self, snap: usize) {
if self.track_uv_51() {
self.upval_chain_51
.last_mut()
.expect("fn ctx")
.locals
.truncate(snap);
}
}
fn enter_fn_51(&mut self, line_defined: u32) {
if self.track_uv_51() {
self.upval_chain_51.push(FnUvSlot {
line_defined,
..Default::default()
});
}
}
fn leave_fn_51(&mut self) {
if self.track_uv_51() {
self.upval_chain_51.pop();
}
}
fn ident_lookup_51(&mut self, name: &str) -> Result<(), SyntaxError> {
if !self.track_uv_51() {
return Ok(());
}
const MAXUPVAL: usize = 60;
let n = self.upval_chain_51.len();
let mut owner: Option<usize> = None;
for k in (0..n).rev() {
if self.upval_chain_51[k]
.locals
.iter()
.any(|s| s.as_ref() == name)
{
owner = Some(k);
break;
}
}
let Some(owner_idx) = owner else {
return Ok(());
};
if owner_idx + 1 == n {
return Ok(());
}
for k in (owner_idx + 1)..n {
let inserted = self.upval_chain_51[k].upvalues.insert(name.into());
if inserted && self.upval_chain_51[k].upvalues.len() > MAXUPVAL {
let line_defined = self.upval_chain_51[k].line_defined;
let where_ = if k == 0 {
"main function".to_string()
} else {
format!("function at line {line_defined}")
};
return Err(SyntaxError {
line: self.tok.line,
msg: format!("too many upvalues (limit is {MAXUPVAL}) in {where_}")
.into_bytes(),
});
}
}
Ok(())
}
fn bump_locals(&mut self, n: u32) -> Result<(), SyntaxError> {
const MAXVARS: u32 = 200;
let depth = self.func_local_count.len();
let &(cur, line_defined) = self.func_local_count.last().expect("func ctx pushed");
let new = cur.saturating_add(n);
if new > MAXVARS {
let where_ = if depth == 1 {
"main function".to_string()
} else {
format!("function at line {line_defined}")
};
return Err(SyntaxError {
line: self.tok.line,
msg: format!("too many local variables (limit is {MAXVARS}) in {where_}")
.into_bytes(),
});
}
self.func_local_count.last_mut().unwrap().0 = new;
Ok(())
}
}