use crate::context::{Arg, Args, Context, Ret};
use crate::{Buffer, Fp, LuaFn, Type, Value};
use alloc::boxed::Box;
use alloc::format;
pub fn concat<A>(cx: Context<A, Args>) -> Result<Context<A, Ret>, Box<dyn core::error::Error>> {
let t = cx.arg(1);
checktab(&t, 1 | 4)?;
let sep = cx
.arg(2)
.to_nilable_str(false)?
.map(|v| v.as_bytes())
.unwrap_or(b"");
let mut i = cx.arg(3).to_nilable_int(false)?.unwrap_or(1);
let last = match cx.arg(4).to_nilable_int(false)? {
Some(v) => v,
None => cx.get_value_len(&t)?,
};
let mut b = Buffer::default();
while i < last {
addfield(&cx, &t, &mut b, i)?;
b.extend_from_slice(sep);
i += 1;
}
if i == last {
addfield(&cx, &t, &mut b, i)?;
}
cx.push_bytes(b)?;
Ok(cx.into())
}
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn sort<A>(cx: Context<A, Args>) -> Result<Context<A, Ret>, Box<dyn core::error::Error>> {
let tab = cx.arg(1);
checktab(&tab, 1 | 2 | 4)?;
let n = cx.get_value_len(&tab)?;
if n <= 1 {
return Ok(cx.into());
} else if n >= 2147483647 {
return Err(tab.error("array too big"));
}
let cmp = cx.arg(2);
let cmp = if let Some(v) = cmp.as_lua_fn() {
Comparer::LuaFn(v)
} else if let Some(v) = cmp.as_fp() {
Comparer::Fp(v)
} else if matches!(cmp.ty(), Some(Type::Nil) | None) {
Comparer::Default
} else {
return Err(cmp.invalid_type("function"));
};
auxsort(&cx, &tab, &cmp, 1, n as u32, 0)?;
Ok(cx.into())
}
pub fn unpack<A>(cx: Context<A, Args>) -> Result<Context<A, Ret>, Box<dyn core::error::Error>> {
let l = cx.arg(1);
let i = cx.arg(2).to_nilable_int(false)?.unwrap_or(1);
let e = match cx.arg(3).to_nilable_int(false)? {
Some(v) => v,
None => cx.get_value_len(&l)?,
};
if i > e {
return Ok(cx.into());
}
if e.checked_sub(i)
.and_then(|v| v.checked_add(1))
.and_then(|v| v.try_into().ok())
.and_then(|v| cx.reserve(v).ok())
.is_none()
{
return Err("too many results to unpack".into());
}
for i in i..=e {
let v = cx.thread().index(&l, i)?;
cx.push(v)?;
}
Ok(cx.into())
}
fn checktab<A>(arg: &Arg<A>, what: u8) -> Result<(), Box<dyn core::error::Error>> {
if arg.ty() == Some(Type::Table) {
return Ok(());
}
arg.metatable()
.flatten()
.filter(move |mt| what & 1 == 0 || mt.contains_str_key("__index"))
.filter(move |mt| what & 2 == 0 || mt.contains_str_key("__newindex"))
.filter(move |mt| what & 4 == 0 || mt.contains_str_key("__len"))
.ok_or_else(|| arg.invalid_type("table"))?;
Ok(())
}
fn addfield<A>(
cx: &Context<A, Args>,
t: &Arg<A>,
b: &mut Buffer,
i: i64,
) -> Result<(), Box<dyn core::error::Error>> {
use core::fmt::Write;
match cx.thread().index(t, i)? {
Value::Int(v) => write!(b, "{v}").unwrap(),
Value::Float(v) => write!(b, "{v}").unwrap(),
Value::Str(v) => b.extend_from_slice(v.as_bytes()),
v => {
return Err(format!(
"invalid value ({}) at index {} in table for 'concat'",
v.ty(),
i
)
.into());
}
}
Ok(())
}
#[cfg(feature = "rand")]
fn auxsort<A>(
cx: &Context<A, Args>,
tab: &Arg<A>,
cmp: &Comparer<A>,
mut lo: u32,
mut up: u32,
mut rnd: u32,
) -> Result<(), Box<dyn core::error::Error>> {
while lo < up {
let a = cx.thread().index(tab, up)?;
let b = cx.thread().index(tab, lo)?;
if sort_comp(cx, &a, &b, cmp)? {
cx.thread().set(tab, lo, a)?;
cx.thread().set(tab, up, b)?;
}
if (up - lo) == 1 {
return Ok(());
}
let p = if (up - lo) < 100 || rnd == 0 {
lo.wrapping_add(up) / 2
} else {
choose_pivot(lo, up, rnd)
};
let a = cx.thread().index(tab, p)?;
let b = cx.thread().index(tab, lo)?;
if sort_comp(cx, &a, &b, cmp)? {
cx.thread().set(tab, p, b)?;
cx.thread().set(tab, lo, a)?;
} else {
let b = cx.thread().index(tab, up)?;
if sort_comp(cx, &b, &a, cmp)? {
cx.thread().set(tab, p, b)?;
cx.thread().set(tab, up, a)?;
}
}
if (up - lo) == 2 {
return Ok(());
}
let a = cx.thread().index(tab, p)?;
let b = cx.thread().index(tab, up.wrapping_sub(1))?;
cx.thread().set(tab, p, b)?;
cx.thread().set(tab, up.wrapping_sub(1), &a)?;
let p = partition(cx, tab, a, lo, up, cmp)?;
let n;
if p.wrapping_sub(lo) < up.wrapping_sub(p) {
auxsort(cx, tab, cmp, lo, p.wrapping_sub(1), rnd)?;
n = p.wrapping_sub(lo);
lo = p.wrapping_add(1);
} else {
auxsort(cx, tab, cmp, p.wrapping_add(1), up, rnd)?;
n = up.wrapping_sub(p);
up = p.wrapping_sub(1);
}
if up.wrapping_sub(lo) / 128 > n {
rnd = rand::random();
}
}
Ok(())
}
fn sort_comp<A>(
cx: &Context<A, Args>,
a: &Value<A>,
b: &Value<A>,
cmp: &Comparer<A>,
) -> Result<bool, Box<dyn core::error::Error>> {
let r: Value<_> = match cmp {
Comparer::Default => return cx.is_value_lt(a, b),
Comparer::Fp(f) => cx.thread().call(*f, (a, b))?,
Comparer::LuaFn(f) => cx.thread().call(*f, (a, b))?,
};
Ok(r.to_bool())
}
fn choose_pivot(lo: u32, up: u32, rnd: u32) -> u32 {
let r4 = up.wrapping_sub(lo) / 4;
rnd.wrapping_rem(r4 * 2).wrapping_add(lo.wrapping_add(r4))
}
fn partition<A>(
cx: &Context<A, Args>,
tab: &Arg<A>,
a: Value<A>,
lo: u32,
up: u32,
cmp: &Comparer<A>,
) -> Result<u32, Box<dyn core::error::Error>> {
let mut i = lo;
let mut j = up.wrapping_sub(1);
loop {
let b = loop {
i = i.wrapping_add(1);
let b = cx.thread().index(tab, i)?;
if !sort_comp(cx, &b, &a, cmp)? {
break b;
}
if i == up.wrapping_sub(1) {
return Err("invalid order function for sorting".into());
}
};
let c = loop {
j = j.wrapping_sub(1);
let c = cx.thread().index(tab, j)?;
if !sort_comp(cx, &a, &c, cmp)? {
break c;
}
if j < i {
return Err("invalid order function for sorting".into());
}
};
if j < i {
cx.thread().set(tab, up.wrapping_sub(1), b)?;
cx.thread().set(tab, i, a)?;
return Ok(i);
}
cx.thread().set(tab, i, c)?;
cx.thread().set(tab, j, b)?;
}
}
enum Comparer<'a, A> {
Default,
Fp(Fp<A>),
LuaFn(&'a LuaFn<A>),
}