diff --git a/core.mal b/core.mal deleted file mode 100644 index 49bd896..0000000 --- a/core.mal +++ /dev/null @@ -1,26 +0,0 @@ -; "not" logic function -(def! not (fn* (a) - (if a false true))) - -; binary "or" logic function -(def! or (fn* (a b) - (if a true - (if b - true - false)))) - -; binary "and" logic function -(def! and (fn* (a b) - (if a - (if b - true - false) - false))) - -; binary "xor" logic function -(def! xor (fn* (a b) - ; use this strange definition to make it independent from - ; "or" and "and" definition - (if a - (if b false true) - (if b true false)))) \ No newline at end of file diff --git a/src/core.rs b/src/core.rs index 4e1b281..830c31b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -42,10 +42,10 @@ pub fn ns_init() -> Env { "*" => Fun(|a| arithmetic_op(1, |a, b| a * b, a), "Returns the product of the arguments"), "/" => Fun(|a| arithmetic_op(1, |a, b| a / b, a), "Returns the division of the arguments"), ">" => Fun(|a| comparison_op(|a, b| a > b, a), "Returns true if the arguments are in strictly descending order, 'nil' otherwise"), - "<" => 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 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"), + "prn" => Fun(|a| {a.iter().for_each(|a| print!("{} ", prt(a))); println!(); 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"), diff --git a/src/env.rs b/src/env.rs index 6385e71..74cd96c 100644 --- a/src/env.rs +++ b/src/env.rs @@ -40,7 +40,9 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result MalRet { } pub fn arithmetic_op(set: isize, f: fn(isize, isize) -> isize, args: &[MalType]) -> MalRet { - if args.is_empty() { - return Ok(M::Int(set)); - } - - let mut left = args[0].if_number()?; - if args.len() > 1 { - let right = &args[1..]; - for el in right { - left = f(left, el.if_number()?); + Ok(M::Int(match args.len() { + 0 => set, + 1 => f(set, args[0].if_number()?), + _ => { + // TODO: Maybe an accumulator + let mut left = args[0].if_number()?; + for el in &args[1..] { + left = f(left, el.if_number()?); + } + left } - } - - Ok(M::Int(left)) + })) } use MalType::{Bool, Nil}; pub fn comparison_op(f: fn(isize, isize) -> bool, args: &[MalType]) -> MalRet { + if args.is_empty() { + return Ok(Nil); + } let (left, rights) = car_cdr(args)?; let mut left = left.if_number()?; for right in rights { diff --git a/src/eval.rs b/src/eval.rs index 3df143c..66222a6 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -193,16 +193,47 @@ mod tests { }}; } + macro_rules! load_f { + ($input:expr, $env:expr) => {{ + use crate::reader::{read_str, Reader}; + use std::rc::Rc; + + let r = Reader::new(); + r.push($input); + let args = match read_str(&r) { + Ok(v) => match v { + MalType::List(v) => v, + _ => panic!("Bad command"), + }, + _ => panic!("Bad command"), + }; + &MalType::List(Rc::new(if args.is_empty() { + Vec::new() + } else { + let f_str = match &args[0] { + MalType::Sym(s) => s.as_str(), + _ => panic!("Can't solve function"), + }; + let f = match env_get(&$env.clone(), f_str) { + Ok(v) => v, + _ => panic!("No such function in env"), + }; + [&[f], &args[1..]].concat() + })) + }}; + } + fn _env_empty() -> Env { use crate::env::env_new; env_new(None) } mod forms { - use crate::env::env_get; use crate::eval::tests::_env_empty; - use crate::eval::{def_bang_form, do_form, let_star_form}; + use crate::eval::{ + def_bang_form, do_form, eval_ast, eval_func, fn_star_form, if_form, let_star_form, + }; use crate::types::MalType; #[test] @@ -271,7 +302,7 @@ mod tests { } #[test] - fn _do() { + fn _do_form() { let env = _env_empty(); assert!(matches!( do_form(load!("(do)"), env.clone()), @@ -287,5 +318,83 @@ mod tests { )); assert!(matches!(env_get(&env.clone(), "a"), Ok(MalType::Int(1)))); } + + #[test] + fn _if_form() { + let env = _env_empty(); + assert!(matches!( + if_form(load!("(if)"), env.clone()), + Err(e) if !e.is_recoverable())); + assert!(matches!( + if_form(load!("(if 1)"), env.clone()), + Err(e) if !e.is_recoverable())); + assert!(matches!( + if_form(load!("(if 1 2 3 4)"), env.clone()), + Err(e) if !e.is_recoverable())); + assert!(matches!( + if_form(load!("(if nil 1)"), env.clone()), + Ok(MalType::Nil) + )); + assert!(matches!( + if_form(load!("(if nil 1 2)"), env.clone()), + Ok(MalType::Int(2)) + )); + assert!(matches!( + if_form(load!("(if true 1)"), env.clone()), + Ok(MalType::Int(1)) + )); + assert!(matches!( + if_form(load!("(if true 1 2)"), env.clone()), + Ok(MalType::Int(1)) + )); + } + + #[test] + fn fn_star() { + let env = _env_empty(); + assert!(matches!( + fn_star_form(load!("(fn* (a b) 1 2)"), env.clone()), + Ok(MalType::MalFun { eval, params, ast, .. }) + if eval == eval_ast + && matches!((*params).clone(), MalType::List(v) + if matches!(&v[0], MalType::Sym(v) if v == "a") + && matches!(&v[1], MalType::Sym(v) if v == "b") + && matches!((*ast).clone(), MalType::List(v) + if matches!(&v[0], MalType::Int(1)) + && matches!(&v[1], MalType::Int(2)))))); + // We trust the fact that the env does not do silly stuff + assert!(matches!( + fn_star_form(load!("(fn*)"), env.clone()), + Err(e) if !e.is_recoverable())); + assert!(matches!( + fn_star_form(load!("(fn* 1)"), env.clone()), + Err(e) if !e.is_recoverable())); + } + + #[test] + fn _eval_func() { + let env = _env_empty(); + assert!(matches!( + def_bang_form(load!("(def! or (fn* (a b) (if a a b)))"), env.clone()), + Ok(_) + )); + assert!(matches!( + eval_func(&MalType::Int(1)), + Err(e) if !e.is_recoverable())); + assert!(matches!( + eval_func(load_f!("()", env.clone())), + Err(e) if !e.is_recoverable())); + assert!(matches!( + eval_func(load_f!("(or nil nil)", env.clone())), + Ok(v) if matches!(v, MalType::Nil))); + assert!(matches!( + eval_func(load_f!("(or 1 nil)", env.clone())), + Ok(MalType::Int(1)) + )); + assert!(matches!( + eval_func(load_f!("(or nil 1)", env.clone())), + Ok(MalType::Int(1)) + )); + } } } diff --git a/src/mal_tests/mod.rs b/src/mal_tests/mod.rs index a896dc6..75bc4bb 100644 --- a/src/mal_tests/mod.rs +++ b/src/mal_tests/mod.rs @@ -1,22 +1,14 @@ #[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; - load_file!(format!("tests/{}.mal", $file).as_str(), &ns_init()); + assert!(matches!( + load_file(format!("tests/{}.mal", $file).as_str(), &ns_init()), + Ok(_) + )); }}; } @@ -40,6 +32,11 @@ mod functional { test!("equals"); } + #[test] + fn arithmetic() { + test!("arithmetic") + } + #[test] fn fibonacci() { test!("fibonacci"); diff --git a/tests/arithmetic.mal b/tests/arithmetic.mal new file mode 100644 index 0000000..5a38510 --- /dev/null +++ b/tests/arithmetic.mal @@ -0,0 +1,51 @@ +; + +(assert (= (+) 0)) +(assert (= (+ 1) 1)) +(assert (= (+ 1 1) 2)) +(assert (= (+ 1 2 3 4) 10)) + +; - +(assert (= (-) 0)) +(assert (= (- 1) -1)) +(assert (= (- 1 2) -1)) +(assert (= (- 10 1 2 3) 4)) + +; * +(assert (= (*) 1)) +(assert (= (* 2) 2)) +(assert (= (* 2 3) 6)) +(assert (= (* -2 3) -6)) +(assert (= (* -2 -3) 6)) +(assert (= (* 10 1 2 3) 60)) + +; / +(assert (= (/) 1)) +(assert (= (/ 1) 1)) +(assert (= (/ 2) 0)) +(assert (= (/ 3 2) 1)) +(assert (= (/ 128 2 4) 16)) + +; > +(assert (not (>))) +(assert (> 1)) +(assert (> 3 2 1)) +(assert (not (> 3 1 2))) +(assert (not (> 1 1))) +; < +(assert (not (<))) +(assert (< 1)) +(assert (< 1 2 3)) +(assert (not (< 1 3 2))) +(assert (not (< 1 1))) +; >= +(assert (not (>=))) +(assert (>= 1)) +(assert (>= 3 2 1)) +(assert (not (>= 3 1 2))) +(assert (>= 1 1)) +; <= +(assert (not (<=))) +(assert (<= 1)) +(assert (<= 1 2 3)) +(assert (not (<= 1 3 2))) +(assert (<= 1 1)) \ No newline at end of file diff --git a/tests/lists.mal b/tests/lists.mal new file mode 100644 index 0000000..39cf24e --- /dev/null +++ b/tests/lists.mal @@ -0,0 +1,9 @@ +; list? +(assert (list? (list 1 2 3))) +(assert (not (list? 1))) + +; empty? +(assert (empty? ())) +(assert (empty? (list))) +(assert (not (empty? ()))) +(assert (not (empty? (list (1 2 3))))) \ No newline at end of file