use crate::numeric::{self, Num};
use crate::runtime::Value;
use crate::runtime::value::f2i_exact;
use crate::version::LuaVersion;
use crate::vm::builtins::{arg_error, raise_str};
use crate::vm::error::LuaError;
use crate::vm::exec::Vm;
#[derive(Clone, Copy)]
pub(crate) struct Args {
pub fs: u32,
pub n: u32,
}
impl Args {
pub(crate) fn new(fs: u32, n: u32) -> Self {
Args { fs, n }
}
pub(crate) fn is_none(self, i: u32) -> bool {
i >= self.n
}
pub(crate) fn is_none_or_nil(self, vm: &Vm, i: u32) -> bool {
self.is_none(i) || vm.nat_arg(self.fs, self.n, i).is_nil()
}
pub(crate) fn get(self, vm: &Vm, i: u32) -> Value {
vm.nat_arg(self.fs, self.n, i)
}
}
pub(crate) fn typename_at(vm: &Vm, a: Args, i: u32) -> String {
if a.is_none(i) {
return "no value".to_string();
}
typename_of(vm, a.get(vm, i))
}
pub(crate) fn typename_of(vm: &Vm, v: Value) -> String {
if vm.version() >= LuaVersion::Lua53 {
match v {
Value::LightUserdata(_) => "light userdata".to_string(),
_ => vm.obj_typename(v),
}
} else {
v.type_name().to_string()
}
}
pub(crate) fn type_error(vm: &mut Vm, a: Args, i: u32, expected: &str) -> LuaError {
let got = typename_at(vm, a, i);
arg_error(vm, i + 1, &format!("{expected} expected, got {got}"))
}
pub(crate) fn check_any(vm: &mut Vm, a: Args, i: u32) -> Result<Value, LuaError> {
if a.is_none(i) {
return Err(arg_error(vm, i + 1, "value expected"));
}
Ok(a.get(vm, i))
}
pub(crate) fn to_num(vm: &Vm, v: Value) -> Option<Num> {
match v {
Value::Int(x) => Some(Num::Int(x)),
Value::Float(f) => Some(Num::Float(f)),
Value::Str(s) => crate::vm::exec::str_to_num(s.as_bytes(), vm.version()),
_ => None,
}
}
pub(crate) fn check_number(vm: &mut Vm, a: Args, i: u32) -> Result<f64, LuaError> {
match to_num(vm, a.get(vm, i)) {
Some(n) if !a.is_none(i) => Ok(n.as_f64()),
_ => Err(type_error(vm, a, i, "number")),
}
}
pub(crate) fn opt_number(vm: &mut Vm, a: Args, i: u32, default: f64) -> Result<f64, LuaError> {
if a.is_none_or_nil(vm, i) {
return Ok(default);
}
check_number(vm, a, i)
}
pub(crate) fn check_integer(vm: &mut Vm, a: Args, i: u32) -> Result<i64, LuaError> {
if let Value::Int(x) = a.get(vm, i) {
return Ok(x);
}
let n = match to_num(vm, a.get(vm, i)) {
Some(n) if !a.is_none(i) => n,
_ => return Err(type_error(vm, a, i, "number")),
};
match n {
Num::Int(x) => Ok(x),
Num::Float(f) if vm.version() <= LuaVersion::Lua52 => Ok(f as i64),
Num::Float(f) => {
f2i_exact(f).ok_or_else(|| arg_error(vm, i + 1, "number has no integer representation"))
}
}
}
pub(crate) fn opt_integer(vm: &mut Vm, a: Args, i: u32, default: i64) -> Result<i64, LuaError> {
if a.is_none_or_nil(vm, i) {
return Ok(default);
}
check_integer(vm, a, i)
}
const MAXCSTACK_51: i64 = 8000;
pub(crate) fn check_stack(vm: &mut Vm, a: Args, n: i64, msg: &str) -> Result<(), LuaError> {
let fits = if vm.version() == LuaVersion::Lua51 {
n <= MAXCSTACK_51 && i64::from(a.n) + n <= MAXCSTACK_51
} else {
n < vm.stack_room()
};
if fits {
Ok(())
} else {
Err(raise_str(vm, &format!("stack overflow ({msg})")))
}
}
pub(crate) fn check_unsigned52(vm: &mut Vm, a: Args, i: u32) -> Result<u32, LuaError> {
match to_num(vm, a.get(vm, i)) {
Some(n) if !a.is_none(i) => Ok((n.as_f64() + 6_755_399_441_055_744.0).to_bits() as u32),
_ => Err(type_error(vm, a, i, "number")),
}
}
pub(crate) fn to_str_bytes(vm: &Vm, v: Value) -> Option<Vec<u8>> {
match v {
Value::Str(s) => Some(s.as_bytes().to_vec()),
Value::Int(x) => Some(numeric::num_to_string(Num::Int(x)).into_bytes()),
Value::Float(f) => {
Some(numeric::num_to_string_for(Num::Float(f), vm.float_fmt()).into_bytes())
}
_ => None,
}
}
pub(crate) fn check_int(vm: &mut Vm, a: Args, i: u32) -> Result<i32, LuaError> {
check_integer(vm, a, i).map(|x| x as i32)
}
pub(crate) fn opt_int(vm: &mut Vm, a: Args, i: u32, default: i32) -> Result<i32, LuaError> {
if a.is_none_or_nil(vm, i) {
return Ok(default);
}
check_int(vm, a, i)
}
pub(crate) fn arg_expected(vm: &mut Vm, a: Args, i: u32, tname: &str) -> LuaError {
if vm.version() >= LuaVersion::Lua54 {
type_error(vm, a, i, tname)
} else {
arg_error(vm, i + 1, &format!("{tname} expected"))
}
}
pub(crate) fn check_string(
vm: &mut Vm,
a: Args,
i: u32,
) -> Result<crate::runtime::Gc<crate::runtime::LuaStr>, LuaError> {
let v = a.get(vm, i);
if let Value::Str(s) = v {
return Ok(s);
}
match to_str_bytes(vm, v) {
Some(b) if !a.is_none(i) => {
let s = vm.heap.intern(&b);
vm.nat_set_arg(a.fs, i, Value::Str(s));
Ok(s)
}
_ => Err(type_error(vm, a, i, "string")),
}
}
pub(crate) fn opt_string(
vm: &mut Vm,
a: Args,
i: u32,
) -> Result<Option<crate::runtime::Gc<crate::runtime::LuaStr>>, LuaError> {
if a.is_none_or_nil(vm, i) {
return Ok(None);
}
check_string(vm, a, i).map(Some)
}
pub(crate) fn check_table(
vm: &mut Vm,
a: Args,
i: u32,
) -> Result<crate::runtime::Gc<crate::runtime::Table>, LuaError> {
match a.get(vm, i) {
Value::Table(t) if !a.is_none(i) => Ok(t),
_ => Err(type_error(vm, a, i, "table")),
}
}
pub(crate) fn check_function(vm: &mut Vm, a: Args, i: u32) -> Result<Value, LuaError> {
match a.get(vm, i) {
v @ (Value::Closure(_) | Value::Native(_)) if !a.is_none(i) => Ok(v),
_ => Err(type_error(vm, a, i, "function")),
}
}
pub(crate) fn check_option(
vm: &mut Vm,
a: Args,
i: u32,
def: Option<&str>,
options: &[&str],
) -> Result<usize, LuaError> {
let name = match def {
Some(d) => match opt_string(vm, a, i)? {
Some(s) => s.as_bytes().to_vec(),
None => d.as_bytes().to_vec(),
},
None => check_string(vm, a, i)?.as_bytes().to_vec(),
};
let name = name
.split(|&b| b == 0)
.next()
.expect("split yields a first piece");
if let Some(k) = options.iter().position(|o| o.as_bytes() == name) {
return Ok(k);
}
let shown = String::from_utf8_lossy(name);
Err(arg_error(vm, i + 1, &format!("invalid option '{shown}'")))
}