luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use super::StdLib;
use crate::vm::{FuncDef, Value, VM};
use crate::{error::Result, LuaError};

mod format;
mod r#match;
mod packing;

pub(super) fn module(stdlib: &mut StdLib) -> Result<()> {
    stdlib
        .module("string")
        .func("byte", byte)?
        .func("char", char)?
        .func("dump", dump)?
        .func("find", r#match::find)?
        .func("format", format::format)?
        .func("gmatch", r#match::gmatch)?
        .func("gsub", r#match::gsub)?
        .func("len", len)?
        .func("lower", lower)?
        .func("match", r#match::r#match)?
        .func("pack", packing::pack)?
        .func("packsize", packing::packsize)?
        .func("rep", rep)?
        .func("reverse", reverse)?
        .func("sub", sub)?
        .func("unpack", packing::unpack)?
        .func("upper", upper)?;
    Ok(())
}

fn _index_start(num: i64, len: usize) -> usize {
    if num < 0 {
        (len as u64).saturating_add_signed(num) as usize
    } else {
        (num as usize).saturating_sub(1)
    }
}

fn index_start(val: Value, len: usize) -> Result<usize> {
    Ok(_index_start(val.to_number_coerce()?.coerce_int()?, len))
}

fn _index_end(num: i64, len: usize) -> usize {
    if num >= 0 {
        (num as usize).min(len)
    } else if num < -(len as i64) {
        0
    } else {
        (len as u64).saturating_add_signed(num) as usize + 1
    }
}

fn index_end_opt(val: Value, len: usize, default: i64) -> Result<usize> {
    let num = match val {
        Value::Nil => default,
        v => v.to_number_coerce()?.coerce_int()?,
    };
    Ok(_index_end(num, len))
}

fn byte(vm: &mut VM) -> Result<Value> {
    let mut out = Vec::new();

    let s = vm.arg_string_coerce(0)?;
    let i = match vm.arg_or_nil(1) {
        Value::Nil => 1,
        v => v.to_int_coerce()?,
    };
    let start = _index_start(i, s.len()).min(s.len());
    let end = index_end_opt(vm.arg_or_nil(2), s.len(), i)?;

    if start < end {
        for i in start..end {
            if let Some(b) = s.get(i) {
                out.push(Value::int(*b as i64));
            }
        }
    }

    Ok(Value::Mult(out))
}

fn char(vm: &mut VM) -> Result<Value> {
    let mut out = Vec::new();

    for arg in vm.arg_split(0) {
        let num = arg.to_int_coerce()?;
        if num < (u8::MIN as i64) || (u8::MAX as i64) < num {
            return err!(LuaError::ValueRange);
        }
        out.push(num as u8);
    }

    Ok(Value::str_bytes(out))
}

fn dump(vm: &mut VM) -> Result<Value> {
    let function = vm.arg_func(0)?;
    let strip = vm.arg_or_nil(1).is_truthy();
    match &*function {
        FuncDef::Defined(func) => Ok(Value::str_bytes(super::dump::dump(func, strip)?)),
        _ => err!(LuaError::DumpInvalid),
    }
}

fn len(vm: &mut VM) -> Result<Value> {
    let s = vm.arg_string_coerce(0)?;

    Ok(Value::int(s.len() as i64))
}

fn lower(vm: &mut VM) -> Result<Value> {
    let s = vm.arg_string_coerce(0)?;
    Ok(Value::str_bytes(
        s.into_iter()
            .map(|c| char::from(c).to_ascii_lowercase() as u8)
            .collect(),
    ))
}

fn rep(vm: &mut VM) -> Result<Value> {
    let mut out = Vec::new();

    let s = vm.arg_string_coerce(0)?;
    let n = vm.arg_int_coerce(1)?;
    let sep = match vm.arg_or_nil(2) {
        Value::Nil => Vec::new(),
        v => v.to_string_coerce()?,
    };

    if n <= 0 {
        return Ok(Value::str(""));
    } else if s
        .len()
        .saturating_mul(n as usize)
        .saturating_add(sep.len().saturating_mul(n as usize - 1))
        >= i32::MAX as usize
    {
        return err!(LuaError::StringTooLarge);
    }

    for i in 0..n {
        if i > 0 {
            out.extend(&sep);
        }
        out.extend(&s);
    }

    Ok(Value::str_bytes(out))
}

fn reverse(vm: &mut VM) -> Result<Value> {
    let s = vm.arg_string_coerce(0)?;
    Ok(Value::str_bytes(s.iter().copied().rev().collect()))
}

fn sub(vm: &mut VM) -> Result<Value> {
    let s = vm.arg_string_coerce(0)?;

    let start = _index_start(vm.arg_int_coerce(1)?, s.len());
    let end = index_end_opt(vm.arg_or_nil(2), s.len(), -1)?;

    Ok(if start <= end {
        Value::str_bytes(s[start..end].into())
    } else {
        Value::str("")
    })
}

fn upper(vm: &mut VM) -> Result<Value> {
    let s = vm.arg_string_coerce(0)?;
    Ok(Value::str_bytes(
        s.into_iter()
            .map(|c| char::from(c).to_ascii_uppercase() as u8)
            .collect(),
    ))
}