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(),
))
}