use std::io::Write;
use crate::runtime::Value;
use crate::version::LuaVersion;
use crate::vm::argcheck::{self, Args};
use crate::vm::error::LuaError;
use crate::vm::exec::Vm;
pub(crate) fn open_base(vm: &mut Vm) {
let f = vm.native(nat_assert);
vm.set_global("assert", f).expect("stdlib registration");
let f = vm.native(nat_error);
vm.set_global("error", f).expect("stdlib registration");
let f = vm.native(nat_pcall);
vm.set_global("pcall", f).expect("stdlib registration");
let f = vm.native(nat_xpcall);
vm.set_global("xpcall", f).expect("stdlib registration");
let f = vm.native(nat_type);
vm.set_global("type", f).expect("stdlib registration");
let f = vm.native(nat_print);
vm.set_global("print", f).expect("stdlib registration");
let f = vm.native(nat_tostring);
vm.set_global("tostring", f).expect("stdlib registration");
let f = vm.native(nat_rawget);
vm.set_global("rawget", f).expect("stdlib registration");
let f = vm.native(nat_rawset);
vm.set_global("rawset", f).expect("stdlib registration");
let f = vm.native(nat_rawequal);
vm.set_global("rawequal", f).expect("stdlib registration");
if vm.version() >= crate::version::LuaVersion::Lua52 {
let f = vm.native(nat_rawlen);
vm.set_global("rawlen", f).expect("stdlib registration");
}
let f = vm.native(nat_setmetatable);
vm.set_global("setmetatable", f)
.expect("stdlib registration");
let f = vm.native(nat_getmetatable);
vm.set_global("getmetatable", f)
.expect("stdlib registration");
let f = vm.native(nat_select);
vm.set_global("select", f).expect("stdlib registration");
let next_obj = vm.native(nat_next);
vm.set_global("next", next_obj)
.expect("stdlib registration");
let pairs_next = if vm.version() == LuaVersion::Lua51 {
vm.native(nat_next)
} else {
next_obj
};
let pairs_obj = vm.native_with(nat_pairs, Box::new([pairs_next]));
vm.set_global("pairs", pairs_obj)
.expect("stdlib registration");
let ipairs_it = vm.native(ipairs_iter);
let ipairs_obj = vm.native_with(nat_ipairs, Box::new([ipairs_it]));
vm.set_global("ipairs", ipairs_obj)
.expect("stdlib registration");
let f = vm.native(nat_tonumber);
vm.set_global("tonumber", f).expect("stdlib registration");
let load_obj = vm.native(nat_load);
vm.set_global("load", load_obj)
.expect("stdlib registration");
let f = vm.native(crate::vm::lib_gc::nat_collectgarbage);
vm.set_global("collectgarbage", f)
.expect("stdlib registration");
if vm.version() >= crate::version::LuaVersion::Lua54 {
let f = vm.native(nat_warn);
vm.set_global("warn", f).expect("stdlib registration");
}
if vm.version() == crate::version::LuaVersion::Lua52 {
vm.set_global("loadstring", load_obj)
.expect("stdlib registration");
}
if vm.version() == crate::version::LuaVersion::Lua51 {
let f = vm.native(nat_loadstring);
vm.set_global("loadstring", f).expect("stdlib registration");
let f = vm.native(crate::vm::lib_table::t_unpack);
vm.set_global("unpack", f).expect("stdlib registration");
let f = vm.native(nat_gcinfo);
vm.set_global("gcinfo", f).expect("stdlib registration");
let f = vm.native(nat_setfenv);
vm.set_global("setfenv", f).expect("stdlib registration");
let f = vm.native(nat_getfenv);
vm.set_global("getfenv", f).expect("stdlib registration");
let proxies = vm.heap.new_table();
let weak_k = vm.heap.new_table();
let mode_k = Value::Str(vm.heap.intern(b"__mode"));
let mode_v = Value::Str(vm.heap.intern(b"k"));
unsafe { weak_k.as_mut() }
.set(&mut vm.heap, mode_k, mode_v)
.expect("valid key");
unsafe { proxies.as_mut() }.set_metatable(Some(weak_k));
let f = vm.native_with(nat_newproxy, Box::new([Value::Table(proxies)]));
vm.set_global("newproxy", f).expect("stdlib registration");
}
let version = match vm.version() {
crate::version::LuaVersion::Lua51 => "Lua 5.1",
crate::version::LuaVersion::Lua52 => "Lua 5.2",
crate::version::LuaVersion::Lua53 => "Lua 5.3",
crate::version::LuaVersion::Lua54 => "Lua 5.4",
crate::version::LuaVersion::MacroLua => "Lua 5.4",
crate::version::LuaVersion::Lua55 => "Lua 5.5",
};
let v = Value::Str(vm.heap.intern(version.as_bytes()));
vm.set_global("_VERSION", v).expect("stdlib registration");
let g = Value::Table(vm.globals());
vm.set_global("_G", g).expect("stdlib registration");
}
fn nat_assert(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let v = a.get(vm, 0);
if v.truthy() {
let vals: Vec<Value> = (0..nargs).map(|i| vm.nat_arg(fs, nargs, i)).collect();
return Ok(vm.nat_return(fs, &vals));
}
match vm.version() {
LuaVersion::Lua51 | LuaVersion::Lua52 => {
if vm.version() == LuaVersion::Lua51 {
argcheck::check_any(vm, a, 0)?;
}
match argcheck::opt_string(vm, a, 1)? {
Some(msg) => Err(raise(vm, Value::Str(msg))),
None => Err(raise_str(vm, "assertion failed!")),
}
}
_ => {
argcheck::check_any(vm, a, 0)?;
if nargs >= 2 {
Err(raise(vm, a.get(vm, 1)))
} else {
Err(raise_str(vm, "assertion failed!"))
}
}
}
}
fn nat_error(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let level = argcheck::opt_int(vm, a, 1, 1)?;
let msg = a.get(vm, 0);
if level <= 0 {
return Err(LuaError(msg));
}
let text = match msg {
Value::Str(s) => s.as_bytes().to_vec(),
Value::Int(_) | Value::Float(_) if vm.version() <= LuaVersion::Lua52 => {
argcheck::to_str_bytes(vm, msg).expect("a number converts to a string")
}
v => return Err(LuaError(v)),
};
let mut out = vm
.position_prefix_at_level(level as i64)
.map(String::into_bytes)
.unwrap_or_default();
out.extend_from_slice(&text);
Err(LuaError(Value::Str(vm.heap.intern(&out))))
}
fn raise(vm: &mut Vm, msg: Value) -> LuaError {
match msg {
Value::Str(s) => {
let text = match vm.position_prefix_at_level(1) {
Some(p) => {
let mut t = p.into_bytes();
t.extend_from_slice(s.as_bytes());
t
}
None => s.as_bytes().to_vec(),
};
LuaError(Value::Str(vm.heap.intern(&text)))
}
v => LuaError(v),
}
}
pub(crate) fn raise_str(vm: &mut Vm, msg: &str) -> LuaError {
raise_bytes(vm, msg.as_bytes())
}
pub(crate) fn raise_bytes(vm: &mut Vm, msg: &[u8]) -> LuaError {
let s = Value::Str(vm.heap.intern(msg));
raise(vm, s)
}
pub(crate) fn nat_pcall(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let f = argcheck::check_any(vm, a, 0)?;
let args: Vec<Value> = (1..nargs).map(|i| vm.nat_arg(fs, nargs, i)).collect();
match vm.call_value(f, &args) {
Ok(results) => {
let mut out = Vec::with_capacity(results.len() + 1);
out.push(Value::Bool(true));
out.extend(results);
Ok(vm.nat_return(fs, &out))
}
Err(e) => Ok(vm.nat_return(fs, &[Value::Bool(false), e.0])),
}
}
fn nat_type(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let v = argcheck::check_any(vm, Args::new(fs, nargs), 0)?;
let s = Value::Str(vm.heap.intern(v.type_name().as_bytes()));
Ok(vm.nat_return(fs, &[s]))
}
fn nat_print(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let global_tostring = if vm.version() <= LuaVersion::Lua53 {
let g = Value::Table(vm.globals());
let key = Value::Str(vm.heap.intern(b"tostring"));
Some(vm.index_value(g, key)?)
} else {
None
};
let mut out = Vec::new();
for i in 0..nargs {
let v = vm.nat_arg(fs, nargs, i);
let piece = match global_tostring {
Some(ts) => match vm.call_noyield(ts, &[v]) {
Ok(r) => match r.first().and_then(|&s| argcheck::to_str_bytes(vm, s)) {
Some(b) => Ok(b),
None => Err(raise_str(vm, "'tostring' must return a string to 'print'")),
},
Err(e) => Err(e),
},
None => vm.tostring_value(v),
};
let piece = match piece {
Ok(b) => b,
Err(e) => {
write_stdout(&out);
return Err(e);
}
};
if i > 0 {
out.push(b'\t');
}
let piece = match piece.iter().position(|&c| c == 0) {
Some(nul) if vm.version() == LuaVersion::Lua51 => &piece[..nul],
_ => &piece[..],
};
out.extend_from_slice(piece);
}
out.push(b'\n');
write_stdout(&out);
Ok(0)
}
fn write_stdout(bytes: &[u8]) {
let _ = std::io::stdout().write_all(bytes);
}
fn nat_tostring(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let v = argcheck::check_any(vm, Args::new(fs, nargs), 0)?;
if let Value::Int(i) = v
&& vm.metatable_of(v).is_none()
{
let mut buf = [0u8; 20];
let bytes = crate::numeric::write_i64_dec(i, &mut buf);
let s = Value::Str(vm.heap.intern(bytes));
return Ok(vm.nat_return(fs, &[s]));
}
if vm.version() <= LuaVersion::Lua52 {
use crate::vm::exec::Mm;
let mm = vm.get_mm(v, Mm::ToString);
if !mm.is_nil() {
let r = vm.call_noyield(mm, &[v])?;
let mut out = r.into_iter().next().unwrap_or(Value::Nil);
if vm.version() == LuaVersion::Lua52
&& let Some(b) = match out {
Value::Int(_) | Value::Float(_) => argcheck::to_str_bytes(vm, out),
_ => None,
}
{
out = Value::Str(vm.heap.intern(&b));
}
return Ok(vm.nat_return(fs, &[out]));
}
}
let bytes = vm.tostring_value(v)?;
let s = Value::Str(vm.heap.intern(&bytes));
Ok(vm.nat_return(fs, &[s]))
}
fn nat_rawget(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let t = argcheck::check_table(vm, a, 0)?;
let k = argcheck::check_any(vm, a, 1)?;
let v = t.get(k);
Ok(vm.nat_return(fs, &[v]))
}
fn nat_rawset(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let t = argcheck::check_table(vm, a, 0)?;
let k = argcheck::check_any(vm, a, 1)?;
let v = argcheck::check_any(vm, a, 2)?;
vm.raw_set(t, k, v)?;
Ok(vm.nat_return(fs, &[Value::Table(t)]))
}
fn nat_rawequal(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let x = argcheck::check_any(vm, a, 0)?;
let y = argcheck::check_any(vm, a, 1)?;
Ok(vm.nat_return(fs, &[Value::Bool(x.raw_eq(y))]))
}
fn nat_rawlen(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let n = match a.get(vm, 0) {
Value::Table(t) => t.len(),
Value::Str(s) => s.len() as i64,
_ => return Err(argcheck::arg_expected(vm, a, 0, "table or string")),
};
Ok(vm.nat_return(fs, &[Value::Int(n)]))
}
fn nat_setmetatable(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
use crate::vm::exec::Mm;
let a = Args::new(fs, nargs);
let t = argcheck::check_table(vm, a, 0)?;
let mt = match a.get(vm, 1) {
_ if a.is_none(1) => return Err(argcheck::arg_expected(vm, a, 1, "nil or table")),
Value::Nil => None,
Value::Table(m) => Some(m),
_ => return Err(argcheck::arg_expected(vm, a, 1, "nil or table")),
};
if !vm.get_mm(Value::Table(t), Mm::Metatable).is_nil() {
return Err(raise_str(vm, "cannot change a protected metatable"));
}
unsafe { t.as_mut() }.set_metatable(mt);
vm.barrier_back_table(t);
vm.check_finalizer(t);
Ok(vm.nat_return(fs, &[Value::Table(t)]))
}
fn nat_getmetatable(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
use crate::vm::exec::Mm;
let v = argcheck::check_any(vm, Args::new(fs, nargs), 0)?;
let protected = vm.get_mm(v, Mm::Metatable);
if !protected.is_nil() {
return Ok(vm.nat_return(fs, &[protected]));
}
let mt = vm.metatable_of(v).map(Value::Table).unwrap_or(Value::Nil);
Ok(vm.nat_return(fs, &[mt]))
}
fn nat_select(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let n = match a.get(vm, 0) {
Value::Int(i) if vm.version() >= LuaVersion::Lua53 => i,
Value::Str(s) if s.as_bytes().first() == Some(&b'#') => {
return Ok(vm.nat_return(fs, &[Value::Int(nargs as i64 - 1)]));
}
_ if vm.version() <= LuaVersion::Lua52 => argcheck::check_int(vm, a, 0)? as i64,
_ => argcheck::check_integer(vm, a, 0)?,
};
let top = nargs as i64;
let i = if n < 0 {
top + n
} else if n > top {
top
} else {
n
};
if i < 1 {
return Err(arg_error(vm, 1, "index out of range"));
}
let vals: Vec<Value> = (i..top).map(|k| vm.nat_arg(fs, nargs, k as u32)).collect();
Ok(vm.nat_return(fs, &vals))
}
fn nat_next(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let t = argcheck::check_table(vm, a, 0)?;
let k = a.get(vm, 1);
if let Value::Float(f) = k
&& vm.version() >= LuaVersion::Lua53
&& crate::runtime::value::f2i_exact(f).is_some()
{
return Err(vm.plain_err("invalid key to 'next'"));
}
match t.next(k) {
Ok(Some((k, v))) => Ok(vm.nat_return(fs, &[k, v])),
Ok(None) => Ok(vm.nat_return(fs, &[Value::Nil])),
Err(_) => Err(vm.plain_err("invalid key to 'next'")),
}
}
pub(crate) fn nat_pairs(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
use crate::vm::exec::Mm;
let a = Args::new(fs, nargs);
let ver = vm.version();
if ver >= LuaVersion::Lua53 {
argcheck::check_any(vm, a, 0)?;
}
let t = a.get(vm, 0);
if ver >= LuaVersion::Lua52 {
let mm = vm.get_mm(t, Mm::Pairs);
if !mm.is_nil() {
let n = pairs_mm_results(vm);
let res = vm.call_noyield(mm, &[t])?;
let mut out = [Value::Nil; 4];
for (slot, v) in out.iter_mut().zip(res) {
*slot = v;
}
return Ok(vm.nat_return(fs, &out[..n]));
}
}
if ver <= LuaVersion::Lua52 {
argcheck::check_table(vm, a, 0)?;
}
let it = vm.nat_upval(fs, 0);
if ver >= LuaVersion::Lua55 {
Ok(vm.nat_return(fs, &[it, t, Value::Nil, Value::Nil]))
} else {
Ok(vm.nat_return(fs, &[it, t, Value::Nil]))
}
}
pub(crate) fn pairs_mm_results(vm: &Vm) -> usize {
if vm.version() >= LuaVersion::Lua55 {
4
} else {
3
}
}
#[doc(hidden)]
pub fn ipairs_iter(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let i = match vm.nat_arg(fs, nargs, 1) {
Value::Int(i) if vm.version() >= LuaVersion::Lua53 => i,
_ if vm.version() <= LuaVersion::Lua52 => return ipairs_iter_raw(vm, fs, nargs),
_ => argcheck::check_integer(vm, Args::new(fs, nargs), 1)?,
};
let tv = vm.nat_arg(fs, nargs, 0);
let next_i = i.wrapping_add(1);
let v = vm.index_value(tv, Value::Int(next_i))?;
if v.is_nil() {
Ok(vm.nat_return(fs, &[Value::Nil]))
} else {
Ok(vm.nat_return(fs, &[Value::Int(next_i), v]))
}
}
#[cold]
fn ipairs_iter_raw(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let i = argcheck::check_int(vm, a, 1)?.wrapping_add(1);
let t = argcheck::check_table(vm, a, 0)?;
let v = t.get_int(i as i64);
if !v.is_nil() {
Ok(vm.nat_return(fs, &[Value::Int(i as i64), v]))
} else if vm.version() == LuaVersion::Lua51 {
Ok(vm.nat_return(fs, &[]))
} else {
Ok(vm.nat_return(fs, &[Value::Nil]))
}
}
fn nat_ipairs(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let ver = vm.version();
if ver >= LuaVersion::Lua53 {
argcheck::check_any(vm, a, 0)?;
}
let t = a.get(vm, 0);
if (ver == LuaVersion::Lua52 || ver == LuaVersion::Lua53)
&& let Some(mt) = vm.metatable_of(t)
{
let key = Value::Str(vm.heap.intern(b"__ipairs"));
let mm = mt.get(key);
if !mm.is_nil() {
let rs = vm.call_noyield(mm, &[t])?;
let mut out = [Value::Nil; 3];
for (slot, v) in out.iter_mut().zip(rs) {
*slot = v;
}
return Ok(vm.nat_return(fs, &out));
}
}
if ver <= LuaVersion::Lua52 {
argcheck::check_table(vm, a, 0)?;
}
let it = vm.nat_upval(fs, 0);
Ok(vm.nat_return(fs, &[it, t, Value::Int(0)]))
}
pub(crate) fn arg_error(vm: &mut Vm, n: u32, extra: &str) -> LuaError {
let extraargs = if vm.version() >= LuaVersion::Lua55 {
let ts = vm.thread_stack(None);
if ts.levels.is_empty() {
0
} else {
u32::try_from(vm.level_ar(&ts, 0).extraargs).expect("a __call chain length is small")
}
} else {
0
};
let call_name = vm.running_call_name();
let (argword, n) = if n <= extraargs {
("extra argument", n)
} else {
let n = n - extraargs;
if let Some(("method", name)) = &call_name {
let n = n - 1; if n == 0 {
return raise_str(vm, &format!("calling '{name}' on bad self ({extra})"));
}
("argument", n)
} else {
("argument", n)
}
};
let name = match call_name {
Some((_, name)) => name,
None => unnamed_native_name(vm),
};
raise_str(vm, &format!("bad {argword} #{n} to '{name}' ({extra})"))
}
fn unnamed_native_name(vm: &mut Vm) -> String {
if vm.version() == crate::version::LuaVersion::Lua51 {
return "?".to_string();
}
let Some(target) = vm.running_natives.last().map(|nc| nc.f) else {
return "?".to_string();
};
vm.pushglobalfuncname(target)
.unwrap_or_else(|| "?".to_string())
}
pub(crate) fn nat_tonumber(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
if vm.version() == LuaVersion::Lua51 {
return tonumber_51(vm, a);
}
if a.is_none_or_nil(vm, 1) {
let out = std_tonumber(vm, a)?;
return Ok(vm.nat_return(fs, &[out]));
}
let out = if vm.version() == LuaVersion::Lua52 {
let s = argcheck::check_string(vm, a, 0)?;
let base = argcheck::check_int(vm, a, 1)?;
if !(2..=36).contains(&base) {
return Err(arg_error(vm, 2, "base out of range"));
}
match str2number_base(s.as_bytes(), base as u32) {
Some((neg, digits)) => {
let n = digits.fold(0.0f64, |n, d| n * base as f64 + d as f64);
Value::Float(if neg { -n } else { n })
}
None => Value::Nil,
}
} else {
let base = argcheck::check_integer(vm, a, 1)?;
let Value::Str(s) = a.get(vm, 0) else {
return Err(argcheck::type_error(vm, a, 0, "string"));
};
if !(2..=36).contains(&base) {
return Err(arg_error(vm, 2, "base out of range"));
}
match str2number_base(s.as_bytes(), base as u32) {
Some((neg, digits)) => {
let n = digits.fold(0u64, |n, d| {
n.wrapping_mul(base as u64).wrapping_add(d as u64)
});
Value::Int(if neg { n.wrapping_neg() } else { n } as i64)
}
None => Value::Nil,
}
};
Ok(vm.nat_return(fs, &[out]))
}
fn std_tonumber(vm: &mut Vm, a: Args) -> Result<Value, LuaError> {
let v = a.get(vm, 0);
match v {
Value::Int(_) | Value::Float(_) => return Ok(v),
Value::Str(_) => {
if let Some(n) = argcheck::to_num(vm, v) {
return Ok(match n {
crate::numeric::Num::Int(i) => Value::Int(i),
crate::numeric::Num::Float(f) => Value::Float(f),
});
}
}
_ => {}
}
argcheck::check_any(vm, a, 0)?;
Ok(Value::Nil)
}
fn is_c_space(c: u8) -> bool {
matches!(c, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r')
}
fn alnum_digit(c: u8) -> Option<u32> {
match c {
b'0'..=b'9' => Some((c - b'0') as u32),
b'a'..=b'z' => Some((c - b'a') as u32 + 10),
b'A'..=b'Z' => Some((c - b'A') as u32 + 10),
_ => None,
}
}
fn str2number_base(s: &[u8], base: u32) -> Option<(bool, impl Iterator<Item = u32> + '_)> {
let start = s.iter().position(|&c| !is_c_space(c)).unwrap_or(s.len());
let mut rest = &s[start..];
let neg = rest.first() == Some(&b'-');
if matches!(rest.first(), Some(b'-' | b'+')) {
rest = &rest[1..];
}
let n = rest
.iter()
.take_while(|c| c.is_ascii_alphanumeric())
.count();
if n == 0 || rest[n..].iter().any(|&c| !is_c_space(c)) {
return None;
}
let digits = &rest[..n];
if digits
.iter()
.any(|&c| alnum_digit(c).is_none_or(|d| d >= base))
{
return None;
}
Some((
neg,
digits
.iter()
.map(|&c| alnum_digit(c).expect("checked above")),
))
}
#[cold]
fn tonumber_51(vm: &mut Vm, a: Args) -> Result<u32, LuaError> {
let base = argcheck::opt_int(vm, a, 1, 10)?;
if base == 10 {
argcheck::check_any(vm, a, 0)?;
let out = match a.get(vm, 0) {
v @ (Value::Int(_) | Value::Float(_)) => v,
v @ Value::Str(_) => match argcheck::to_num(vm, v) {
Some(n) => Value::Float(n.as_f64()),
None => Value::Nil,
},
_ => Value::Nil,
};
return Ok(vm.nat_return(a.fs, &[out]));
}
let s = argcheck::check_string(vm, a, 0)?;
if !(2..=36).contains(&base) {
return Err(arg_error(vm, 2, "base out of range"));
}
let out = match strtoul(s.as_bytes(), base as u32) {
Some(n) => Value::Float(n as f64),
None => Value::Nil,
};
Ok(vm.nat_return(a.fs, &[out]))
}
fn strtoul(s: &[u8], base: u32) -> Option<u64> {
let s = &s[..s.iter().position(|&c| c == 0).unwrap_or(s.len())];
let mut i = s.iter().position(|&c| !is_c_space(c)).unwrap_or(s.len());
let neg = s.get(i) == Some(&b'-');
if matches!(s.get(i), Some(b'-' | b'+')) {
i += 1;
}
if base == 16
&& s.get(i) == Some(&b'0')
&& matches!(s.get(i + 1), Some(b'x' | b'X'))
&& s.get(i + 2)
.and_then(|&c| alnum_digit(c))
.is_some_and(|d| d < 16)
{
i += 2;
}
let digits_start = i;
let mut n: u64 = 0;
let mut overflow = false;
while let Some(d) = s.get(i).and_then(|&c| alnum_digit(c)).filter(|&d| d < base) {
match n
.checked_mul(base as u64)
.and_then(|n| n.checked_add(d as u64))
{
Some(v) => n = v,
None => overflow = true,
}
i += 1;
}
if i == digits_start || s[i..].iter().any(|&c| !is_c_space(c)) {
return None;
}
Some(if overflow {
u64::MAX
} else if neg {
n.wrapping_neg()
} else {
n
})
}
pub(crate) fn nat_load(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
if vm.version() == LuaVersion::Lua51 {
let name = argcheck::opt_string(vm, a, 1)?;
let reader = argcheck::check_function(vm, a, 0)?;
let name = name.map_or_else(|| b"=(load)".to_vec(), |n| n.as_bytes().to_vec());
return match read_chunk(vm, reader)? {
Ok(src) => load_chunk(vm, a, &src, &name, None),
Err(msg) => Ok(vm.nat_return(fs, &[Value::Nil, msg])),
};
}
let text = argcheck::to_str_bytes(vm, a.get(vm, 0));
let mode = load_mode(vm, a, 2)?;
let (src, name) = match text {
Some(src) => {
let name = argcheck::opt_string(vm, a, 1)?;
let name = name.map_or_else(|| src.clone(), |n| n.as_bytes().to_vec());
(src, name)
}
None => {
let name = argcheck::opt_string(vm, a, 1)?;
let name = name.map_or_else(|| b"=(load)".to_vec(), |n| n.as_bytes().to_vec());
let reader = argcheck::check_function(vm, a, 0)?;
match read_chunk(vm, reader)? {
Ok(src) => (src, name),
Err(msg) => return Ok(vm.nat_return(fs, &[Value::Nil, msg])),
}
}
};
load_chunk(vm, a, &src, &name, mode.as_deref())
}
fn nat_loadstring(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let src = argcheck::check_string(vm, a, 0)?;
let name = argcheck::opt_string(vm, a, 1)?.unwrap_or(src);
load_chunk(vm, a, src.as_bytes(), name.as_bytes(), None)
}
fn load_mode(vm: &mut Vm, a: Args, i: u32) -> Result<Option<Vec<u8>>, LuaError> {
let mode = argcheck::opt_string(vm, a, i)?.map(|m| m.as_bytes().to_vec());
if vm.version() >= LuaVersion::Lua55 {
if mode.as_ref().is_some_and(|m| m.contains(&b'B')) {
return Err(arg_error(vm, i + 1, "invalid mode"));
}
return Ok(mode);
}
Ok(Some(mode.unwrap_or_else(|| b"bt".to_vec())))
}
fn read_chunk(vm: &mut Vm, reader: Value) -> Result<Result<Vec<u8>, Value>, LuaError> {
let mut buf = Vec::new();
let mut try_early = true;
let input_budget = vm.loader_input_budget();
loop {
let r = match vm.call_noyield(reader, &[]) {
Ok(r) => r,
Err(e) => return Ok(Err(e.0)),
};
let piece = match r.first() {
None | Some(Value::Nil) => break,
Some(&v) => match argcheck::to_str_bytes(vm, v) {
Some(b) => b,
None => {
let m = Value::Str(vm.heap.intern(b"reader function must return a string"));
return Ok(Err(m));
}
},
};
if piece.is_empty() {
break;
}
if piece.len() > input_budget.saturating_sub(buf.len()) {
return Ok(Err(Value::Str(vm.heap.intern(b"not enough memory"))));
}
buf.extend_from_slice(&piece);
if try_early && buf.len() >= 2 && !crate::vm::dump::is_binary_chunk(&buf) {
try_early = false;
let ver = vm.version();
if let Err(e) = crate::frontend::parse(&buf, ver) {
let msg_str = String::from_utf8_lossy(&e.msg);
let eof_related = msg_str.contains("<eof>") || msg_str.contains("near eof");
if !eof_related {
break;
}
}
}
}
Ok(Ok(buf))
}
fn load_chunk(
vm: &mut Vm,
a: Args,
src: &[u8],
name: &[u8],
mode: Option<&[u8]>,
) -> Result<u32, LuaError> {
let binary = crate::vm::dump::is_binary_chunk(src);
let kind: &[u8] = if binary { b"b" } else { b"t" };
if let Some(mode) = mode.map(|m| &m[..m.iter().position(|&c| c == 0).unwrap_or(m.len())])
&& !mode.contains(&kind[0])
{
let msg = format!(
"attempt to load a {} chunk (mode is '{}')",
if binary { "binary" } else { "text" },
String::from_utf8_lossy(mode)
);
let m = Value::Str(vm.heap.intern(msg.as_bytes()));
return Ok(vm.nat_return(a.fs, &[Value::Nil, m]));
}
match vm.load(src, name) {
Ok(cl) => {
if vm.version() >= LuaVersion::Lua52 && !a.is_none(3) && !cl.upvals().is_empty() {
let env = a.get(vm, 3);
let uv = vm.heap.new_upvalue(crate::runtime::UpvalState::Closed(env));
unsafe { cl.as_mut() }.upvals_mut()[0] = uv;
}
Ok(vm.nat_return(a.fs, &[Value::Closure(cl)]))
}
Err(e) => {
let m = vm.load_error_value(&e, name);
Ok(vm.nat_return(a.fs, &[Value::Nil, m]))
}
}
}
fn nat_newproxy(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
use crate::runtime::userdata::UserdataPayload;
let Value::Table(proxies) = vm.nat_upval(fs, 0) else {
unreachable!("newproxy is registered with its metatable set")
};
let arg = vm.nat_arg(fs, nargs, 0);
let mt = match arg {
v if !v.truthy() => None,
Value::Bool(true) => {
let m = vm.heap.new_table();
unsafe { proxies.as_mut() }
.set(&mut vm.heap, Value::Table(m), Value::Bool(true))
.expect("a table key is never nil or NaN");
vm.barrier_back_table(proxies);
Some(m)
}
v => match vm.metatable_of(v) {
Some(m) if proxies.get(Value::Table(m)).truthy() => Some(m),
_ => return Err(arg_error(vm, 1, "boolean or proxy expected")),
},
};
let u = vm.heap.new_userdata(UserdataPayload::Empty, false);
if let Some(mt) = mt {
unsafe { u.as_mut() }.set_metatable(Some(mt));
vm.heap.register_finalizable_userdata(u);
}
Ok(vm.nat_return(fs, &[Value::Userdata(u)]))
}
fn nat_gcinfo(vm: &mut Vm, fs: u32, _nargs: u32) -> Result<u32, LuaError> {
let kb = (vm.heap.bytes() as f64 / 1024.0).floor() as i64;
Ok(vm.nat_return(fs, &[Value::Int(kb)]))
}
enum FenvTarget {
Lua(crate::runtime::Gc<crate::runtime::LuaClosure>),
C,
}
fn fenv_target(vm: &mut Vm, a: Args, level_optional: bool) -> Result<FenvTarget, LuaError> {
match a.get(vm, 0) {
Value::Closure(c) => return Ok(FenvTarget::Lua(c)),
Value::Native(_) => return Ok(FenvTarget::C),
_ => {}
}
let level = if level_optional {
argcheck::opt_int(vm, a, 0, 1)?
} else {
argcheck::check_int(vm, a, 0)?
};
if level < 0 {
return Err(arg_error(vm, 1, "level must be non-negative"));
}
if level == 0 {
return Ok(FenvTarget::C);
}
use crate::vm::callstack::DbgKind;
match vm.dbg_frame(level as i64) {
Some(DbgKind::Lua(_)) => Ok(FenvTarget::Lua(
vm.lua_closure_at_level(level as i64)
.expect("a Lua level has a closure"),
)),
Some(DbgKind::C(_)) => Ok(FenvTarget::C),
Some(DbgKind::Tail) => Err(raise_str(
vm,
&format!("no function environment for tail call at level {level}"),
)),
None => Err(arg_error(vm, 1, "invalid level")),
}
}
fn env_upvalue(cl: crate::runtime::Gc<crate::runtime::LuaClosure>) -> Option<usize> {
cl.proto.upvals.iter().position(|d| &*d.name == "_ENV")
}
fn nat_setfenv(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let env = argcheck::check_table(vm, a, 1)?;
let target = fenv_target(vm, a, false)?;
if argcheck::to_num(vm, a.get(vm, 0)).is_some_and(|n| n.as_f64() == 0.0) {
vm.set_globals(env);
return Ok(vm.nat_return(fs, &[]));
}
let cl = match target {
FenvTarget::Lua(cl) => cl,
FenvTarget::C => {
return Err(raise_str(
vm,
"'setfenv' cannot change environment of given object",
));
}
};
let env_idx = env_upvalue(cl)
.ok_or_else(|| raise_str(vm, "'setfenv' cannot change environment of given object"))?;
let uv = cl.upvals()[env_idx];
unsafe { uv.as_mut() }.set_closed(Value::Table(env));
vm.barrier_forward_upvalue(uv, Value::Table(env));
Ok(vm.nat_return(fs, &[Value::Closure(cl)]))
}
fn nat_getfenv(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
use crate::runtime::UpvalState;
let env = match fenv_target(vm, Args::new(fs, nargs), true)? {
FenvTarget::Lua(c) => match env_upvalue(c) {
Some(i) => match c.upvals()[i].state() {
UpvalState::Closed(v) => v,
UpvalState::Open { slot, thread } => vm.read_slot(slot, thread),
},
None => Value::Table(vm.globals()),
},
FenvTarget::C => Value::Table(vm.globals()),
};
Ok(vm.nat_return(fs, &[env]))
}
pub(crate) fn nat_warn(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let mut parts = Vec::with_capacity(nargs.max(1) as usize);
for i in 0..nargs.max(1) {
parts.push(argcheck::check_string(vm, a, i)?);
}
let n = parts.len();
for (i, p) in parts.iter().enumerate() {
vm.emit_warn(p.as_bytes(), i + 1 < n);
}
Ok(vm.nat_return(fs, &[]))
}
pub(crate) fn nat_xpcall(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
xpcall_native(vm, fs, nargs, vm.version() > LuaVersion::Lua51)
}
pub(crate) fn nat_host_xpcall(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
xpcall_native(vm, fs, nargs, true)
}
fn xpcall_native(vm: &mut Vm, fs: u32, nargs: u32, forward: bool) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let h = xpcall_handler(vm, a)?;
let f = vm.nat_arg(fs, nargs, 0);
let first_arg = if forward { 2 } else { nargs };
let args: Vec<Value> = (first_arg..nargs)
.map(|i| vm.nat_arg(fs, nargs, i))
.collect();
match vm.call_value(f, &args) {
Ok(results) => {
let mut out = Vec::with_capacity(results.len() + 1);
out.push(Value::Bool(true));
out.extend(results);
Ok(vm.nat_return(fs, &out))
}
Err(e) => {
let m = handle_error(vm, h, e.0);
Ok(vm.nat_return(fs, &[Value::Bool(false), m]))
}
}
}
pub(crate) fn xpcall_handler(vm: &mut Vm, a: Args) -> Result<Value, LuaError> {
if vm.version() >= LuaVersion::Lua53 {
argcheck::check_function(vm, a, 1)
} else {
argcheck::check_any(vm, a, 1)
}
}
fn handle_error(vm: &mut Vm, h: Value, err: Value) -> Value {
let callable =
vm.version() >= LuaVersion::Lua53 || matches!(h, Value::Closure(_) | Value::Native(_));
match callable.then(|| vm.call_value(h, &[err])) {
Some(Ok(hr)) => hr.first().copied().unwrap_or(Value::Nil),
_ => Value::Str(vm.heap.intern(b"error in error handling")),
}
}