Adding test for reader.rs

- will implement other tests later

Signed-off-by: teo3300 <matteo.rogora@live.it>
This commit is contained in:
teo3300
2023-11-22 18:26:28 +09:00
parent 78dee9c848
commit 2d5791a766
4 changed files with 182 additions and 38 deletions

View File

@ -3,31 +3,31 @@ use crate::env::{arithmetic_op, car, comparison_op, env_new, env_set, mal_exit,
// This is the first time I implement a macro, and I'm copying it
// so I will comment this a LOT
macro_rules! env_init {
($outer:expr) => {
// match any istance with no args
{
// the macro prevent the macro from disrupting the external code
// this is the block of code that will substitute the macro
env_new($outer)
// returns an empty map
}
};
($outer:expr, $($key:expr => $val:expr),*) => {
// Only if previous statements did not match,
// note that the expression with fat arrow is arbitrary,
// could have been slim arrow, comma or any other
// recognizable structure
{
// create an empty map
let map = env_init!($outer);
$( // Do this for all elements of the arguments list
env_set(&map, $key, &$val);
)*
// return the new map
map
}
};
}
($outer:expr) => {
// match any istance with no args
{
// the macro prevent the macro from disrupting the external code
// this is the block of code that will substitute the macro
env_new($outer)
// returns an empty map
}
};
($outer:expr, $($key:expr => $val:expr),*) => {
// Only if previous statements did not match,
// note that the expression with fat arrow is arbitrary,
// could have been slim arrow, comma or any other
// recognizable structure
{
// create an empty map
let map = env_init!($outer);
$( // Do this for all elements of the arguments list
env_set(&map, $key, &$val);
)*
// return the new map
map
}
};
}
use crate::printer::prt;
use crate::types::mal_comp;

View File

