From 1f47c9f57ecb79bc07dffe870f121e7f05dc5f49 Mon Sep 17 00:00:00 2001 From: teo3300 Date: Sat, 25 Nov 2023 22:29:06 +0900 Subject: [PATCH] Added some other tests - builtin eq test - load_file return resul Signed-off-by: teo3300 --- src/core.rs | 10 ++++---- src/eval.rs | 10 ++++---- src/main.rs | 8 ++++--- src/parse_tools.rs | 32 +++++++++++++++---------- src/reader.rs | 8 +++---- src/tests/mod.rs | 21 +++++++++++++++-- src/types.rs | 41 +++++++++++++++++++++----------- tests/equals.mal | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 143 insertions(+), 45 deletions(-) create mode 100644 tests/equals.mal diff --git a/src/core.rs b/src/core.rs index a1572f1..0b7769c 100644 --- a/src/core.rs +++ b/src/core.rs @@ -31,7 +31,7 @@ macro_rules! env_init { use crate::printer::prt; use crate::types::MalType::{Bool, Fun, Int, List, Nil, Str}; -use crate::types::{mal_assert, mal_comp}; +use crate::types::{mal_assert, mal_comp, MalArgs}; pub fn ns_init() -> Env { env_init!(None, @@ -45,12 +45,12 @@ pub fn ns_init() -> Env { "<" => Fun(|a| comparison_op(|a, b| a > b, a), "Returns true if the arguments are in strictly ascending order, 'nil' otherwise"), ">=" => Fun(|a| comparison_op(|a, b| a >= b, a), "Returns true if the arguments are in descending order, 'nil' otherwise"), "<=" => Fun(|a| comparison_op(|a, b| a <= b, a), "Returns true if the arguments are in ascending order, 'nil' otherwise"), - "prn" => Fun(|a| { println!("{} ", prt(car(a)?)); Ok(Nil) }, "Print readably all the arguments passed to it"), - "list" => Fun(|a| Ok(List(a.to_vec())), "Return the arguments as a list"), - "list?" => Fun(|a| Ok(Bool(matches!(car(a)?, List(_)))), "Return true if the first argument is a list, false otherwise"), + "prn" => Fun(|a| { println!("{} ", prt(car(a)?)); Ok(Nil) }, "Print readably all the arguments"), + "list" => Fun(|a| Ok(List(MalArgs::new(a.to_vec()))), "Return the arguments as a list"), + "list?" => Fun(|a| Ok(Bool(a.iter().all(|el| matches!(el, 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"), - "assert" => Fun(mal_assert, "Panic if one of the argument is false") + "assert" => Fun(mal_assert, "Panic if one of the arguments is false") ) } diff --git a/src/eval.rs b/src/eval.rs index e2be8fd..3df143c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -94,7 +94,7 @@ fn fn_star_form(list: &[MalType], env: Env) -> MalRet { Ok(M::MalFun { eval: eval_ast, params: Rc::new(binds.clone()), - ast: Rc::new(M::List(exprs.to_vec())), + ast: Rc::new(M::List(MalArgs::new(exprs.to_vec()))), env, }) } @@ -123,7 +123,7 @@ fn apply(list: &MalArgs, env: Env) -> MalRet { M::Sym(sym) if sym == "fn*" => fn_star_form(cdr, env), M::Sym(sym) if sym == "help" => help_form(cdr, env), // Filter out special forms - _ => eval_func(&eval_ast(&M::List(list.to_vec()), env)?), + _ => eval_func(&eval_ast(&M::List(MalArgs::new(list.to_vec())), env)?), } } @@ -140,11 +140,11 @@ pub fn eval(ast: &MalType, env: Env) -> MalRet { /// Separately evaluate all elements in a collection (list or vector) fn eval_collection(list: &MalArgs, env: Env) -> Result { - let mut ret = MalArgs::new(); - for el in list { + let mut ret = Vec::new(); + for el in list.as_ref() { ret.push(eval(el, env.clone())?); } - Ok(ret) + Ok(MalArgs::new(ret)) } /// Evaluate the values of a map diff --git a/src/main.rs b/src/main.rs index fffa1c9..fa8e3c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,11 @@ fn main() { let reply_env = ns_init(); // load all files passed as arguments - args().collect::>()[1..] - .iter() - .for_each(|f| load_file(f, &reply_env)); + args().collect::>()[1..].iter().for_each(|f| { + if let Err(e) = load_file(f, &reply_env) { + println!("{}", e.message()) + } + }); interactive(reply_env); } diff --git a/src/parse_tools.rs b/src/parse_tools.rs index b72c22d..359ad84 100644 --- a/src/parse_tools.rs +++ b/src/parse_tools.rs @@ -1,12 +1,12 @@ use crate::env::Env; use crate::reader::Reader; use crate::step4_if_fn_do::rep; -use crate::types::MalErr; +use crate::types::{MalErr, MalRet, MalType::Nil}; use regex::Regex; use std::fs::File; use std::io::{self, BufRead, BufReader}; -pub fn load_file(filename: &str, env: &Env) { +pub fn load_file(filename: &str, env: &Env) -> MalRet { let file_desc = File::open(filename); let file = match file_desc { Ok(file) => file, @@ -36,22 +36,30 @@ pub fn load_file(filename: &str, env: &Env) { Err(error) if error.is_recoverable() => Err(error), tmp => { parser.clear(); - tmp.map_err(|error| { - println!("; Error @ {}", error.message()); - error - }) + Ok(tmp.map_err(|error| { + MalErr::unrecoverable(format!("; Error @ {}", error.message()).as_str()) + })?) } } } - Err(err) => eprintln!("Error reading line: {}", err), + Err(err) => { + return Err(MalErr::unrecoverable( + format!("Error reading line: {}", err).as_str(), + )) + } } } if let Err(error) = last { - println!( - "; ERROR parsing: '{}'\n; {}\n; the environment is in an unknown state", - filename, - error.message() - ) + Err(MalErr::unrecoverable( + format!( + "; ERROR parsing: '{}'\n; {}\n; the environment is in an unknown state", + filename, + error.message() + ) + .as_str(), + )) + } else { + Ok(Nil) } } diff --git a/src/reader.rs b/src/reader.rs index 0cff1ec..2c3c7ac 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -69,7 +69,7 @@ impl Reader { fn read_list(&self, terminator: &str) -> MalRet { self.next()?; - let mut vector = MalArgs::new(); + let mut vector = Vec::new(); while self.peek()? != terminator { vector.push(self.read_form()?) @@ -77,9 +77,9 @@ impl Reader { self.next()?; match terminator { - ")" => Ok(List(vector)), - "]" => Ok(Vector(vector)), - "}" => make_map(vector), + ")" => Ok(List(MalArgs::new(vector))), + "]" => Ok(Vector(MalArgs::new(vector))), + "}" => make_map(MalArgs::new(vector)), t => Err(MalErr::unrecoverable( format!("Unknown collection terminator: {}", t).as_str(), )), diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0a24273..21ca527 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,12 +1,24 @@ #[cfg(test)] mod functional { + + macro_rules! load_file { + ($file:expr, $env:expr) => {{ + match load_file($file, $env) { + Ok(v) => v, + Err(_) => { + panic!() + } + } + }}; + } + 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); + load_file!("core.mal", &env); + load_file!(format!("tests/{}.mal", $file).as_str(), &env); }}; } @@ -14,4 +26,9 @@ mod functional { fn fibonacci() { test!("fibonacci"); } + + #[test] + fn builtin_equals() { + test!("equals"); + } } diff --git a/src/types.rs b/src/types.rs index 23ea300..e320c1e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -54,16 +54,27 @@ impl MalType { use crate::types::MalType as M; -fn mal_eq(a: &M, b: &M) -> MalRet { - Ok(M::Bool(match (a, b) { - (M::Nil, M::Nil) => true, - (M::Bool(a), M::Bool(b)) => a == b, - (M::Int(a), M::Int(b)) => a == b, - (M::Key(a), M::Key(b)) | (M::Str(a), M::Str(b)) => a == b, - (M::List(a), M::List(b)) | (M::Vector(a), M::Vector(b)) => a - .iter() - .zip(b.iter()) - .all(|(a, b)| matches!(mal_eq(a, b), Ok(M::Bool(true)))), +fn mal_eq(a: &MalType, b: &[MalType]) -> MalRet { + Ok(M::Bool(match a { + M::Nil => b.iter().all(|el| matches!(el, M::Nil)), + M::Bool(a) => b.iter().all(|el| matches!(el, M::Bool(b) if a == b)), + M::Int(a) => b.iter().all(|el| matches!(el, M::Int(b) if a == b)), + M::Key(a) => b.iter().all(|el| matches!(el, M::Key(b) if a == b)), + M::Str(a) => b.iter().all(|el| matches!(el, M::Str(b) if a == b)), + M::List(a) => b.iter().all(|el| { + matches!(el, M::List(b) + if a.len() == b.len() + && a.iter().zip(b.iter()).all( + |(a, b)| matches!(mal_eq(a, &[b.clone()]), + Ok(M::Bool(true))))) + }), + M::Vector(a) => b.iter().all(|el| { + matches!(el, M::Vector(b) + if a.len() == b.len() + && a.iter().zip(b.iter()).all( + |(a, b)| matches!(mal_eq(a, &[b.clone()]), + Ok(M::Bool(true))))) + }), _ => { return Err(MalErr::unrecoverable( "Comparison not implemented for 'Map', 'Fun', 'MalFun' and 'Sym'", @@ -73,10 +84,12 @@ fn mal_eq(a: &M, b: &M) -> MalRet { } pub fn mal_comp(args: &[MalType]) -> MalRet { - let (car, cdr) = car_cdr(args)?; - match cdr.len() { + match args.len() { 0 => Ok(M::Bool(true)), - _ => mal_eq(car, &cdr[0]), + _ => { + let (car, cdr) = car_cdr(args)?; + mal_eq(car, cdr) + } } } @@ -125,7 +138,7 @@ impl MalErr { } } -pub type MalArgs = Vec; +pub type MalArgs = Rc>; pub type MalMap = HashMap; pub type MalRet = Result; diff --git a/tests/equals.mal b/tests/equals.mal new file mode 100644 index 0000000..78d0a20 --- /dev/null +++ b/tests/equals.mal @@ -0,0 +1,58 @@ +; This is used for assert-eq in other tests + +; For each type do the following tests: +; single element +; equal elements +; 1 different +; compare with foreign type + +; empty +(assert (=)) ; nothing to compare with + +; nil +(assert (= nil)) +(assert (= nil nil nil)) +(assert (not (= nil true))) +(assert (not (= nil 1))) + +; bool +(assert (= true)) +(assert (= true true true)) +(assert (not (= true false true))) +(assert (not (= true 1))) + +; int +(assert (= 1)) +(assert (= 1 1 1)) +(assert (not (= 1 2 1))) +(assert (not (= 1 nil))) + +; key +(assert (= :a)) +(assert (= :a :a :a)) +(assert (not (= :a :b :a))) +(assert (not (= :a "a"))) + +; string +(assert (= "a")) +(assert (= "a" "a" "a")) +(assert (not (= "a" "b" "a"))) +(assert (not (= "a" :a))) + +; add comparison for same elements with different length + +; list +(assert (= (list 1 1 1))) +(assert (= (list 1 1 1) (list 1 1 1) (list 1 1 1))) +(assert (not (= (list 1 1 1) (list 1 2 1) (list 1 1 1)))) +(assert (not (= (list 1) (list 1 1)))) +(assert (not (= (list 1 1) (list 1)))) +(assert (not (= () (list 1)))) +(assert (not (= (list 1) ()))) +(assert (not (= (list 1 1 1) [1 1 1]))) + +; vector +(assert (= [1 1 1])) +(assert (= [1 1 1] [1 1 1] [1 1 1])) +(assert (not (= [1 1 1] [1 2 1] [1 1 1]))) +(assert (not (= [1 1 1] (list 1 1 1)))) \ No newline at end of file