use eclisp::{Lisp, LispContext, LispError, ListError};
fn test_eval(lisp: &LispContext<'_>) -> Result<(), LispError> {
let value = lisp.eval("(+ 1 2)")?;
assert_eq!(value.try_as_i64()?, 3);
Ok(())
}
fn test_errors(lisp: &LispContext<'_>) -> Result<(), LispError> {
assert!(lisp.eval("(error \"test\")").is_err());
Ok(())
}
fn test_values(lisp: &LispContext<'_>) -> Result<(), LispError> {
let value = lisp.eval(r#""hello""#)?;
assert_eq!(value.try_as_string()?, "hello");
Ok(())
}
fn test_multiple_forms(lisp: &LispContext<'_>) -> Result<(), LispError> {
let result = lisp.eval(
r#"
(defparameter *x* 10)
(defparameter *y* 20)
(+ *x* *y*)
"#,
)?;
assert_eq!(result.try_as_i64()?, 30);
Ok(())
}
fn test_reader_errors(lisp: &LispContext<'_>) -> Result<(), LispError> {
let result = lisp.eval(
r#"
(defparameter *x* 10)
(defun broken (
"#,
);
assert!(result.is_err());
let result = lisp.eval("(+ 2 3)")?;
assert_eq!(result.try_as_i64()?, 5);
Ok(())
}
fn test_strings(lisp: &LispContext<'_>) -> Result<(), LispError> {
let value = lisp.string("Hello, 世界!")?;
assert!(value.is_string());
assert_eq!(value.try_as_string()?, "Hello, 世界!");
let empty = lisp.string("")?;
assert_eq!(empty.try_as_string()?, "");
let code = lisp.string("(+ 1 2)")?;
assert_eq!(code.try_as_string()?, "(+ 1 2)");
Ok(())
}
fn test_call(lisp: &LispContext<'_>) -> Result<(), LispError> {
let a = lisp.eval("10")?;
let b = lisp.eval("20")?;
let result = lisp.call("+", &[&a, &b])?;
assert_eq!(result.try_as_i64()?, 30);
lisp.eval(
r#"
(defun multiply (a b)
(* a b))
"#,
)?;
let result = lisp.call("multiply", &[&a, &b])?;
assert_eq!(result.try_as_i64()?, 200);
let result = lisp.call("list", &[])?;
assert!(result.is_nil());
assert!(lisp.call("multiply", &[&a]).is_err());
Ok(())
}
fn test_value_constructors(lisp: &LispContext<'_>) -> Result<(), LispError> {
let int_value = lisp.int(42)?;
assert_eq!(int_value.try_as_i64()?, 42);
let float_value = lisp.float(3.15)?;
assert_eq!(float_value.try_as_f64()?, 3.15);
let string_value = lisp.string("Hello")?;
assert_eq!(string_value.try_as_string()?, "Hello");
let string_value = lisp.string("Hello\0世界")?;
assert_eq!(string_value.try_as_string()?, "Hello\0世界");
let list_value = lisp.list(&[&int_value, &float_value, &string_value])?;
assert!(list_value.is_list());
let mut iter = list_value.iter();
assert_eq!(iter.next().unwrap()?.try_as_i64()?, 42);
assert_eq!(iter.next().unwrap()?.try_as_f64()?, 3.15);
assert_eq!(iter.next().unwrap()?.try_as_string()?, "Hello\0世界");
assert!(iter.next().is_none());
let cons_value = lisp.cons(&int_value, &float_value)?;
assert!(cons_value.is_cons());
assert!(cons_value.is_list());
assert_eq!(cons_value.car()?.try_as_i64()?, 42);
assert_eq!(cons_value.cdr()?.try_as_f64()?, 3.15);
let mut iter = cons_value.iter();
assert_eq!(iter.next().unwrap()?.try_as_i64()?, 42);
assert!(matches!(iter.next(), Some(Err(ListError::ImproperList))));
assert!(iter.next().is_none());
Ok(())
}
fn test_big_ints(lisp: &LispContext<'_>) -> Result<(), LispError> {
let value = lisp.int(i64::MAX)?;
assert_eq!(value.try_as_i64()?, i64::MAX);
let value = lisp.int(i64::MIN)?;
assert_eq!(value.try_as_i64()?, i64::MIN);
Ok(())
}
fn test_improper_list(lisp: &LispContext<'_>) -> Result<(), LispError> {
let a = lisp.int(1)?;
let b = lisp.int(2)?;
let c = lisp.int(3)?;
let tail = lisp.cons(&b, &c)?;
let improper_list = lisp.cons(&a, &tail)?;
assert!(improper_list.is_cons());
assert!(improper_list.is_list());
let mut iter = improper_list.iter();
assert_eq!(iter.next().unwrap()?.try_as_i64()?, 1);
assert_eq!(iter.next().unwrap()?.try_as_i64()?, 2);
assert!(matches!(iter.next(), Some(Err(ListError::ImproperList))));
assert!(iter.next().is_none());
Ok(())
}
fn test_error_recovery(lisp: &LispContext<'_>) -> Result<(), LispError> {
assert!(lisp.eval("(error \"test\")").is_err());
let value = lisp.int(42)?;
assert_eq!(value.try_as_i64()?, 42);
let value = lisp.float(3.15)?;
assert_eq!(value.try_as_f64()?, 3.15);
let value = lisp.string("hello")?;
assert_eq!(value.try_as_string()?, "hello");
let list = lisp.list(&[&value])?;
assert!(list.is_list());
assert_eq!(lisp.eval("(+ 1 2)")?.try_as_i64()?, 3);
Ok(())
}
fn test_value_ref(ctx: &LispContext<'_>) -> Result<(), LispError> {
let owned = ctx.int(42)?;
let borrowed = owned.as_ref();
assert!(borrowed.is_int());
assert_eq!(borrowed.try_as_i64()?, 42);
let second_root = ctx.root(borrowed)?;
drop(owned);
assert_eq!(second_root.try_as_i64()?, 42);
Ok(())
}
fn function_refs(lisp: &Lisp) -> Result<(), LispError> {
lisp.enter(|ctx| ctx.eval("(defun increment (x) (+ x 1))"))?;
let func = lisp.fun("increment")?;
let cloned = func.clone();
let result = lisp.enter(|ctx| {
let arg = ctx.int(5)?;
ctx.callfun(&cloned, &[&arg])
})?;
assert_eq!(result.try_as_i64()?, 6);
drop(func);
drop(cloned);
Ok(())
}
fn run(name: &str, lisp: &LispContext<'_>, test: fn(&LispContext<'_>) -> Result<(), LispError>) {
print!("test {name} ... ");
match test(lisp) {
Ok(()) => println!("ok"),
Err(error) => panic!("{name} failed: {error}"),
}
}
fn run_raw(name: &str, lisp: &Lisp, test: fn(&Lisp) -> Result<(), LispError>) {
print!("test {name} ... ");
match test(lisp) {
Ok(()) => println!("ok"),
Err(error) => panic!("{name} failed: {error}"),
}
}
fn main() -> Result<(), LispError> {
let lisp = Lisp::new()?;
lisp.enter(|ctx| {
run("eval", &ctx, test_eval);
run("errors", &ctx, test_errors);
run("values", &ctx, test_values);
run("multiple_forms", &ctx, test_multiple_forms);
run("reader_errors", &ctx, test_reader_errors);
run("strings", &ctx, test_strings);
run("call", &ctx, test_call);
run("value_constructors", &ctx, test_value_constructors);
run("big_ints", &ctx, test_big_ints);
run("improper_list", &ctx, test_improper_list);
run("error_recovery", &ctx, test_error_recovery);
run("value_ref", &ctx, test_value_ref);
});
run_raw("function_refs", &lisp, function_refs);
Ok(())
}