@ -14,10 +14,10 @@ use core::ns_init;
use parse_tools::{interactive, load_file};
fn main() {
// Initialize ns environment
let reply_env = ns_init();
// setup env
//let args: Vec<String> = args().collect();
// load all files passed as arguments
args().collect::<Vec<String>>()[1..]
.iter()
.for_each(|f| load_file(f, &reply_env));

View File

@ -14,7 +14,7 @@ pub struct Reader {
type Tokens = Vec<String>;
// TODO: instead of panic on missing ")" try implementing a multi line parsing
// DONE: instead of panic on missing ")" try implementing a multi line parsing
// Status on return should always be The last element of the last opened lists
// (append to the "last" list) while traversing
impl Reader {
@ -28,7 +28,7 @@ impl Reader {
pub fn push(&self, input: &str) {
self.ptr.set(0);
// reset the state of the parser and push the additional strings
self.tokens.borrow_mut().append(&mut tokenize(input))
self.tokens.borrow_mut().append(&mut tokenize(input));
}
pub fn clear(&self) {
@ -56,6 +56,11 @@ impl Reader {
self.get_token(self.ptr.get() - 1)
}
/// Returns true if the reader has been consumed entirely
pub fn ended(&self) -> bool {
self.tokens.borrow().len() == self.ptr.get()
}
/// Repeatedly calls `read_form` of the reader object until it finds a ")" token
/// EOF -> Return an error (Dyck language error)
/// Accumulates results into a MalList
@ -117,10 +122,6 @@ impl Reader {
_ => self.read_atom(),
}
}
pub fn ended(&self) -> bool {
self.tokens.borrow().len() == self.ptr.get()
}
}
/// Call `tokenize` on a string
@ -142,3 +143,148 @@ fn tokenize(input: &str) -> Tokens {
.collect::<Vec<String>>();
tokens
}
////////////////////////////////////////////////////////////////////////////////
// Tests //
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use crate::types::{MalMap, MalType as M, Severity};
use super::Reader;
fn reader_setup1() -> Reader {
let r = Reader::new();
r.push("()[]{} \"str\" :key sym 1 ; comment");
return r;
}
#[test]
fn init() {
let r = Reader::new();
assert_eq!(r.tokens.borrow().len(), 0);
assert_eq!(r.ptr.get(), 0);
}
#[test]
fn push() {
let r = reader_setup1();
let mut tokens = Vec::new();
for el in r.tokens.borrow().iter() {
tokens.push(el.clone());
}
assert_eq!(
tokens,
vec!["(", ")", "[", "]", "{", "}", "\"str\"", ":key", "sym", "1"]
);
}
#[test]
fn get_token() {
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));
}
#[test]
fn get() {
let r = reader_setup1();
assert!(matches!(r.peek(), Ok(i) if i == "("));
assert_eq!(r.ptr.get(), 0);
assert!(matches!(r.next(), Ok(i) if i == "("));
assert_eq!(r.ptr.get(), 1);
assert!(matches!(r.next(), Ok(i) if i == ")"));
assert_eq!(r.ptr.get(), 2);
}
#[test]
fn clear() {
let r = reader_setup1();
r.clear();
assert_eq!(r.tokens.borrow().len(), 0);
assert_eq!(r.ptr.get(), 0);
}
#[test]
fn ended() {
let r = reader_setup1();
assert!(!r.ended());
for _ in r.tokens.borrow().iter() {
assert!(matches!(r.next(), Ok(_)))
}
assert!(r.ended());
}
#[test]
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));
}
#[test]
fn read_atom() {
let r = Reader::new();
r.push("nil 1 true a \"s\" :a ) ] }");
assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Nil)));
assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Int(1))));
assert!(matches!(r.read_atom(), Ok(x) if matches!(x, M::Bool(true))));
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));
}
#[test]
fn read_form() {
let r = Reader::new();
// Test list
let expected = vec![1, 2, 12];
r.push("(1 2 12)");
assert!(matches!(
r.read_form(), Ok(x)
if matches!(x.clone(), M::List(list)
if list.len() == expected.len()
&& list.iter().zip(expected)
.all(|(x, y)| matches!(x, M::Int(v) if (*v as isize) == y)))));
r.clear();
// Test vector
let exp = vec![1, 2, 12];
r.push("[1 2 12]");
assert!(matches!(
r.read_form(), Ok(x)
if matches!(x.clone(), M::Vector(list)
if list.len() == exp.len()
&& list.iter().zip(exp)
.all(|(x, y)| matches!(x, M::Int(v) if (*v as isize) == y)))));
r.clear();
// Test map
r.push("{\"i\" 1 \"s\" \"str\" \"t\" true \"n\" nil :s :sym}");
let t = match r.read_form() {
Ok(x) => match x {
M::Map(x) => x,
_ => {
assert!(false);
MalMap::new()
}
},
_ => {
assert!(false);
MalMap::new()
}
};
assert!(matches!(t.get("n"), Some(x) if matches!(x.clone(), M::Nil)));
assert!(matches!(t.get("t"), Some(x) if matches!(x.clone(), M::Bool(v) if v)));
assert!(matches!(t.get("i"), Some(x) if matches!(x.clone(), M::Int(v) if v == 1)));
assert!(matches!(t.get("s"), Some(x) if matches!(x.clone(), M::Str(v) if v == "str")));
assert!(matches!(t.get("ʞ:s"), Some(x) if matches!(x.clone(), M::Key(v) if v == "ʞ:sym")));
}
}

View File

@ -59,10 +59,8 @@ fn mal_eq(a: &M, b: &M) -> MalRet {
(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
(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)))),
@ -78,7 +76,7 @@ pub fn mal_comp(args: &[MalType]) -> MalRet {
let (car, cdr) = car_cdr(args)?;
match cdr.len() {
0 => Ok(M::Bool(true)),
_ => mal_eq(car, &cdr[0])
_ => mal_eq(car, &cdr[0]),
}
}