Added some other tests

- builtin eq test
- load_file return resul

Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2023-11-25 22:29:06 +09:00
parent 1ea3ecbb92
commit 1f47c9f57e
8 changed files with 143 additions and 45 deletions

View File

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

View File

@ -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<MalArgs, MalErr> {
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

View File

@ -19,9 +19,11 @@ fn main() {
let reply_env = ns_init();
// load all files passed as arguments
args().collect::<Vec<String>>()[1..]
.iter()
.for_each(|f| load_file(f, &reply_env));
args().collect::<Vec<String>>()[1..].iter().for_each(|f| {
if let Err(e) = load_file(f, &reply_env) {
println!("{}", e.message())
}
});
interactive(reply_env);
}

View File

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

View File

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

View File

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

View File

@ -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<MalType>;
pub type MalArgs = Rc<Vec<MalType>>;
pub type MalMap = HashMap<String, MalType>;
pub type MalRet = Result<MalType, MalErr>;

58
tests/equals.mal Normal file
View File

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