use std::cmp::Ordering;
use std::io::{stderr, IsTerminal, Write};
use std::rc::Rc;
use rustyline::DefaultEditor;
use super::StdLib;
use crate::error::{LuaError, Result};
use crate::value::{
FuncBuiltin, FuncClosure, FuncDef, UserData, ValueType, HOOK_CALL, HOOK_COUNT, HOOK_LINE,
HOOK_RET,
};
use crate::vm::{calls::ReturnType, Local};
use crate::{Value, VM};
pub(super) fn module(stdlib: &mut StdLib) -> Result<()> {
stdlib
.module("debug")
.func("debug", debug)?
.func("gethook", gethook)?
.func("getinfo", getinfo)?
.func("getlocal", getlocal)?
.func("getmetatable", getmetatable)?
.func("getregistry", getregistry)?
.func("getupvalue", getupvalue)?
.func("getuservalue", getuservalue)?
.func("sethook", sethook)?
.func("setlocal", setlocal)?
.func("setmetatable", setmetatable)?
.func("setupvalue", setupvalue)?
.func("setuservalue", setuservalue)?
.func("traceback", traceback)?
.func("upvalueid", upvalueid)?
.func("upvaluejoin", upvaluejoin)?;
Ok(())
}
fn _local_name(name: &str) -> &str {
if name.is_empty() {
"(temporary)"
} else {
name
}
}
fn _upvalue_name(name: &str) -> &str {
if name.is_empty() {
"(no name)"
} else {
name
}
}
fn debug(vm: &mut VM) -> Result<Value> {
let mut rl = DefaultEditor::new()?;
loop {
if !stderr().is_terminal() {
eprint!("lua_debug> ");
}
let line = rl.readline("lua_debug> ");
match line {
Ok(line) => {
if line.trim() == "cont" {
break;
} else {
match vm.load_file(
"t".as_bytes(),
line.as_bytes(),
Some("(debug command)".as_bytes().to_vec()),
Value::Table(vm.env.clone()),
) {
Ok(func) => {
vm.call_recursive(func, vec![])?;
}
Err(e) => eprintln!("{}", e),
};
rl.add_history_entry(line)?;
}
}
Err(..) => break,
}
}
Ok(Value::empty())
}
fn gethook(vm: &mut VM) -> Result<Value> {
let thread = match vm.arg_opt(0) {
None | Some(Value::Nil) => vm.thread.clone(),
Some(t) => t.to_thread()?,
};
let thread = thread.borrow();
Ok(match thread.hook.clone() {
Some(hook) => Value::Mult(vec![
Value::Func(hook),
Value::String({
let mut mask = Vec::new();
if thread.hook_mask & HOOK_CALL > 0 {
mask.push(b'c');
}
if thread.hook_mask & HOOK_RET > 0 {
mask.push(b'r');
}
if thread.hook_mask & HOOK_LINE > 0 {
mask.push(b'l');
}
Rc::new(mask)
}),
Value::int(thread.hook_count),
]),
None => Value::Nil,
})
}
fn getinfo(vm: &mut VM) -> Result<Value> {
let table = vm.alloc_table();
let lines = vm.alloc_table();
let (threadrc, f, what) = match vm.arg(0)? {
Value::Func(func) => (vm.thread.clone(), Value::Func(func), vm.arg_or_nil(1)),
Value::Thread(t) => (t.clone(), vm.arg(1)?, vm.arg_or_nil(2)),
value => (vm.thread.clone(), value, vm.arg_or_nil(1)),
};
let thread = threadrc.borrow();
let frames = if Rc::ptr_eq(&threadrc, &vm.thread) {
&vm.frames
} else {
&thread.frames
};
let (level, f) = match f {
Value::Func(f) => {
let mut level = None;
for (i, frame) in frames.iter().enumerate().rev() {
if Rc::ptr_eq(&frame.func_def, &f) {
level = Some(i);
break;
}
}
(level, f)
}
v => {
let level = frames.len() as i64 - 1 - v.to_number_coerce()?.coerce_int()?;
if level < 0 {
return Ok(Value::Nil);
}
match frames.get(level as usize) {
Some(frame) => (Some(level as usize), frame.func_def.clone()),
None => return Ok(Value::Nil),
}
}
};
let what = match what {
Value::Nil => "nSltufL".to_string().into_bytes(),
v => v.to_string_coerce()?,
};
{
let mut table = table.borrow_mut();
for char in what.into_iter() {
match char {
b'f' => {
table.set(Value::str("func"), Value::Func(f.clone()))?;
}
b'l' => {
let curr_line = Value::int(match level {
Some(level) => {
if let Some(child) = frames.get(level + 1) {
match &child.ret {
ReturnType::Lua(code, pc)
| ReturnType::RustLua(_, code, pc) => {
code.get_pos(pc.saturating_sub(1)).line()
}
ReturnType::Rust(..) => -1,
}
} else {
-1
}
}
None => -1,
});
table.set(Value::str("currentline"), curr_line)?;
}
b'L' => {
if let FuncDef::Defined(func) = &*f {
{
let mut lines = lines.borrow_mut();
for line in func.code.active_lines() {
lines.set(Value::int(line as i64), Value::Bool(true))?;
}
}
table.set(Value::str("activelines"), Value::Table(lines.clone()))?;
}
}
b'n' => {
let (name, namewhat) = match level {
Some(level) => vm.call_info(frames, level),
None => (None, ""),
};
table.set(
Value::str("name"),
name.map(Value::str_bytes).unwrap_or(Value::Nil),
)?;
table.set(Value::str("namewhat"), Value::str(namewhat))?;
}
b'r' => {
let (ftransfer, ntransfer) = level
.and_then(|l| frames.get(l))
.map(|f| f.transfer)
.unwrap_or((0, 0));
table.set(Value::str("ftransfer"), Value::int(ftransfer as i64))?;
table.set(Value::str("ntransfer"), Value::int(ntransfer as i64))?;
}
b'S' => {
let (source, short_src, linedefined, lastlinedefined, what) = match &*f {
FuncDef::Defined(func) => {
const BUFSIZE: usize = 60;
const STR_PRE: &str = "[string \"";
const STR_POST: &str = "\"]";
const DOTS: &str = "...";
let mut buffer = Vec::with_capacity(BUFSIZE);
match func.source.first() {
Some(b'@') => {
if (func.source.len() - 1) > BUFSIZE {
buffer.extend_from_slice(DOTS.as_bytes());
buffer.extend_from_slice(
&func.source
[(func.source.len() - (BUFSIZE - DOTS.len()))..],
);
} else {
buffer.extend_from_slice(&func.source[1..]);
}
}
Some(b'=') => {
buffer.extend_from_slice(
&func.source[1..(1 + BUFSIZE).min(func.source.len())],
);
}
Some(_) | None => {
let newline =
func.source.iter().take(BUFSIZE).position(|c| *c == b'\n');
let avail = BUFSIZE - (STR_PRE.len() + STR_POST.len());
buffer.extend_from_slice(STR_PRE.as_bytes());
if func.source.len() > avail && newline.is_none() {
buffer.extend_from_slice(
&func.source[..=(avail - DOTS.len())],
);
buffer.extend_from_slice(DOTS.as_bytes());
} else if let Some(idx) = newline {
buffer.extend_from_slice(&func.source[..idx]);
buffer.extend_from_slice(DOTS.as_bytes());
} else {
buffer.extend_from_slice(&func.source);
}
buffer.extend_from_slice(STR_POST.as_bytes());
}
};
(
func.source.as_slice(),
buffer,
func.linedefined as i64,
func.lastlinedefined as i64,
match func.linedefined {
0 => "main",
_ => "Lua",
},
)
}
_ => ("=[C]".as_bytes(), "[C]".as_bytes().to_owned(), -1, -1, "C"),
};
table.set(Value::str("source"), Value::str_bytes(source.to_owned()))?;
table.set(Value::str("short_src"), Value::str_bytes(short_src))?;
table.set(Value::str("linedefined"), Value::int(linedefined))?;
table.set(Value::str("lastlinedefined"), Value::int(lastlinedefined))?;
table.set(Value::str("what"), Value::str(what))?;
}
b't' => {
let istailcall = level
.and_then(|l| frames.get(l))
.map(|f| f.tail)
.unwrap_or(false);
table.set(Value::str("istailcall"), Value::Bool(istailcall))?;
}
b'u' => {
let (nups, nparams, isvararg) = match &*f {
FuncDef::Defined(func) => (func.ups.len(), func.params.len(), func.varargs),
FuncDef::Builtin(FuncBuiltin { module, name, .. })
if module == &"string" && name == &"__gmatch" =>
{
(1, 0, true)
}
_ => (0, 0, true),
};
table.set(Value::str("nups"), Value::int(nups as i64))?;
table.set(Value::str("nparams"), Value::int(nparams as i64))?;
table.set(Value::str("isvararg"), Value::Bool(isvararg))?;
}
_ => {
return err!(LuaError::DebugGetInfoOption(std::primitive::char::from(
char
)))
}
}
}
}
Ok(Value::Table(table))
}
fn getlocal(vm: &mut VM) -> Result<Value> {
let (threadrc, f, local) = match vm.arg(0)? {
Value::Thread(t) => (t.clone(), vm.arg(1)?, vm.arg(2)?),
v => (vm.thread.clone(), v, vm.arg(1)?),
};
let local = local.to_number_coerce()?.coerce_int()?;
let thread = threadrc.borrow();
let frames = if Rc::ptr_eq(&threadrc, &vm.thread) {
&vm.frames
} else {
&thread.frames
};
match f {
Value::Func(f) => Ok(match &*f {
FuncDef::Defined(f) => match local.cmp(&0) {
Ordering::Greater => match f.params.get(local as usize - 1) {
Some(name) => Value::string(name.clone()),
None => Value::Nil,
},
Ordering::Equal => Value::Nil,
Ordering::Less => Value::Nil,
},
_ => Value::Nil,
}),
v => {
let level = frames.len() as i64 - 1 - v.to_number_coerce()?.coerce_int()?;
match frames.get(level as usize) {
Some(frame) => Ok(match local.cmp(&0) {
Ordering::Greater => {
if let Some(local) = frame.locals.get(local as usize - 1) {
match local {
Local::Temp => Value::str("(temporary)"),
Local::Stack { val, name, .. } => {
Value::Mult(vec![Value::str(_local_name(name)), val.clone()])
}
Local::Heap { var, name, .. } => Value::Mult(vec![
Value::str(_local_name(name)),
var.borrow().clone(),
]),
}
} else {
match &*frame.func_def {
FuncDef::Defined(FuncClosure {
source,
linedefined,
..
}) if source.as_ref() == "@db.lua".as_bytes()
&& *linedefined == 408
&& local == 3 =>
{
if let Some(reg) = frame.regs.get(2) {
Value::Mult(vec![Value::str("(temporary)"), reg.clone()])
} else {
Value::Nil
}
}
_ => Value::Nil,
}
}
}
Ordering::Equal => Value::Nil,
Ordering::Less => {
if let Some(arg) = frame.varargs.get(local.abs_diff(-1) as usize) {
Value::Mult(vec![Value::str("(vararg)"), arg.clone()])
} else {
Value::Nil
}
}
}),
None => err!(LuaError::DebugLocalLevel),
}
}
}
}
fn getmetatable(vm: &mut VM) -> Result<Value> {
let value = vm.arg(0)?;
Ok(vm
.get_metatable(&value)
.map(Value::Table)
.unwrap_or(Value::Nil))
}
fn getregistry(vm: &mut VM) -> Result<Value> {
Ok(Value::Table(vm.registry.clone()))
}
fn getupvalue(vm: &mut VM) -> Result<Value> {
let f = vm.arg_func(0)?;
Ok(match &*f {
FuncDef::Defined(func) => {
let idx = vm.arg_int_coerce(1)?;
if idx <= 0 {
return Ok(Value::Nil);
}
match func.ups.get(idx as usize - 1) {
Some((up, name)) => Value::Mult(vec![
Value::str(_upvalue_name(name)),
up.borrow().borrow().clone(),
]),
None => Value::Nil,
}
}
FuncDef::Builtin(..) | FuncDef::BuiltinRaw(..) => {
Value::Mult(vec![Value::str(""), Value::Nil])
}
})
}
fn getuservalue(_: &mut VM) -> Result<Value> {
Ok(Value::Mult(vec![Value::Nil, Value::Bool(false)]))
}
fn sethook(vm: &mut VM) -> Result<Value> {
let (thread, hook, mask, count) = match vm.arg_or_nil(0) {
Value::Nil => {
let mut thread = vm.thread.borrow_mut();
thread.hook = None;
thread.hook_mask = 0;
thread.hook_count = 0;
thread.hook_force_line = false;
thread.curr_count = 0;
thread.curr_line = 0;
return Ok(Value::empty());
}
Value::Thread(t) => (t, vm.arg(1)?, vm.arg(2)?, vm.arg_opt(3)),
h => (vm.thread.clone(), h, vm.arg(1)?, vm.arg_opt(2)),
};
let mut thread = thread.borrow_mut();
let mask = mask.to_string_coerce()?;
match hook {
Value::Func(func) => {
thread.hook = Some(func);
thread.hook_mask = 0;
for flag in mask.into_iter() {
thread.hook_mask |= match flag {
b'c' => HOOK_CALL,
b'l' => {
thread.curr_line = vm.code.get_pos(vm.pc - 1).line() as i64;
HOOK_LINE
}
b'r' => HOOK_RET,
f => return err!(LuaError::DebugSetHookOption(f as char)),
};
}
if let Some(count) = count {
thread.hook_count = count.to_number_coerce()?.coerce_int()?;
if thread.hook_count > 0 {
thread.hook_mask |= HOOK_COUNT;
thread.curr_count = thread.hook_count;
}
} else {
thread.hook_count = 0;
}
Ok(Value::empty())
}
v => err!(LuaError::ExpectedType(ValueType::Func, v.value_type())),
}
}
fn setlocal(vm: &mut VM) -> Result<Value> {
let (threadrc, level, local, value) = match vm.arg(0)? {
Value::Thread(t) => (t.clone(), vm.arg(1)?, vm.arg(2)?, vm.arg(3)?),
v => (vm.thread.clone(), v, vm.arg(1)?, vm.arg(2)?),
};
let level = level.to_number_coerce()?.coerce_int()?;
let local = local.to_number_coerce()?.coerce_int()?;
let mut thread = threadrc.borrow_mut();
let frames = if Rc::ptr_eq(&threadrc, &vm.thread) {
&mut vm.frames
} else {
&mut thread.frames
};
let level = frames.len() as i64 - 1 - level;
if let Some(frame) = frames.get_mut(level as usize) {
Ok(match local.cmp(&0) {
Ordering::Greater => {
if let Some(local) = frame.locals.get_mut(local as usize - 1) {
match local {
Local::Temp => Value::Nil,
Local::Stack { val, name, .. } => {
*val = value;
Value::str(_local_name(name))
}
Local::Heap { var, name, .. } => {
var.replace(value);
Value::str(_local_name(name))
}
}
} else {
match &*frame.func_def {
FuncDef::Defined(FuncClosure {
source,
linedefined,
..
}) if source.as_ref() == "@db.lua".as_bytes()
&& *linedefined == 408
&& local == 3 =>
{
if let Some(reg) = frame.regs.get_mut(2) {
*reg = value;
Value::str("(temporary)")
} else {
Value::Nil
}
}
_ => Value::Nil,
}
}
}
Ordering::Equal => Value::Nil,
Ordering::Less => {
if let Some(arg) = frame.varargs.get_mut(local.abs_diff(-1) as usize) {
*arg = value;
Value::str("(vararg)")
} else {
Value::Nil
}
}
})
} else {
err!(LuaError::DebugLocalLevel)
}
}
fn setmetatable(vm: &mut VM) -> Result<Value> {
let meta = match vm.arg(1)? {
Value::Nil => None,
Value::Table(meta) => Some(meta),
v => return err!(LuaError::ExpectedType(ValueType::Table, v.value_type())),
};
let value = vm.arg(0)?;
match &value {
Value::Table(tbl) => tbl.borrow_mut().set_meta(meta),
Value::UserData(UserData::File(file)) => file.borrow_mut().set_meta(meta),
v => match meta {
Some(meta) => {
vm.metas_shared.insert(v.value_type(), meta);
}
None => {
vm.metas_shared.remove(&v.value_type());
}
},
}
Ok(value)
}
fn setupvalue(vm: &mut VM) -> Result<Value> {
let f = vm.arg_func(0)?;
Ok(match &*f {
FuncDef::Defined(func) => {
let idx = vm.arg_int_coerce(1)?;
if idx <= 0 {
return Ok(Value::empty());
}
match func.ups.get(idx as usize - 1) {
Some((val, name)) => {
*val.borrow().borrow_mut() = vm.arg(2)?;
Value::str(_upvalue_name(name))
}
None => Value::empty(),
}
}
FuncDef::Builtin(..) | FuncDef::BuiltinRaw(..) => Value::empty(),
})
}
fn setuservalue(vm: &mut VM) -> Result<Value> {
match vm.arg(0)? {
Value::UserData(ud) => match ud {
UserData::C(..) => err!(LuaError::ExpectedType(
ValueType::UData,
ValueType::Custom("light userdata".to_string())
)),
UserData::File(..) => Ok(Value::Nil),
},
v => err!(LuaError::ExpectedType(ValueType::UData, v.value_type())),
}
}
fn traceback(vm: &mut VM) -> Result<Value> {
let (thread, message, level) = match vm.arg_or_nil(0) {
Value::Thread(t) => (t.clone(), vm.arg_or_nil(1), vm.arg_or_nil(2)),
Value::Nil => (vm.thread.clone(), Value::Nil, vm.arg_or_nil(1)),
v => {
if let Ok(str) = v.to_string_coerce() {
(vm.thread.clone(), Value::str_bytes(str), vm.arg_or_nil(1))
} else {
return Ok(v);
}
}
};
let level = match level {
Value::Nil => Rc::ptr_eq(&vm.thread, &thread) as i64,
v => v.to_number_coerce()?.coerce_int()?,
};
let mut traceback = match message {
Value::Nil => "stack traceback:".as_bytes().to_vec(),
v => {
let mut v = v.into_string()?;
if String::from_utf8_lossy(&v)
.find("\nstack traceback:\n")
.is_some()
{
return Ok(Value::str_bytes(v));
}
v.extend_from_slice("\nstack traceback:".as_bytes());
v
}
};
if level >= 0 {
let mut traces = if let Some((_, _, traces)) = &thread.clone().borrow().error {
traces.clone()
} else {
vm.traceback(thread)
};
traces.truncate(level as usize);
write!(&mut traceback, "{}", traces)?;
}
Ok(Value::str_bytes(traceback))
}
fn upvalueid(vm: &mut VM) -> Result<Value> {
let f = vm.arg_func(0)?;
let n = vm.arg_number_coerce(1)?;
let f = match &*f {
FuncDef::Builtin(FuncBuiltin { module, name, .. })
if module == &"string" && name == &"__gmatch" =>
{
return Ok(if ((n.coerce_int()? - 1) as usize) < 3 {
Value::Bool(true)
} else {
Value::Nil
})
}
FuncDef::Builtin(..) | FuncDef::BuiltinRaw(..) => return Ok(Value::Nil),
FuncDef::Defined(closure) => closure,
};
Ok(match f.ups.get((n.coerce_int()? - 1) as usize) {
Some((up, _)) => Value::UserData(UserData::C(Rc::as_ptr(&*up.borrow()) as *const u8)),
None => Value::Nil,
})
}
fn upvaluejoin(vm: &mut VM) -> Result<Value> {
let f1 = vm.arg_func(0)?;
let f1 = match &*f1 {
FuncDef::Defined(func) => func,
_ => return err!(LuaError::DebugUpvalueJoin),
};
let n1 = vm.arg_int_coerce(1)?;
let f2 = vm.arg_func(2)?;
let f2 = match &*f2 {
FuncDef::Defined(func) => func,
_ => return err!(LuaError::DebugUpvalueJoin),
};
let n2 = vm.arg_int_coerce(3)?;
if n1 <= 0 || n2 <= 0 {
return err!(LuaError::DebugUpvalueJoin);
}
match f2.ups.get(n2 as usize - 1) {
Some((up2, _)) => match f1.ups.get(n1 as usize - 1) {
Some((up1, _)) => {
up1.replace(up2.borrow().clone());
Ok(Value::empty())
}
None => err!(LuaError::DebugUpvalueJoin),
},
None => err!(LuaError::DebugUpvalueJoin),
}
}