use crate::lib_registry::LibraryModule;
use crate::lua_value::{LuaValue, LuaValueKind};
use crate::lua_vm::LuaResult;
use crate::lua_vm::LuaState;
use crate::lua_vm::{LuaError, LuaRng};
use crate::platform_time;
#[inline(always)]
fn checknumber(l: &mut LuaState, n: usize, fname: &str) -> Result<f64, LuaError> {
let v = unsafe { l.get_arg_unchecked(n) };
if let Some(f) = v.as_number() {
return Ok(f);
}
let t = crate::stdlib::debug::objtypename(l, &v);
Err(l.error(format!(
"bad argument #{} to '{}' (number expected, got {})",
n, fname, t
)))
}
pub fn create_math_lib() -> LibraryModule {
let mut module = crate::lib_module!("math", {
"abs" => math_abs,
"acos" => math_acos,
"asin" => math_asin,
"atan" => math_atan,
"ceil" => math_ceil,
"cos" => math_cos,
"deg" => math_deg,
"exp" => math_exp,
"floor" => math_floor,
"fmod" => math_fmod,
"frexp" => math_frexp,
"ldexp" => math_ldexp,
"log" => math_log,
"max" => math_max,
"min" => math_min,
"modf" => math_modf,
"rad" => math_rad,
"random" => math_random,
"randomseed" => math_randomseed,
"sin" => math_sin,
"sqrt" => math_sqrt,
"tan" => math_tan,
"tointeger" => math_tointeger,
"type" => math_type,
"ult" => math_ult,
});
module = module.with_value("pi", |_vm| Ok(LuaValue::float(std::f64::consts::PI)));
module = module.with_value("huge", |_vm| Ok(LuaValue::float(f64::INFINITY)));
module = module.with_value("maxinteger", |_vm| Ok(LuaValue::integer(i64::MAX)));
module = module.with_value("mininteger", |_vm| Ok(LuaValue::integer(i64::MIN)));
module
}
fn math_abs(l: &mut LuaState) -> LuaResult<usize> {
let value = unsafe { l.get_arg_unchecked(1) };
if let Some(i) = value.as_integer() {
unsafe { l.push_value_unchecked(LuaValue::integer(i.wrapping_abs())) };
return Ok(1);
}
if let Some(f) = value.as_float() {
unsafe { l.push_value_unchecked(LuaValue::float(f.abs())) };
return Ok(1);
}
Err(l.error("bad argument #1 to 'abs' (number expected)".to_string()))
}
fn math_acos(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "acos")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.acos())) };
Ok(1)
}
fn math_asin(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "asin")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.asin())) };
Ok(1)
}
fn math_atan(l: &mut LuaState) -> LuaResult<usize> {
let y = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'atan' (number expected)".to_string()))?
.as_number()
.ok_or_else(|| l.error("bad argument #1 to 'atan' (number expected)".to_string()))?;
let x = l.get_arg(2).and_then(|v| v.as_number()).unwrap_or(1.0);
l.push_value(LuaValue::float(y.atan2(x)))?;
Ok(1)
}
fn math_ceil(l: &mut LuaState) -> LuaResult<usize> {
let value = unsafe { l.get_arg_unchecked(1) };
if let Some(i) = value.as_integer() {
unsafe { l.push_value_unchecked(LuaValue::integer(i)) };
return Ok(1);
}
if let Some(f) = value.as_float() {
let ceiled = f.ceil();
if ceiled >= (i64::MIN as f64) && ceiled < -(i64::MIN as f64) {
unsafe { l.push_value_unchecked(LuaValue::integer(ceiled as i64)) };
} else {
unsafe { l.push_value_unchecked(LuaValue::float(ceiled)) };
}
return Ok(1);
}
Err(l.error("bad argument #1 to 'ceil' (number expected)".to_string()))
}
fn math_cos(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "cos")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.cos())) };
Ok(1)
}
fn math_deg(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "deg")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.to_degrees())) };
Ok(1)
}
fn math_exp(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "exp")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.exp())) };
Ok(1)
}
fn math_floor(l: &mut LuaState) -> LuaResult<usize> {
let value = unsafe { l.get_arg_unchecked(1) };
if let Some(i) = value.as_integer() {
unsafe { l.push_value_unchecked(LuaValue::integer(i)) };
return Ok(1);
}
if let Some(f) = value.as_float() {
let floored = f.floor();
if floored >= (i64::MIN as f64) && floored < -(i64::MIN as f64) {
unsafe { l.push_value_unchecked(LuaValue::integer(floored as i64)) };
} else {
unsafe { l.push_value_unchecked(LuaValue::float(floored)) };
}
return Ok(1);
}
Err(l.error("bad argument #1 to 'floor' (number expected)".to_string()))
}
fn math_fmod(l: &mut LuaState) -> LuaResult<usize> {
let arg1 = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'fmod' (number expected)".to_string()))?;
let arg2 = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'fmod' (number expected)".to_string()))?;
if arg1.is_integer() && arg2.is_integer() {
let a = arg1.as_integer_strict().unwrap();
let b = arg2.as_integer_strict().unwrap();
if b == 0 {
return Err(l.error("bad argument #2 to 'fmod' (zero)".to_string()));
}
let result = if b == -1 { 0 } else { a % b };
l.push_value(LuaValue::integer(result))?;
return Ok(1);
}
let x = arg1
.as_number()
.ok_or_else(|| l.error("bad argument #1 to 'fmod' (number expected)".to_string()))?;
let y = arg2
.as_number()
.ok_or_else(|| l.error("bad argument #2 to 'fmod' (number expected)".to_string()))?;
if y == 0.0 {
return Err(l.error("bad argument #2 to 'fmod' (zero)".to_string()));
}
l.push_value(LuaValue::float(x % y))?;
Ok(1)
}
fn math_log(l: &mut LuaState) -> LuaResult<usize> {
let x = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'log' (number expected)".to_string()))?
.as_number()
.ok_or_else(|| l.error("bad argument #1 to 'log' (number expected)".to_string()))?;
let base = l.get_arg(2).and_then(|v| v.as_number());
let result = if let Some(b) = base { x.log(b) } else { x.ln() };
l.push_value(LuaValue::float(result))?;
Ok(1)
}
fn lua_num_lt(a: &LuaValue, b: &LuaValue) -> bool {
match (a.as_integer_strict(), b.as_integer_strict()) {
(Some(ai), Some(bi)) => ai < bi,
(Some(ai), None) => {
let bf = b.as_number().unwrap_or(f64::NAN);
lua_int_lt_float(ai, bf)
}
(None, Some(bi)) => {
let af = a.as_number().unwrap_or(f64::NAN);
lua_float_lt_int(af, bi)
}
(None, None) => {
let af = a.as_number().unwrap_or(f64::NAN);
let bf = b.as_number().unwrap_or(f64::NAN);
af < bf
}
}
}
fn lua_int_lt_float(a: i64, b: f64) -> bool {
if b.is_nan() {
return false;
}
if b >= -(i64::MIN as f64) {
true } else if b < (i64::MIN as f64) {
false } else {
let bi = b as i64;
if (bi as f64) == b {
a < bi } else {
(a as f64) < b }
}
}
fn lua_float_lt_int(a: f64, b: i64) -> bool {
if a.is_nan() {
return false;
}
if a < (i64::MIN as f64) {
true } else if a >= -(i64::MIN as f64) {
false } else {
let ai = a as i64;
if (ai as f64) == a {
ai < b } else {
a < (b as f64)
}
}
}
fn math_max(l: &mut LuaState) -> LuaResult<usize> {
let argc = l.arg_count();
if argc == 0 {
return Err(l.error("bad argument to 'max' (value expected)".to_string()));
}
if argc == 2 {
let a = unsafe { l.get_arg_unchecked(1) };
let b = unsafe { l.get_arg_unchecked(2) };
let result = if lua_num_lt(&a, &b) { b } else { a };
if a.as_number().is_none() {
return Err(l.error("bad argument #1 to 'max' (number expected)".to_string()));
}
if b.as_number().is_none() {
return Err(l.error("bad argument #2 to 'max' (number expected)".to_string()));
}
unsafe { l.push_value_unchecked(result) };
return Ok(1);
}
let first = unsafe { l.get_arg_unchecked(1) };
let _ = first
.as_number()
.ok_or_else(|| l.error("bad argument #1 to 'max' (number expected)".to_string()))?;
let mut max_arg = first;
for i in 2..=argc {
let arg = l
.get_arg(i)
.ok_or_else(|| l.error(format!("bad argument #{} to 'max' (number expected)", i)))?;
let _ = arg
.as_number()
.ok_or_else(|| l.error(format!("bad argument #{} to 'max' (number expected)", i)))?;
if lua_num_lt(&max_arg, &arg) {
max_arg = arg;
}
}
unsafe { l.push_value_unchecked(max_arg) };
Ok(1)
}
fn math_min(l: &mut LuaState) -> LuaResult<usize> {
let argc = l.arg_count();
if argc == 0 {
return Err(l.error("bad argument to 'min' (value expected)".to_string()));
}
if argc == 2 {
let a = unsafe { l.get_arg_unchecked(1) };
let b = unsafe { l.get_arg_unchecked(2) };
let result = if lua_num_lt(&b, &a) { b } else { a };
if a.as_number().is_none() {
return Err(l.error("bad argument #1 to 'min' (number expected)".to_string()));
}
if b.as_number().is_none() {
return Err(l.error("bad argument #2 to 'min' (number expected)".to_string()));
}
unsafe { l.push_value_unchecked(result) };
return Ok(1);
}
let first = unsafe { l.get_arg_unchecked(1) };
let _ = first
.as_number()
.ok_or_else(|| l.error("bad argument #1 to 'min' (number expected)".to_string()))?;
let mut min_arg = first;
for i in 2..=argc {
let arg = l
.get_arg(i)
.ok_or_else(|| l.error(format!("bad argument #{} to 'min' (number expected)", i)))?;
let _ = arg
.as_number()
.ok_or_else(|| l.error(format!("bad argument #{} to 'min' (number expected)", i)))?;
if lua_num_lt(&arg, &min_arg) {
min_arg = arg;
}
}
unsafe { l.push_value_unchecked(min_arg) };
Ok(1)
}
fn math_modf(l: &mut LuaState) -> LuaResult<usize> {
let x = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'modf' (number expected)".to_string()))?
.as_number()
.ok_or_else(|| l.error("bad argument #1 to 'modf' (number expected)".to_string()))?;
let int_part = x.trunc();
let frac_part = if x.is_infinite() { 0.0 } else { x - int_part };
if !x.is_nan()
&& !x.is_infinite()
&& int_part >= i64::MIN as f64
&& int_part < -(i64::MIN as f64)
{
l.push_value(LuaValue::integer(int_part as i64))?;
} else {
l.push_value(LuaValue::float(int_part))?;
}
l.push_value(LuaValue::float(frac_part))?;
Ok(2)
}
fn math_rad(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "rad")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.to_radians())) };
Ok(1)
}
fn math_random(l: &mut LuaState) -> LuaResult<usize> {
let argc = l.arg_count();
if argc > 2 {
return Err(l.error("wrong number of arguments to 'random'".to_string()));
}
match argc {
0 => {
let r = l.vm_mut().rng.next_float();
l.push_value(LuaValue::float(r))?;
Ok(1)
}
1 => {
let rv = l.vm_mut().rng.next_rand();
let up = l
.get_arg(1)
.ok_or_else(|| {
l.error("bad argument #1 to 'random' (number expected)".to_string())
})?
.as_integer()
.ok_or_else(|| {
l.error("bad argument #1 to 'random' (number expected, got float)".to_string())
})?;
if up == 0 {
l.push_value(LuaValue::integer(rv as i64))?;
return Ok(1);
}
if up < 1 {
return Err(l.error("bad argument #1 to 'random' (interval is empty)".to_string()));
}
let result = project(rv, 1, up as u64)?;
l.push_value(LuaValue::integer(result))?;
Ok(1)
}
_ => {
let rv = l.vm_mut().rng.next_rand();
let low = l
.get_arg(1)
.ok_or_else(|| {
l.error("bad argument #1 to 'random' (number expected)".to_string())
})?
.as_integer()
.ok_or_else(|| {
l.error("bad argument #1 to 'random' (number expected, got float)".to_string())
})?;
let up = l
.get_arg(2)
.ok_or_else(|| {
l.error("bad argument #2 to 'random' (number expected)".to_string())
})?
.as_integer()
.ok_or_else(|| {
l.error("bad argument #2 to 'random' (number expected, got float)".to_string())
})?;
if low > up {
return Err(l.error("bad argument #2 to 'random' (interval is empty)".to_string()));
}
let result = project(rv, low as u64, up as u64)?;
l.push_value(LuaValue::integer(result))?;
Ok(1)
}
}
}
fn project(rv: u64, low: u64, up: u64) -> LuaResult<i64> {
let range = up.wrapping_sub(low).wrapping_add(1);
if range == 0 {
Ok(low.wrapping_add(rv) as i64)
} else {
Ok(low.wrapping_add(rv % range) as i64)
}
}
fn math_randomseed(l: &mut LuaState) -> LuaResult<usize> {
let argc = l.arg_count();
let (n1, n2) = if argc == 0 || (argc >= 1 && l.get_arg(1).is_none_or(|v| v.is_nil())) {
let time = platform_time::unix_nanos();
(time as i64, 0i64)
} else {
let seed1 = l.get_arg(1).and_then(|v| v.as_integer()).ok_or_else(|| {
l.error("bad argument #1 to 'randomseed' (number expected)".to_string())
})?;
let seed2 = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(0);
(seed1, seed2)
};
l.vm_mut().rng = LuaRng::from_seed(n1, n2);
l.push_value(LuaValue::integer(n1))?;
l.push_value(LuaValue::integer(n2))?;
Ok(2)
}
fn math_sin(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "sin")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.sin())) };
Ok(1)
}
fn math_sqrt(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "sqrt")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.sqrt())) };
Ok(1)
}
fn math_tan(l: &mut LuaState) -> LuaResult<usize> {
let x = checknumber(l, 1, "tan")?;
unsafe { l.push_value_unchecked(LuaValue::float(x.tan())) };
Ok(1)
}
fn math_tointeger(l: &mut LuaState) -> LuaResult<usize> {
let val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'tointeger' (value expected)".to_string()))?;
let result = if let Some(i) = val.as_integer() {
LuaValue::integer(i)
} else if let Some(f) = val.as_number() {
float_to_integer(f)
} else if let Some(s) = val.as_str() {
let s_str = s.trim();
if let Ok(i) = s_str.parse::<i64>() {
LuaValue::integer(i)
} else if let Ok(f) = s_str.parse::<f64>() {
float_to_integer(f)
} else {
LuaValue::nil()
}
} else {
LuaValue::nil()
};
l.push_value(result)?;
Ok(1)
}
fn float_to_integer(f: f64) -> LuaValue {
if !f.is_finite() {
return LuaValue::nil();
}
if f.fract() != 0.0 {
return LuaValue::nil();
}
const MIN_F: f64 = i64::MIN as f64; const MAX_PLUS_ONE: f64 = 9223372036854775808.0;
if (MIN_F..MAX_PLUS_ONE).contains(&f) {
let i = f as i64;
if i as f64 == f {
LuaValue::integer(i)
} else {
LuaValue::nil()
}
} else {
LuaValue::nil()
}
}
fn math_type(l: &mut LuaState) -> LuaResult<usize> {
let val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'type' (value expected)".to_string()))?;
let cs = &l.vm_mut().const_strings;
let result = match val.kind() {
LuaValueKind::Integer => cs.str_integer,
LuaValueKind::Float => cs.str_float,
_ => {
l.push_value(LuaValue::nil())?;
return Ok(1);
}
};
l.push_value(result)?;
Ok(1)
}
fn math_ult(l: &mut LuaState) -> LuaResult<usize> {
let m_value = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'ult' (integer expected)".to_string()))?;
let Some(m) = m_value.as_integer() else {
return Err(l.error("bad argument #1 to 'ult' (integer expected)".to_string()));
};
let n_value = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'ult' (integer expected)".to_string()))?;
let Some(n) = n_value.as_integer() else {
return Err(l.error("bad argument #2 to 'ult' (integer expected)".to_string()));
};
let result = (m as u64) < (n as u64);
l.push_value(LuaValue::boolean(result))?;
Ok(1)
}
fn math_frexp(l: &mut LuaState) -> LuaResult<usize> {
let x = l
.get_arg(1)
.and_then(|v| v.as_float().or_else(|| v.as_integer().map(|i| i as f64)))
.ok_or_else(|| l.error("bad argument #1 to 'frexp' (number expected)".to_string()))?;
if x == 0.0 {
l.push_value(LuaValue::float(0.0))?;
l.push_value(LuaValue::integer(0))?;
return Ok(2);
}
if x.is_infinite() || x.is_nan() {
l.push_value(LuaValue::float(x))?;
l.push_value(LuaValue::integer(0))?;
return Ok(2);
}
let bits = x.to_bits();
let sign = if (bits >> 63) != 0 { -1.0 } else { 1.0 };
let abs_x = x.abs();
let mut exp = ((bits >> 52) & 0x7FF) as i64 - 1022;
let mut mantissa =
sign * f64::from_bits((abs_x.to_bits() & 0x000FFFFFFFFFFFFF) | 0x3FE0000000000000);
if ((bits >> 52) & 0x7FF) == 0 {
let norm = abs_x * (2.0f64.powi(53));
let norm_bits = norm.to_bits();
exp = ((norm_bits >> 52) & 0x7FF) as i64 - 1022 - 53;
mantissa = sign * f64::from_bits((norm_bits & 0x000FFFFFFFFFFFFF) | 0x3FE0000000000000);
}
l.push_value(LuaValue::float(mantissa))?;
l.push_value(LuaValue::integer(exp))?;
Ok(2)
}
fn math_ldexp(l: &mut LuaState) -> LuaResult<usize> {
let m = l
.get_arg(1)
.and_then(|v| v.as_float().or_else(|| v.as_integer().map(|i| i as f64)))
.ok_or_else(|| l.error("bad argument #1 to 'ldexp' (number expected)".to_string()))?;
let e = l
.get_arg(2)
.and_then(|v| v.as_integer())
.ok_or_else(|| l.error("bad argument #2 to 'ldexp' (number expected)".to_string()))?;
let mut result = m;
let mut exp = e;
while exp > 1023 {
result *= 2.0f64.powi(1023);
exp -= 1023;
if result.is_infinite() {
break;
}
}
while exp < -1074 {
result *= 2.0f64.powi(-1074);
exp += 1074;
if result == 0.0 {
break;
}
}
if !result.is_infinite() && result != 0.0 {
result *= 2.0f64.powi(exp as i32);
}
l.push_value(LuaValue::float(result))?;
Ok(1)
}