From a39914bc35a8d4b77542ac418711d4e8ad057b887d0bf019ed47204f571e807e Mon Sep 17 00:00:00 2001 From: Apache Date: Fri, 21 Jun 2024 16:18:47 -0500 Subject: [PATCH] Add simple tests --- src/executor.rs | 219 +++++++++++++++++++++++++++++++---------------- src/main.rs | 14 ++- src/parser.rs | 91 +++++++++++++++++++- src/test.lisp | 18 ++-- src/tokenizer.rs | 90 ++++++++++++++++++- 5 files changed, 336 insertions(+), 96 deletions(-) diff --git a/src/executor.rs b/src/executor.rs index afe618d..c4a2c95 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,16 +1,88 @@ use std::{collections::HashMap, fmt}; +#[cfg(test)] +mod tests { + use crate::{parse, LispState, LispValue, Tokenizer, TokenizerError}; + + use super::add_function; + + fn get_exec_result(source: &str) -> Result, TokenizerError> { + let mut state = LispState::new(); + + state.table.insert(String::from("output"), LispValue::List(vec![], false)); + add_function(&mut state.table, "print", |st, args| { + if let LispValue::List(v, _) = st.table.get_mut("output").unwrap() { + v.push(LispValue::List(args, false)) + } + + vec![LispValue::Nil] + }); + + let mut tokenizer = Tokenizer::new(String::from(source)); + let instructions = parse(tokenizer.tokenize()?); + + state.execute(instructions); + + let mut out = Vec::new(); + if let LispValue::List(outerlist, _) = &state.table["output"] { + for list in outerlist { + if let LispValue::List(innerlist, _) = list { + for innervalue in innerlist { + out.push(innervalue.to_owned()); + } + } + } + } + + Ok(out) + } + + #[test] + fn test_hello_world() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + LispValue::String(String::from("Hello, World!")) + ], + get_exec_result(" + (print \"Hello, World!\") + ")? + ); + Ok(()) + } + + #[test] + fn test_math() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + LispValue::Float(16.05) + ], + get_exec_result(" + (print + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) + ) + ")? + ); + Ok(()) + } +} + use crate::PreLispValue; pub struct LispState { table: HashMap, } -fn add_function + Clone>(table: &mut HashMap, name: T, func: fn(Vec) -> Vec) { +fn add_function + Clone>(table: &mut HashMap, name: T, func: fn(&mut LispState, Vec) -> Vec) { table.insert(name.clone().into(), LispValue::RustFunction(name.into(), func)); } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum LispValue { Nil, String(String), @@ -18,11 +90,12 @@ pub enum LispValue { Float(f32), List(Vec, bool), LispFunction(String, Vec), - RustFunction(String, fn(Vec) -> Vec), + RustFunction(String, fn(&mut LispState, Vec) -> Vec), } impl fmt::Display for LispValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(unreachable_patterns)] match self { LispValue::Nil => write!(f, "nil"), LispValue::String(str) => write!(f, "\"{}\"", str), @@ -38,9 +111,9 @@ impl fmt::Display for LispValue { } if *quoted { - write!(f, "'({})", strs.join(", ")) + write!(f, "'({})", strs.join(" ")) } else { - write!(f, "({})", strs.join(", ")) + write!(f, "({})", strs.join(" ")) } }, val => { @@ -75,9 +148,9 @@ impl LispState { pub fn new() -> LispState { let mut table = HashMap::new(); - add_function(&mut table, "print", |x| { + add_function(&mut table, "print", |_, args| { let mut strings = Vec::new(); - for val in x { + for val in args { strings.push(val.to_string()); } @@ -87,9 +160,9 @@ impl LispState { vec![LispValue::Nil] }); - add_function(&mut table, "concat", |x| { + add_function(&mut table, "concat", |_, args| { let mut strings = Vec::new(); - for val in x { + for val in args { strings.push(val.to_string()); } @@ -97,15 +170,15 @@ impl LispState { vec![LispValue::String(str)] }); - add_function(&mut table, "add", |x| { - if x.len() < 1 { + add_function(&mut table, "add", |_, args| { + if args.len() < 1 { return vec![LispValue::Nil] - } else if x.len() == 1 { - return vec![x[0].clone()]; + } else if args.len() == 1 { + return vec![args[0].clone()]; } let mut t = "int"; - for v in &x { + for v in &args { match v { LispValue::Float(_) => t = "float", LispValue::Integer(_) => (), @@ -116,7 +189,7 @@ impl LispState { match t { "int" => { let mut sum = 0; - for v in &x { + for v in &args { if let LispValue::Integer(n) = v { sum += n; } else { @@ -127,7 +200,7 @@ impl LispState { }, "float" => { let mut sum = 0.0 as f32; - for v in &x { + for v in &args { if let LispValue::Float(n) = v { sum += n; } else if let LispValue::Integer(n) = v{ @@ -142,15 +215,15 @@ impl LispState { } }); - add_function(&mut table, "sub", |x| { - if x.len() < 1 { + add_function(&mut table, "sub", |_, args| { + if args.len() < 1 { return vec![LispValue::Nil] - } else if x.len() == 1 { - return vec![x[0].clone()]; + } else if args.len() == 1 { + return vec![args[0].clone()]; } let mut t = "int"; - for v in &x { + for v in &args { match v { LispValue::Float(_) => t = "float", LispValue::Integer(_) => (), @@ -160,10 +233,10 @@ impl LispState { match t { "int" => { - if let LispValue::Integer(n) = x[0] { + if let LispValue::Integer(n) = args[0] { let mut sum = n; - for i in 1..x.len() { - if let LispValue::Integer(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Integer(n) = &args[i] { sum -= n; } else { unreachable!() @@ -175,24 +248,24 @@ impl LispState { } }, "float" => { - if let LispValue::Float(n) = x[0] { + if let LispValue::Float(n) = args[0] { let mut sum = n; - for i in 1..x.len() { - if let LispValue::Float(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { sum -= *n; - } else if let LispValue::Integer(n) = &x[i] { + } else if let LispValue::Integer(n) = &args[i] { sum -= *n as f32; } else { unreachable!() } } return vec![LispValue::Float(sum)]; - } else if let LispValue::Integer(n) = x[0] { + } else if let LispValue::Integer(n) = args[0] { let mut sum = n as f32; - for i in 1..x.len() { - if let LispValue::Float(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { sum -= *n; - } else if let LispValue::Integer(n) = &x[i] { + } else if let LispValue::Integer(n) = &args[i] { sum -= *n as f32; } else { unreachable!() @@ -207,15 +280,15 @@ impl LispState { } }); - add_function(&mut table, "mul", |x| { - if x.len() < 1 { + add_function(&mut table, "mul", |_, args| { + if args.len() < 1 { return vec![LispValue::Nil] - } else if x.len() == 1 { - return vec![x[0].clone()]; + } else if args.len() == 1 { + return vec![args[0].clone()]; } let mut t = "int"; - for v in &x { + for v in &args { match v { LispValue::Float(_) => t = "float", LispValue::Integer(_) => (), @@ -225,10 +298,10 @@ impl LispState { match t { "int" => { - if let LispValue::Integer(n) = x[0] { + if let LispValue::Integer(n) = args[0] { let mut sum = n; - for i in 1..x.len() { - if let LispValue::Integer(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Integer(n) = &args[i] { sum *= n; } else { unreachable!() @@ -240,24 +313,24 @@ impl LispState { } }, "float" => { - if let LispValue::Float(n) = x[0] { + if let LispValue::Float(n) = args[0] { let mut product = n; - for i in 1..x.len() { - if let LispValue::Float(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { product *= *n; - } else if let LispValue::Integer(n) = &x[i] { + } else if let LispValue::Integer(n) = &args[i] { product *= *n as f32; } else { unreachable!() } } return vec![LispValue::Float(product)]; - } else if let LispValue::Integer(n) = x[0] { + } else if let LispValue::Integer(n) = args[0] { let mut product = n as f32; - for i in 1..x.len() { - if let LispValue::Float(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { product *= *n; - } else if let LispValue::Integer(n) = &x[i] { + } else if let LispValue::Integer(n) = &args[i] { product *= *n as f32; } else { unreachable!() @@ -272,15 +345,15 @@ impl LispState { } }); - add_function(&mut table, "div", |x| { - if x.len() < 1 { + add_function(&mut table, "div", |_, args| { + if args.len() < 1 { return vec![LispValue::Nil] - } else if x.len() == 1 { - return vec![x[0].clone()]; + } else if args.len() == 1 { + return vec![args[0].clone()]; } let mut t = "int"; - for v in &x { + for v in &args { match v { LispValue::Float(_) => t = "float", LispValue::Integer(_) => (), @@ -290,10 +363,10 @@ impl LispState { match t { "int" => { - if let LispValue::Integer(n) = x[0] { + if let LispValue::Integer(n) = args[0] { let mut sum = n; - for i in 1..x.len() { - if let LispValue::Integer(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Integer(n) = &args[i] { sum /= n; } else { unreachable!() @@ -305,24 +378,24 @@ impl LispState { } }, "float" => { - if let LispValue::Float(n) = x[0] { + if let LispValue::Float(n) = args[0] { let mut product = n; - for i in 1..x.len() { - if let LispValue::Float(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { product /= *n; - } else if let LispValue::Integer(n) = &x[i] { + } else if let LispValue::Integer(n) = &args[i] { product /= *n as f32; } else { unreachable!() } } return vec![LispValue::Float(product)]; - } else if let LispValue::Integer(n) = x[0] { + } else if let LispValue::Integer(n) = args[0] { let mut product = n as f32; - for i in 1..x.len() { - if let LispValue::Float(n) = &x[i] { + for i in 1..args.len() { + if let LispValue::Float(n) = &args[i] { product /= *n; - } else if let LispValue::Integer(n) = &x[i] { + } else if let LispValue::Integer(n) = &args[i] { product /= *n as f32; } else { unreachable!() @@ -366,7 +439,7 @@ impl LispState { out } - fn eval_list(list: &Vec, quoted: bool) -> (Vec, bool) { + fn eval_list(&mut self, list: &Vec, quoted: bool) -> (Vec, bool) { if list.len() < 1 { return (vec![LispValue::List(Vec::new(), quoted)], false); } @@ -375,7 +448,7 @@ impl LispState { for i in 0..list.len() { if let LispValue::List(elements, quoted) = &list[i] { - let (elems, astuple) = Self::eval_list(elements, *quoted); + let (elems, astuple) = self.eval_list(elements, *quoted); if astuple { list[i] = elems.get(0).unwrap_or(&LispValue::Nil).to_owned(); for j in 1..elems.len() { @@ -392,23 +465,23 @@ impl LispState { } if let LispValue::RustFunction(_name, f) = &list[0] { - (f(list[1..].to_vec()), true) - } else if let LispValue::LispFunction(_name, ins) = &list[0] { - (Self::call_lisp_func(&list[0], list[1..].to_vec()), true) + (f(self, list[1..].to_vec()), true) + } else if let LispValue::LispFunction(_name, _) = &list[0] { + (self.call_lisp_func(&list[0], list[1..].to_vec()), true) } else { (list.to_vec(), false) } } - fn call_lisp_func(f: &LispValue, args: Vec) -> Vec { - todo!() + fn call_lisp_func(&mut self, f: &LispValue, args: Vec) -> Vec { + todo!("Calling lisp function {} with args {:?}", f, args) } - pub fn execute(&self, instructions: Vec) { + pub fn execute(&mut self, instructions: Vec) { let instructions = Self::handle_prevalues(self, instructions); for val in instructions { if let LispValue::List(elements, quoted) = val { - Self::eval_list(&elements, quoted); + self.eval_list(&elements, quoted); } } } diff --git a/src/main.rs b/src/main.rs index 2493f98..1430ecc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,20 +7,16 @@ use parser::*; mod executor; use executor::*; -fn main() { +fn main() -> Result<(), TokenizerError> { let source = std::fs::read_to_string("src/test.lisp").unwrap(); let mut tokenizer = Tokenizer::new(source); - let tokens = match tokenizer.tokenize() { - Ok(tokens) => tokens, - Err(e) => { - println!("{}", e); - Vec::new() - } - }; + let tokens = tokenizer.tokenize()?; let instructions = parse(tokens); - let state = LispState::new(); + let mut state = LispState::new(); state.execute(instructions); + + Ok(()) } diff --git a/src/parser.rs b/src/parser.rs index b443505..6feea1a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,91 @@ use std::fmt; +#[cfg(test)] +mod tests { + use crate::{parse, PreLispValue, Tokenizer, TokenizerError}; + + fn quick_parse(source: &str) -> Result, TokenizerError> { + let mut tokenizer = Tokenizer::new(String::from(source)); + let tokens = tokenizer.tokenize()?; + Ok(parse(tokens)) + } + + fn var(name: &str) -> PreLispValue { + PreLispValue::Var(String::from(name)) + } + fn string(s: &str) -> PreLispValue { + PreLispValue::String(String::from(s)) + } + fn list(v: Vec, quoted: bool) -> PreLispValue { + PreLispValue::List(v, quoted) + } + fn int(n: i32) -> PreLispValue { + PreLispValue::Integer(n) + } + fn float(n: f32) -> PreLispValue { + PreLispValue::Float(n) + } + + #[test] + fn test_hello_world() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + list(vec![ + var("print"), + string("Hello, World!") + ], false) + ], + quick_parse(" + (print \"Hello, World!\") + ")? + ); + Ok(()) + } + + #[test] + fn test_math() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + list(vec![ + var("print"), + list(vec![ + var("add"), + list(vec![ + var("div"), + float(4.5), + list(vec![ + var("sub"), + float(0.5), + float(0.2) + ], false) + ], false), + list(vec![ + var("mul"), + int(21), + float(0.05) + ], false) + ], false) + ], false) + ], + quick_parse(" + (print + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) + ) + ")? + ); + Ok(()) + } +} + use crate::Token; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum PreLispValue { Nil, String(String), @@ -110,7 +193,11 @@ fn parse_exp(tokens: Vec, quoted: bool) -> PreLispValue { unquote_next_list = false; }, Token::Identifier(name) => { - values.push(PreLispValue::Var(name.to_owned())); + if name == &String::from("nil") { + values.push(PreLispValue::Nil) + } else { + values.push(PreLispValue::Var(name.to_owned())); + } }, Token::Integer(num) => { values.push(PreLispValue::Integer(*num)); diff --git a/src/test.lisp b/src/test.lisp index e63d04e..7afb957 100644 --- a/src/test.lisp +++ b/src/test.lisp @@ -1,13 +1,11 @@ ; This is a comment -(print "Hello, world!") - -(print (1 2 3)) - (print - (add 15 105.3) -) - -(print - '(add 15 105.3) -) + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) +) \ No newline at end of file diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 745b5ab..215be72 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1,6 +1,92 @@ use std::fmt; -#[derive(Debug, Clone)] +#[cfg(test)] +mod tests { + use crate::{Token, Tokenizer, TokenizerError}; + + fn quick_tokenize(source: &str) -> Result, TokenizerError> { + let mut tokenizer = Tokenizer::new(String::from(source)); + return tokenizer.tokenize(); + } + + fn open() -> Token { + Token::OpenParen + } + fn close() -> Token { + Token::CloseParen + } + fn iden(s: &str) -> Token { + Token::Identifier(String::from(s)) + } + fn string(s: &str) -> Token { + Token::String(String::from(s)) + } + fn int(n: i32) -> Token { + Token::Integer(n) + } + fn float(n: f32) -> Token { + Token::Float(n) + } + + + #[test] + fn test_hello_world() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + open(), + iden("print"), + string("Hello, World!"), + close() + ], + quick_tokenize(" + (print \"Hello, World!\") + ")? + ); + Ok(()) + } + + #[test] + fn test_math() -> Result<(), TokenizerError> { + assert_eq!( + vec![ + open(), + iden("print"), + open(), + iden("add"), + open(), + iden("div"), + float(4.5), + open(), + iden("sub"), + float(0.5), + float(0.2), + close(), + close(), + open(), + iden("mul"), + int(21), + float(0.05), + close(), + close(), + close() + ], + quick_tokenize(" + (print + (add + (div + 4.5 + (sub 0.5 0.2) + ) + (mul 21 0.05) + ) + ) + ")? + ); + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq)] pub enum Token { OpenParen, CloseParen, @@ -12,7 +98,7 @@ pub enum Token { Float(f32) } - +#[derive(Debug)] pub struct TokenizerError { line: u64, column: u64,