piccolo 0.3.3

Stackless Lua VM implemented in pure Rust
Documentation
use std::{cell::RefCell, f64, rc::Rc};

use gc_arena::Mutation;
use rand::{rngs::SmallRng, Rng, SeedableRng};

use crate::{
    raw_ops, Callback, CallbackReturn, Context, FromMultiValue, IntoMultiValue, IntoValue, Table,
    Value, Variadic,
};

pub fn load_math<'gc>(ctx: Context<'gc>) {
    fn callback<'gc, F, A, R>(name: &'static str, mc: &Mutation<'gc>, f: F) -> Callback<'gc>
    where
        F: Fn(Context<'gc>, A) -> Option<R> + 'static,
        A: FromMultiValue<'gc>,
        R: IntoMultiValue<'gc>,
    {
        Callback::from_fn(mc, move |ctx, _, mut stack| {
            if let Some(res) = f(ctx, stack.consume(ctx)?) {
                stack.replace(ctx, res);
                Ok(CallbackReturn::Return)
            } else {
                Err(format!("Bad argument to {name}").into_value(ctx).into())
            }
        })
    }

    fn to_int(v: Value) -> Value {
        if let Some(i) = v.to_integer() {
            Value::Integer(i)
        } else {
            v
        }
    }

    let math = Table::new(&ctx);
    let seeded_rng: Rc<RefCell<SmallRng>> = Rc::new(RefCell::new(SmallRng::from_entropy()));

    math.set(
        ctx,
        "abs",
        callback("abs", &ctx, |_, v: Value| {
            Some(if let Value::Integer(i) = v {
                Value::Integer(i.abs())
            } else {
                v.to_number()?.abs().into()
            })
        }),
    )
    .unwrap();

    math.set(
        ctx,
        "acos",
        callback("acos", &ctx, |_, v: f64| Some(v.acos())),
    )
    .unwrap();

    math.set(
        ctx,
        "asin",
        callback("asin", &ctx, |_, v: f64| Some(v.asin())),
    )
    .unwrap();

    math.set(
        ctx,
        "atan",
        callback("atan", &ctx, |_, (a, b): (f64, Option<f64>)| {
            Some(if let Some(b) = b {
                a.atan2(b)
            } else {
                a.atan()
            })
        }),
    )
    .unwrap();

    math.set(
        ctx,
        "ceil",
        callback("ceil", &ctx, |_, v: f64| Some(to_int(v.ceil().into()))),
    )
    .unwrap();

    math.set(ctx, "cos", callback("cos", &ctx, |_, v: f64| Some(v.cos())))
        .unwrap();

    math.set(
        ctx,
        "deg",
        callback("deg", &ctx, |_, v: f64| Some(v.to_degrees())),
    )
    .unwrap();

    math.set(
        ctx,
        "exp",
        callback("exp", &ctx, |_, v: f64| Some(f64::consts::E.powf(v))),
    )
    .unwrap();

    math.set(
        ctx,
        "floor",
        callback("floor", &ctx, |_, v: f64| Some(to_int(v.floor().into()))),
    )
    .unwrap();

    math.set(
        ctx,
        "fmod",
        callback("fmod", &ctx, |_, (f, g): (f64, f64)| {
            let result = (f % g).abs();
            Some(if f < 0.0 { -result } else { result })
        }),
    )
    .unwrap();

    math.set(ctx, "huge", Value::Number(f64::INFINITY)).unwrap();

    math.set(
        ctx,
        "log",
        callback("log", &ctx, |_, (v, base): (f64, Option<f64>)| match base {
            None => Some(v.ln()),
            Some(base) => Some(v.log(base)),
        }),
    )
    .unwrap();

    math.set(
        ctx,
        "max",
        callback("max", &ctx, |_, v: Variadic<Vec<Value>>| {
            if v.is_empty() {
                None
            } else {
                v.into_iter()
                    .try_fold(Value::Number(-f64::INFINITY), |max, entry| {
                        Some(if raw_ops::less_than(max, entry)? {
                            entry
                        } else {
                            max
                        })
                    })
            }
        }),
    )
    .unwrap();

    math.set(ctx, "maxinteger", Value::Integer(i64::MAX))
        .unwrap();

    math.set(
        ctx,
        "min",
        callback("min", &ctx, |_, v: Variadic<Vec<Value>>| {
            if v.is_empty() {
                None
            } else {
                v.into_iter()
                    .try_fold(Value::Number(f64::INFINITY), |max, entry| {
                        Some(if raw_ops::less_than(entry, max)? {
                            entry
                        } else {
                            max
                        })
                    })
            }
        }),
    )
    .unwrap();

    math.set(ctx, "mininteger", Value::Integer(i64::MIN))
        .unwrap();

    math.set(
        ctx,
        "modf",
        callback("modf", &ctx, |_, f: f64| Some((f as i64, f % 1.0))),
    )
    .unwrap();

    math.set(ctx, "pi", Value::Number(f64::consts::PI)).unwrap();

    math.set(
        ctx,
        "rad",
        callback("rad", &ctx, |_, v: f64| Some(v.to_radians())),
    )
    .unwrap();

    let random_rng = seeded_rng.clone();
    math.set(
        ctx,
        "random",
        callback(
            "random",
            &ctx,
            move |_, (a, b): (Option<i64>, Option<i64>)| -> Option<Value> {
                let rng = &random_rng;
                match (a, b) {
                    (None, None) => Some(rng.borrow_mut().gen::<f64>().into()),
                    (Some(0), None) => Some(rng.borrow_mut().gen::<i64>().into()),
                    (Some(a), None) if a < 0 => None,
                    (Some(a), None) => Some(rng.borrow_mut().gen_range(1..=a).into()),
                    (Some(a), Some(b)) if a <= b => Some(rng.borrow_mut().gen_range(a..=b).into()),
                    _ => None,
                }
            },
        ),
    )
    .unwrap();

    let randomseed_rng = seeded_rng.clone();
    math.set(
        ctx,
        "randomseed",
        callback(
            "randomseed",
            &ctx,
            move |_, (u, l): (Option<u64>, Option<u64>)| {
                let rng = &randomseed_rng;
                match (u, l) {
                    (None, None) => {
                        *rng.borrow_mut() = SmallRng::from_entropy();
                        Some(())
                    }
                    (Some(seed), None) | (Some(seed), Some(0)) => {
                        *rng.borrow_mut() = SmallRng::seed_from_u64(seed);
                        Some(())
                    }
                    (Some(high), Some(low)) => {
                        let high_bytes = high.to_ne_bytes();
                        let low_bytes = low.to_ne_bytes();
                        let seed = std::array::from_fn(|idx| {
                            let idx_mod_16 = idx % 16;
                            if idx_mod_16 >= 8 {
                                high_bytes[idx_mod_16 - 8]
                            } else {
                                low_bytes[idx_mod_16]
                            }
                        });
                        *rng.borrow_mut() = SmallRng::from_seed(seed);
                        Some(())
                    }
                    _ => None,
                }
            },
        ),
    )
    .unwrap();

    math.set(ctx, "sin", callback("sin", &ctx, |_, v: f64| Some(v.sin())))
        .unwrap();

    math.set(
        ctx,
        "sqrt",
        callback("sqrt", &ctx, |_, v: f64| Some(v.sqrt())),
    )
    .unwrap();

    math.set(ctx, "tan", callback("tan", &ctx, |_, v: f64| Some(v.tan())))
        .unwrap();

    math.set(
        ctx,
        "tointeger",
        callback("tointeger", &ctx, |_, v: Value| {
            Some(if let Some(i) = v.to_integer() {
                i.into()
            } else {
                Value::Nil
            })
        }),
    )
    .unwrap();

    math.set(
        ctx,
        "type",
        callback("type", &ctx, |ctx, v: Value| {
            Some(match v {
                Value::Integer(_) => "integer".into_value(ctx),
                Value::Number(_) => "float".into_value(ctx),
                _ => Value::Nil,
            })
        }),
    )
    .unwrap();

    math.set(
        ctx,
        "ult",
        callback("ult", &ctx, |_, (a, b): (i64, i64)| {
            Some(Value::Boolean((a as u64) < (b as u64)))
        }),
    )
    .unwrap();

    ctx.set_global("math", math).unwrap();
}