use lua_types::{LuaError, LuaType, LuaValue};
use crate::state_stub::{LuaState, LuaStateStubExt as _};
const PI: f64 = 3.141592653589793238462643383279502884_f64;
const FIGS: u32 = 53;
const SHIFT64_FIG: u32 = 64 - FIGS;
type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
#[expect(dead_code, reason = "ported stdlib helper; not yet wired into the runtime")]
struct LibReg {
name: &'static [u8],
func: Option<LuaCFunction>,
}
struct RanState {
s: [u64; 4],
}
thread_local! {
static RAN_STATE: std::cell::RefCell<RanState> =
std::cell::RefCell::new(RanState { s: [0xff, 0xff, 0xff, 0xff] });
}
fn next_rand(s: &mut [u64; 4]) -> u64 {
let s0 = s[0];
let s1 = s[1];
let s2 = s[2] ^ s0;
let s3 = s[3] ^ s1;
let res = s1.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
s[0] = s0 ^ s3;
s[1] = s1 ^ s2;
s[2] = s2 ^ (s1 << 17);
s[3] = s3.rotate_left(45);
res
}
fn rand_to_float(x: u64) -> f64 {
let sx = (x >> SHIFT64_FIG) as i64;
let scale_fig: f64 = 0.5 / ((1u64 << (FIGS - 1)) as f64);
let mut res = (sx as f64) * scale_fig;
if sx < 0 {
res += 1.0;
}
debug_assert!(0.0 <= res && res < 1.0);
res
}
fn set_seed_words(s: &mut [u64; 4], n1: u64, n2: u64) {
s[0] = n1;
s[1] = 0xff; s[2] = n2;
s[3] = 0;
for _ in 0..16 {
next_rand(s); }
}
fn project(mut ran: u64, n: u64, s: &mut [u64; 4]) -> u64 {
if (n & n.wrapping_add(1)) == 0 {
return ran & n;
}
let mut lim = n;
lim |= lim >> 1;
lim |= lim >> 2;
lim |= lim >> 4;
lim |= lim >> 8;
lim |= lim >> 16;
lim |= lim >> 32; debug_assert!((lim & lim.wrapping_add(1)) == 0); debug_assert!(lim >= n);
debug_assert!((lim >> 1) < n);
loop {
ran &= lim;
if ran <= n {
break;
}
ran = next_rand(s);
}
ran
}
fn push_num_int(state: &mut LuaState, d: f64) {
let min_f = i64::MIN as f64; let max_plus1_f = -(i64::MIN as f64); if d >= min_f && d < max_plus1_f {
state.push(LuaValue::Int(d as i64));
} else {
state.push(LuaValue::Float(d));
}
}
fn math_abs(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.value_at(1), LuaValue::Int(_)) {
let n = state.to_integer(1).unwrap_or(0);
let n = if n < 0 {
(0u64.wrapping_sub(n as u64)) as i64
} else {
n
};
state.push(LuaValue::Int(n));
} else {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.abs()));
}
Ok(1)
}
fn math_sin(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.sin()));
Ok(1)
}
fn math_cos(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.cos()));
Ok(1)
}
fn math_tan(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.tan()));
Ok(1)
}
fn math_asin(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.asin()));
Ok(1)
}
fn math_acos(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.acos()));
Ok(1)
}
fn math_atan(state: &mut LuaState) -> Result<usize, LuaError> {
let y = state.check_number(1)?;
let x = state.opt_number(2, 1.0)?;
state.push(LuaValue::Float(y.atan2(x)));
Ok(1)
}
fn math_toint(state: &mut LuaState) -> Result<usize, LuaError> {
let maybe_n: Option<i64> = state.to_integer_opt(1);
if let Some(n) = maybe_n {
state.push(LuaValue::Int(n));
} else {
state.check_any(1)?;
state.push(LuaValue::Bool(false));
}
Ok(1)
}
fn math_floor(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.value_at(1), LuaValue::Int(_)) {
lua_vm::api::set_top(state, 1)?;
} else {
let d = state.check_number(1)?.floor();
push_num_int(state, d);
}
Ok(1)
}
fn math_ceil(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.value_at(1), LuaValue::Int(_)) {
lua_vm::api::set_top(state, 1)?;
} else {
let d = state.check_number(1)?.ceil();
push_num_int(state, d);
}
Ok(1)
}
fn math_fmod(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.value_at(1), LuaValue::Int(_))
&& matches!(state.value_at(2), LuaValue::Int(_))
{
let a = state.to_integer(1).unwrap_or(0);
let d = state.to_integer(2).unwrap_or(0);
if (d as u64).wrapping_add(1) <= 1 {
if d == 0 {
return Err(LuaError::arg_error(2, "zero"));
}
state.push(LuaValue::Int(0));
} else {
state.push(LuaValue::Int(a % d));
}
} else {
let x = state.check_number(1)?;
let y = state.check_number(2)?;
state.push(LuaValue::Float(x % y));
}
Ok(1)
}
fn math_modf(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.value_at(1), LuaValue::Int(_)) {
lua_vm::api::set_top(state, 1)?; state.push(LuaValue::Float(0.0)); } else {
let n = state.check_number(1)?;
let ip = if n < 0.0 { n.ceil() } else { n.floor() };
push_num_int(state, ip);
let frac = if n == ip { 0.0 } else { n - ip };
state.push(LuaValue::Float(frac));
}
Ok(2)
}
fn math_sqrt(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.sqrt()));
Ok(1)
}
fn math_ult(state: &mut LuaState) -> Result<usize, LuaError> {
let a = state.check_integer(1)?;
let b = state.check_integer(2)?;
state.push(LuaValue::Bool((a as u64) < (b as u64)));
Ok(1)
}
fn math_log(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
let res = if matches!(state.type_at(2), LuaType::None | LuaType::Nil) {
x.ln()
} else {
let base = state.check_number(2)?;
if base == 2.0 {
x.log2()
} else if base == 10.0 {
x.log10()
} else {
x.ln() / base.ln()
}
};
state.push(LuaValue::Float(res));
Ok(1)
}
fn math_exp(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x.exp()));
Ok(1)
}
fn math_deg(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x * (180.0 / PI)));
Ok(1)
}
fn math_rad(state: &mut LuaState) -> Result<usize, LuaError> {
let x = state.check_number(1)?;
state.push(LuaValue::Float(x * (PI / 180.0)));
Ok(1)
}
fn math_min(state: &mut LuaState) -> Result<usize, LuaError> {
let n = state.get_top();
let mut imin: i32 = 1;
if n < 1 {
return Err(LuaError::arg_error(1, "value expected"));
}
for i in 2..=n {
if state.compare_lt(i, imin)? {
imin = i;
}
}
state.push_value(imin)?;
Ok(1)
}
fn math_max(state: &mut LuaState) -> Result<usize, LuaError> {
let n = state.get_top();
let mut imax: i32 = 1;
if n < 1 {
return Err(LuaError::arg_error(1, "value expected"));
}
for i in 2..=n {
if state.compare_lt(imax, i)? {
imax = i;
}
}
state.push_value(imax)?;
Ok(1)
}
fn math_type(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.type_at(1), LuaType::Number) {
if matches!(state.value_at(1), LuaValue::Int(_)) {
state.push_string(b"integer")?;
} else {
state.push_string(b"float")?;
}
} else {
state.check_any(1)?;
state.push(LuaValue::Bool(false));
}
Ok(1)
}
fn math_random(state: &mut LuaState) -> Result<usize, LuaError> {
let rv = advance_prng(state)?;
let n_args = state.get_top();
if n_args == 0 {
state.push(LuaValue::Float(rand_to_float(rv)));
return Ok(1);
}
let (low, up) = match n_args {
1 => {
let up = state.check_integer(1)?;
if up == 0 {
state.push(LuaValue::Int(rv as i64));
return Ok(1);
}
(1i64, up)
}
2 => {
let low = state.check_integer(1)?;
let up = state.check_integer(2)?;
(low, up)
}
_ => {
return Err(LuaError::runtime(format_args!(
"wrong number of arguments"
)));
}
};
if low > up {
return Err(LuaError::arg_error(1, "interval is empty"));
}
let range = (up as u64).wrapping_sub(low as u64);
let p = project_from_upvalue(state, rv, range)?;
state.push(LuaValue::Int((p as u64).wrapping_add(low as u64) as i64));
Ok(1)
}
fn math_randomseed(state: &mut LuaState) -> Result<usize, LuaError> {
if matches!(state.type_at(1), LuaType::None) {
apply_random_seed(state)?;
} else {
let n1 = state.check_integer(1)? as u64;
let n2 = state.opt_integer(2, 0)? as u64;
apply_set_seed(state, n1, n2)?;
}
Ok(2)
}
fn advance_prng(_state: &mut LuaState) -> Result<u64, LuaError> {
Ok(RAN_STATE.with(|r| next_rand(&mut r.borrow_mut().s)))
}
fn project_from_upvalue(
_state: &mut LuaState,
ran: u64,
n: u64,
) -> Result<u64, LuaError> {
Ok(RAN_STATE.with(|r| project(ran, n, &mut r.borrow_mut().s)))
}
fn apply_random_seed(state: &mut LuaState) -> Result<(), LuaError> {
let entropy = state.global().entropy_hook.map(|hook| hook()).unwrap_or(0);
let seed1 = entropy;
let seed2: u64 = entropy.rotate_left(17) ^ 0x9e37_79b9_7f4a_7c15;
apply_set_seed(state, seed1, seed2)
}
fn apply_set_seed(state: &mut LuaState, n1: u64, n2: u64) -> Result<(), LuaError> {
RAN_STATE.with(|r| set_seed_words(&mut r.borrow_mut().s, n1, n2));
state.push(LuaValue::Int(n1 as i64));
state.push(LuaValue::Int(n2 as i64));
Ok(())
}
fn set_rand_func(state: &mut LuaState) -> Result<(), LuaError> {
apply_random_seed(state)?;
state.pop_n(2);
state.push_c_function(math_random)?;
state.set_field(-2, b"random")?;
state.push_c_function(math_randomseed)?;
state.set_field(-2, b"randomseed")?;
Ok(())
}
#[expect(dead_code, reason = "ported stdlib helper; not yet wired into the runtime")]
static MATHLIB: &[LibReg] = &[
LibReg { name: b"abs", func: Some(math_abs) },
LibReg { name: b"acos", func: Some(math_acos) },
LibReg { name: b"asin", func: Some(math_asin) },
LibReg { name: b"atan", func: Some(math_atan) },
LibReg { name: b"ceil", func: Some(math_ceil) },
LibReg { name: b"cos", func: Some(math_cos) },
LibReg { name: b"deg", func: Some(math_deg) },
LibReg { name: b"exp", func: Some(math_exp) },
LibReg { name: b"tointeger", func: Some(math_toint) },
LibReg { name: b"floor", func: Some(math_floor) },
LibReg { name: b"fmod", func: Some(math_fmod) },
LibReg { name: b"ult", func: Some(math_ult) },
LibReg { name: b"log", func: Some(math_log) },
LibReg { name: b"max", func: Some(math_max) },
LibReg { name: b"min", func: Some(math_min) },
LibReg { name: b"modf", func: Some(math_modf) },
LibReg { name: b"rad", func: Some(math_rad) },
LibReg { name: b"sin", func: Some(math_sin) },
LibReg { name: b"sqrt", func: Some(math_sqrt) },
LibReg { name: b"tan", func: Some(math_tan) },
LibReg { name: b"type", func: Some(math_type) },
LibReg { name: b"random", func: None },
LibReg { name: b"randomseed", func: None },
LibReg { name: b"pi", func: None },
LibReg { name: b"huge", func: None },
LibReg { name: b"maxinteger", func: None },
LibReg { name: b"mininteger", func: None },
];
static MATHLIB_FUNCS: &[(&[u8], LuaCFunction)] = &[
(b"abs", math_abs),
(b"acos", math_acos),
(b"asin", math_asin),
(b"atan", math_atan),
(b"ceil", math_ceil),
(b"cos", math_cos),
(b"deg", math_deg),
(b"exp", math_exp),
(b"tointeger", math_toint),
(b"floor", math_floor),
(b"fmod", math_fmod),
(b"ult", math_ult),
(b"log", math_log),
(b"max", math_max),
(b"min", math_min),
(b"modf", math_modf),
(b"rad", math_rad),
(b"sin", math_sin),
(b"sqrt", math_sqrt),
(b"tan", math_tan),
(b"type", math_type),
];
pub fn luaopen_math(state: &mut LuaState) -> Result<usize, LuaError> {
state.new_lib(MATHLIB_FUNCS)?;
state.push(LuaValue::Float(PI));
state.set_field(-2, b"pi")?;
state.push(LuaValue::Float(f64::INFINITY));
state.set_field(-2, b"huge")?;
state.push(LuaValue::Int(i64::MAX));
state.set_field(-2, b"maxinteger")?;
state.push(LuaValue::Int(i64::MIN));
state.set_field(-2, b"mininteger")?;
set_rand_func(state)?;
Ok(1)
}