talc/talc-std/src/collection.rs

209 lines
5.1 KiB
Rust

use std::cmp::Ordering;
use talc_lang::{exception::{exception, Result}, symbol::SYM_TYPE_ERROR, throw, value::{function::NativeFunc, Value}, vmcall, Vm};
use talc_macros::native_func;
use crate::unpack_args;
pub fn load(vm: &mut Vm) {
vm.set_global_name("push", push().into());
vm.set_global_name("pop", pop().into());
vm.set_global_name("reverse", reverse().into());
vm.set_global_name("clear", clear().into());
vm.set_global_name("sort", sort().into());
vm.set_global_name("sort_by", sort_by().into());
vm.set_global_name("sort_key", sort_key().into());
}
#[native_func(2)]
pub fn push(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list, item] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "push expected list, found {list:#}")
};
list.borrow_mut().push(item);
Ok(Value::Nil)
}
#[native_func(1)]
pub fn pop(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "pop expected list, found {list:#}")
};
let v = list.borrow_mut().pop();
v.ok_or_else(|| exception!(*SYM_TYPE_ERROR, "attempt to pop empty list"))
}
#[native_func(1)]
pub fn reverse(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "reversed expected list, found {list:#}")
};
list.borrow_mut().reverse();
Ok(Value::List(list))
}
#[native_func(1)]
pub fn clear(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, col] = unpack_args!(args);
match &col {
Value::List(list) => list.borrow_mut().clear(),
Value::Table(table) => table.borrow_mut().clear(),
_ => throw!(*SYM_TYPE_ERROR, "clear expected list or table, found {col:#}")
}
Ok(col)
}
fn call_comparison(vm: &mut Vm, by: Value, l: Value, r: Value) -> Result<i64> {
let ord = vmcall!(vm; by, l, r)?;
let Value::Int(ord) = ord else {
throw!(*SYM_TYPE_ERROR, "comparison function should return an integer")
};
Ok(ord)
}
fn partition(vals: &mut [Value]) -> (usize, usize) {
let pivot = vals[vals.len() / 2].clone();
let mut lt = 0;
let mut eq = 0;
let mut gt = vals.len() - 1;
while eq <= gt {
let ord = vals[eq].partial_cmp(&pivot);
match ord {
Some(Ordering::Less) => {
vals.swap(eq, lt);
lt += 1;
eq += 1;
},
Some(Ordering::Greater) => {
vals.swap(eq, gt);
gt -= 1;
},
Some(Ordering::Equal) | None => {
eq += 1;
},
}
}
(lt, gt)
}
fn partition_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<(usize, usize)> {
let pivot = vals[vals.len() / 2].clone();
let mut lt = 0;
let mut eq = 0;
let mut gt = vals.len() - 1;
while eq <= gt {
let ord = call_comparison(vm, by.clone(), vals[eq].clone(), pivot.clone())?;
match ord {
..=-1 => {
vals.swap(eq, lt);
lt += 1;
eq += 1;
},
1.. => {
vals.swap(eq, gt);
gt -= 1;
},
0 => {
eq += 1;
},
}
}
Ok((lt, gt))
}
fn insertion(vals: &mut [Value]) {
for i in 0..vals.len() {
let mut j = i;
while j > 0 && vals[j-1] > vals[j] {
vals.swap(j, j-1);
j -= 1;
}
}
}
fn insertion_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<()> {
for i in 0..vals.len() {
let mut j = i;
while j > 0 {
let ord = call_comparison(vm, by.clone(), vals[j-1].clone(), vals[j].clone())?;
if ord <= 0 {
break;
}
vals.swap(j, j-1);
j -= 1;
}
}
Ok(())
}
fn sort_inner(vals: &mut [Value], by: Option<&Value>, vm: &mut Vm) -> Result<()> {
if vals.len() <= 1 {
return Ok(())
}
if vals.len() <= 8 {
match by {
Some(by) => insertion_by(vals, by, vm)?,
None => insertion(vals),
}
return Ok(())
}
let (lt, gt) = match by {
Some(by) => partition_by(vals, by, vm)?,
None => partition(vals),
};
sort_inner(&mut vals[..lt], by, vm)?;
sort_inner(&mut vals[(gt+1)..], by, vm)
}
#[native_func(1)]
pub fn sort(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}")
};
sort_inner(&mut list.borrow_mut(), None, vm)?;
Ok(list.into())
}
#[native_func(2)]
pub fn sort_by(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, by, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}")
};
sort_inner(&mut list.borrow_mut(), Some(&by), vm)?;
Ok(list.into())
}
#[native_func(2)]
pub fn sort_key(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, key, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}")
};
let f = move |vm: &mut Vm, args: Vec<Value>| {
let [_, a, b] = unpack_args!(args);
let a = vmcall!(vm; key.clone(), a)?;
let b = vmcall!(vm; key.clone(), b)?;
match a.partial_cmp(&b) {
Some(Ordering::Greater) => Ok(Value::Int(1)),
Some(Ordering::Equal) => Ok(Value::Int(0)),
Some(Ordering::Less) => Ok(Value::Int(-1)),
None => throw!(*SYM_TYPE_ERROR, "values returned from sort key were incomparable"),
}
};
let nf = NativeFunc::new(Box::new(f), 2).into();
sort_inner(&mut list.borrow_mut(), Some(&nf), vm)?;
Ok(list.into())
}