use std::{fs, path::Path, rc::Rc}; use lysp::{ error::MachineErrorAt, vm::{env::Environment, machine::Machine, prelude, value::Value}, }; #[track_caller] fn eval_str_in(code: &str, env: &Rc) -> Result { let mut machine = Machine::default(); machine.evaluate_str(Default::default(), None, env, code) } #[track_caller] fn eval_str(code: &str) -> Value { let env = Rc::new(Environment::default()); prelude::load(&env); match eval_str_in(code, &env) { Ok(value) => value, Err(error) => { eprintln!("Couldn't evaluate expression:"); eprintln!(); eprintln!(" {code}"); eprintln!(); eprintln!(":: {error}"); panic!("TEST FAILED"); } } } #[track_caller] fn eval_str_err(code: &str) -> MachineErrorAt { let env = Rc::new(Environment::default()); prelude::load(&env); match eval_str_in(code, &env) { Ok(value) => { eprintln!("Expected the code to fail to evaluate, but it returned success:"); eprintln!(); eprintln!(" {code}"); eprintln!(); eprintln!("Returned"); eprintln!(); eprintln!(":: {value}"); panic!("TEST FAILED"); } Err(error) => error, } } #[track_caller] fn eval_file>(path: P) -> Value { let code = fs::read_to_string(path).expect("file read failed"); eval_str(&code) } #[test] fn test_math() { // math assert_eq!(eval_str("(+ 1 2 3)"), Value::Number(6.into())); assert_eq!(eval_str("(- 3 2 1)"), Value::Number(0.into())); assert_eq!(eval_str("(* 2 3 4)"), Value::Number(24.into())); assert_eq!(eval_str("(/ 16 4 2)"), Value::Number(2.into())); assert_eq!(eval_str("(% 35 16)"), Value::Number(3.into())); // TODO // assert_eq!(eval_str("(| 1 2 4)"), Value::Number(7.into())); // assert_eq!(eval_str("(& 1 2 4)"), Value::Number(0.into())); // assert_eq!(eval_str("(& 1 3 7)"), Value::Number(1.into())); // assert_eq!(eval_str("(^ 1 3 8)"), Value::Number(10.into())); // comparison assert_eq!(eval_str("(< 1 2 3 4 5)"), Value::Boolean(true.into())); assert_eq!(eval_str("(< 1 2 3 3 4 5)"), Value::Boolean(false.into())); assert_eq!(eval_str("(> 1 2 3 4 5)"), Value::Boolean(false.into())); assert_eq!(eval_str("(>= 5 5 4 3 2 1)"), Value::Boolean(true.into())); assert_eq!(eval_str("(> 5 5 4 3 2 1)"), Value::Boolean(false.into())); assert_eq!(eval_str("(<= 1 2 3 3 3 4)"), Value::Boolean(true.into())); assert_eq!(eval_str("(/= 1 2 3 4)"), Value::Boolean(true.into())); assert_eq!(eval_str("(/= 1 2 2 4)"), Value::Boolean(false.into())); assert_eq!(eval_str("(= 1 2 3 4)"), Value::Boolean(false.into())); assert_eq!(eval_str("(= 1 1 1 1)"), Value::Boolean(true.into())); // logic // TODO // assert_eq!(eval_str("(&& 1 0)"), Value::Boolean(false.into())); // assert_eq!(eval_str("(&& #t 1 \"yes\")"), Value::Boolean(true.into())); // assert_eq!( // eval_str("(|| #f NIL \"\" ())"), // Value::Boolean(false.into()) // ); // assert_eq!( // eval_str("(|| #f '(1 2 3) \"\" ())"), // Value::Boolean(true.into()) // ); } #[test] fn test_abort() { // let err = eval_str_err("(assert (= 1 (+ 1 1)))"); // let EvalError::Machine(MachineError { // error: MachineErrorKind::Aborted(message), // .. // }) = err // else { // panic!("Invalid error returned") // }; // assert_eq!(&message, ": assertion failed: (= 1 (+ 1 1))"); // let err = eval_str_err("(abort 1234)"); // let EvalError::Machine(MachineError { // error: MachineErrorKind::Aborted(message), // .. // }) = err // else { // panic!("Invalid error returned") // }; // assert_eq!(&message, "1234"); } #[test] fn test_lambda() { assert_eq!( eval_str("((lambda (x) (+ x 1)) 1)"), Value::Number(2.into()) ); assert_eq!( eval_str("((lambda (x) (+ ((lambda () 2)) 1)) 1)"), Value::Number(3.into()) ); } #[test] fn test_math_behaves_the_same_inside_apply() { assert_eq!(eval_str("(apply + '(1 2 3 4))"), eval_str("(+ 1 2 3 4)")); assert_eq!(eval_str("(apply * '(1 2 3 4))"), eval_str("(* 1 2 3 4)")); assert_eq!(eval_str("(apply - '(1 2 3 4))"), eval_str("(- 1 2 3 4)")); assert_eq!(eval_str("(apply / '(100 25 2))"), eval_str("(/ 100 25 2)")); assert_eq!(eval_str("(apply % '(90 37))"), eval_str("(% 90 37)")); // TODO // assert_eq!(eval_str("(apply | '(1 2 4))"), eval_str("(| 1 2 4)")); // assert_eq!(eval_str("(apply & '(1 3 7))"), eval_str("(& 1 3 7)")); // assert_eq!(eval_str("(apply ^ '(1 3 7))"), eval_str("(^ 1 3 7)")); // assert_eq!(eval_str("(apply < '(1 2 3 4))"), eval_str("(< 1 2 3 4)")); // assert_eq!(eval_str("(apply > '(1 2 3 4))"), eval_str("(> 1 2 3 4)")); } #[test] fn test_setq() { assert_eq!(eval_str("(setq a 1234) a\n"), Value::Number(1234.into())); } #[test] fn test_let() { // Should fail eval_str_err("(let (a 1234 b (+ a 1)) NIL)"); eval_str_err("(let (a 1234) (let (a 9999 b (+ a 4321)) b))"); assert_eq!( eval_str("(let (a 1234 b 4321) (+ a b))"), Value::Number(5555.into()) ); assert_eq!( eval_str("(let* (a 1234 b (+ a 4321)) (+ b 4444))"), Value::Number(9999.into()) ); // Nested let assert_eq!( eval_str("(let (a 1234) (let (b 4321) (+ a b)))"), Value::Number(5555.into()) ); // Does shadow assert_eq!( eval_str("(let (a 1234) (let* (a 9999 b (+ a 4321)) b))"), Value::Number(14320.into()) ); } #[test] fn test_macro() { assert_eq!( eval_str("(defmacro stringify (x) (cons 'list `,x)) (stringify (1 2 3 4))"), Value::list_or_nil([ Value::Number(1.into()), Value::Number(2.into()), Value::Number(3.into()), Value::Number(4.into()) ]) ); } #[test] fn test_examples_work() { const EXCLUDE: &[&str] = &["repl.lysp", "echo.lysp", "io.lysp"]; // None of them should crash at least for file in fs::read_dir("examples").unwrap() { let entry = file.unwrap(); let filename = entry.file_name(); let filename = filename.to_str().unwrap(); if !filename.ends_with(".lysp") || EXCLUDE.contains(&filename) { continue; } eprintln!("Eval {}", entry.path().display()); eval_file(entry.path()); } }