use crate::{
Error, TulispContext, TulispObject,
eval::{DummyEval, funcall},
list,
};
pub fn length(list: &TulispObject) -> Result<i64, Error> {
list.base_iter()
.count()
.try_into()
.map_err(|e: _| Error::out_of_range(format!("{}", e)))
}
pub fn last(list: &TulispObject, n: Option<i64>) -> Result<TulispObject, Error> {
if list.null() {
return Ok(list.clone());
}
if !list.consp() {
return Err(Error::type_mismatch(format!(
"expected list, got: {}",
list
)));
}
let len = length(list)?;
if let Some(n) = n {
if n < 0 {
return Err(Error::out_of_range(format!(
"n must be positive. got: {}",
n
)));
}
if n < len {
return nthcdr(len - n, list.clone());
}
} else {
return nthcdr(len - 1, list.clone());
}
Ok(list.clone())
}
pub fn nthcdr(n: i64, list: TulispObject) -> Result<TulispObject, Error> {
let mut next = list;
for _ in 0..n {
if next.null() {
return Ok(next);
}
next = next.cdr()?;
}
Ok(next)
}
pub fn nth(n: i64, list: TulispObject) -> Result<TulispObject, Error> {
nthcdr(n, list).and_then(|x| x.car())
}
pub fn alist_from<const N: usize>(input: [(TulispObject, TulispObject); N]) -> TulispObject {
let alist = TulispObject::nil();
for (key, value) in input.into_iter() {
let _ = alist.push(TulispObject::cons(key, value));
}
alist
}
pub fn plist_from<const N: usize>(input: [(TulispObject, TulispObject); N]) -> TulispObject {
let plist = TulispObject::nil();
for (key, value) in input.into_iter() {
let _ = plist.push(key);
let _ = plist.push(value);
}
plist
}
pub fn assoc(
ctx: &mut TulispContext,
key: &TulispObject,
alist: &TulispObject,
testfn: Option<TulispObject>,
) -> Result<TulispObject, Error> {
if !alist.listp() {
return Err(Error::type_mismatch(format!(
"expected alist. got: {}",
alist
)));
}
if let Some(testfn) = testfn {
let pred = ctx.eval(&testfn)?;
let testfn = |_1: &TulispObject, _2: &TulispObject| -> Result<bool, Error> {
funcall::<DummyEval>(ctx, &pred, &list!(,_1.clone() ,_2.clone()).unwrap())
.map(|x| x.is_truthy())
};
assoc_find(key, alist, testfn)
} else {
let testfn = |_1: &TulispObject, _2: &TulispObject| Ok(_1.equal(_2));
assoc_find(key, alist, testfn)
}
}
pub fn alist_get(
ctx: &mut TulispContext,
key: &TulispObject,
alist: &TulispObject,
default_value: Option<TulispObject>,
_remove: Option<TulispObject>, testfn: Option<TulispObject>,
) -> Result<TulispObject, Error> {
let x = assoc(ctx, key, alist, testfn)?;
if x.is_truthy() {
x.cdr()
} else {
Ok(default_value.unwrap_or_else(TulispObject::nil))
}
}
fn assoc_find(
key: &TulispObject,
alist: &TulispObject,
mut testfn: impl FnMut(&TulispObject, &TulispObject) -> Result<bool, Error>,
) -> Result<TulispObject, Error> {
let mut cur = alist.clone();
while cur.consp() {
if cur.caar_and_then(|caar| testfn(caar, key))? {
return cur.car();
}
cur = cur.cdr()?;
}
Ok(TulispObject::nil())
}
pub fn plist_get(plist: &TulispObject, property: &TulispObject) -> Result<TulispObject, Error> {
let mut cur = plist.clone();
while cur.consp() {
if cur.car_and_then(|car| Ok(car.eq(property)))? {
return cur.cadr();
}
cur = cur.cddr()?;
}
Ok(TulispObject::nil())
}
#[cfg(test)]
mod tests {
use crate::lists::{Error, TulispContext, alist_from, alist_get, plist_from, plist_get};
#[test]
fn test_alist() -> Result<(), Error> {
let mut ctx = TulispContext::new();
let a = ctx.intern("a");
let b = ctx.intern("b");
let c = ctx.intern("c");
let d = ctx.intern("d");
let list = alist_from([
(a.clone(), 20.into()),
(b.clone(), 30.into()),
(c.clone(), 40.into()),
]);
assert!(alist_get(&mut ctx, &b, &list, None, None, None)?.equal(&30.into()));
assert_eq!(
alist_get(&mut ctx, &d, &list, None, None, None)?.null(),
true
);
Ok(())
}
#[test]
fn test_plist() -> Result<(), Error> {
let mut ctx = TulispContext::new();
let a = ctx.intern("a");
let b = ctx.intern("b");
let c = ctx.intern("c");
let d = ctx.intern("d");
let list = plist_from([
(a.clone(), 20.into()),
(b.clone(), 30.into()),
(c.clone(), 40.into()),
]);
assert!(plist_get(&list, &b)?.equal(&30.into()));
assert_eq!(plist_get(&list, &d)?.null(), true);
Ok(())
}
}