Add simple tests

This commit is contained in:
Apache 2024-06-21 16:18:47 -05:00
parent 2f0744d5ee
commit a39914bc35
Signed by: apache
GPG key ID: 6B10F3EAF14F4C77
5 changed files with 336 additions and 96 deletions

View file

@ -1,16 +1,88 @@
use std::{collections::HashMap, fmt}; 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<Vec<LispValue>, 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; use crate::PreLispValue;
pub struct LispState { pub struct LispState {
table: HashMap<String, LispValue>, table: HashMap<String, LispValue>,
} }
fn add_function<T: Into<String> + Clone>(table: &mut HashMap<String, LispValue>, name: T, func: fn(Vec<LispValue>) -> Vec<LispValue>) { fn add_function<T: Into<String> + Clone>(table: &mut HashMap<String, LispValue>, name: T, func: fn(&mut LispState, Vec<LispValue>) -> Vec<LispValue>) {
table.insert(name.clone().into(), LispValue::RustFunction(name.into(), func)); table.insert(name.clone().into(), LispValue::RustFunction(name.into(), func));
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum LispValue { pub enum LispValue {
Nil, Nil,
String(String), String(String),
@ -18,11 +90,12 @@ pub enum LispValue {
Float(f32), Float(f32),
List(Vec<LispValue>, bool), List(Vec<LispValue>, bool),
LispFunction(String, Vec<LispValue>), LispFunction(String, Vec<LispValue>),
RustFunction(String, fn(Vec<LispValue>) -> Vec<LispValue>), RustFunction(String, fn(&mut LispState, Vec<LispValue>) -> Vec<LispValue>),
} }
impl fmt::Display for LispValue { impl fmt::Display for LispValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[allow(unreachable_patterns)]
match self { match self {
LispValue::Nil => write!(f, "nil"), LispValue::Nil => write!(f, "nil"),
LispValue::String(str) => write!(f, "\"{}\"", str), LispValue::String(str) => write!(f, "\"{}\"", str),
@ -38,9 +111,9 @@ impl fmt::Display for LispValue {
} }
if *quoted { if *quoted {
write!(f, "'({})", strs.join(", ")) write!(f, "'({})", strs.join(" "))
} else { } else {
write!(f, "({})", strs.join(", ")) write!(f, "({})", strs.join(" "))
} }
}, },
val => { val => {
@ -75,9 +148,9 @@ impl LispState {
pub fn new() -> LispState { pub fn new() -> LispState {
let mut table = HashMap::new(); let mut table = HashMap::new();
add_function(&mut table, "print", |x| { add_function(&mut table, "print", |_, args| {
let mut strings = Vec::new(); let mut strings = Vec::new();
for val in x { for val in args {
strings.push(val.to_string()); strings.push(val.to_string());
} }
@ -87,9 +160,9 @@ impl LispState {
vec![LispValue::Nil] vec![LispValue::Nil]
}); });
add_function(&mut table, "concat", |x| { add_function(&mut table, "concat", |_, args| {
let mut strings = Vec::new(); let mut strings = Vec::new();
for val in x { for val in args {
strings.push(val.to_string()); strings.push(val.to_string());
} }
@ -97,15 +170,15 @@ impl LispState {
vec![LispValue::String(str)] vec![LispValue::String(str)]
}); });
add_function(&mut table, "add", |x| { add_function(&mut table, "add", |_, args| {
if x.len() < 1 { if args.len() < 1 {
return vec![LispValue::Nil] return vec![LispValue::Nil]
} else if x.len() == 1 { } else if args.len() == 1 {
return vec![x[0].clone()]; return vec![args[0].clone()];
} }
let mut t = "int"; let mut t = "int";
for v in &x { for v in &args {
match v { match v {
LispValue::Float(_) => t = "float", LispValue::Float(_) => t = "float",
LispValue::Integer(_) => (), LispValue::Integer(_) => (),
@ -116,7 +189,7 @@ impl LispState {
match t { match t {
"int" => { "int" => {
let mut sum = 0; let mut sum = 0;
for v in &x { for v in &args {
if let LispValue::Integer(n) = v { if let LispValue::Integer(n) = v {
sum += n; sum += n;
} else { } else {
@ -127,7 +200,7 @@ impl LispState {
}, },
"float" => { "float" => {
let mut sum = 0.0 as f32; let mut sum = 0.0 as f32;
for v in &x { for v in &args {
if let LispValue::Float(n) = v { if let LispValue::Float(n) = v {
sum += n; sum += n;
} else if let LispValue::Integer(n) = v{ } else if let LispValue::Integer(n) = v{
@ -142,15 +215,15 @@ impl LispState {
} }
}); });
add_function(&mut table, "sub", |x| { add_function(&mut table, "sub", |_, args| {
if x.len() < 1 { if args.len() < 1 {
return vec![LispValue::Nil] return vec![LispValue::Nil]
} else if x.len() == 1 { } else if args.len() == 1 {
return vec![x[0].clone()]; return vec![args[0].clone()];
} }
let mut t = "int"; let mut t = "int";
for v in &x { for v in &args {
match v { match v {
LispValue::Float(_) => t = "float", LispValue::Float(_) => t = "float",
LispValue::Integer(_) => (), LispValue::Integer(_) => (),
@ -160,10 +233,10 @@ impl LispState {
match t { match t {
"int" => { "int" => {
if let LispValue::Integer(n) = x[0] { if let LispValue::Integer(n) = args[0] {
let mut sum = n; let mut sum = n;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Integer(n) = &x[i] { if let LispValue::Integer(n) = &args[i] {
sum -= n; sum -= n;
} else { } else {
unreachable!() unreachable!()
@ -175,24 +248,24 @@ impl LispState {
} }
}, },
"float" => { "float" => {
if let LispValue::Float(n) = x[0] { if let LispValue::Float(n) = args[0] {
let mut sum = n; let mut sum = n;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Float(n) = &x[i] { if let LispValue::Float(n) = &args[i] {
sum -= *n; sum -= *n;
} else if let LispValue::Integer(n) = &x[i] { } else if let LispValue::Integer(n) = &args[i] {
sum -= *n as f32; sum -= *n as f32;
} else { } else {
unreachable!() unreachable!()
} }
} }
return vec![LispValue::Float(sum)]; 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; let mut sum = n as f32;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Float(n) = &x[i] { if let LispValue::Float(n) = &args[i] {
sum -= *n; sum -= *n;
} else if let LispValue::Integer(n) = &x[i] { } else if let LispValue::Integer(n) = &args[i] {
sum -= *n as f32; sum -= *n as f32;
} else { } else {
unreachable!() unreachable!()
@ -207,15 +280,15 @@ impl LispState {
} }
}); });
add_function(&mut table, "mul", |x| { add_function(&mut table, "mul", |_, args| {
if x.len() < 1 { if args.len() < 1 {
return vec![LispValue::Nil] return vec![LispValue::Nil]
} else if x.len() == 1 { } else if args.len() == 1 {
return vec![x[0].clone()]; return vec![args[0].clone()];
} }
let mut t = "int"; let mut t = "int";
for v in &x { for v in &args {
match v { match v {
LispValue::Float(_) => t = "float", LispValue::Float(_) => t = "float",
LispValue::Integer(_) => (), LispValue::Integer(_) => (),
@ -225,10 +298,10 @@ impl LispState {
match t { match t {
"int" => { "int" => {
if let LispValue::Integer(n) = x[0] { if let LispValue::Integer(n) = args[0] {
let mut sum = n; let mut sum = n;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Integer(n) = &x[i] { if let LispValue::Integer(n) = &args[i] {
sum *= n; sum *= n;
} else { } else {
unreachable!() unreachable!()
@ -240,24 +313,24 @@ impl LispState {
} }
}, },
"float" => { "float" => {
if let LispValue::Float(n) = x[0] { if let LispValue::Float(n) = args[0] {
let mut product = n; let mut product = n;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Float(n) = &x[i] { if let LispValue::Float(n) = &args[i] {
product *= *n; product *= *n;
} else if let LispValue::Integer(n) = &x[i] { } else if let LispValue::Integer(n) = &args[i] {
product *= *n as f32; product *= *n as f32;
} else { } else {
unreachable!() unreachable!()
} }
} }
return vec![LispValue::Float(product)]; 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; let mut product = n as f32;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Float(n) = &x[i] { if let LispValue::Float(n) = &args[i] {
product *= *n; product *= *n;
} else if let LispValue::Integer(n) = &x[i] { } else if let LispValue::Integer(n) = &args[i] {
product *= *n as f32; product *= *n as f32;
} else { } else {
unreachable!() unreachable!()
@ -272,15 +345,15 @@ impl LispState {
} }
}); });
add_function(&mut table, "div", |x| { add_function(&mut table, "div", |_, args| {
if x.len() < 1 { if args.len() < 1 {
return vec![LispValue::Nil] return vec![LispValue::Nil]
} else if x.len() == 1 { } else if args.len() == 1 {
return vec![x[0].clone()]; return vec![args[0].clone()];
} }
let mut t = "int"; let mut t = "int";
for v in &x { for v in &args {
match v { match v {
LispValue::Float(_) => t = "float", LispValue::Float(_) => t = "float",
LispValue::Integer(_) => (), LispValue::Integer(_) => (),
@ -290,10 +363,10 @@ impl LispState {
match t { match t {
"int" => { "int" => {
if let LispValue::Integer(n) = x[0] { if let LispValue::Integer(n) = args[0] {
let mut sum = n; let mut sum = n;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Integer(n) = &x[i] { if let LispValue::Integer(n) = &args[i] {
sum /= n; sum /= n;
} else { } else {
unreachable!() unreachable!()
@ -305,24 +378,24 @@ impl LispState {
} }
}, },
"float" => { "float" => {
if let LispValue::Float(n) = x[0] { if let LispValue::Float(n) = args[0] {
let mut product = n; let mut product = n;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Float(n) = &x[i] { if let LispValue::Float(n) = &args[i] {
product /= *n; product /= *n;
} else if let LispValue::Integer(n) = &x[i] { } else if let LispValue::Integer(n) = &args[i] {
product /= *n as f32; product /= *n as f32;
} else { } else {
unreachable!() unreachable!()
} }
} }
return vec![LispValue::Float(product)]; 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; let mut product = n as f32;
for i in 1..x.len() { for i in 1..args.len() {
if let LispValue::Float(n) = &x[i] { if let LispValue::Float(n) = &args[i] {
product /= *n; product /= *n;
} else if let LispValue::Integer(n) = &x[i] { } else if let LispValue::Integer(n) = &args[i] {
product /= *n as f32; product /= *n as f32;
} else { } else {
unreachable!() unreachable!()
@ -366,7 +439,7 @@ impl LispState {
out out
} }
fn eval_list(list: &Vec<LispValue>, quoted: bool) -> (Vec<LispValue>, bool) { fn eval_list(&mut self, list: &Vec<LispValue>, quoted: bool) -> (Vec<LispValue>, bool) {
if list.len() < 1 { if list.len() < 1 {
return (vec![LispValue::List(Vec::new(), quoted)], false); return (vec![LispValue::List(Vec::new(), quoted)], false);
} }
@ -375,7 +448,7 @@ impl LispState {
for i in 0..list.len() { for i in 0..list.len() {
if let LispValue::List(elements, quoted) = &list[i] { 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 { if astuple {
list[i] = elems.get(0).unwrap_or(&LispValue::Nil).to_owned(); list[i] = elems.get(0).unwrap_or(&LispValue::Nil).to_owned();
for j in 1..elems.len() { for j in 1..elems.len() {
@ -392,23 +465,23 @@ impl LispState {
} }
if let LispValue::RustFunction(_name, f) = &list[0] { if let LispValue::RustFunction(_name, f) = &list[0] {
(f(list[1..].to_vec()), true) (f(self, list[1..].to_vec()), true)
} else if let LispValue::LispFunction(_name, ins) = &list[0] { } else if let LispValue::LispFunction(_name, _) = &list[0] {
(Self::call_lisp_func(&list[0], list[1..].to_vec()), true) (self.call_lisp_func(&list[0], list[1..].to_vec()), true)
} else { } else {
(list.to_vec(), false) (list.to_vec(), false)
} }
} }
fn call_lisp_func(f: &LispValue, args: Vec<LispValue>) -> Vec<LispValue> { fn call_lisp_func(&mut self, f: &LispValue, args: Vec<LispValue>) -> Vec<LispValue> {
todo!() todo!("Calling lisp function {} with args {:?}", f, args)
} }
pub fn execute(&self, instructions: Vec<PreLispValue>) { pub fn execute(&mut self, instructions: Vec<PreLispValue>) {
let instructions = Self::handle_prevalues(self, instructions); let instructions = Self::handle_prevalues(self, instructions);
for val in instructions { for val in instructions {
if let LispValue::List(elements, quoted) = val { if let LispValue::List(elements, quoted) = val {
Self::eval_list(&elements, quoted); self.eval_list(&elements, quoted);
} }
} }
} }

View file

@ -7,20 +7,16 @@ use parser::*;
mod executor; mod executor;
use executor::*; use executor::*;
fn main() { fn main() -> Result<(), TokenizerError> {
let source = std::fs::read_to_string("src/test.lisp").unwrap(); let source = std::fs::read_to_string("src/test.lisp").unwrap();
let mut tokenizer = Tokenizer::new(source); let mut tokenizer = Tokenizer::new(source);
let tokens = match tokenizer.tokenize() { let tokens = tokenizer.tokenize()?;
Ok(tokens) => tokens,
Err(e) => {
println!("{}", e);
Vec::new()
}
};
let instructions = parse(tokens); let instructions = parse(tokens);
let state = LispState::new(); let mut state = LispState::new();
state.execute(instructions); state.execute(instructions);
Ok(())
} }

View file

@ -1,8 +1,91 @@
use std::fmt; use std::fmt;
#[cfg(test)]
mod tests {
use crate::{parse, PreLispValue, Tokenizer, TokenizerError};
fn quick_parse(source: &str) -> Result<Vec<PreLispValue>, 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<PreLispValue>, 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; use crate::Token;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum PreLispValue { pub enum PreLispValue {
Nil, Nil,
String(String), String(String),
@ -110,7 +193,11 @@ fn parse_exp(tokens: Vec<Token>, quoted: bool) -> PreLispValue {
unquote_next_list = false; unquote_next_list = false;
}, },
Token::Identifier(name) => { Token::Identifier(name) => {
if name == &String::from("nil") {
values.push(PreLispValue::Nil)
} else {
values.push(PreLispValue::Var(name.to_owned())); values.push(PreLispValue::Var(name.to_owned()));
}
}, },
Token::Integer(num) => { Token::Integer(num) => {
values.push(PreLispValue::Integer(*num)); values.push(PreLispValue::Integer(*num));

View file

@ -1,13 +1,11 @@
; This is a comment ; This is a comment
(print "Hello, world!")
(print (1 2 3))
(print (print
(add 15 105.3) (add
(div
4.5
(sub 0.5 0.2)
)
(mul 21 0.05)
) )
(print
'(add 15 105.3)
) )

View file

@ -1,6 +1,92 @@
use std::fmt; use std::fmt;
#[derive(Debug, Clone)] #[cfg(test)]
mod tests {
use crate::{Token, Tokenizer, TokenizerError};
fn quick_tokenize(source: &str) -> Result<Vec<Token>, 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 { pub enum Token {
OpenParen, OpenParen,
CloseParen, CloseParen,
@ -12,7 +98,7 @@ pub enum Token {
Float(f32) Float(f32)
} }
#[derive(Debug)]
pub struct TokenizerError { pub struct TokenizerError {
line: u64, line: u64,
column: u64, column: u64,