From 0cbfaaafecfec8e684af35ae55a128040b33d79b Mon Sep 17 00:00:00 2001 From: teo3300 Date: Sat, 25 Nov 2023 20:30:21 +0900 Subject: [PATCH] Some more tests - Added hook pre-commit - Added some reader tests - Added some eval tests - Added functional tests Signed-off-by: teo3300 --- .git_hooks_pre-commit-shadow | 1 + test.mal => core.mal | 0 src/core.rs | 5 +- src/eval.rs | 101 +++++++++++++++++++++++++++++++++-- src/main.rs | 1 + src/reader.rs | 42 +++++++++------ src/tests/mod.rs | 19 +++++++ src/types.rs | 8 +++ tests/fibonacci.mal | 21 ++++++++ 9 files changed, 176 insertions(+), 22 deletions(-) create mode 120000 .git_hooks_pre-commit-shadow rename test.mal => core.mal (100%) create mode 100644 src/tests/mod.rs create mode 100644 tests/fibonacci.mal diff --git a/.git_hooks_pre-commit-shadow b/.git_hooks_pre-commit-shadow new file mode 120000 index 0000000..da0cb14 --- /dev/null +++ b/.git_hooks_pre-commit-shadow @@ -0,0 +1 @@ +.git/hooks/pre-commit \ No newline at end of file diff --git a/test.mal b/core.mal similarity index 100% rename from test.mal rename to core.mal diff --git a/src/core.rs b/src/core.rs index 15eafe2..0beab0d 100644 --- a/src/core.rs +++ b/src/core.rs @@ -30,7 +30,7 @@ macro_rules! env_init { } use crate::printer::prt; -use crate::types::mal_comp; +use crate::types::{mal_comp, mal_assert}; use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str}; pub fn ns_init() -> Env { @@ -50,6 +50,7 @@ pub fn ns_init() -> Env { "list?" => Fun(|a| Ok(Bool(matches!(car(a)?, List(_)))), "Return true if the first argument is a list, false otherwise"), "empty?" => Fun(|a| Ok(Bool(car(a)?.if_list()?.is_empty())), "Return true if the first parameter is an empty list, false otherwise, returns an error if the element is not a list"), "count" => Fun(|a| Ok(Int(car(a)?.if_list()?.len() as isize)), "Return the number of elements in the first argument"), - "=" => Fun(mal_comp, "Return true if the first two parameters are the same type and content, in case of lists propagate to all elements") + "=" => Fun(mal_comp, "Return true if the first two parameters are the same type and content, in case of lists propagate to all elements"), + "assert" => Fun(mal_assert, "Panic if one of the argument is false") ) } diff --git a/src/eval.rs b/src/eval.rs index f8b72cc..0023922 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -65,11 +65,6 @@ fn let_star_form(list: &[MalType], env: Env) -> MalRet { /// Evaluate all the elements in a list using eval_ast and return the /// result of the last evaluation fn do_form(list: &[MalType], env: Env) -> MalRet { - if list.is_empty() { - return Err(MalErr::unrecoverable("do form: provide a list as argument")); - } - // TODO: this may be different - let mut ret = M::Nil; for ast in list { ret = eval(ast, env.clone())?; @@ -171,3 +166,99 @@ fn eval_ast(ast: &MalType, env: Env) -> MalRet { _ => Ok(ast.clone()), } } + +//////////////////////////////////////////////////////////////////////////////// +// Tests // +//////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + + use crate::env::Env; + + macro_rules! load { + ($input:expr) => {{ + use crate::env::cdr; + use crate::reader::{read_str, Reader}; + + let r = Reader::new(); + r.push($input); + cdr(&match read_str(&r) { + Ok(v) => match v { + MalType::List(v) => v, + _ => panic!("Bad command"), + }, + _ => panic!("Bad command"), + }) + }}; + } + + fn _env_empty() -> Env { + use crate::env::env_new; + env_new(None) + } + + mod forms { + + use crate::env::env_get; + use crate::eval::{def_bang_form, let_star_form, do_form}; + use crate::eval::tests::_env_empty; + use crate::types::MalType; + + #[test] + fn def_bang() { + let env = _env_empty(); + + assert!(matches!( //x empty + def_bang_form( + load!("(def!) ; empty"), + env.clone()), + Err(e) + if !e.is_recoverable())); + assert!(matches!( //x 1 arg + def_bang_form( + load!("(def! a) ; 1 arg"), + env.clone()), + Err(e) + if !e.is_recoverable())); + assert!(matches!( //x 3 args + def_bang_form( + load!("(def! a 1 2) ; 3 args"), + env.clone()), + Err(e) + if !e.is_recoverable())); + + assert!(matches!( + //v 2 args + def_bang_form(load!("(def! a 1) ; correct a = 1"), env.clone()), + Ok(MalType::Int(1)) + )); + assert!(matches!(env_get(&env, "a"), Ok(MalType::Int(1)))); + } + + #[test] + fn let_star() { + let env = _env_empty(); + assert!(matches!(let_star_form(load!("(let*)"), env.clone()), Err(e) if !e.is_recoverable())); + assert!(matches!(let_star_form(load!("(let* 1)"), env.clone()), Err(e) if !e.is_recoverable())); + assert!(matches!(let_star_form(load!("(let* (a))"), env.clone()), Err(e) if !e.is_recoverable())); + assert!(matches!(let_star_form(load!("(let* ())"), env.clone()), Ok(MalType::Nil))); + assert!(matches!(let_star_form(load!("(let* (a 1))"), env.clone()), Ok(MalType::Nil))); + assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable())); + assert!(matches!(let_star_form(load!("(let* (a 1 b 2) a b)"), env.clone()), Ok(MalType::Int(2)))); + assert!(matches!(env_get(&env.clone(), "a"), Err(e) if !e.is_recoverable())); + assert!(matches!(env_get(&env.clone(), "b"), Err(e) if !e.is_recoverable())); + assert!(matches!(let_star_form(load!("(let* (a 1 b 2) (def! c 1) a b)"), env.clone()), Ok(MalType::Int(2)))); + assert!(matches!(env_get(&env.clone(), "c"), Err(e) if !e.is_recoverable())); + } + + #[test] + fn _do() { + let env = _env_empty(); + assert!(matches!(do_form(load!("(do)"), env.clone()), Ok(MalType::Nil))); + assert!(matches!(do_form(load!("(do true)"), env.clone()), Ok(MalType::Bool(true)))); + assert!(matches!(do_form(load!("(do (def! a 1) 2)"), env.clone()), Ok(MalType::Int(2)))); + assert!(matches!(env_get(&env.clone(), "a"), Ok(MalType::Int(1)))); + } + } +} diff --git a/src/main.rs b/src/main.rs index 05aca65..fffa1c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod parse_tools; mod printer; mod reader; mod step4_if_fn_do; +mod tests; mod types; use core::ns_init; diff --git a/src/reader.rs b/src/reader.rs index 92982ef..0cff1ec 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -150,9 +150,12 @@ fn tokenize(input: &str) -> Tokens { #[cfg(test)] mod tests { - use crate::types::{MalMap, MalType as M, Severity}; + use crate::{ + reader::read_str, + types::{MalMap, MalType as M}, + }; - use super::Reader; + use super::{tokenize, Reader}; fn reader_setup1() -> Reader { let r = Reader::new(); @@ -167,13 +170,22 @@ mod tests { assert_eq!(r.ptr.get(), 0); } + #[test] + fn do_tokenize() { + assert_eq!( + tokenize("()[]{} \"str\" :key sym 1 ; comment"), + vec!["(", ")", "[", "]", "{", "}", "\"str\"", ":key", "sym", "1"] + ); + } + #[test] fn push() { let r = reader_setup1(); let mut tokens = Vec::new(); - for el in r.tokens.borrow().iter() { - tokens.push(el.clone()); - } + r.tokens + .borrow() + .iter() + .for_each(|i| tokens.push(i.clone())); assert_eq!( tokens, vec!["(", ")", "[", "]", "{", "}", "\"str\"", ":key", "sym", "1"] @@ -185,7 +197,7 @@ mod tests { let r = reader_setup1(); assert!(matches!(r.get_token(0), Ok(i) if i == "(")); assert!(matches!(r.get_token(9), Ok(i) if i == "1")); - assert!(matches!(r.get_token(10), Err(e) if e.severity() == Severity::Recoverable)); + assert!(matches!(r.get_token(10), Err(e) if e.is_recoverable())); } #[test] @@ -221,8 +233,8 @@ mod tests { fn errors() { let r = Reader::new(); // Correct throws error - assert!(matches!(r.peek(), Err(e) if e.severity() == Severity::Recoverable)); - assert!(matches!(r.next(), Err(e) if e.severity() == Severity::Recoverable)); + assert!(matches!(r.peek(), Err(e) if e.is_recoverable())); + assert!(matches!(r.next(), Err(e) if e.is_recoverable())); } #[test] @@ -235,20 +247,20 @@ mod tests { assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Sym(v) if v == "a"))); assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Str(v) if v == "s"))); assert!(matches!(r.read_atom(), Ok(x) if matches!(x.clone(), M::Key(v) if v == "ʞ:a"))); - assert!(matches!(r.read_atom(), Err(e) if e.severity() == Severity::Unrecoverable)); - assert!(matches!(r.read_atom(), Err(e) if e.severity() == Severity::Unrecoverable)); - assert!(matches!(r.read_atom(), Err(e) if e.severity() == Severity::Unrecoverable)); + assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable())); + assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable())); + assert!(matches!(r.read_atom(), Err(e) if !e.is_recoverable())); } #[test] - fn read_form() { + fn _read_str() { let r = Reader::new(); // Test list let expected = vec![1, 2, 12]; r.push("(1 2 12)"); assert!(matches!( - r.read_form(), Ok(x) + read_str(&r), Ok(x) if matches!(x.clone(), M::List(list) if list.len() == expected.len() && list.iter().zip(expected) @@ -259,7 +271,7 @@ mod tests { let exp = vec![1, 2, 12]; r.push("[1 2 12]"); assert!(matches!( - r.read_form(), Ok(x) + read_str(&r), Ok(x) if matches!(x.clone(), M::Vector(list) if list.len() == exp.len() && list.iter().zip(exp) @@ -268,7 +280,7 @@ mod tests { // Test map r.push("{\"i\" 1 \"s\" \"str\" \"t\" true \"n\" nil :s :sym}"); - let t = match r.read_form() { + let t = match read_str(&r) { Ok(x) => match x { M::Map(x) => x, _ => { diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..5215377 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod functional { + macro_rules! test { + ($file:expr) => { + { + use crate::core::ns_init; + use crate::load_file; + let env = ns_init(); + load_file("core.mal", &env); + load_file(format!("tests/{}.mal", $file).as_str(), &env); + } + }; + } + + #[test] + fn fibonacci() { + test!("fibonacci"); + } +} \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 7ca8f92..ac7b483 100644 --- a/src/types.rs +++ b/src/types.rs @@ -80,6 +80,14 @@ pub fn mal_comp(args: &[MalType]) -> MalRet { } } +pub fn mal_assert(args: &[MalType]) -> MalRet { + args.iter().for_each(|i| match i { + M::Nil | M::Bool(false) => panic!(), + _ => () + }); + Ok(M::Nil) +} + #[derive(PartialEq, Clone, Copy)] pub enum Severity { Recoverable, diff --git a/tests/fibonacci.mal b/tests/fibonacci.mal new file mode 100644 index 0000000..a585fc9 --- /dev/null +++ b/tests/fibonacci.mal @@ -0,0 +1,21 @@ +(def! n-fib (fn* (n) + (if (<= n 0) 0 ; 0 base case + (if (= n 1) 1 ; 1 base case + (+ (n-fib (- n 1)) (n-fib (- n 2))))))) ; recursive + +(def! assert-fib (fn* (n expected) ; check fibonacci result + (if (= (n-fib n) expected) nil + (do (prn (list + "Expected" + expected + "got" + (n-fib n))) + (assert false))))) ; cause test panic + +(assert-fib 0 0) +(assert-fib 1 1) +(assert-fib 2 1) +(assert-fib 3 2) +(assert-fib 4 3) +(assert-fib 5 5) +(assert-fib 6 8) \ No newline at end of file