Other tests

Lotta tests in
- eval
- functional tests

Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2023-12-06 11:35:55 +09:00
parent 83e45334a5
commit 816a09504a
7 changed files with 200 additions and 56 deletions

View File

@ -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"),

View File

@ -40,7 +40,9 @@ pub fn env_binds(outer: Env, binds: &MalType, exprs: &[MalType]) -> Result<Env,
let env = env_new(Some(outer));
let binds = binds.if_list()?;
if binds.len() != exprs.len() {
return Err(MalErr::unrecoverable("Wrong number of arguments"));
return Err(MalErr::unrecoverable(
format!("Expected {} args, got {}", binds.len(), exprs.len()).as_str(),
));
} // TODO: May be possible to leave this be and not set additional elements at all
for (bind, expr) in binds.iter().zip(exprs.iter()) {
let bind = bind.if_symbol()?;
@ -81,24 +83,26 @@ pub fn call_func(func: &MalType, args: &[MalType]) -> 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 {

View File

@ -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))
));
}
}
}

View File

@ -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");