eclisp 0.1.0

Bindings for Embeddable Common Lisp
use eclisp::{Lisp, LispError, ListError};

fn test_eval(lisp: &Lisp) -> Result<(), LispError> {
    let value = lisp.eval("(+ 1 2)")?;
    assert_eq!(value.try_as_i64()?, 3);
    Ok(())
}

fn test_errors(lisp: &Lisp) -> Result<(), LispError> {
    assert!(lisp.eval("(error \"test\")").is_err());
    Ok(())
}

fn test_values(lisp: &Lisp) -> Result<(), LispError> {
    let value = lisp.eval(r#""hello""#)?;
    assert_eq!(value.try_as_string()?, "hello");
    Ok(())
}

fn test_multiple_forms(lisp: &Lisp) -> 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: &Lisp) -> 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: &Lisp) -> 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: &Lisp) -> 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: &Lisp) -> Result<(), LispError> {
    let int_value = lisp.int(42)?;
    assert_eq!(int_value.try_as_i64()?, 42);

    let float_value = lisp.float(3.14)?;
    assert_eq!(float_value.try_as_f64()?, 3.14);

    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.14);
    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.14);

    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: &Lisp) -> 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: &Lisp) -> 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: &Lisp) -> 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.14)?;
    assert_eq!(value.try_as_f64()?, 3.14);

    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 run(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()?;

    run("eval", &lisp, test_eval);
    run("errors", &lisp, test_errors);
    run("values", &lisp, test_values);
    run("multiple_forms", &lisp, test_multiple_forms);
    run("reader_errors", &lisp, test_reader_errors);
    run("strings", &lisp, test_strings);
    run("call", &lisp, test_call);
    run("value_constructors", &lisp, test_value_constructors);
    run("big_ints", &lisp, test_big_ints);
    run("improper_list", &lisp, test_improper_list);
    run("error_recovery", &lisp, test_error_recovery);

    Ok(())
}