use crate::compiler::expr_parser::buildglobal;
use expr_parser::{body, expr, suffixedexp};
use crate::Instruction;
use crate::compiler::func_state::{BlockCnt, FuncState, LabelDesc, LhsAssign, LhsAssignId};
use crate::compiler::parser::LuaTokenKind;
use crate::compiler::{BlockCntId, ExpDesc, ExpKind, ExpUnion, VarKind, code, expr_parser};
use crate::lua_vm::OpCode;
pub fn statlist(fs: &mut FuncState) -> Result<(), String> {
while !block_follow(fs, true) {
if fs.lexer.current_token() == LuaTokenKind::TkReturn {
statement(fs)?;
return Ok(()); }
statement(fs)?;
}
Ok(())
}
fn block_follow(fs: &FuncState, withuntil: bool) -> bool {
match fs.lexer.current_token() {
LuaTokenKind::TkElse
| LuaTokenKind::TkElseIf
| LuaTokenKind::TkEnd
| LuaTokenKind::TkEof => true,
LuaTokenKind::TkUntil => withuntil,
_ => false,
}
}
fn statement(fs: &mut FuncState) -> Result<(), String> {
let line = fs.lexer.line;
fs.enter_level()?;
match fs.lexer.current_token() {
LuaTokenKind::TkSemicolon => {
fs.lexer.bump();
}
LuaTokenKind::TkIf => {
ifstat(fs, line)?;
}
LuaTokenKind::TkWhile => {
whilestat(fs, line)?;
}
LuaTokenKind::TkDo => {
fs.lexer.bump(); block(fs)?;
check_match(fs, LuaTokenKind::TkEnd, LuaTokenKind::TkDo, line)?;
}
LuaTokenKind::TkFor => {
forstat(fs, line)?;
}
LuaTokenKind::TkRepeat => {
repeatstat(fs, line)?;
}
LuaTokenKind::TkFunction => {
funcstat(fs, line)?;
}
LuaTokenKind::TkLocal => {
fs.lexer.bump(); if testnext(fs, LuaTokenKind::TkFunction) {
localfunc(fs)?;
} else {
localstat(fs)?;
}
}
LuaTokenKind::TkDbColon => {
fs.lexer.bump(); let name = str_checkname(fs)?;
labelstat(fs, &name, line)?;
}
LuaTokenKind::TkReturn => {
fs.lexer.bump(); retstat(fs)?;
}
LuaTokenKind::TkBreak => {
fs.lexer.bump(); breakstat(fs)?;
}
LuaTokenKind::TkGoto => {
fs.lexer.bump(); gotostat(fs)?;
}
_ => {
let mut is_global_stat = false;
if fs.lexer.current_token_text() == "global" {
let next = fs.lexer.peek_next_token();
if matches!(
next,
LuaTokenKind::TkMul
| LuaTokenKind::TkLt
| LuaTokenKind::TkName
| LuaTokenKind::TkFunction
) {
is_global_stat = true;
globalstatfunc(fs, line)?;
}
}
if !is_global_stat {
exprstat(fs)?;
}
}
}
fs.freereg = fs.nvarstack();
fs.leave_level();
Ok(())
}
fn testnext(fs: &mut FuncState, expected: LuaTokenKind) -> bool {
if fs.lexer.current_token() == expected {
fs.lexer.bump();
true
} else {
false
}
}
fn check(fs: &mut FuncState, expected: LuaTokenKind) -> Result<(), String> {
if fs.lexer.current_token() != expected {
return error_expected(fs, expected);
}
Ok(())
}
fn error_expected(fs: &mut FuncState, token: LuaTokenKind) -> Result<(), String> {
let msg = format!("'{}' expected", token);
Err(fs.token_error(&msg))
}
pub fn check_match(
fs: &mut FuncState,
what: LuaTokenKind,
who: LuaTokenKind,
where_: usize,
) -> Result<(), String> {
if !testnext(fs, what) {
if where_ == fs.lexer.line {
error_expected(fs, what)?;
} else {
return Err(fs.token_error(&format!(
"'{}' expected (to close '{}' at line {})",
what, who, where_
)));
}
}
Ok(())
}
fn str_checkname(fs: &mut FuncState) -> Result<String, String> {
check(fs, LuaTokenKind::TkName)?;
let name = fs.lexer.current_token_text().to_string();
fs.lexer.bump();
Ok(name)
}
pub fn enterblock(fs: &mut FuncState, bl_id: BlockCntId, isloop: u8) {
let prev_id = fs.block_cnt_id.take();
let parent_in_scope = if let Some(prev_id) = prev_id {
fs.compiler_state
.get_blockcnt_mut(prev_id)
.map(|bl| bl.in_scope)
.unwrap_or(false)
} else {
false
};
let nactvar = fs.nactvar;
if let Some(bl) = fs.compiler_state.get_blockcnt_mut(bl_id) {
bl.previous = prev_id;
bl.first_label = fs.labels.len();
bl.first_goto = fs.pending_gotos.len();
bl.nactvar = nactvar;
bl.upval = false;
bl.is_loop = isloop;
bl.in_scope = parent_in_scope; }
fs.block_cnt_id = Some(bl_id);
}
fn solvegoto(
fs: &mut FuncState,
g: usize,
label: &LabelDesc,
bl_upval: bool,
varnames: &std::collections::HashMap<u16, String>,
) -> Result<(), String> {
let gt = &fs.pending_gotos[g].clone();
if gt.nactvar < label.nactvar {
let varname = varnames.get(>.nactvar).map(|s| s.as_str()).unwrap_or("?");
return Err(fs.sem_error(&format!(
"<goto {}> at line {} jumps into the scope of '{}'",
gt.name, gt.line, varname
)));
}
let needs_close = gt.close || (label.nactvar < gt.nactvar && bl_upval);
let mut pc = gt.pc;
if needs_close {
let stklevel = label.stklevel;
let jmp_instr = fs.chunk.code[pc];
fs.chunk.code[pc + 1] = jmp_instr;
fs.chunk.code[pc] = Instruction::create_abc(OpCode::Close, stklevel as u32, 0, 0);
pc += 1; }
code::patchlist(fs, pc as isize, label.pc as isize);
fs.pending_gotos.remove(g);
Ok(())
}
fn findlabel<'a>(fs: &'a FuncState, name: &str) -> Option<&'a LabelDesc> {
fs.labels.iter().find(|lb| lb.name == name)
}
fn checkrepeated(fs: &FuncState, name: &str) -> Result<(), String> {
if let Some(lb) = findlabel(fs, name) {
return Err(fs.sem_error(&format!(
"label '{}' already defined on line {}",
name, lb.line
)));
}
Ok(())
}
fn createlabel(fs: &mut FuncState, name: &str, line: usize, last: bool) {
let pc = code::get_label(fs);
let mut label = LabelDesc {
name: name.to_string(),
pc,
line,
nactvar: fs.nactvar,
stklevel: 0, close: false,
};
if last {
if let Some(bl) = &fs.current_block_cnt() {
label.nactvar = bl.nactvar;
}
}
label.stklevel = fs.reglevel(label.nactvar);
fs.labels.push(label);
}
fn solvegotos_on_leaveblock(
fs: &mut FuncState,
bl: &BlockCnt,
outlevel: u8,
goto_levels: &[(usize, u8)],
varnames: &std::collections::HashMap<u16, String>,
) -> Result<(), String> {
let mut igt = bl.first_goto; let mut level_idx = 0;
while igt < fs.pending_gotos.len() {
let gt_name = fs.pending_gotos[igt].name.clone();
let label_opt = fs.labels[bl.first_label..]
.iter()
.find(|lb| lb.name == gt_name)
.cloned();
if let Some(label) = label_opt {
let bl_upval = bl.upval;
solvegoto(fs, igt, &label, bl_upval, varnames)?;
} else {
let gt_level = if level_idx < goto_levels.len() && goto_levels[level_idx].0 == igt {
let level = goto_levels[level_idx].1;
level_idx += 1;
level
} else {
0
};
if bl.upval && gt_level > outlevel {
fs.pending_gotos[igt].close = true;
}
fs.pending_gotos[igt].nactvar = bl.nactvar;
igt += 1; }
}
fs.labels.truncate(bl.first_label);
Ok(())
}
pub fn leaveblock(fs: &mut FuncState) -> Result<(), String> {
if let Some(bl_id) = fs.block_cnt_id {
let (nactvar, first_goto, is_loop, has_previous, previous_id, upval) = {
if let Some(bl) = fs.compiler_state.get_blockcnt_mut(bl_id) {
(
bl.nactvar,
bl.first_goto,
bl.is_loop,
bl.previous.is_some(),
bl.previous,
bl.upval,
)
} else {
return Ok(());
}
};
let stklevel = fs.reglevel(nactvar);
let mut goto_levels = Vec::new();
for i in first_goto..fs.pending_gotos.len() {
let gt_nactvar = fs.pending_gotos[i].nactvar;
let gt_level = fs.reglevel(gt_nactvar);
goto_levels.push((i, gt_level));
}
let mut varnames = std::collections::HashMap::new();
for i in 0..fs.nactvar {
if let Some(vd) = fs.actvar.get(i as usize) {
varnames.insert(i, vd.name.clone());
}
}
if has_previous && upval {
code::code_abc(fs, OpCode::Close, stklevel as u32, 0, 0);
}
fs.freereg = stklevel;
fs.remove_vars(nactvar);
if is_loop == 2 {
createlabel(fs, "break", 0, false);
}
if let Some(bl) = fs.compiler_state.get_blockcnt_mut(bl_id) {
let bl_info = BlockCnt {
previous: bl.previous,
first_label: bl.first_label,
first_goto: bl.first_goto,
nactvar: bl.nactvar,
upval: bl.upval,
is_loop: bl.is_loop,
in_scope: bl.in_scope,
};
solvegotos_on_leaveblock(fs, &bl_info, stklevel, &goto_levels, &varnames)?;
}
if !has_previous && !fs.pending_gotos.is_empty() {
let gt = &fs.pending_gotos[0];
return Err(fs.sem_error(&format!(
"no visible label '{}' for <goto> at line {}",
gt.name, gt.line
)));
}
fs.block_cnt_id = previous_id;
}
Ok(())
}
fn block(fs: &mut FuncState) -> Result<(), String> {
let bl_id = fs.compiler_state.alloc_blockcnt(BlockCnt::default());
enterblock(fs, bl_id, 0);
statlist(fs)?;
leaveblock(fs)?;
Ok(())
}
fn retstat(fs: &mut FuncState) -> Result<(), String> {
use ExpKind;
let mut first = fs.nvarstack();
let mut nret: i32;
let mut e = ExpDesc::new_void();
let insidetbc = fs
.current_block_cnt()
.map(|bl| bl.in_scope)
.unwrap_or(false);
if block_follow(fs, true) || fs.lexer.current_token() == LuaTokenKind::TkSemicolon {
nret = 0;
} else {
nret = explist(fs, &mut e)? as i32;
if nret > 254 {
return Err(fs.errorlimit(254, "returns"));
}
if matches!(e.kind, ExpKind::VCALL | ExpKind::VVARARG) {
code::setmultret(fs, &mut e);
if e.kind == ExpKind::VCALL && nret == 1 && !insidetbc {
let pc = e.u.info() as usize;
if pc < fs.chunk.code.len() {
let instr = &mut fs.chunk.code[pc];
*instr =
Instruction::from_u32((instr.as_u32() & !0x7F) | (OpCode::TailCall as u32));
}
}
nret = -1; } else if nret == 1 {
first = code::exp2anyreg(fs, &mut e);
} else {
code::exp2nextreg(fs, &mut e);
}
}
code::ret(fs, first, nret as u8);
testnext(fs, LuaTokenKind::TkSemicolon);
Ok(())
}
pub fn explist(fs: &mut FuncState, e: &mut ExpDesc) -> Result<usize, String> {
use expr_parser::expr_internal;
let mut n = 1;
expr_internal(fs, e)?;
while testnext(fs, LuaTokenKind::TkComma) {
code::exp2nextreg(fs, e);
fs.check_pending_checklimit()?; *e = ExpDesc::new_void(); expr_internal(fs, e)?;
n += 1;
}
Ok(n)
}
fn newgotoentry(fs: &mut FuncState, name: String, line: usize) -> usize {
let pc = code::jump(fs);
code::code_abc(fs, OpCode::Close, 0, 1, 0);
let stklevel = fs.reglevel(fs.nactvar);
let label = LabelDesc {
name,
pc,
line,
nactvar: fs.nactvar,
stklevel,
close: false,
};
fs.pending_gotos.push(label);
pc
}
fn breakstat(fs: &mut FuncState) -> Result<(), String> {
let line = fs.lexer.line;
let mut found_loop = false;
let mut current_bl_id = fs.block_cnt_id;
while let Some(bl_id) = current_bl_id {
if let Some(bl) = fs.compiler_state.get_blockcnt_mut(bl_id) {
if bl.is_loop > 0 {
bl.is_loop = 2; found_loop = true;
break;
}
current_bl_id = bl.previous;
} else {
break;
}
}
if !found_loop {
return Err("break outside loop".to_string());
}
newgotoentry(fs, "break".to_string(), line);
Ok(())
}
fn gotostat(fs: &mut FuncState) -> Result<(), String> {
let line = fs.lexer.line;
let name = str_checkname(fs)?;
newgotoentry(fs, name.clone(), line);
Ok(())
}
fn labelstat(fs: &mut FuncState, name: &str, line: usize) -> Result<(), String> {
check(fs, LuaTokenKind::TkDbColon)?;
fs.lexer.bump();
while fs.lexer.current_token() == LuaTokenKind::TkSemicolon
|| fs.lexer.current_token() == LuaTokenKind::TkDbColon
{
statement(fs)?;
}
checkrepeated(fs, name)?;
let is_last = block_follow(fs, false);
createlabel(fs, name, line, is_last);
Ok(())
}
fn test_then_block(fs: &mut FuncState, escapelist: &mut isize) -> Result<(), String> {
fs.lexer.bump();
let condtrue = cond(fs)?;
check(fs, LuaTokenKind::TkThen)?;
fs.lexer.bump();
block(fs)?;
if fs.lexer.current_token() == LuaTokenKind::TkElse
|| fs.lexer.current_token() == LuaTokenKind::TkElseIf
{
let jmp = code::jump(fs) as isize;
code::concat(fs, escapelist, jmp);
}
code::patchtohere(fs, condtrue);
Ok(())
}
fn ifstat(fs: &mut FuncState, line: usize) -> Result<(), String> {
let mut escapelist: isize = -1;
test_then_block(fs, &mut escapelist)?;
while fs.lexer.current_token() == LuaTokenKind::TkElseIf {
test_then_block(fs, &mut escapelist)?;
}
if testnext(fs, LuaTokenKind::TkElse) {
block(fs)?;
}
check_match(fs, LuaTokenKind::TkEnd, LuaTokenKind::TkIf, line)?;
code::patchtohere(fs, escapelist);
Ok(())
}
fn cond(fs: &mut FuncState) -> Result<isize, String> {
let mut v = expr(fs)?;
if v.kind == ExpKind::VNIL {
v.kind = ExpKind::VFALSE; }
code::goiftrue(fs, &mut v);
Ok(v.f)
}
fn whilestat(fs: &mut FuncState, line: usize) -> Result<(), String> {
fs.lexer.bump(); let whileinit = code::getlabel(fs);
let condexit = cond(fs)?;
let bl_id = fs.compiler_state.alloc_blockcnt(BlockCnt {
previous: None,
first_label: 0,
first_goto: 0,
nactvar: 0,
upval: false,
is_loop: 1,
in_scope: true,
});
enterblock(fs, bl_id, 1);
check(fs, LuaTokenKind::TkDo)?;
fs.lexer.bump(); block(fs)?; code::jumpto(fs, whileinit);
check_match(fs, LuaTokenKind::TkEnd, LuaTokenKind::TkWhile, line)?;
leaveblock(fs)?;
code::patchtohere(fs, condexit);
Ok(())
}
fn repeatstat(fs: &mut FuncState, line: usize) -> Result<(), String> {
let repeat_init = code::getlabel(fs);
let bl1_id = fs.compiler_state.alloc_blockcnt(BlockCnt {
previous: None,
first_label: 0,
first_goto: 0,
nactvar: 0,
upval: false,
is_loop: 1, in_scope: true,
});
enterblock(fs, bl1_id, 1);
let bl2_id = fs.compiler_state.alloc_blockcnt(BlockCnt {
previous: None,
first_label: 0,
first_goto: 0,
nactvar: 0,
upval: false,
is_loop: 0, in_scope: true,
});
enterblock(fs, bl2_id, 0);
fs.lexer.bump(); statlist(fs)?; check_match(fs, LuaTokenKind::TkUntil, LuaTokenKind::TkRepeat, line)?;
let mut condexit = cond(fs)?;
let bl2_upval = fs
.compiler_state
.get_blockcnt_mut(bl2_id)
.map(|bl| bl.upval)
.unwrap_or(false);
let bl2_nactvar = fs
.compiler_state
.get_blockcnt_mut(bl2_id)
.map(|bl| bl.nactvar)
.unwrap_or(0);
leaveblock(fs)?;
if bl2_upval {
let exit = code::jump(fs); code::patchtohere(fs, condexit); code::code_abc(fs, OpCode::Close, fs.reglevel(bl2_nactvar) as u32, 0, 0);
condexit = code::jump(fs) as isize; code::patchtohere(fs, exit as isize); }
code::patchlist(fs, condexit, repeat_init as isize);
leaveblock(fs)?;
Ok(())
}
fn forstat(fs: &mut FuncState, line: usize) -> Result<(), String> {
let bl_id = fs.compiler_state.alloc_blockcnt(BlockCnt {
previous: None,
first_label: 0,
first_goto: 0,
nactvar: fs.nactvar,
upval: false,
is_loop: 1,
in_scope: true,
});
enterblock(fs, bl_id, 1);
fs.lexer.bump(); let varname = str_checkname(fs)?;
match fs.lexer.current_token() {
LuaTokenKind::TkAssign => {
fornum(fs, varname, line)?;
}
LuaTokenKind::TkComma | LuaTokenKind::TkIn => {
forlist(fs, varname)?;
}
_ => {
return Err(fs.token_error("'=' or 'in' expected"));
}
}
check_match(fs, LuaTokenKind::TkEnd, LuaTokenKind::TkFor, line)?;
leaveblock(fs)?;
Ok(())
}
fn fornum(fs: &mut FuncState, varname: String, line: usize) -> Result<(), String> {
let base = fs.freereg;
fs.new_localvar("(for state)".to_string(), VarKind::VDKREG);
fs.new_localvar("(for state)".to_string(), VarKind::VDKREG);
fs.new_localvar(varname, VarKind::RDKCONST);
check(fs, LuaTokenKind::TkAssign)?; fs.lexer.bump();
let mut e = expr(fs)?;
code::exp2nextreg(fs, &mut e);
check(fs, LuaTokenKind::TkComma)?;
fs.lexer.bump();
let mut e = expr(fs)?;
code::exp2nextreg(fs, &mut e);
if testnext(fs, LuaTokenKind::TkComma) {
let mut e = expr(fs)?;
code::exp2nextreg(fs, &mut e);
} else {
code::code_asbx(fs, OpCode::LoadI, fs.freereg as u32, 1);
code::reserve_regs(fs, 1);
}
fs.adjust_local_vars(2)?;
let prep_pc = code::code_asbx(fs, OpCode::ForPrep, base as u32, 0);
fs.freereg -= 1;
check(fs, LuaTokenKind::TkDo)?;
fs.lexer.bump();
let bl_id = fs.compiler_state.alloc_blockcnt(BlockCnt {
previous: None,
first_label: 0,
first_goto: 0,
nactvar: fs.nactvar,
upval: false,
is_loop: 0, in_scope: true,
});
enterblock(fs, bl_id, 0);
fs.adjust_local_vars(1)?;
code::reserve_regs(fs, 1);
block(fs)?;
leaveblock(fs)?;
let loop_pc = code::code_abx(fs, OpCode::ForLoop, base as u32, 0);
fix_for_jump(fs, prep_pc, loop_pc, false)?;
fix_for_jump(fs, loop_pc, prep_pc + 1, true)?;
code::fixline(fs, line);
Ok(())
}
fn forlist(fs: &mut FuncState, indexname: String) -> Result<(), String> {
let mut nvars = 4;
let base = fs.freereg;
fs.new_localvar("(for state)".to_string(), VarKind::VDKREG);
fs.new_localvar("(for state)".to_string(), VarKind::VDKREG);
fs.new_localvar("(for state)".to_string(), VarKind::VDKREG);
fs.new_localvar(indexname, VarKind::RDKCONST);
while testnext(fs, LuaTokenKind::TkComma) {
let varname = str_checkname(fs)?;
fs.new_localvar(varname, VarKind::VDKREG);
nvars += 1;
}
check(fs, LuaTokenKind::TkIn)?;
fs.lexer.bump();
let line = fs.lexer.line;
let mut e = ExpDesc::new_void();
let nexps = explist(fs, &mut e)?;
adjust_assign(fs, 4, nexps, &mut e);
fs.adjust_local_vars(3)?;
mark_to_be_closed(fs);
code::checkstack(fs, 2);
fs.check_pending_checklimit()?;
check(fs, LuaTokenKind::TkDo)?;
fs.lexer.bump();
let prep_pc = code::code_abx(fs, OpCode::TForPrep, base as u32, 0);
fs.freereg -= 1;
let bl_id = fs.compiler_state.alloc_blockcnt(BlockCnt {
previous: None,
first_label: 0,
first_goto: 0,
nactvar: fs.nactvar,
upval: false,
is_loop: 0, in_scope: true,
});
enterblock(fs, bl_id, 0);
fs.adjust_local_vars((nvars - 3) as u16)?;
code::reserve_regs(fs, (nvars - 3) as u8);
fs.check_pending_checklimit()?;
block(fs)?;
leaveblock(fs)?;
let label_after_block = fs.pc;
fix_for_jump(fs, prep_pc, label_after_block, false)?;
code::code_abc(fs, OpCode::TForCall, base as u32, 0, (nvars - 3) as u32);
code::fixline(fs, line);
let endfor_pc = code::code_abx(fs, OpCode::TForLoop, base as u32, 0);
fix_for_jump(fs, endfor_pc, prep_pc + 1, true)?;
code::fixline(fs, line);
Ok(())
}
fn funcstat(fs: &mut FuncState, line: usize) -> Result<(), String> {
fs.lexer.bump();
let mut base = ExpDesc::new_void();
expr_parser::singlevar(fs, &mut base)?;
while fs.lexer.current_token() == LuaTokenKind::TkDot {
expr_parser::fieldsel(fs, &mut base)?;
}
let is_method = fs.lexer.current_token() == LuaTokenKind::TkColon;
if is_method {
expr_parser::fieldsel(fs, &mut base)?;
}
let mut func_val = ExpDesc::new_void();
expr_parser::body(fs, &mut func_val, is_method)?;
check_readonly_at_line(fs, &mut base, line)?;
storevar(fs, &base, &mut func_val);
code::fixline(fs, line);
Ok(())
}
fn localfunc(fs: &mut FuncState) -> Result<(), String> {
let name = str_checkname(fs)?;
fs.new_localvar(name, VarKind::VDKREG);
fs.adjust_local_vars(1)?;
let mut v = ExpDesc::new_void();
body(fs, &mut v, false)?;
Ok(())
}
fn getvarattribute(fs: &mut FuncState, default: VarKind) -> Result<VarKind, String> {
if testnext(fs, LuaTokenKind::TkLt) {
if fs.lexer.current_token() != LuaTokenKind::TkName {
return Err("expected attribute name".to_string());
}
let attr = fs.lexer.current_token_text().to_string();
fs.lexer.bump();
check(fs, LuaTokenKind::TkGt)?;
fs.lexer.bump();
if attr == "const" {
Ok(VarKind::RDKCONST)
} else if attr == "close" {
Ok(VarKind::RDKTOCLOSE)
} else {
Err(fs.sem_error(&format!("unknown attribute '{}'", attr)))
}
} else {
Ok(default) }
}
fn getglobalattribute(fs: &mut FuncState, default: VarKind) -> Result<VarKind, String> {
let kind = getvarattribute(fs, default)?;
match kind {
VarKind::RDKTOCLOSE => Err(fs.sem_error("global variables cannot be to-be-closed")),
VarKind::RDKCONST => Ok(VarKind::GDKCONST), _ => Ok(kind),
}
}
fn fix_for_jump(fs: &mut FuncState, pc: usize, dest: usize, back: bool) -> Result<(), String> {
let mut offset = (dest as isize) - (pc as isize) - 1;
if back {
offset = -offset;
}
if offset < 0 || offset > Instruction::MAX_BX as isize {
return Err(format!(
"Warning: for-loop jump offset out of range: offset={}",
offset
));
}
Instruction::set_bx(&mut fs.chunk.code[pc], offset as u32);
Ok(())
}
pub fn mark_upval(fs: &mut FuncState, level: u16) {
let mut bl_id_opt = fs.block_cnt_id;
while let Some(bl_id) = bl_id_opt {
if let Some(bl) = fs.compiler_state.get_blockcnt_mut(bl_id) {
if bl.nactvar <= level {
bl.upval = true;
fs.needclose = true;
break;
}
bl_id_opt = bl.previous;
}
}
}
fn mark_to_be_closed(fs: &mut FuncState) {
if let Some(bl) = fs.current_block_cnt() {
bl.upval = true;
bl.in_scope = true; }
fs.needclose = true;
}
fn check_to_close(fs: &mut FuncState, level: isize) {
if level != -1 {
mark_to_be_closed(fs);
let reg_level = fs.reglevel(level as u16);
code::code_abc(fs, OpCode::Tbc, reg_level as u32, 0, 0);
}
}
fn localstat(fs: &mut FuncState) -> Result<(), String> {
let mut toclose: isize = -1;
let mut nvars = 0;
let mut e = ExpDesc::new_void();
let defkind = getvarattribute(fs, VarKind::VDKREG)?;
loop {
let vname = str_checkname(fs)?;
let kind = getvarattribute(fs, defkind)?;
fs.new_localvar(vname, kind);
if kind == VarKind::RDKTOCLOSE {
if toclose != -1 {
return Err(fs.sem_error("multiple to-be-closed variables in local list"));
}
toclose = (fs.nactvar + nvars) as isize;
}
nvars += 1;
if !testnext(fs, LuaTokenKind::TkComma) {
break;
}
}
let nexps = if testnext(fs, LuaTokenKind::TkAssign) {
explist(fs, &mut e)?
} else {
e.kind = ExpKind::VVOID;
0
};
let last_vidx = fs.nactvar + nvars - 1;
let can_optimize = if let Some(var_desc) = fs.get_local_var_desc(last_vidx) {
nvars as usize == nexps && var_desc.kind == VarKind::RDKCONST
} else {
false
};
let const_value = if can_optimize {
code::exp2const(fs, &e)
} else {
None
};
if let Some(value) = const_value {
let ridx = fs.reglevel(fs.nactvar);
if let Some(var_desc) = fs.get_local_var_desc(last_vidx) {
var_desc.kind = VarKind::RDKCTC;
var_desc.const_value = Some(value); var_desc.ridx = ridx as i16;
}
fs.adjust_local_vars(nvars - 1)?;
fs.nactvar += 1;
check_to_close(fs, toclose);
return Ok(());
}
adjust_assign(fs, nvars as usize, nexps, &mut e);
fs.check_pending_checklimit()?; fs.adjust_local_vars(nvars)?;
check_to_close(fs, toclose);
Ok(())
}
fn vkisvar(k: ExpKind) -> bool {
use ExpKind;
matches!(
k,
ExpKind::VLOCAL
| ExpKind::VVARGVAR
| ExpKind::VGLOBAL
| ExpKind::VUPVAL
| ExpKind::VCONST
| ExpKind::VINDEXED
| ExpKind::VVARGIND | ExpKind::VINDEXUP
| ExpKind::VINDEXI
| ExpKind::VINDEXSTR
)
}
fn vkisindexed(k: ExpKind) -> bool {
use ExpKind;
matches!(
k,
ExpKind::VINDEXED
| ExpKind::VINDEXUP
| ExpKind::VINDEXI
| ExpKind::VINDEXSTR
| ExpKind::VVARGIND
)
}
fn check_conflict(fs: &mut FuncState, lh_id: LhsAssignId, v: &ExpDesc) {
use ExpKind;
let mut conflict = false;
let extra = fs.freereg;
let mut nodes_to_update: Vec<(LhsAssignId, bool, bool)> = Vec::new();
let mut current = Some(lh_id);
while let Some(node_id) = current {
if let Some(node) = fs.compiler_state.get_lhs_assign(node_id) {
let mut update_t = false;
let mut update_idx = false;
if vkisindexed(node.v.kind) {
if node.v.kind == ExpKind::VINDEXUP {
if v.kind == ExpKind::VUPVAL && node.v.u.ind().t == v.u.info() as i16 {
conflict = true;
update_t = true;
}
} else {
if v.kind == ExpKind::VLOCAL && node.v.u.ind().t == v.u.var().ridx {
conflict = true;
update_t = true; }
if node.v.kind == ExpKind::VINDEXED
&& v.kind == ExpKind::VLOCAL
&& node.v.u.ind().idx == v.u.var().ridx
{
conflict = true;
update_idx = true; }
}
}
if update_t || update_idx {
nodes_to_update.push((node_id, update_t, update_idx));
}
current = node.prev;
} else {
break;
}
}
if conflict {
if v.kind == ExpKind::VLOCAL {
code::code_abc(fs, OpCode::Move, extra as u32, v.u.var().ridx as u32, 0);
} else {
code::code_abc(fs, OpCode::GetUpval, extra as u32, v.u.info() as u32, 0);
}
code::reserve_regs(fs, 1);
for (node_id, update_t, update_idx) in nodes_to_update {
if let Some(node) = fs.compiler_state.get_lhs_assign_mut(node_id) {
if update_t {
if node.v.kind == ExpKind::VINDEXUP {
node.v.kind = ExpKind::VINDEXSTR;
}
node.v.u.ind_mut().t = extra as i16;
}
if update_idx {
node.v.u.ind_mut().idx = extra as i16;
}
}
}
}
}
fn adjust_assign(fs: &mut FuncState, nvars: usize, nexps: usize, e: &mut ExpDesc) {
use ExpKind;
let needed = nvars as isize - nexps as isize;
if needed > 0 {
code::checkstack(fs, needed as u8);
}
if matches!(e.kind, ExpKind::VCALL | ExpKind::VVARARG) {
let mut extra = needed + 1;
if extra < 0 {
extra = 0;
}
code::setreturns(fs, e, extra as u8);
} else {
if e.kind != ExpKind::VVOID {
code::exp2nextreg(fs, e);
}
if needed > 0 {
code::nil(fs, fs.freereg, needed as u8);
}
}
if needed > 0 {
code::reserve_regs(fs, needed as u8);
} else {
fs.freereg = (fs.freereg as isize + needed) as u8;
}
}
fn storevar(fs: &mut FuncState, var: &ExpDesc, ex: &mut ExpDesc) {
use ExpKind;
match var.kind {
ExpKind::VLOCAL => {
code::free_exp(fs, ex);
code::exp2reg(fs, ex, var.u.var().ridx as u8);
}
ExpKind::VUPVAL => {
let e = code::exp2anyreg(fs, ex);
code::code_abc(fs, OpCode::SetUpval, e as u32, var.u.info() as u32, 0);
}
ExpKind::VINDEXED => {
code::code_abrk(
fs,
OpCode::SetTable,
var.u.ind().t as u32,
var.u.ind().idx as u32,
ex,
);
}
ExpKind::VVARGIND => {
fs.chunk.needs_vararg_table = true;
code::code_abrk(
fs,
OpCode::SetTable,
var.u.ind().t as u32,
var.u.ind().idx as u32,
ex,
);
}
ExpKind::VINDEXUP => {
code::code_abrk(
fs,
OpCode::SetTabUp,
var.u.ind().t as u32,
var.u.ind().idx as u32,
ex,
);
}
ExpKind::VINDEXI => {
code::code_abrk(
fs,
OpCode::SetI,
var.u.ind().t as u32,
var.u.ind().idx as u32,
ex,
);
}
ExpKind::VINDEXSTR => {
code::code_abrk(
fs,
OpCode::SetField,
var.u.ind().t as u32,
var.u.ind().idx as u32,
ex,
);
}
_ => {
}
}
code::free_exp(fs, ex);
}
fn restassign(fs: &mut FuncState, lh_id: LhsAssignId, nvars: usize) -> Result<(), String> {
let mut e = ExpDesc::new_void();
let mut lh_v_for_check = {
let lh = fs
.compiler_state
.get_lhs_assign(lh_id)
.ok_or_else(|| "invalid LhsAssign id".to_string())?;
lh.v.clone()
};
check_readonly(fs, &mut lh_v_for_check)?;
let lh_v = {
let lh = fs
.compiler_state
.get_lhs_assign(lh_id)
.ok_or_else(|| "invalid LhsAssign id".to_string())?;
lh.v.clone()
};
if !vkisvar(lh_v.kind) {
return Err(fs.syntax_error("syntax error").to_string());
}
if testnext(fs, LuaTokenKind::TkComma) {
let mut nv_v = ExpDesc::new_void();
expr_parser::suffixedexp(fs, &mut nv_v)?;
if !vkisindexed(nv_v.kind) {
check_conflict(fs, lh_id, &nv_v);
}
let updated_lh_v = {
let lh = fs
.compiler_state
.get_lhs_assign(lh_id)
.ok_or_else(|| "invalid LhsAssign id".to_string())?;
lh.v.clone()
};
let prev_id = fs
.compiler_state
.get_lhs_assign(lh_id)
.and_then(|lh| lh.prev);
let new_prev_id = fs.compiler_state.alloc_lhs_assign(LhsAssign {
prev: prev_id,
v: updated_lh_v,
});
let nv_id = fs.compiler_state.alloc_lhs_assign(LhsAssign {
prev: Some(new_prev_id),
v: nv_v,
});
restassign(fs, nv_id, nvars + 1)?;
} else {
check(fs, LuaTokenKind::TkAssign)?;
fs.lexer.bump(); let nexps = explist(fs, &mut e)?;
if nexps != nvars {
adjust_assign(fs, nvars, nexps, &mut e);
} else {
code::setoneret(fs, &mut e);
let updated_lh_v = fs
.compiler_state
.get_lhs_assign(lh_id)
.map(|lh| lh.v.clone())
.unwrap_or(lh_v.clone());
storevar(fs, &updated_lh_v, &mut e);
return Ok(());
}
}
e.kind = ExpKind::VNONRELOC;
e.u = ExpUnion::Info((fs.freereg - 1) as i32);
let updated_lh_v = fs
.compiler_state
.get_lhs_assign(lh_id)
.map(|lh| lh.v.clone())
.unwrap_or(lh_v);
storevar(fs, &updated_lh_v, &mut e);
Ok(())
}
fn exprstat(fs: &mut FuncState) -> Result<(), String> {
let mut lh_v = ExpDesc::new_void();
suffixedexp(fs, &mut lh_v)?;
if fs.lexer.current_token() == LuaTokenKind::TkAssign
|| fs.lexer.current_token() == LuaTokenKind::TkComma
{
let lh_id = fs.compiler_state.alloc_lhs_assign(LhsAssign {
prev: None,
v: lh_v,
});
restassign(fs, lh_id, 1)?;
} else {
if lh_v.kind != ExpKind::VCALL {
return Err(fs.syntax_error("syntax error").to_string());
}
let pc = lh_v.u.info() as usize;
if pc < fs.chunk.code.len() {
Instruction::set_c(&mut fs.chunk.code[pc], 1);
}
}
Ok(())
}
fn check_readonly(fs: &mut FuncState, e: &mut ExpDesc) -> Result<(), String> {
use ExpKind;
if e.kind == ExpKind::VVARGIND {
fs.chunk.needs_vararg_table = true;
e.kind = ExpKind::VINDEXED;
}
let varname: Option<String> = match e.kind {
ExpKind::VCONST => {
let vidx = e.u.info() as usize;
if let Some(var_desc) = fs.actvar.get(vidx) {
Some(var_desc.name.clone())
} else {
Some("<const>".to_string())
}
}
ExpKind::VLOCAL | ExpKind::VVARGVAR => {
let vidx = e.u.var().vidx;
if let Some(var_desc) = fs.get_local_var_desc(vidx) {
match var_desc.kind {
VarKind::RDKCONST
| VarKind::RDKCTC
| VarKind::RDKTOCLOSE
| VarKind::RDKVAVAR => Some(var_desc.name.clone()),
_ => None,
}
} else {
None
}
}
ExpKind::VUPVAL => {
let upval_idx = e.u.info() as usize;
if upval_idx < fs.upvalues.len() {
let upval = &fs.upvalues[upval_idx];
match upval.kind {
VarKind::RDKCONST
| VarKind::RDKCTC
| VarKind::RDKTOCLOSE
| VarKind::RDKVAVAR => Some(upval.name.clone()),
_ => None,
}
} else {
None
}
}
ExpKind::VINDEXUP | ExpKind::VINDEXSTR | ExpKind::VINDEXED => {
if e.u.ind().ro {
let keystr_idx = e.u.ind().keystr;
if keystr_idx >= 0 && (keystr_idx as usize) < fs.chunk.constants.len() {
if let Some(name) = fs.chunk.constants[keystr_idx as usize].as_str() {
Some(name.to_string())
} else {
Some("<readonly field>".to_string())
}
} else {
Some("<readonly field>".to_string())
}
} else {
None
}
}
ExpKind::VINDEXI => {
return Ok(());
}
_ => None,
};
if let Some(name) = varname {
let msg = format!("attempt to assign to const variable '{}'", name);
return Err(fs.sem_error(&msg));
}
Ok(())
}
fn check_readonly_at_line(fs: &mut FuncState, e: &mut ExpDesc, line: usize) -> Result<(), String> {
let saved_line = fs.lexer.line;
let saved_lastline = fs.lexer.lastline;
fs.lexer.line = line;
fs.lexer.lastline = line;
let result = check_readonly(fs, e);
fs.lexer.line = saved_line;
fs.lexer.lastline = saved_lastline;
result
}
fn globalstatfunc(fs: &mut FuncState, line: usize) -> Result<(), String> {
fs.lexer.bump();
if testnext(fs, LuaTokenKind::TkFunction) {
globalfunc(fs, line)?;
} else {
globalstat(fs)?;
}
Ok(())
}
fn globalstat(fs: &mut FuncState) -> Result<(), String> {
let defkind = getglobalattribute(fs, VarKind::GDKREG)?;
if testnext(fs, LuaTokenKind::TkMul) {
fs.new_localvar(String::new(), defkind);
fs.nactvar += 1; return Ok(());
}
globalnames(fs, defkind)?;
Ok(())
}
fn globalnames(fs: &mut FuncState, defkind: VarKind) -> Result<(), String> {
let mut nvars = 0;
let mut lastidx: u16;
loop {
if fs.lexer.current_token() != LuaTokenKind::TkName {
return Err(fs.syntax_error("name expected"));
}
let vname = fs.lexer.current_token_text().to_string();
fs.lexer.bump();
let kind = getglobalattribute(fs, defkind)?;
lastidx = fs.new_localvar(vname, kind);
nvars += 1;
if !testnext(fs, LuaTokenKind::TkComma) {
break;
}
}
if testnext(fs, LuaTokenKind::TkAssign) {
let line = fs.lexer.line; let firstidx = (lastidx as i32 - nvars + 1) as usize;
initglobal(fs, nvars, firstidx, 0, line)?;
}
fs.nactvar = (fs.nactvar as i32 + nvars) as u16;
Ok(())
}
fn initglobal(
fs: &mut FuncState,
nvars: i32,
firstidx: usize,
n: i32,
line: usize,
) -> Result<(), String> {
if n == nvars {
let mut e = ExpDesc::new_void();
let nexps = explist(fs, &mut e)?;
adjust_assign(fs, nvars as usize, nexps, &mut e);
} else {
let var_desc = &fs.actvar[firstidx + n as usize];
let varname = var_desc.name.clone();
let mut var = ExpDesc::new_void();
buildglobal(fs, &varname, &mut var)?;
initglobal(fs, nvars, firstidx, n + 1, line)?;
checkglobal(fs, &varname, line)?;
storevartop(fs, &var);
}
Ok(())
}
fn globalfunc(fs: &mut FuncState, line: usize) -> Result<(), String> {
if fs.lexer.current_token() != LuaTokenKind::TkName {
return Err(fs.syntax_error("function name expected"));
}
let fname = fs.lexer.current_token_text().to_string();
fs.lexer.bump();
fs.new_localvar(fname.clone(), VarKind::GDKREG);
fs.nactvar += 1;
let mut var = ExpDesc::new_void();
buildglobal(fs, &fname, &mut var)?;
let mut b = ExpDesc::new_void();
body(fs, &mut b, false)?;
checkglobal(fs, &fname, line)?;
storevar(fs, &var, &mut b);
code::fixline(fs, line);
Ok(())
}
fn checkglobal(fs: &mut FuncState, varname: &str, line: usize) -> Result<(), String> {
let mut var = ExpDesc::new_void();
buildglobal(fs, varname, &mut var)?;
let k = var.u.ind().keystr;
code::codecheckglobal(fs, &mut var, k, line);
Ok(())
}
fn storevartop(fs: &mut FuncState, var: &ExpDesc) {
let mut e = ExpDesc::new_void();
e.kind = ExpKind::VNONRELOC;
e.u = ExpUnion::Info((fs.freereg - 1) as i32);
storevar(fs, var, &mut e);
}