use core::cmp::Ordering;
use std::cell::RefCell;
use std::sync::Arc;
use rpds::HashTrieSet;
use crate::calcit::{Calcit, CalcitErr, CalcitErrKind, CalcitList, CalcitTuple};
use crate::util::number::f64_to_usize;
use crate::builtins;
use crate::call_stack::CallStackList;
use crate::runner;
pub fn new_list(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
Ok(Calcit::List(Arc::new(xs.into())))
}
pub fn count(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(
CalcitErrKind::Arity,
"&list:count requires exactly 1 argument (a list), but received:",
xs,
);
}
match &xs[0] {
Calcit::List(ys) => Ok(Calcit::Number(ys.len() as f64)),
a => {
let msg = format!(
"&list:count requires a list as argument, but received a value of type: {}",
crate::builtins::meta::type_of(&[a.clone()])?.lisp_str()
);
let hint = crate::calcit::format_proc_examples_hint(&crate::calcit::CalcitProc::NativeListCount).unwrap_or_else(|| {
String::from(
"💡 Hint: Use `count` function which works on multiple types including lists, or ensure you're passing a list to &list:count",
)
});
CalcitErr::err_str_with_hint(CalcitErrKind::Type, msg, hint)
}
}
}
pub fn nth(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
let msg = format!(
"&list:nth requires exactly 2 arguments (list and index), but received {} arguments",
xs.len()
);
let hint = String::from(
"💡 Correct usage: `&list:nth ([] 1 2 3) 0` => 1\n First argument: a list\n Second argument: numeric index (0-based)",
);
return CalcitErr::err_nodes_with_hint(CalcitErrKind::Arity, msg, xs, hint);
}
match (&xs[0], &xs[1]) {
(Calcit::List(ys), Calcit::Number(n)) => match f64_to_usize(*n) {
Ok(idx) => match ys.get(idx) {
Some(v) => Ok((*v).to_owned()),
None => Ok(Calcit::Nil),
},
Err(e) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:nth requires a valid non-negative integer index, {e}"),
),
},
(_, _) => {
let msg = format!(
"&list:nth requires (list, number) but received: ({}, {})",
crate::builtins::meta::type_of(&[xs[0].clone()])?.lisp_str(),
crate::builtins::meta::type_of(&[xs[1].clone()])?.lisp_str()
);
let hint =
String::from("💡 Correct usage: `&list:nth ([] 1 2 3) 0` => 1\n Or use higher-level `nth` which works on multiple types");
CalcitErr::err_str_with_hint(CalcitErrKind::Type, msg, hint)
}
}
}
pub fn slice(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 && xs.len() != 3 {
return CalcitErr::err_str(
CalcitErrKind::Arity,
format!("&list:slice expected 2 or 3 arguments, but received: {}", CalcitList::from(xs)),
);
}
match (&xs[0], &xs[1]) {
(Calcit::List(ys), Calcit::Number(from)) => {
let from_idx = f64_to_usize(*from)?;
let to_idx = match xs.get(2) {
Some(Calcit::Number(to)) => f64_to_usize(*to)?,
Some(a) => {
return CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:slice expected a number for index, but received: {a}"),
);
}
None => ys.len(),
};
Ok(Calcit::List(Arc::new(ys.slice(from_idx, to_idx)?)))
}
(a, b) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:slice expected a list and numbers for indexes, but received: {a} {b}"),
),
}
}
pub fn last(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:last expected 1 argument (a list), but received:", xs);
}
match &xs[0] {
Calcit::List(ys) => {
let n = ys.len();
if n == 0 {
Ok(Calcit::Nil)
} else {
match ys.get(n - 1) {
Some(v) => Ok((*v).to_owned()),
None => Ok(Calcit::Nil),
}
}
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:last expected a list, but received: {a}")),
}
}
pub fn append(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_str(
CalcitErrKind::Arity,
format!("&list:append expected 2 arguments, but received: {}", CalcitList::from(xs)),
);
}
match &xs[0] {
Calcit::List(ys) => Ok(Calcit::List(Arc::new(ys.push_right(xs[1].to_owned())))),
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:append expected a list, but received: {a}")),
}
}
pub fn prepend(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
match (xs.first(), xs.get(1)) {
(Some(Calcit::List(ys)), Some(a)) => Ok(Calcit::List(Arc::new(ys.push_left(a.to_owned())))),
(Some(a), _) => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:prepend expected a list, but received: {a}")),
(None, _) => CalcitErr::err_str(CalcitErrKind::Arity, "&list:prepend expected 2 arguments, but received none"),
}
}
pub fn rest(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
let hint = String::from("💡 Usage: `&list:rest ([] 1 2 3)` => ([] 2 3)\n Returns all elements except the first one");
return CalcitErr::err_nodes_with_hint(
CalcitErrKind::Arity,
"&list:rest requires exactly 1 argument (a list), but received:",
xs,
hint,
);
}
match &xs[0] {
Calcit::List(ys) => {
if ys.is_empty() {
Ok(Calcit::Nil)
} else {
Ok(Calcit::List(Arc::new(ys.drop_left())))
}
}
a => {
let msg = format!(
"&list:rest requires a list as argument, but received: {}",
crate::builtins::meta::type_of(&[a.clone()])?.lisp_str()
);
let hint = String::from(
"💡 Hint: Use the higher-level `rest` function which works on multiple types,\n or ensure you're passing a list to &list:rest",
);
CalcitErr::err_str_with_hint(CalcitErrKind::Type, msg, hint)
}
}
}
pub fn butlast(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:butlast expected a list, but received:", xs);
}
match &xs[0] {
Calcit::Nil => Ok(Calcit::Nil),
Calcit::List(ys) => {
if ys.is_empty() {
Ok(Calcit::Nil)
} else {
Ok(Calcit::List(Arc::new(ys.butlast()?)))
}
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:butlast expected a list, but received: {a}")),
}
}
pub fn concat(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
let mut total_size = 0;
for x in xs {
if let Calcit::List(zs) = x {
total_size += zs.len();
} else {
return CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:concat expected list arguments, but received: {x}"),
);
}
}
let mut ys = Vec::with_capacity(total_size);
for x in xs {
if let Calcit::List(zs) = x {
ys.extend(zs.iter().map(|v| v.to_owned()));
}
}
Ok(Calcit::List(Arc::new(CalcitList::Vector(ys))))
}
pub fn range(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.is_empty() || xs.len() > 3 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:range expected 1 to 3 arguments, but received:", xs);
}
let (base, bound) = match (&xs[0], xs.get(1)) {
(Calcit::Number(bound), None) => (0.0, *bound),
(Calcit::Number(base), Some(Calcit::Number(bound))) => (*base, *bound),
(a, b) => {
return CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:range expected numbers for base and bound, but received: {a} {b:?}"),
);
}
};
let step = match xs.get(2) {
Some(Calcit::Number(n)) => *n,
Some(a) => {
return CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:range expected a number for step, but received: {a}"),
);
}
None => 1.0,
};
if (bound - base).abs() < f64::EPSILON {
return Ok(Calcit::from(CalcitList::default()));
}
if step == 0.0 || (bound > base && step < 0.0) || (bound < base && step > 0.0) {
return CalcitErr::err_str(
CalcitErrKind::Unexpected,
"&list:range cannot construct list with a step of 0 or invalid step direction",
);
}
let mut ys = vec![];
let mut i = base;
if step > 0.0 {
while i < bound {
ys.push(Calcit::Number(i));
i += step;
}
} else {
while i > bound {
ys.push(Calcit::Number(i));
i += step;
}
}
Ok(Calcit::from(ys))
}
pub fn reverse(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:reverse expected a list, but received:", xs);
}
match &xs[0] {
Calcit::Nil => Ok(Calcit::Nil),
Calcit::List(ys) => Ok(Calcit::from(ys.reverse())),
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:reverse expected a list, but received: {a}")),
}
}
pub fn foldl(xs: &[Calcit], call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
if xs.len() == 3 {
let mut ret = xs[1].to_owned();
match (&xs[0], &xs[2]) {
(Calcit::List(xs), Calcit::Fn { info, .. }) => {
for x in xs.iter() {
ret = runner::run_fn(&[ret, (*x).to_owned()], info, call_stack)?;
}
Ok(ret)
}
(Calcit::List(xs), Calcit::Proc(proc)) => {
for x in xs.iter() {
ret = builtins::handle_proc(*proc, &[ret, (*x).to_owned()], call_stack)?;
}
Ok(ret)
}
(Calcit::Set(xs), Calcit::Fn { info, .. }) => {
for x in xs {
ret = runner::run_fn(&[ret, x.to_owned()], info, call_stack)?;
}
Ok(ret)
}
(Calcit::Set(xs), Calcit::Proc(proc)) => {
for x in xs {
ret = builtins::handle_proc(*proc, &[ret, x.to_owned()], call_stack)?;
}
Ok(ret)
}
(Calcit::Map(xs), Calcit::Fn { info, .. }) => {
for (k, x) in xs {
ret = runner::run_fn(
&[ret, Calcit::from(CalcitList::from(&[k.to_owned(), x.to_owned()]))],
info,
call_stack,
)?;
}
Ok(ret)
}
(Calcit::Map(xs), Calcit::Proc(proc)) => {
for (k, x) in xs {
ret = builtins::handle_proc(
*proc,
&[ret, Calcit::from(CalcitList::from(&[k.to_owned(), x.to_owned()]))],
call_stack,
)?;
}
Ok(ret)
}
(a, b) => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldl expected a list and a function, but received: {a} {b}"),
call_stack,
a.get_location().or_else(|| b.get_location()),
)),
}
} else {
Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!("&list:foldl expected 3 arguments, but received: {}", CalcitList::from(xs)),
call_stack,
))
}
}
pub fn foldl_shortcut(xs: &[Calcit], call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
if xs.len() == 4 {
let acc = &xs[1];
let default_value = &xs[2];
match (&xs[0], &xs[3]) {
(Calcit::List(xs), Calcit::Fn { info, .. }) => {
let mut state = acc.to_owned();
for x in xs.iter() {
let pair = runner::run_fn(&[state.to_owned(), (*x).to_owned()], info, call_stack)?;
match pair {
Calcit::Tuple(CalcitTuple { tag: x0, extra, .. }) => match &*x0 {
Calcit::Bool(b) => {
let x1 = extra.first().ok_or(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
"&list:foldl-shortcut expected a value in the tuple",
call_stack,
x0.get_location(),
))?;
if *b {
return Ok((*x1).to_owned());
} else {
x1.clone_into(&mut state)
}
}
a => {
return Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldl-shortcut return value must be a boolean, but received: {a}"),
call_stack,
a.get_location(),
));
}
},
_ => {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Type,
format!("&list:foldl-shortcut return value must be `:: boolean accumulator`, but received: {pair}"),
call_stack,
));
}
}
}
Ok(default_value.to_owned())
}
(Calcit::Set(xs), Calcit::Fn { info, .. }) => {
let mut state = acc.to_owned();
for x in xs {
let pair = runner::run_fn(&[state.to_owned(), x.to_owned()], info, call_stack)?;
match pair {
Calcit::Tuple(CalcitTuple { tag: x0, extra, .. }) => match &*x0 {
Calcit::Bool(b) => {
let x1 = extra.first().ok_or(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
"&list:foldl-shortcut expected a value in the tuple",
call_stack,
x0.get_location(),
))?;
if *b {
return Ok((*x1).to_owned());
} else {
x1.clone_into(&mut state)
}
}
a => {
return Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldl-shortcut return value must be a boolean, but received: {a}"),
call_stack,
a.get_location(),
));
}
},
_ => {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Type,
format!("&list:foldl-shortcut return value must be `:: boolean accumulator`, but received: {pair}"),
call_stack,
));
}
}
}
Ok(default_value.to_owned())
}
(Calcit::Map(xs), Calcit::Fn { info, .. }) => {
let mut state = acc.to_owned();
for (k, x) in xs {
let pair = runner::run_fn(
&[state.to_owned(), Calcit::from(CalcitList::from(&[k.to_owned(), x.to_owned()]))],
info,
call_stack,
)?;
match pair {
Calcit::Tuple(CalcitTuple { tag: x0, extra, .. }) => match &*x0 {
Calcit::Bool(b) => {
let x1 = extra.first().ok_or(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
"&list:foldl-shortcut expected a value in the tuple",
call_stack,
x0.get_location(),
))?;
if *b {
return Ok((*x1).to_owned());
} else {
(*x1).clone_into(&mut state)
}
}
a => {
return Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldl-shortcut return value must be a boolean, but received: {a}"),
call_stack,
a.get_location(),
));
}
},
_ => {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Type,
format!("&list:foldl-shortcut return value must be `:: boolean accumulator`, but received: {pair}"),
call_stack,
));
}
}
}
Ok(default_value.to_owned())
}
(a, b) => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldl-shortcut expected a list and a function, but received: {a} {b}"),
call_stack,
a.get_location().or_else(|| b.get_location()),
)),
}
} else {
Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!(
"&list:foldl-shortcut expected 4 arguments (list, state, default, fn), but received: {}",
CalcitList::from(xs)
),
call_stack,
))
}
}
pub fn foldr_shortcut(xs: &[Calcit], call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
if xs.len() == 4 {
let acc = &xs[1];
let default_value = &xs[2];
match (&xs[0], &xs[3]) {
(Calcit::List(xs), Calcit::Fn { info, .. }) => {
let mut state = acc.to_owned();
let size = xs.len();
for i in 0..size {
let x = xs[size - 1 - i].to_owned();
let pair = runner::run_fn(&[state.to_owned(), x.to_owned()], info, call_stack)?;
match pair {
Calcit::Tuple(CalcitTuple { tag: x0, extra, .. }) => match &*x0 {
Calcit::Bool(b) => {
let x1 = extra.first().ok_or(CalcitErr::use_msg_stack_location(
CalcitErrKind::Arity,
"&list:foldr-shortcut expected a value in the tuple",
call_stack,
x0.get_location(),
))?;
if *b {
return Ok((*x1).to_owned());
} else {
(*x1).clone_into(&mut state)
}
}
a => {
return Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldr-shortcut return value must be a boolean, but received: {a}"),
call_stack,
a.get_location(),
));
}
},
_ => {
return Err(CalcitErr::use_msg_stack(
CalcitErrKind::Type,
format!("&list:foldr-shortcut return value must be `:: boolean accumulator`, but received: {pair}"),
call_stack,
));
}
}
}
Ok(default_value.to_owned())
}
(a, b) => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:foldr-shortcut expected a list and a function, but received: {a} {b}"),
call_stack,
a.get_location().or_else(|| b.get_location()),
)),
}
} else {
Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!(
"&list:foldr-shortcut expected 4 arguments (list, state, default, fn), but received: {}",
CalcitList::from(xs)
),
call_stack,
))
}
}
pub fn sort(xs: &[Calcit], call_stack: &CallStackList) -> Result<Calcit, CalcitErr> {
let comparator_result_to_ordering = |result: Result<Calcit, CalcitErr>, error_slot: &RefCell<Option<CalcitErr>>| -> Ordering {
match result {
Ok(Calcit::Number(x)) if x < 0.0 => Ordering::Less,
Ok(Calcit::Number(x)) if x > 0.0 => Ordering::Greater,
Ok(Calcit::Number(_)) => Ordering::Equal,
Ok(v) => {
*error_slot.borrow_mut() = Some(CalcitErr::use_msg_stack(
CalcitErrKind::Type,
format!("&list:sort comparator must return a number, but received: {v}"),
call_stack,
));
Ordering::Equal
}
Err(e) => {
*error_slot.borrow_mut() = Some(CalcitErr::use_msg_stack(
CalcitErrKind::Unexpected,
format!("&list:sort failed: {e}"),
call_stack,
));
Ordering::Equal
}
}
};
match xs {
[Calcit::List(values)] => {
let mut next_values: Vec<Calcit> = values.to_vec();
next_values.sort();
Ok(Calcit::List(Arc::new(CalcitList::Vector(next_values))))
}
[Calcit::List(values), Calcit::Fn { info, .. }] => {
let mut next_values: Vec<Calcit> = values.to_vec();
let sorting_error: RefCell<Option<CalcitErr>> = RefCell::new(None);
next_values.sort_by(|a, b| -> Ordering {
if sorting_error.borrow().is_some() {
return Ordering::Equal;
}
let result = runner::run_fn(&[(*a).to_owned(), (*b).to_owned()], info, call_stack);
comparator_result_to_ordering(result, &sorting_error)
});
if let Some(e) = sorting_error.into_inner() {
return Err(e);
}
Ok(Calcit::List(Arc::new(CalcitList::Vector(next_values))))
}
[Calcit::List(values), Calcit::Proc(proc)] => {
let mut next_values: Vec<Calcit> = values.to_vec();
let sorting_error: RefCell<Option<CalcitErr>> = RefCell::new(None);
next_values.sort_by(|a, b| -> Ordering {
if sorting_error.borrow().is_some() {
return Ordering::Equal;
}
let result = builtins::handle_proc(*proc, &[(*a).to_owned(), (*b).to_owned()], call_stack);
comparator_result_to_ordering(result, &sorting_error)
});
if let Some(e) = sorting_error.into_inner() {
return Err(e);
}
Ok(Calcit::List(Arc::new(CalcitList::Vector(next_values))))
}
[a, b] => Err(CalcitErr::use_msg_stack_location(
CalcitErrKind::Type,
format!("&list:sort expected a list and a function, but received: {a} {b}"),
call_stack,
a.get_location().or_else(|| b.get_location()),
)),
_ => Err(CalcitErr::use_msg_stack(
CalcitErrKind::Arity,
format!(
"&list:sort expected 1 or 2 arguments, but received: {}",
Calcit::List(Arc::new(xs.into()))
),
call_stack,
)),
}
}
pub fn first(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_str(CalcitErrKind::Arity, "&list:first expected 1 argument, but received none");
}
match &xs[0] {
Calcit::List(ys) => {
if ys.is_empty() {
Ok(Calcit::Nil)
} else {
Ok((ys[0]).to_owned())
}
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:first expected a list, but received: {a}")),
}
}
pub fn assoc_before(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 3 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:assoc-before expected 3 arguments, but received:", xs);
}
match (&xs[0], &xs[1]) {
(Calcit::List(zs), Calcit::Number(n)) => match f64_to_usize(*n) {
Ok(idx) => {
Ok(Calcit::List(Arc::new(zs.assoc_before(idx, xs[2].to_owned())?)))
}
Err(e) => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:assoc-before expected a valid index, {e}")),
},
(a, b) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:assoc-before expected a list and an index, but received: {a} {b}"),
),
}
}
pub fn assoc_after(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 3 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:assoc-after expected 3 arguments, but received:", xs);
}
match (&xs[0], &xs[1]) {
(Calcit::List(zs), Calcit::Number(n)) => match f64_to_usize(*n) {
Ok(idx) => {
Ok(Calcit::from(zs.assoc_after(idx, xs[2].to_owned())?))
}
Err(e) => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:assoc-after expected a valid index, {e}")),
},
(a, b) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:assoc-after expected a list and an index, but received: {a} {b}"),
),
}
}
pub fn list_ques(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "list? expects exactly 1 argument, but received:", xs);
}
Ok(Calcit::Bool(matches!(&xs[0], Calcit::List(_))))
}
pub fn empty_ques(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:empty? expected a list, but received:", xs);
}
match &xs[0] {
Calcit::List(ys) => Ok(Calcit::Bool(ys.is_empty())),
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:empty? expected a list, but received: {a}")),
}
}
pub fn contains_ques(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_nodes(
CalcitErrKind::Arity,
"&list:contains? expected a list and an index, but received:",
xs,
);
}
match (&xs[0], &xs[1]) {
(Calcit::List(xs), Calcit::Number(n)) => match f64_to_usize(*n) {
Ok(idx) => Ok(Calcit::Bool(idx < xs.len())),
Err(_) => Ok(Calcit::Bool(false)),
},
(a, b) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:contains? expected a list and an index, but received: {a} {b}"),
),
}
}
pub fn includes_ques(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
match (xs.first(), xs.get(1)) {
(Some(Calcit::List(xs)), Some(a)) => Ok(Calcit::Bool(xs.index_of(a).is_some())),
(Some(a), ..) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:includes? expected a list and a value, but received: {a}"),
),
(None, ..) => CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:includes? expected 2 arguments, but received:", xs),
}
}
pub fn assoc(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 3 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:assoc expected 3 arguments, but received:", xs);
}
match (&xs[0], &xs[1]) {
(Calcit::List(zs), Calcit::Number(n)) => match f64_to_usize(*n) {
Ok(idx) => {
if idx < zs.len() {
let mut ys: CalcitList = (**zs).to_owned();
ys = ys.assoc(idx, xs[2].to_owned())?;
Ok(Calcit::from(ys))
} else {
Ok(Calcit::List(Arc::new(xs.into())))
}
}
Err(e) => CalcitErr::err_str(CalcitErrKind::Type, e),
},
(a, b) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:assoc expected a list and an index, but received: {a} {b}"),
),
}
}
pub fn dissoc(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:dissoc expected 2 arguments, but received:", xs);
}
match (&xs[0], &xs[1]) {
(Calcit::List(xs), Calcit::Number(n)) => match f64_to_usize(*n) {
Ok(at) => Ok(Calcit::from(xs.dissoc(at)?)),
Err(e) => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:dissoc expected a valid index, {e}")),
},
(Calcit::List(_xs), a) => CalcitErr::err_str(
CalcitErrKind::Type,
format!("&list:dissoc expected a number for index, but received: {a}"),
),
(a, ..) => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:dissoc expected a list, but received: {a}")),
}
}
pub fn list_to_set(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:to-set expected 1 argument, but received:", xs);
}
match &xs[0] {
Calcit::List(ys) => {
let mut zs = rpds::HashTrieSet::new_sync();
ys.traverse(&mut |y| {
zs.insert_mut(y.to_owned());
});
Ok(Calcit::Set(zs))
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:to-set expected a list, but received: {a}")),
}
}
pub fn distinct(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_nodes(CalcitErrKind::Arity, "&list:distinct expected 1 argument, but received:", xs);
}
match &xs[0] {
Calcit::List(ys) => {
let mut seen = HashTrieSet::new_sync();
let mut zs = CalcitList::new_inner();
ys.traverse(&mut |y| {
if !seen.contains(y) {
seen.insert_mut(y.to_owned());
zs = zs.push_right(y.to_owned());
}
});
Ok(Calcit::from(CalcitList::List(zs)))
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&list:distinct expected a list, but received: {a}")),
}
}
use std::sync::Mutex;
pub fn buf_list_new(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if !xs.is_empty() {
return CalcitErr::err_str(CalcitErrKind::Arity, format!("&buf-list:new expects 0 args, got {}", xs.len()));
}
Ok(Calcit::BufList(Arc::new(Mutex::new(Vec::new()))))
}
pub fn buf_list_push(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_str(CalcitErrKind::Arity, format!("&buf-list:push expects 2 args, got {}", xs.len()));
}
match &xs[0] {
Calcit::BufList(buf) => {
let mut items = buf.lock().expect("BufList lock");
items.push(xs[1].to_owned());
Ok(xs[0].to_owned())
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&buf-list:push expects a buf-list, got: {a}")),
}
}
pub fn buf_list_concat(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 2 {
return CalcitErr::err_str(CalcitErrKind::Arity, format!("&buf-list:concat expects 2 args, got {}", xs.len()));
}
match (&xs[0], &xs[1]) {
(Calcit::BufList(buf), Calcit::List(list)) => {
let mut items = buf.lock().expect("BufList lock");
for item in list.iter() {
items.push(item.to_owned());
}
Ok(xs[0].to_owned())
}
(Calcit::BufList(_), b) => CalcitErr::err_str(CalcitErrKind::Type, format!("&buf-list:concat expects list as 2nd arg, got: {b}")),
(a, _) => CalcitErr::err_str(CalcitErrKind::Type, format!("&buf-list:concat expects a buf-list, got: {a}")),
}
}
pub fn buf_list_to_list(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_str(CalcitErrKind::Arity, format!("&buf-list:to-list expects 1 arg, got {}", xs.len()));
}
match &xs[0] {
Calcit::BufList(buf) => {
let items = buf.lock().expect("BufList lock");
Ok(Calcit::from(items.clone()))
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&buf-list:to-list expects a buf-list, got: {a}")),
}
}
pub fn buf_list_count(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
if xs.len() != 1 {
return CalcitErr::err_str(CalcitErrKind::Arity, format!("&buf-list:count expects 1 arg, got {}", xs.len()));
}
match &xs[0] {
Calcit::BufList(buf) => {
let items = buf.lock().expect("BufList lock");
Ok(Calcit::Number(items.len() as f64))
}
a => CalcitErr::err_str(CalcitErrKind::Type, format!("&buf-list:count expects a buf-list, got: {a}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::calcit::CalcitProc;
#[test]
fn sort_returns_error_when_comparator_not_number() {
let values = Calcit::from(vec![Calcit::Number(3.0), Calcit::Number(1.0), Calcit::Number(2.0)]);
let comparator = Calcit::Proc(CalcitProc::Not);
let result = sort(&[values, comparator], &CallStackList::default());
assert!(result.is_err());
}
#[test]
fn sort_accepts_single_argument_natural_order() {
let values = Calcit::from(vec![Calcit::Number(3.0), Calcit::Number(1.0), Calcit::Number(2.0)]);
let result = sort(&[values], &CallStackList::default()).expect("sort should support single list argument");
assert_eq!(
result,
Calcit::from(vec![Calcit::Number(1.0), Calcit::Number(2.0), Calcit::Number(3.0)])
);
}
}