use crate::frontend::ast::*;
use crate::frontend::error::SyntaxError;
use crate::frontend::goto_check::GotoCheck;
use crate::frontend::lexer::{Lexed, Lexer};
use crate::frontend::span::Span;
use crate::frontend::token::{Near, Token, TokenInfo, near_text};
use crate::version::LuaVersion;
const MAX_DEPTH: u32 = 200;
const MAXVARS: 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],
},
}
struct Cur {
info: TokenInfo,
char: Option<u8>,
}
impl<'s> TokenSource<'s> {
fn next_token(&mut self) -> Result<Cur, SyntaxError> {
match self {
TokenSource::Lexer(l) => Ok(match l.next_lexed()? {
Lexed::Tok(info) => Cur { info, char: None },
Lexed::Char(c, mut info) => {
info.tok = Token::At;
Cur {
info,
char: Some(c),
}
}
}),
TokenSource::PreExpanded {
tokens,
cursor,
src,
} => {
if *cursor >= tokens.len() {
let line = tokens.last().map(|t| t.line).unwrap_or(1);
let _ = src;
Ok(Cur {
info: TokenInfo {
tok: Token::Eof,
span: Span::new(0, 0),
line,
},
char: None,
})
} else {
let t = tokens[*cursor].clone();
*cursor += 1;
Ok(Cur {
info: t,
char: None,
})
}
}
}
}
fn src(&self) -> &'s [u8] {
match self {
TokenSource::Lexer(l) => l.src(),
TokenSource::PreExpanded { src, .. } => src,
}
}
fn line(&self) -> u32 {
match self {
TokenSource::Lexer(l) => l.line(),
TokenSource::PreExpanded { tokens, cursor, .. } => tokens
.get(cursor.saturating_sub(1))
.or(tokens.last())
.map_or(1, |t| t.line),
}
}
}
pub fn parse(src: &[u8], version: LuaVersion) -> Result<Chunk, SyntaxError> {
parse_at_depth(src, version, 0).map(|p| p.chunk)
}
pub(crate) struct Parsed {
pub(crate) chunk: Chunk,
pub(crate) end_lines: Vec<u32>,
}
pub(crate) fn parse_at_depth(
src: &[u8],
version: LuaVersion,
c_depth: u32,
) -> Result<Parsed, SyntaxError> {
let lex = Lexer::new(src, version);
parse_from_source(TokenSource::Lexer(lex), version, c_depth)
}
pub fn parse_tokens(
tokens: Vec<TokenInfo>,
src: &[u8],
version: LuaVersion,
) -> Result<Chunk, SyntaxError> {
parse_tokens_at_depth(tokens, src, version, 0).map(|p| p.chunk)
}
pub(crate) fn parse_tokens_at_depth(
tokens: Vec<TokenInfo>,
src: &[u8],
version: LuaVersion,
c_depth: u32,
) -> Result<Parsed, SyntaxError> {
parse_from_source(
TokenSource::PreExpanded {
tokens,
cursor: 0,
src,
},
version,
c_depth,
)
}
fn parse_from_source<'s>(
mut lex: TokenSource<'s>,
version: LuaVersion,
c_depth: u32,
) -> Result<Parsed, SyntaxError> {
let cur = lex.next_token()?;
let mut p = Parser {
lex,
tok: cur.info,
tok_char: cur.char,
peeked: None,
prev_line: 1,
exprs: Vec::new(),
stats: Vec::new(),
stat_lines: Vec::new(),
end_lines: Vec::new(),
depth: c_depth,
version,
func_local_count: vec![(0, 0, 0)],
funcs: vec![FnFlow {
vararg: true,
loops: 0,
}],
gotos: GotoCheck::new(version),
last_line: 1,
upval_chain_51: if version <= LuaVersion::Lua51 {
vec![FnUvSlot {
line_defined: 0,
..Default::default()
}]
} else {
Vec::new()
},
};
if let Some(g) = p.gotos.as_mut() {
g.enter_function();
}
let block = p.block()?;
if p.tok.tok != Token::Eof {
return Err(p.error_expected("<eof>"));
}
p.close_function()?;
let end_line = p.prev_line;
Ok(Parsed {
chunk: Chunk {
exprs: p.exprs,
stats: p.stats,
stat_lines: p.stat_lines,
block,
end_line,
},
end_lines: p.end_lines,
})
}
struct Parser<'s> {
lex: TokenSource<'s>,
tok: TokenInfo,
tok_char: Option<u8>,
peeked: Option<Cur>,
funcs: Vec<FnFlow>,
gotos: Option<GotoCheck>,
last_line: u32,
prev_line: u32,
exprs: Vec<Expr>,
stats: Vec<Stat>,
stat_lines: Vec<u32>,
end_lines: Vec<u32>,
depth: u32,
version: LuaVersion,
func_local_count: Vec<(u32, u32, u32)>,
upval_chain_51: Vec<FnUvSlot>,
}
struct FnFlow {
vararg: bool,
loops: u32,
}
#[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> {
self.last_line = self.lex.line();
let next = match self.peeked.take() {
Some(t) => t,
None => self.lex.next_token()?,
};
self.prev_line = self.tok.line;
self.tok_char = next.char;
Ok(std::mem::replace(&mut self.tok, next.info))
}
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().info.tok)
}
fn near(&self) -> Vec<u8> {
match self.tok_char {
Some(c) => near_text(self.version, Near::Char(c)),
None => self
.tok
.tok
.near_bytes(self.lex.src(), self.tok.span, self.version),
}
}
fn error(&self, msg: impl AsRef<str>) -> SyntaxError {
if self.tok_char == Some(0) {
return self.plain_error(msg.as_ref());
}
let mut bytes = msg.as_ref().as_bytes().to_vec();
bytes.extend_from_slice(b" near ");
bytes.extend_from_slice(&self.near());
SyntaxError {
line: self.lex.line(),
msg: bytes,
}
}
fn plain_error(&self, msg: impl Into<Vec<u8>>) -> SyntaxError {
SyntaxError {
line: self.lex.line(),
msg: msg.into(),
}
}
fn error_expected(&self, what: &str) -> SyntaxError {
if what.starts_with('<') && self.version >= LuaVersion::Lua52 {
self.error(format!("{what} expected"))
} else {
self.error(format!("'{what}' expected"))
}
}
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_expected(what));
}
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.lex.line() {
return Err(self.error_expected(what));
}
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_expected("<name>"));
}
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;
let limit = if self.version >= LuaVersion::Lua54 {
MAX_DEPTH - 1
} else {
MAX_DEPTH
};
if self.depth > limit {
return Err(self.levels_error());
}
Ok(())
}
fn levels_error(&self) -> SyntaxError {
match self.version {
LuaVersion::Lua51 => self.plain_error("chunk has too many syntax levels"),
LuaVersion::Lua52 | LuaVersion::Lua53 => self.error(format!(
"too many C levels (limit is 200) in {}",
self.where_()
)),
_ => SyntaxError::unpositioned("C stack overflow"),
}
}
fn where_(&self) -> String {
let &(_, line_defined, _) = self.func_local_count.last().expect("func ctx");
if self.func_local_count.len() == 1 {
"main function".to_string()
} else {
format!("function at line {line_defined}")
}
}
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 push_ended_stat(&mut self, s: Stat) -> StatId {
let id = self.push_stat(s);
let idx = id.0 as usize;
self.end_lines.resize(idx + 1, 0);
self.end_lines[idx] = self.prev_line;
id
}
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();
self.goto_step(|g| {
g.enter_block(false);
Ok(())
})?;
let mut stats = Vec::new();
loop {
if self.gotos.as_ref().is_some_and(GotoCheck::has_open_labels)
&& !matches!(self.tok.tok, Token::Semi | Token::DColon)
{
let last = matches!(
self.tok.tok,
Token::Else | Token::Elseif | Token::End | Token::Eof
);
self.goto_step(|g| g.finish_labels(last))?;
}
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.break_stat()?;
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.goto_step(GotoCheck::leave_block)?;
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()?;
self.set_stat_line(stat, start_line);
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()?;
let text = name.text.clone();
self.goto_step(|g| g.label_before_close(&text, start_line))?;
self.expect(Token::DColon, "::")?;
Some(self.push_stat(Stat::Label(name)))
}
Token::Break => {
let line = self.tok.line;
self.break_stat()?;
Some(self.push_stat(Stat::Break { line }))
}
Token::Goto => {
let mut line = self.lex.line();
self.advance()?;
if self.version >= LuaVersion::Lua55 {
line = start_line;
} else if self.version >= LuaVersion::Lua54 {
line = self.lex.line();
}
let name = self.expect_name()?;
let text = name.text.clone();
self.goto_step(|g| g.goto_stat(&text, line))?;
Some(self.push_stat(Stat::Goto(name)))
}
_ => Some(self.expr_stat()?),
};
if let Some(sid) = stat {
self.set_stat_line(sid, start_line);
}
Ok(stat)
}
fn set_stat_line(&mut self, sid: StatId, line: u32) {
let idx = sid.0 as usize;
if self.stat_lines.len() <= idx {
self.stat_lines.resize(idx + 1, 0);
}
self.stat_lines[idx] = line;
}
fn break_stat(&mut self) -> Result<(), SyntaxError> {
let line = self.lex.line();
let in_loop = self.funcs.last().expect("func ctx").loops > 0;
if !in_loop && self.version >= LuaVersion::Lua55 {
return Err(self.error("break outside loop"));
}
self.advance()?;
if !in_loop && self.version <= LuaVersion::Lua51 {
return Err(self.error("no loop to break"));
}
if self.version >= LuaVersion::Lua55 {
return Ok(());
}
self.goto_step(|g| g.goto_stat("break", line))
}
fn loop_block(&mut self, vars: &[Name]) -> Result<Block, SyntaxError> {
self.funcs.last_mut().expect("func ctx").loops += 1;
self.goto_step(|g| {
g.enter_block(true);
g.enter_block(false);
for v in vars {
g.declare(&v.text);
}
Ok(())
})?;
let body = self.block()?;
self.goto_step(|g| {
g.leave_block()?;
g.leave_block()
})?;
self.funcs.last_mut().expect("func ctx").loops -= 1;
Ok(body)
}
fn close_function(&mut self) -> Result<(), SyntaxError> {
let _ = self.funcs.pop().expect("func ctx");
self.goto_step(GotoCheck::leave_block)
}
fn goto_step(
&mut self,
step: impl FnOnce(&mut GotoCheck) -> Result<(), String>,
) -> Result<(), SyntaxError> {
match self.gotos.as_mut().map(step) {
Some(Err(msg)) if self.version >= LuaVersion::Lua55 => Err(SyntaxError {
line: self.last_line,
msg: msg.into_bytes(),
}),
Some(Err(msg)) => Err(self.plain_error(msg)),
_ => Ok(()),
}
}
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.loop_block(&[])?;
self.expect_match(Token::End, "end", "while", line)?;
Ok(self.push_ended_stat(Stat::While { cond, body }))
}
fn repeat_stat(&mut self) -> Result<StatId, SyntaxError> {
let line = self.tok.line;
self.advance()?;
let body = self.loop_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.loop_block(std::slice::from_ref(&first))?;
self.expect_match(Token::End, "end", "for", line)?;
Ok(self.push_ended_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.loop_block(&vars)?;
self.expect_match(Token::End, "end", "for", line)?;
Ok(self.push_ended_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()?;
self.new_local()?;
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.new_local()?;
self.activate_locals()?;
self.add_local_51(&name.text);
let text = name.text.clone();
self.goto_step(|g| {
g.declare(&text);
Ok(())
})?;
let body = self.func_body(line)?;
return Ok(self.push_stat(Stat::LocalFunction { name, body }));
}
let (collective, names, exprs) = self.attnamelist()?;
self.activate_locals()?;
self.goto_step(|g| {
for an in &names {
g.declare(&an.name.text);
}
Ok(())
})?;
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 text = name.text.clone();
self.goto_step(|g| {
g.declare(&text);
Ok(())
})?;
let body = self.func_body(line)?;
return Ok(self.push_stat(Stat::GlobalFunction { name, body }));
}
let leading = self.attrib()?;
if self.accept(Token::Star)? {
self.goto_step(|g| {
g.declare("*");
Ok(())
})?;
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()
};
self.goto_step(|g| {
for an in &names {
g.declare(&an.name.text);
}
Ok(())
})?;
Ok(self.push_stat(Stat::Global {
collective: leading,
names,
exprs,
}))
}
fn expr_stat(&mut self) -> Result<StatId, SyntaxError> {
let first = self.suffixed_expr()?;
let is_call = matches!(
self.exprs[first.0 as usize],
Expr::Call { .. } | Expr::MethodCall { .. }
);
let assign = if self.version <= LuaVersion::Lua51 {
!is_call
} else {
matches!(self.tok.tok, Token::Assign | Token::Comma)
};
if !assign {
if !is_call {
return Err(self.error("syntax error"));
}
return Ok(self.push_stat(Stat::Call(first)));
}
let mut targets = vec![first];
let mut entered = 0;
loop {
let last = *targets.last().expect("one target");
if !matches!(
self.exprs[last.0 as usize],
Expr::Name(_) | Expr::Index { .. }
) {
return Err(self.error("syntax error"));
}
if !self.accept(Token::Comma)? {
break;
}
let nvars = targets.len() as u32;
targets.push(self.suffixed_expr()?);
match self.version {
LuaVersion::Lua51 => {
let limit = MAX_DEPTH.saturating_sub(self.depth);
if nvars > limit {
return Err(self.plain_error(format!(
"{} has more than {limit} variables in assignment",
self.where_()
)));
}
}
LuaVersion::Lua52 | LuaVersion::Lua53 => {
if nvars + self.depth > MAX_DEPTH {
return Err(self.levels_error());
}
}
_ => {
self.enter()?;
entered += 1;
}
}
}
self.expect(Token::Assign, "=")?;
let exprs = self.exprlist()?;
self.depth -= entered;
Ok(self.push_stat(Stat::Assign { targets, exprs }))
}
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 => {
if !self.funcs.last().expect("func ctx").vararg {
return Err(self.error("cannot use '...' outside a vararg function"));
}
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, 0));
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.new_local()?;
self.add_local_51(&n.text);
}
break;
}
Token::Name(_) => {
let p = self.expect_name()?;
self.new_local()?;
self.add_local_51(&p.text);
params.push(p);
}
_ => return Err(self.error("<name> or '...' expected")),
}
if !self.accept(Token::Comma)? {
break;
}
}
}
self.activate_locals()?;
self.goto_step(|g| {
g.enter_function();
for p in ¶ms {
g.declare(&p.text);
}
Ok(())
})?;
self.expect(Token::RParen, ")")?;
self.funcs.push(FnFlow {
vararg: !matches!(vararg, Vararg::None),
loops: 0,
});
let block = self.block()?;
let end_line = self.tok.line; self.expect_match(Token::End, "end", "function", line)?;
self.close_function()?;
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!("{where_} has more than {MAXUPVAL} upvalues").into_bytes(),
});
}
}
Ok(())
}
fn new_local(&mut self) -> Result<(), SyntaxError> {
let &(active, line_defined, pending) = self.func_local_count.last().expect("func ctx");
if self.version <= LuaVersion::Lua54 && active + pending + 1 > MAXVARS {
return Err(if self.version <= LuaVersion::Lua51 {
let what = if self.func_local_count.len() == 1 {
"main function".to_string()
} else {
format!("function at line {line_defined}")
};
self.plain_error(format!("{what} has more than {MAXVARS} local variables"))
} else {
self.local_limit_error()
});
}
self.func_local_count.last_mut().expect("func ctx").2 += 1;
Ok(())
}
fn activate_locals(&mut self) -> Result<(), SyntaxError> {
let (active, _, pending) = *self.func_local_count.last().expect("func ctx");
if self.version >= LuaVersion::Lua55 && active + pending > MAXVARS {
return Err(self.local_limit_error());
}
let slot = self.func_local_count.last_mut().expect("func ctx");
slot.0 += pending;
slot.2 = 0;
Ok(())
}
fn local_limit_error(&self) -> SyntaxError {
self.error(format!(
"too many local variables (limit is {MAXVARS}) in {}",
self.where_()
))
}
}