use std::cell::RefCell;
use std::collections::HashMap;
use std::io::{Cursor, Write};
use std::mem;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
use rand::{thread_rng, SeedableRng};
use rand_xoshiro::Xoshiro256StarStar;
mod args;
mod calls;
mod code;
mod gc;
mod operators;
mod stdlib;
use crate::ast::Parser;
use crate::compiler::{Compiler, LUA_ENV};
use crate::error::{
Error, LuaError, LuaErrorSrc, LuaTrace, LuaTraceScope, LuaTraceSrc, LuaTraces, Result,
};
use crate::value::{
CallResult, FileDesc, FileHandle, FuncBuiltin, FuncBuiltinRaw, FuncClosure, FuncDef, Numeric,
ProtectedHandler, Table, Thread, ThreadState, UserData, Value, ValueType, HOOK_CALL,
HOOK_COUNT, HOOK_LINE, HOOK_RET,
};
use crate::LUA_VERSION;
use gc::Heap;
pub use calls::FuncCont;
use calls::ReturnType;
pub use code::{Code, FuncObject, Literal, OpCode, Oper, VarType};
pub use operators::{BinOp, UnOp};
#[derive(Clone, Debug)]
enum Local {
Temp,
Stack {
val: Value,
name: String,
to_close: bool,
},
Heap {
var: Rc<RefCell<Value>>,
name: String,
to_close: bool,
},
}
#[derive(Debug)]
pub struct StackFrame {
recursive: bool,
tail: bool,
protected: Option<Option<ProtectedHandler>>,
func_def: Rc<FuncDef>,
ret: ReturnType,
ret_reg: Option<usize>,
regs: Vec<Value>,
locals: Vec<Local>,
ups: Vec<(RefCell<Rc<RefCell<Value>>>, String)>,
varargs: Vec<Value>,
meta: Option<&'static str>,
transfer: (usize, usize),
}
#[derive(Debug)]
enum OpResult {
Return(Value),
Yield(Value),
}
pub struct VM {
code: Rc<Code>,
pc: usize,
frames: Vec<StackFrame>,
thread: Rc<RefCell<Thread>>,
rust_call_depth: usize,
rng: Xoshiro256StarStar,
env: Rc<RefCell<Table>>,
registry: Rc<RefCell<Table>>,
heap: Heap,
strings: HashMap<Vec<u8>, *const Vec<u8>>,
metas_shared: HashMap<ValueType, Rc<RefCell<Table>>>,
started: Instant,
warn: bool,
interrupt: Arc<AtomicBool>,
inp: Rc<RefCell<FileHandle>>,
out: Rc<RefCell<FileHandle>>,
stdout: Rc<RefCell<FileHandle>>,
}
impl Default for VM {
fn default() -> Self {
let env = stdlib::StdLib::build().unwrap();
let registry = {
let mut meta = Table::default();
meta.set(Value::str("__mode"), Value::str("k")).unwrap();
let mut hooks = Table::default();
hooks.set_meta(Some(Rc::new(RefCell::new(meta))));
let mut reg = Table::default();
reg.set(
Value::str("_HOOKKEY"),
Value::Table(Rc::new(RefCell::new(hooks))),
)
.unwrap();
let package = env.borrow().get(&Value::str("package")).to_table().unwrap();
reg.set(
Value::str("_LOADED"),
package.borrow().get(&Value::str("loaded")),
)
.unwrap();
reg.set(
Value::str("_PRELOAD"),
package.borrow().get(&Value::str("preload")),
)
.unwrap();
reg.set(Value::str("_PACKAGE"), Value::Table(package))
.unwrap();
Rc::new(RefCell::new(reg))
};
let mut metas_shared = HashMap::new();
if let Value::Table(tbl) = env.borrow().get(&Value::str("string")) {
let mut meta = Table::default();
meta.set(Value::str("__index"), Value::Table(tbl.clone()))
.unwrap();
metas_shared.insert(ValueType::String, Rc::new(RefCell::new(meta)));
}
let inp = env
.borrow()
.get(&Value::str("io"))
.to_table()
.unwrap()
.borrow()
.get(&Value::str("stdin"))
.to_file()
.unwrap();
let out = env
.borrow()
.get(&Value::str("io"))
.to_table()
.unwrap()
.borrow()
.get(&Value::str("stdout"))
.to_file()
.unwrap();
Self {
code: Rc::new(Code::new()),
pc: 0,
frames: Vec::new(),
thread: Rc::new(RefCell::new(Thread {
state: ThreadState::Running,
func: None,
frames: Vec::new(),
pc: None,
protected: None,
error: None,
error_close: None,
hook_allowed: true,
hook: None,
hook_mask: 0,
hook_count: 0,
hook_force_line: false,
curr_count: 0,
curr_line: 0,
})),
rust_call_depth: 0,
rng: Xoshiro256StarStar::from_rng(thread_rng()).unwrap(),
env,
registry,
heap: Heap::default(),
strings: HashMap::new(),
metas_shared,
started: Instant::now(),
warn: false,
interrupt: Arc::new(AtomicBool::new(false)),
stdout: out.clone(),
inp,
out,
}
}
}
impl VM {
pub fn with_buffer() -> Self {
let vm = Self::default();
vm.stdout.replace(FileHandle::new(
FileDesc::Buffer(Rc::new(RefCell::new(Cursor::new(Vec::new())))),
None,
));
vm
}
pub fn take_buffer(self) -> Option<Vec<u8>> {
self.stdout
.replace(FileHandle::new(
FileDesc::Buffer(Rc::new(RefCell::new(Cursor::new(Vec::new())))),
None,
))
.take_buffer()
}
pub fn set_args(&mut self, filename: Option<&str>) {
let mut args = Table::default();
let offset = filename
.and_then(|f| std::env::args().position(|s| s == f))
.unwrap_or_default() as i64;
for (i, arg) in std::env::args().enumerate() {
args.set(Value::int(i as i64 - offset), Value::string(arg))
.unwrap();
}
self.env
.borrow_mut()
.set(Value::str("arg"), Value::Table(Rc::new(RefCell::new(args))))
.unwrap();
}
pub fn set_warn(&mut self, warn: bool) {
self.warn = warn;
}
pub fn set_interrupt(&mut self, interrupt: Arc<AtomicBool>) {
self.interrupt = interrupt;
}
pub fn load_env(&mut self) {
let (major, minor) = LUA_VERSION;
let package = self
.env
.borrow()
.get(&Value::str("package"))
.to_table()
.expect("no table 'package'");
macro_rules! load {
( $env:literal ) => {{
let env =
std::env::var_os(format!("LUA_{}_{}_{}", $env.to_uppercase(), major, minor))
.or(std::env::var_os(format!("LUA_{}", $env.to_uppercase())));
let var = $env.to_lowercase();
if let Some(path) = env {
let mut path = path.to_string_lossy().into_owned();
if path.find(";;").is_some() {
path = path
.replace(
";;",
&format!(
";{};",
String::from_utf8_lossy(
package
.borrow()
.get(&Value::str(&var))
.to_string()
.expect("string expected"),
)
),
)
.trim_matches(';')
.to_string();
}
package
.borrow_mut()
.set(Value::str(&var), Value::string(path))
.expect("could not set table");
}
}};
}
load!("PATH");
load!("CPATH");
}
pub fn get_global(&self, name: &str) -> Value {
self.env.borrow().get(&Value::str(name))
}
pub fn load_lib(&mut self, glob: &str, module: &str) -> Result<()> {
let module = self.module_load(module.as_bytes().to_vec())?.into_single();
self.env.borrow_mut().set(Value::str(glob), module)
}
pub fn run_str(&mut self, input: &[u8], filename: Option<&str>) -> Result<Value> {
self._run_file("t".as_bytes(), input, filename)
}
pub fn run_file(&mut self, input: &[u8], filename: Option<&str>) -> Result<Value> {
self._run_file("bt".as_bytes(), input, filename)
}
fn _run_file(&mut self, mode: &[u8], input: &[u8], filename: Option<&str>) -> Result<Value> {
let main = self.load_file(
mode,
input,
filename.map(|f| {
let mut b = f.as_bytes().to_owned();
b.insert(0, b'@');
b
}),
Value::Table(self.env.clone()),
)?;
let args = self
.env
.borrow()
.get(&Value::str("arg"))
.to_table()
.map_err(|_| {
Error::from_lua(LuaError::CustomString("'arg' is not a table".to_string()))
})?;
let count = args.borrow().border();
let args = (1..=count)
.map(|idx| args.borrow().get(&Value::int(idx as i64)))
.collect();
self.run(main, args)
}
pub fn run_str_interactive(&mut self, mut input: &[u8]) -> Result<()> {
if input.starts_with(&[b'=']) {
input = &input[1..];
}
let expr = {
let mut expr = vec![b'r', b'e', b't', b'u', b'r', b'n', b' '];
expr.extend_from_slice(input);
expr
};
let src = "=stdin".to_owned().into_bytes();
let main = if Parser::parse(&expr, Rc::new(src.clone())).is_ok() {
self.load_file(
"t".as_bytes(),
&expr,
Some(src),
Value::Table(self.env.clone()),
)
} else {
self.load_file(
"t".as_bytes(),
input,
Some(src),
Value::Table(self.env.clone()),
)
}?;
let ret = self.run(main, vec![])?;
let args = ret.into_vec();
if !args.is_empty() {
let print = self
.env
.borrow()
.get(&Value::str("print"))
.to_func()
.map_err(|_| {
Error::from_lua(LuaError::CustomString("error calling 'print'".to_string()))
})?;
match self.call_prepare(print, args, None, None, false, None)? {
CallResult::Continue => {
self.run_loop()?;
}
CallResult::Return(_) => {}
CallResult::Yield(..) => unreachable!(),
}
}
Ok(())
}
pub fn load_file(
&mut self,
mode: &[u8],
input: &[u8],
name: Option<Vec<u8>>,
env: Value,
) -> Result<Rc<FuncDef>> {
if stdlib::dump::has_sig(&input) {
if !mode.contains(&b'b') {
return err!(LuaError::LoadMode(
"binary",
String::from_utf8_lossy(&mode).into_owned()
));
}
stdlib::dump::undump(self, &input, env)
} else {
if !mode.contains(&b't') {
return err!(LuaError::LoadMode(
"text",
String::from_utf8_lossy(&mode).into_owned()
));
}
let source = Rc::new(name.unwrap_or_else(|| input.to_owned()));
let main = self.compile_str(input, source.clone())?;
let main = self.alloc_closure(FuncClosure {
regs: main.regs,
locals_cap: main.locals_cap,
params: main.params,
varargs: main.varargs,
ups: vec![(
RefCell::new(Rc::new(RefCell::new(env))),
LUA_ENV.to_string(),
)],
code: main.code,
source,
linedefined: main.linedefined,
lastlinedefined: main.lastlinedefined,
});
Ok(main)
}
}
fn compile_str(&mut self, input: &[u8], source: Rc<Vec<u8>>) -> Result<FuncObject> {
let ast = Parser::parse(input, source.clone())?;
let main = Compiler::compile(ast, source)?;
assert!(main.ups.len() == 1);
assert!(main.varargs);
Ok(main)
}
fn run(&mut self, main: Rc<FuncDef>, args: Vec<Value>) -> Result<Value> {
self.call_prepare(main, args, None, None, false, None)?;
let ret = match self.run_loop()? {
OpResult::Return(ret) => ret,
OpResult::Yield(..) => unreachable!(),
};
assert!(self.frames.is_empty());
assert!(self.rust_call_depth == 0);
let _ = self.out.borrow_mut().flush();
Ok(ret)
}
fn pcall_handle_err(&mut self, error: Error) -> Error {
let handler = self.thread.borrow().protected.clone();
match handler {
Some(handler) => match handler {
ProtectedHandler::PCall => error,
ProtectedHandler::XPCall(func) => {
const MAX_DEPTH: usize = 20;
if self.frames.len() > MAX_DEPTH
&& self
.frames
.iter()
.filter(|f| Rc::ptr_eq(&f.func_def, &func))
.count()
>= MAX_DEPTH
{
Error::from_lua(LuaError::ErrorHandler)
} else {
match self.call_recursive(func, vec![error.into_value()]) {
Ok(val) => Error::from_lua(LuaError::CustomValue(val.into_single())),
Err(err) => self.pcall_handle_err(err),
}
}
}
},
None => error,
}
}
fn traceback_error(&self, error: Error) -> Error {
if !error.has_trace() {
error.traces(self.traceback(self.thread.clone()))
} else {
error
}
}
fn traceback(&self, threadrc: Rc<RefCell<Thread>>) -> LuaTraces {
let thread = threadrc.borrow();
let frames = if Rc::ptr_eq(&threadrc, &self.thread) {
&self.frames
} else {
&thread.frames
};
let mut traces = LuaTraces::default();
for level in (0..frames.len()).rev() {
let (name, namewhat) = self.call_info(frames, level);
let name = name.map(|name| String::from_utf8_lossy(&name).into_owned());
let trace = match &*frames[level].func_def {
FuncDef::Defined(func) => {
let pos = match frames.get(level + 1) {
Some(frame) => match &frame.ret {
ReturnType::Lua(code, pc) | ReturnType::RustLua(_, code, pc) => {
code.get_pos(pc.saturating_sub(1))
}
ReturnType::Rust(..) => unreachable!(),
},
None => self.code.get_pos(self.pc.saturating_sub(1)),
};
let src = match func.source.first() {
Some(b'@' | b'=') => LuaTraceSrc::File(
String::from_utf8_lossy(&func.source[1..]).into_owned(),
pos,
),
Some(_) | None => LuaTraceSrc::Str(
String::from_utf8_lossy(&func.source).into_owned(),
pos,
),
};
let scope = if let Some(name) = name {
LuaTraceScope::Source(namewhat, name)
} else if func.linedefined > 0
&& matches!(func.source.first(), Some(b'@' | b'='))
{
LuaTraceScope::Function(
String::from_utf8_lossy(&(*func.source)[1..]).into_owned(),
func.linedefined,
)
} else {
LuaTraceScope::Main
};
LuaTrace::Lua(src, scope)
}
FuncDef::Builtin(FuncBuiltin { module, name, .. })
| FuncDef::BuiltinRaw(FuncBuiltinRaw { module, name, .. }) => {
LuaTrace::Rust(if module.is_empty() {
name.to_string()
} else {
format!("{}.{}", module, name)
})
}
};
traces.trace(trace);
if frames[level].tail {
traces.trace(LuaTrace::Tail);
}
}
traces
}
fn call_info(&self, frames: &Vec<StackFrame>, level: usize) -> (Option<Vec<u8>>, &'static str) {
match level {
0 => (None, ""),
level => {
let parent = &frames[level - 1];
let current = &frames[level];
match &*parent.func_def {
FuncDef::Defined(func) => {
let pc_call = match current.ret {
ReturnType::Lua(_, pc) | ReturnType::RustLua(_, _, pc) => {
pc.saturating_sub(1)
}
ReturnType::Rust(..) => return (None, ""),
};
match func.code.get_op(pc_call) {
OpCode::Call { func_reg, .. } if !current.recursive => {
Self::reg_info(parent, func_reg, func.code.clone(), pc_call)
}
_ => {
return if let Some(meta) = current.meta {
(Some(meta.as_bytes().to_vec()), "metamethod")
} else {
(Some(vec![b'?']), "hook")
}
}
}
}
_ => (None, ""),
}
}
}
}
fn reg_info(
frame: &StackFrame,
func_reg: &usize,
code: Rc<Code>,
pc_call: usize,
) -> (Option<Vec<u8>>, &'static str) {
for idx in (0..pc_call).rev() {
match code.get_op(idx) {
OpCode::Jump { .. }
| OpCode::JumpClose { .. }
| OpCode::JumpIf { .. }
| OpCode::JumpIfNot { .. } => return (None, ""),
OpCode::GlobalGet { name, dst_reg, .. } if dst_reg == func_reg => {
return (Some(name.clone().into_bytes()), "global");
}
OpCode::LocalGet { src_loc, dst_reg } if dst_reg == func_reg => {
return match frame.locals.get(*src_loc) {
Some(Local::Stack { name, .. } | Local::Heap { name, .. }) => {
if name == "(for state)" {
(Some("for iterator".as_bytes().to_vec()), "for iterator")
} else {
(Some(name.clone().into_bytes()), "local")
}
}
_ => (Some(vec![b'?']), "local"),
}
}
OpCode::TableGet {
ind_reg, dst_reg, ..
} if dst_reg == func_reg => {
if ind_reg == dst_reg {
return (
Some(match code.get_op(idx.saturating_sub(1)) {
OpCode::Lit { val, .. } => <&Literal as Into<Value>>::into(val)
.to_string_coerce()
.unwrap_or(vec![b'?']),
_ => vec![b'?'],
}),
"method",
);
} else {
return (
Some(match code.get_op(idx.saturating_sub(1)) {
OpCode::Lit { val, .. } => <&Literal as Into<Value>>::into(val)
.to_string_coerce()
.unwrap_or(vec![b'?']),
_ => match frame.regs.get(*ind_reg) {
Some(reg) => reg.to_string_coerce().unwrap_or(vec![b'?']),
None => vec![b'?'],
},
}),
"field",
);
}
}
OpCode::UpGet { src_up, dst_reg } if dst_reg == func_reg => {
return (
Some(match frame.ups.get(*src_up) {
Some(up) => up.1.clone().into_bytes(),
None => vec![b'?'],
}),
"upvalue",
);
}
_ => {}
}
}
(None, "")
}
fn close_locals_from(&mut self, loc_from: usize) -> Result<Option<CallResult>> {
loop {
let to_close = self
.frames
.last_mut()
.unwrap()
.locals
.iter_mut()
.skip(loc_from)
.rev()
.filter(|loc| match loc {
Local::Temp => false,
Local::Stack { val, to_close, .. } => *to_close && val.is_truthy(),
Local::Heap { var, to_close, .. } => *to_close && var.borrow().is_truthy(),
})
.next();
if let Some(loc) = to_close {
let val = match mem::replace(loc, Local::Temp) {
Local::Temp => unreachable!(),
Local::Stack { val, .. } => val,
Local::Heap { var, .. } => var.take(),
};
let closer = self
.get_metatable(&val)
.map(|meta| meta.borrow().get(&Value::str("__close")))
.unwrap_or_default();
let func = match closer.to_func() {
Ok(func) => func,
Err(_) => {
return err!(LuaError::CallInvalid(
closer.value_type(),
LuaErrorSrc::Metamethod("close".to_string()),
));
}
};
self.pc -= 1; let res = self.call_prepare(
func,
vec![val, Value::Nil],
None,
None,
false,
Some("close"),
)?;
if !matches!(res, CallResult::Return(_)) {
return Ok(Some(res));
}
self.pc += 1; } else {
return Ok(None);
}
}
}
fn close_locals_error(
&mut self,
frame: &mut StackFrame,
mut error: Error,
) -> Result<CallResult> {
loop {
let to_close = frame
.locals
.iter_mut()
.rev()
.filter(|loc| match loc {
Local::Temp => false,
Local::Stack { val, to_close, .. } => *to_close && val.is_truthy(),
Local::Heap { var, to_close, .. } => *to_close && var.borrow().is_truthy(),
})
.next();
if let Some(loc) = to_close {
let val = match mem::replace(loc, Local::Temp) {
Local::Temp => unreachable!(),
Local::Stack { val, .. } => val,
Local::Heap { var, .. } => var.take(),
};
let closer = self
.get_metatable(&val)
.map(|meta| meta.borrow().get(&Value::str("__close")))
.unwrap_or_default();
let func = match closer.to_func() {
Ok(func) => func,
Err(_) => {
error = Error::from_lua(LuaError::CallInvalid(
closer.value_type(),
LuaErrorSrc::Metamethod("close".to_string()),
));
continue;
}
};
let err_lua = if let Error::Lua { typ, trace, .. } = &error {
Some((typ.clone(), trace.clone()))
} else {
None
};
let err_val = error.to_value();
let res = match self.call_prepare(
func,
vec![val, error.to_value()],
Some((
FuncCont {
name: "__close_cont",
func: Rc::new(move |vm, res| {
if let Some(frame) = vm.thread.borrow_mut().error_close.take() {
vm.frames.push(frame);
}
match res {
Ok(_) => {
if let Some((typ, trace)) = err_lua.clone() {
Err(Error::from_lua(typ).traces(trace))
} else {
err!(LuaError::CustomValue(err_val.clone()))
}
}
Err(e) => Err(e),
}
}),
},
true,
)),
None,
false,
Some("close"),
) {
Ok(res) => res,
Err(e) => {
error = e;
continue;
}
};
if !matches!(res, CallResult::Return(_)) {
return Ok(res);
}
} else {
return Err(error);
}
}
}
fn jump(&mut self, offset: i64) {
if offset >= 0 {
self.pc = self.pc + offset as usize;
} else {
let offset = offset.unsigned_abs() as usize;
assert!(self.pc >= offset);
self.pc = self.pc - offset;
if self.thread.borrow().hook_allowed && self.thread.borrow().hook_mask & HOOK_LINE > 0 {
self.thread.borrow_mut().hook_force_line = true;
}
}
}
fn run_loop(&mut self) -> Result<OpResult> {
self.rust_call_depth += 1;
loop {
match self.run_next() {
Ok(res) => {
if let Some(res) = res {
self.rust_call_depth -= 1;
return Ok(res);
}
}
Err(mut e) => {
e = self.traceback_error(e);
if self.thread.borrow().protected.is_some() {
e = self.pcall_handle_err(e);
}
loop {
let mut frame = match self.frames.pop() {
Some(frame) => frame,
None => {
self.rust_call_depth -= 1;
return Err(e);
}
};
match self.close_locals_error(&mut frame, e) {
Ok(CallResult::Return(_)) => unreachable!(),
Ok(CallResult::Continue) => {
self.thread.borrow_mut().error_close = Some(frame);
break;
}
Ok(CallResult::Yield(yld)) => {
self.thread.borrow_mut().error_close = Some(frame);
return Ok(OpResult::Yield(yld));
}
Err(err) => e = err,
}
if frame.recursive {
self.rust_call_depth -= 1;
return match frame.ret {
ReturnType::Lua(code, pc) => {
self.code = code.clone();
self.pc = pc;
self.thread.borrow_mut().protected.take();
Err(e)
}
ReturnType::Rust(..) | ReturnType::RustLua(..) => {
unreachable!()
}
};
} else {
let mut res = Err(e);
let ret_reg = loop {
match frame.ret {
ReturnType::Lua(code, pc) => {
self.code = code;
self.pc = pc;
break frame.ret_reg;
}
ReturnType::RustLua(func, code, pc) => {
res = (func.func)(self, res);
self.code = code;
self.pc = pc;
break None;
}
ReturnType::Rust(func) => {
res = (func.func)(self, res);
}
}
frame = self.frames.pop().unwrap();
if let Some(prev) = frame.protected {
self.thread.borrow_mut().protected = prev;
}
if self.frames.is_empty() {
self.rust_call_depth -= 1;
return res.map(OpResult::Return);
}
};
match res {
Ok(ret) => {
if let Some(ret_reg) = ret_reg {
self.frames.last_mut().unwrap().regs[ret_reg] = ret;
}
break;
}
Err(err) => e = err,
}
}
}
}
}
}
}
fn run_next(&mut self) -> Result<Option<OpResult>> {
if self.interrupt.load(Ordering::Acquire) {
self.interrupt.store(false, Ordering::Release);
return err!(LuaError::Interrupt);
}
self.debug_trace()?;
let current = self.code.get_op(self.pc);
self.pc = self.pc + 1;
match current {
OpCode::Mov { src_reg, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
frame.regs.swap(*src_reg, *dst_reg);
}
OpCode::MovMult {
src_reg,
ind,
dst_reg,
} => {
let frame = self.frames.last_mut().unwrap();
let val = match &frame.regs[*src_reg] {
Value::Mult(vec) => vec.get(*ind).cloned().unwrap_or(Value::Nil),
v if *ind == 0 => v.clone(),
_ => Value::Nil,
};
frame.regs[*dst_reg] = val;
}
OpCode::Copy { src_reg, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
frame.regs[*dst_reg] = frame.regs[*src_reg].clone();
}
OpCode::Lit { val, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
frame.regs[*dst_reg] = val.into();
}
OpCode::GlobalGet { env, name, dst_reg } => {
let dst_reg = *dst_reg;
let frame = self.frames.last_mut().unwrap();
let env = match env {
VarType::Local(loc) => match frame.locals.get(*loc).unwrap() {
Local::Stack { val, .. } => val.clone(),
Local::Heap { var, .. } => var.borrow().clone(),
Local::Temp => unreachable!(),
},
VarType::Up(up) => {
let (up, _) = frame.ups.get(*up).unwrap();
up.borrow().borrow().clone()
}
};
self.get_table_field_yieldable(env, Value::str(name), dst_reg)?;
}
OpCode::GlobalSet { src_reg, name, env } => {
let frame = self.frames.last_mut().unwrap();
let env = match env {
VarType::Local(loc) => match frame.locals.get(*loc).unwrap() {
Local::Stack { val, .. } => val.clone(),
Local::Heap { var, .. } => var.borrow().clone(),
Local::Temp => unreachable!(),
},
VarType::Up(up) => {
let (up, _) = frame.ups.get(*up).unwrap();
up.borrow().borrow().clone()
}
};
let val = mem::replace(&mut frame.regs[*src_reg], Value::Nil).into_single();
self.set_table_field_yieldable(env, Value::str(name), val)?;
}
OpCode::LocalAlloc {
dst_loc,
name,
to_close,
} => {
let frame = self.frames.last_mut().unwrap();
frame.locals[*dst_loc] = Local::Stack {
val: Value::Nil,
name: name.clone(),
to_close: *to_close,
};
}
OpCode::LocalClose { loc_from } => {
let loc_from = *loc_from;
match self.close_locals_from(loc_from)? {
Some(CallResult::Continue) => {}
None => {
for loc in self
.frames
.last_mut()
.unwrap()
.locals
.iter_mut()
.skip(loc_from)
{
*loc = Local::Temp;
}
}
Some(CallResult::Return(_)) => unreachable!(),
Some(CallResult::Yield(yld)) => return Ok(Some(OpResult::Yield(yld))),
}
}
OpCode::LocalGet { src_loc, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
let loc = frame.locals.get(*src_loc).unwrap();
frame.regs[*dst_reg] = match loc {
Local::Temp => unreachable!(),
Local::Stack { val, .. } => val.clone(),
Local::Heap { var, .. } => var.borrow().clone(),
};
}
OpCode::LocalSet { src_reg, dst_loc } => {
let value = mem::replace(
&mut self.frames.last_mut().unwrap().regs[*src_reg],
Value::Nil,
)
.into_single();
let (to_close, name) = match &self.frames.last().unwrap().locals[*dst_loc] {
Local::Stack { name, to_close, .. } | Local::Heap { name, to_close, .. } => {
(*to_close, name.as_str())
}
_ => (false, "?"),
};
if to_close && value.is_truthy() {
let can_close = self
.get_metatable(&value)
.map(|meta| meta.borrow().get(&Value::str("__close")) != Value::Nil)
.unwrap_or(false);
if !can_close {
return err!(LuaError::LocalNonClose(name.to_string()));
}
}
match &mut self.frames.last_mut().unwrap().locals[*dst_loc] {
Local::Temp => unreachable!(),
Local::Stack { val, .. } => *val = value,
Local::Heap { var, .. } => {
var.replace(value);
}
}
}
OpCode::UpGet { src_up, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
let (var, _) = frame.ups.get(*src_up).unwrap();
let val = var.borrow().borrow().clone();
frame.regs[*dst_reg] = val;
}
OpCode::UpSet { src_reg, dst_up } => {
let frame = self.frames.last_mut().unwrap();
let (var, _) = frame.ups.get(*dst_up).unwrap();
let val = mem::replace(&mut frame.regs[*src_reg], Value::Nil).into_single();
var.borrow().replace(val);
}
OpCode::Varargs { dst_reg } => {
let frame = self.frames.last_mut().unwrap();
let varargs = frame.varargs.clone();
frame.regs[*dst_reg] = Value::Mult(varargs);
}
OpCode::BinOp {
lhs,
rhs,
op,
dst_reg,
} => {
let lhs = lhs.clone();
let rhs = rhs.clone();
let op = *op;
let dst_reg = *dst_reg;
self.bin_op_yieldable(lhs, op, rhs, dst_reg)?;
}
OpCode::UnOp { lhs, op, dst_reg } => {
let lhs = lhs.clone();
let op = *op;
let dst_reg = *dst_reg;
self.un_op_yieldable(lhs, op, dst_reg)?;
}
OpCode::Single { reg } => {
let frame = self.frames.last_mut().unwrap();
let val = mem::replace(&mut frame.regs[*reg], Value::Nil);
frame.regs[*reg] = val.into_single();
}
OpCode::Append { src_reg, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
let val = mem::replace(&mut frame.regs[*src_reg], Value::Nil).into_single();
match &mut frame.regs[*dst_reg] {
Value::Mult(vec) => vec.push(val),
_ => panic!("invalid value"),
}
}
OpCode::Extend { src_reg, dst_reg } => {
let frame = self.frames.last_mut().unwrap();
let val = mem::replace(&mut frame.regs[*src_reg], Value::Nil).into_vec();
match &mut frame.regs[*dst_reg] {
Value::Mult(vec) => vec.extend(val),
_ => panic!("invalid value"),
}
}
OpCode::TableGet {
tbl_reg,
ind_reg,
dst_reg,
} => {
let dst_reg = *dst_reg;
let frame = self.frames.last_mut().unwrap();
let ind = frame.regs[*ind_reg].to_single().clone();
let tbl = mem::replace(&mut frame.regs[*tbl_reg], Value::Nil).into_single();
self.get_table_field_yieldable(tbl, ind, dst_reg)?;
}
OpCode::TableSet {
src_reg,
tbl_reg,
ind_reg,
} => {
let frame = self.frames.last_mut().unwrap();
let val = mem::replace(&mut frame.regs[*src_reg], Value::Nil).into_single();
let tbl = frame.regs[*tbl_reg].clone();
let ind = mem::replace(&mut frame.regs[*ind_reg], Value::Nil).into_single();
self.set_table_field_yieldable(tbl, ind, val)?;
}
OpCode::TableExtend {
src_reg,
tbl_reg,
ind_from,
} => {
let frame = self.frames.last_mut().unwrap();
let vec = mem::replace(&mut frame.regs[*src_reg], Value::Nil).into_vec();
let tbl = frame.regs[*tbl_reg].to_table()?;
let mut tbl = tbl.borrow_mut();
for (i, elem) in (*ind_from..).zip(vec) {
tbl.set(Value::int(i as i64), elem)?;
}
}
OpCode::TableEmpty { dst_reg } => {
let dst_reg = *dst_reg;
let tbl = Value::Table(self.alloc_table());
let frame = self.frames.last_mut().unwrap();
frame.regs[dst_reg] = tbl;
}
OpCode::Closure { func, dst_reg } => {
let dst_reg = *dst_reg;
let frame = self.frames.last_mut().unwrap();
let func = *func.clone();
let mut ups = Vec::new();
for var in func.ups.into_iter() {
match var {
VarType::Local(ind) => {
let loc = frame.locals.get_mut(ind).unwrap();
match loc {
Local::Temp => unreachable!(),
Local::Stack {
val,
name,
to_close,
} => {
let var = Rc::new(RefCell::new(val.clone()));
ups.push((RefCell::new(var.clone()), name.to_string()));
*loc = Local::Heap {
var,
name: name.to_string(),
to_close: *to_close,
};
}
Local::Heap { var, name, .. } => {
ups.push((RefCell::new(var.clone()), name.to_string()));
}
}
}
VarType::Up(ind) => {
let (var, name) = frame.ups.get(ind).unwrap();
ups.push((var.clone(), name.to_string()));
}
}
}
let closure = FuncClosure {
regs: func.regs,
locals_cap: func.locals_cap,
params: func.params,
varargs: func.varargs,
ups,
code: func.code,
source: if let FuncDef::Defined(func) = &*frame.func_def {
func.source.clone()
} else {
Rc::new("=?".as_bytes().to_vec())
},
linedefined: func.linedefined,
lastlinedefined: func.lastlinedefined,
};
self.frames.last_mut().unwrap().regs[dst_reg] =
Value::Func(self.alloc_closure(closure));
}
OpCode::Jump { off } => {
let off = *off;
self.jump(off);
}
OpCode::JumpIf { cmp_reg, off } => {
let frame = self.frames.last_mut().unwrap();
if frame.regs[*cmp_reg].is_truthy() {
let off = *off;
self.jump(off);
}
}
OpCode::JumpIfNot { cmp_reg, off } => {
let frame = self.frames.last_mut().unwrap();
if frame.regs[*cmp_reg].is_falsy() {
let off = *off;
self.jump(off);
}
}
OpCode::JumpClose { loc_from, off } => {
let loc_from = *loc_from;
let off = *off;
match self.close_locals_from(loc_from)? {
Some(CallResult::Continue) => {}
Some(CallResult::Return(_)) => unreachable!(),
Some(CallResult::Yield(yld)) => return Ok(Some(OpResult::Yield(yld))),
None => self.jump(off),
}
}
OpCode::ForNum {
ctrl_reg,
limit_reg,
step_reg,
off_start,
off_end,
ctrl_loc,
} => {
let frame = self.frames.last_mut().unwrap();
let mut ctrl = frame.regs[*ctrl_reg].to_number_coerce().map_err(|e| {
e.map_lua_error(|l| LuaError::ForBadValue("initial", Box::new(l)))
})?;
let limit = frame.regs[*limit_reg].to_number_coerce().map_err(|e| {
e.map_lua_error(|l| LuaError::ForBadValue("limit", Box::new(l)))
})?;
let step = match step_reg {
Some(step_reg) => frame.regs[*step_reg].to_number_coerce().map_err(|e| {
e.map_lua_error(|l| LuaError::ForBadValue("step", Box::new(l)))
})?,
None => Numeric::Integer(1),
};
let mut stop = false;
if *off_start == 0 {
if ctrl.to_int().is_err() || step.to_int().is_err() {
ctrl = Numeric::Float(ctrl.to_float());
frame.regs[*ctrl_reg] = Value::Number(ctrl.clone());
}
} else {
match ctrl.to_int().ok().zip(step.to_int().ok()) {
Some((ctrli, step)) => {
let (res, over) = ctrli.overflowing_add(step);
ctrl = Numeric::Integer(res);
stop = over;
}
None => ctrl = Numeric::Float(ctrl.to_float() + step.to_float()),
}
frame.regs[*ctrl_reg] = Value::Number(ctrl.clone());
}
let (a, b) = match step.to_float().total_cmp(&0.) {
std::cmp::Ordering::Equal => return err!(LuaError::ForStepZero),
std::cmp::Ordering::Less => (&limit, &ctrl),
std::cmp::Ordering::Greater => (&ctrl, &limit),
};
if !stop && operators::num_le(a, b)? {
let loc = mem::replace(&mut frame.locals[*ctrl_loc], Local::Temp);
let new = match loc {
Local::Temp => unreachable!(),
Local::Stack { name, to_close, .. }
| Local::Heap { name, to_close, .. } => Local::Stack {
val: Value::Number(ctrl),
name,
to_close,
},
};
let _ = mem::replace(&mut frame.locals[*ctrl_loc], new);
self.jump(*off_start);
} else {
self.jump(*off_end);
}
}
OpCode::Call {
func_reg,
args_reg,
ret_reg,
tail,
} => {
let reg_args = *args_reg;
let reg_ret = *ret_reg;
let frame = self.frames.last_mut().unwrap();
let mut func = mem::replace(&mut frame.regs[*func_reg], Value::Nil);
let mut args = mem::replace(&mut frame.regs[reg_args], Value::Nil).into_vec();
let func = loop {
match func.to_single() {
Value::Func(func) => break func.clone(),
val => {
if val.is_truthy() {
let meta = self.get_metatable(&val).clone();
if let Some(meta) = meta {
args.insert(0, val.clone());
func = meta.borrow().get(&Value::str("__call"));
continue;
}
}
let func_reg = *func_reg;
let (name, namewhat) = VM::reg_info(
self.frames.last().unwrap(),
&func_reg,
self.code.clone(),
self.pc.saturating_sub(1),
);
let name = name
.map(|n| String::from_utf8_lossy(&n).into_owned())
.unwrap_or("".to_string());
return err!(LuaError::CallInvalid(
val.value_type(),
match namewhat {
"field" => LuaErrorSrc::Field(name),
"global" => LuaErrorSrc::Global(name),
"local" => LuaErrorSrc::Local(name),
"method" => LuaErrorSrc::Method(name),
"up" => LuaErrorSrc::Up(name),
_ => LuaErrorSrc::None,
}
));
}
}
};
let res = self.call_prepare(func, args, None, Some(reg_ret), *tail, None)?;
match res {
CallResult::Continue => {}
CallResult::Return(ret) => self.frames.last_mut().unwrap().regs[reg_ret] = ret,
CallResult::Yield(yld) => return Ok(Some(OpResult::Yield(yld))),
}
}
OpCode::Return { ret_reg } => {
let ret_reg = *ret_reg;
match self.close_locals_from(0)? {
Some(CallResult::Continue) => return Ok(None),
Some(CallResult::Return(_)) => unreachable!(),
Some(CallResult::Yield(yld)) => return Ok(Some(OpResult::Yield(yld))),
None => {}
}
let mut ret_val = mem::replace(
&mut self.frames.last_mut().unwrap().regs[ret_reg],
Value::Nil,
);
loop {
self.debug_return(&ret_val)?;
let frame = self.frames.pop().unwrap();
if let Some(prev) = frame.protected {
self.thread.borrow_mut().protected = prev;
}
if self.frames.is_empty() && self.thread.borrow().error_close.is_none() {
return Ok(Some(OpResult::Return(ret_val)));
}
match frame.ret {
ReturnType::Lua(code, pc) => {
self.code = code;
self.pc = pc;
}
ReturnType::RustLua(func, code, pc) => {
assert!(!frame.recursive);
ret_val = (func.func)(self, Ok(ret_val))?;
self.code = code;
self.pc = pc;
}
ReturnType::Rust(func) => {
assert!(!frame.recursive);
ret_val = (func.func)(self, Ok(ret_val))?;
continue;
}
}
if self.thread.borrow().hook.is_some() && self.thread.borrow().hook_allowed {
let prev_pc = self.pc - 1;
self.thread.borrow_mut().curr_line =
self.code.get_pos(prev_pc).line() as i64;
}
if frame.recursive {
return Ok(Some(OpResult::Return(ret_val)));
} else if let Some(ret_reg) = frame.ret_reg {
self.frames.last_mut().unwrap().regs[ret_reg] = ret_val;
}
break;
}
}
}
Ok(None)
}
fn debug_trace(&mut self) -> Result<()> {
if !self.thread.borrow().hook_allowed || self.thread.borrow().hook.is_none() {
return Ok(());
}
if self.thread.borrow().hook_mask & HOOK_COUNT > 0 {
self.thread.borrow_mut().curr_count -= 1;
if self.thread.borrow().curr_count <= 0 {
let count = self.thread.borrow().hook_count;
self.thread.borrow_mut().curr_count = count;
self.run_hook(vec![Value::str("count")], (0, 0))?;
}
}
if self.thread.borrow().hook_mask & HOOK_LINE > 0 {
let line = self.code.get_pos(self.pc).line() as i64;
if self.thread.borrow().hook_force_line
|| !matches!(self.code.get_op(self.pc), OpCode::LocalClose { .. })
&& line != self.thread.borrow().curr_line
{
self.thread.borrow_mut().hook_force_line = false;
self.thread.borrow_mut().curr_line = line;
let line = if line > 0 {
Value::int(line)
} else {
Value::Nil
};
self.run_hook(vec![Value::str("line"), line], (0, 0))?;
}
}
Ok(())
}
fn debug_call(&mut self) -> Result<()> {
if self.thread.borrow().hook_allowed && self.thread.borrow().hook_mask & HOOK_CALL > 0 {
let transfer = match &*self.frames.last().unwrap().func_def {
FuncDef::Defined(func) => (1, func.params.len()),
FuncDef::Builtin(..) | FuncDef::BuiltinRaw(..) => {
let frame = self.frames.last_mut().unwrap();
frame.locals = frame
.varargs
.iter()
.map(|v| Local::Stack {
val: v.clone(),
name: "(C temporary)".to_string(),
to_close: false,
})
.collect();
(1, frame.varargs.len())
}
};
let tail = self.frames.last().unwrap().tail;
self.run_hook(
vec![Value::str(if tail { "tail call" } else { "call" })],
transfer,
)?;
}
Ok(())
}
fn debug_return(&mut self, ret_val: &Value) -> Result<()> {
if self.thread.borrow().hook_allowed && self.thread.borrow().hook_mask & HOOK_RET > 0 {
let ret = ret_val.clone().into_vec();
let frame = self.frames.last_mut().unwrap();
let first = frame.locals.len() + 1;
let count = ret.len();
frame.locals.extend(ret.iter().map(|v| Local::Stack {
val: v.clone(),
name: "(C temporary)".to_string(),
to_close: false,
}));
self.run_hook(vec![Value::str("return")], (first, count))?;
}
Ok(())
}
fn run_hook(&mut self, args: Vec<Value>, transfer: (usize, usize)) -> Result<()> {
if self.thread.borrow().hook_allowed {
let hook: Rc<FuncDef> = match &self.thread.borrow().hook {
Some(hook) => hook.clone(),
None => return Ok(()),
};
self.thread.borrow_mut().hook_allowed = false;
self.frames.last_mut().unwrap().transfer = transfer;
let res = self.call_recursive(hook, args);
self.frames.last_mut().unwrap().transfer = (0, 0);
self.thread.borrow_mut().hook_allowed = true;
res.map(|_| ())
} else {
Ok(())
}
}
fn get_metatable(&self, value: &Value) -> Option<Rc<RefCell<Table>>> {
match value {
Value::Table(tbl) => tbl.borrow().get_meta().clone(),
Value::UserData(UserData::File(file)) => file.borrow().get_meta().clone(),
v => self.metas_shared.get(&v.value_type()).cloned(),
}
}
pub fn to_string(&mut self, val: Value) -> Result<Vec<u8>> {
if let Some(meta) = self.get_metatable(&val) {
match meta.borrow().get(&Value::str("__tostring")) {
Value::Nil => match meta.borrow().get(&Value::str("__name")) {
Value::String(str) => {
let mut str = str.to_vec();
str.write_all(": ".as_bytes())?;
Ok(str)
}
_ => val.write_as_string(),
},
Value::Func(func) => {
Ok(self
.call_recursive(func, vec![val])?
.to_string_coerce()
.map_err(|err| err.map_lua_error(|_| LuaError::ToStringReturnType))?)
}
val => err!(LuaError::ExpectedType(ValueType::Func, val.value_type())),
}
} else {
val.write_as_string()
}
}
}