luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use std::cmp::Ordering;
use std::convert::TryInto;

use rand::{Rng, SeedableRng};
use rand_xoshiro::Xoshiro256StarStar;

use super::StdLib;
use crate::error::{LuaError, Result};
use crate::vm::{BinOp, Numeric, Oper, Value, VM};

pub(super) fn module(stdlib: &mut StdLib) -> Result<()> {
    stdlib
        .module("math")
        .func("abs", abs)?
        .func("acos", acos)?
        .func("asin", asin)?
        .func("atan", atan)?
        .func("ceil", ceil)?
        .func("cos", cos)?
        .func("deg", deg)?
        .func("exp", exp)?
        .func("floor", floor)?
        .func("fmod", fmod)?
        .cons("huge", Value::float(HUGE))?
        .func("log", log)?
        .func("max", max)?
        .cons("maxinteger", Value::int(MAX_INTEGER))?
        .func("min", min)?
        .cons("mininteger", Value::int(MIN_INTEGER))?
        .func("modf", modf)?
        .cons("pi", Value::float(PI))?
        .func("rad", rad)?
        .func("random", random)?
        .func("randomseed", randomseed)?
        .func("sin", sin)?
        .func("sqrt", sqrt)?
        .func("tan", tan)?
        .func("tointeger", tointeger)?
        .func("type", r#type)?
        .func("ult", ult)?;
    Ok(())
}

fn abs(vm: &mut VM) -> Result<Value> {
    Ok(Value::Number(match vm.arg_number_coerce(0)? {
        Numeric::Integer(i) => Numeric::Integer(i.wrapping_abs()),
        Numeric::Float(f) => Numeric::Float(f.abs()),
    }))
}

fn acos(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.acos()))
}

fn asin(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.asin()))
}

fn atan(vm: &mut VM) -> Result<Value> {
    let y = vm.arg_float_coerce(0)?;
    Ok(Value::float(match vm.arg_opt(1) {
        Some(v) => y.atan2(v.to_float_coerce()?),
        None => y.atan(),
    }))
}

fn ceil(vm: &mut VM) -> Result<Value> {
    Ok(Value::Number(match vm.arg_number_coerce(0)? {
        Numeric::Integer(n) => Numeric::Integer(n),
        Numeric::Float(f) => Numeric::from_f64_try_int(f.ceil()),
    }))
}

fn cos(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.cos()))
}

fn deg(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.to_degrees()))
}

fn exp(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.exp()))
}

fn floor(vm: &mut VM) -> Result<Value> {
    Ok(Value::Number(match vm.arg_number_coerce(0)? {
        Numeric::Integer(n) => Numeric::Integer(n),
        Numeric::Float(f) => Numeric::from_f64_try_int(f.floor()),
    }))
}

fn fmod(vm: &mut VM) -> Result<Value> {
    let x = vm.arg_number_coerce(0)?;
    let y = vm.arg_number_coerce(1)?;
    if y.to_float() == 0.0 {
        return err!(LuaError::MathFmodZero);
    }
    Ok(Value::Number(x.rem(&y)?))
}

const HUGE: f64 = std::f64::INFINITY;

fn log(vm: &mut VM) -> Result<Value> {
    let x = vm.arg_float_coerce(0)?;
    Ok(Value::float(match vm.arg_opt(1) {
        Some(v) => x.log(v.to_float_coerce()?),
        None => x.ln(),
    }))
}

fn max(vm: &mut VM) -> Result<Value> {
    let mut max = vm.arg(0)?;
    for val in vm.arg_split(1).into_iter() {
        if vm
            .bin_op(Oper::Raw(max.clone()), BinOp::Lt, Oper::Raw(val.clone()))?
            .is_truthy()
        {
            max = val;
        }
    }
    Ok(max)
}

const MAX_INTEGER: i64 = std::i64::MAX;

fn min(vm: &mut VM) -> Result<Value> {
    let mut min = vm.arg(0)?;
    for val in vm.arg_split(1).into_iter() {
        if vm
            .bin_op(Oper::Raw(val.clone()), BinOp::Lt, Oper::Raw(min.clone()))?
            .is_truthy()
        {
            min = val;
        }
    }
    Ok(min)
}

const MIN_INTEGER: i64 = std::i64::MIN;

fn modf(vm: &mut VM) -> Result<Value> {
    let x = vm.arg_float_coerce(0)?;
    Ok(Value::Mult(vec![
        Value::Number(Numeric::from_f64_try_int(x.trunc())),
        Value::float(if x.is_infinite() { 0.0 } else { x.fract() }),
    ]))
}

const PI: f64 = std::f64::consts::PI;

fn rad(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.to_radians()))
}

fn random(vm: &mut VM) -> Result<Value> {
    if vm.arg_len() > 2 {
        return err!(LuaError::ArgumentCount);
    }
    let lower = vm
        .arg_opt(0)
        .as_ref()
        .map(Value::to_int_coerce)
        .transpose()?;
    let upper = vm
        .arg_opt(1)
        .as_ref()
        .map(Value::to_int_coerce)
        .transpose()?;
    _random(vm, lower, upper)
}

fn _random(vm: &mut VM, lower: Option<i64>, upper: Option<i64>) -> Result<Value> {
    Ok(match lower {
        Some(lower) => match upper {
            Some(upper) => {
                let range = lower..=upper;
                if range.is_empty() {
                    return err!(LuaError::EmptyInterval);
                }
                Value::int(vm.rng.gen_range(range))
            }
            None => match lower.cmp(&0) {
                Ordering::Greater => Value::int(vm.rng.gen_range(1..=lower)),
                Ordering::Equal => Value::int(vm.rng.gen()),
                Ordering::Less => panic!(),
            },
        },
        None => Value::float(vm.rng.gen()),
    })
}

fn randomseed(vm: &mut VM) -> Result<Value> {
    let x = match vm.arg_opt(0) {
        Some(v) => v.to_number_coerce()?.to_int()?,
        None => _random(vm, Some(0), None)?.to_number()?.to_int()?,
    };
    let y = match vm.arg_opt(1) {
        Some(v) => v.to_number_coerce()?.to_int()?,
        None => 0,
    };
    let seed: Vec<u8> = [
        x.to_ne_bytes(),
        0xffu64.to_ne_bytes(),
        y.to_ne_bytes(),
        0u64.to_ne_bytes(),
    ]
    .concat();
    vm.rng = Xoshiro256StarStar::from_seed(seed.try_into().unwrap());
    // Discard some values (reference implementation does this)
    for _ in 0..16 {
        _random(vm, Some(0), None)?;
    }
    Ok(Value::Mult(vec![Value::int(x), Value::int(y)]))
}

fn sin(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.sin()))
}

fn sqrt(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.sqrt()))
}

fn tan(vm: &mut VM) -> Result<Value> {
    Ok(Value::float(vm.arg_float_coerce(0)?.tan()))
}

fn tointeger(vm: &mut VM) -> Result<Value> {
    Ok(match vm.arg(0)?.to_int_coerce() {
        Ok(n) => Value::int(n),
        Err(..) => Value::Nil,
    })
}

fn r#type(vm: &mut VM) -> Result<Value> {
    Ok(match vm.arg_opt(0) {
        Some(Value::Number(n)) => Value::str(match n {
            Numeric::Integer(..) => "integer",
            Numeric::Float(..) => "float",
        }),
        _ => Value::Nil,
    })
}

fn ult(vm: &mut VM) -> Result<Value> {
    Ok(Value::Bool(
        (vm.arg_int_coerce(0)? as u64) < (vm.arg_int_coerce(1)? as u64),
    ))
}