diff --git a/src/main.rs b/src/main.rs index a44d1e8..d770e83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ use color_eyre::Result; +use printer::print; +use reader::read; use regex::Regex; use rustyline::{config::Configurer, error::ReadlineError}; +pub mod printer; pub mod reader; pub mod types; use types::*; @@ -15,19 +18,19 @@ fn main() -> Result<()> { match entry { Ok(line) => { rl.add_history_entry(line.as_str())?; - eval(line.as_str().into(), env.clone())?; - }, + eval(line.as_str(), env.clone())?; + } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); - break - }, + break; + } Err(ReadlineError::Eof) => { println!("CTRL-D"); - break - }, + break; + } Err(err) => { println!("Error: {:?}", err); - break + break; } } } @@ -46,20 +49,22 @@ fn read_eval_print(str: &str) -> Result<()> { Ok(()) } -fn eval(expr: Expression, env: Environment) -> Result<()> { - let regex = Regex::new(r#"^\(([[:ascii:]] .*)\)$"#).unwrap(); - let exp = regex.captures_iter(&expr.expr); - for expr in exp { - // let regex = Regex::new(r#"^(\w)$"#).unwrap(); - // let mut atom = regex.captures_iter(atom?.get(0).unwrap().as_str()); - // for atom in atom { - // } - for atom in expr.iter() { - if let Some(atom) = atom { - println!("{:?}", atom); - }; - } - } - println!("{}", expr.expr); +fn eval(expr: &str, env: Environment) -> Result<()> { + let ast = read(expr); + let expr = print(&ast); + // let regex = Regex::new(r"^\(([[:ascii:]] .*)\)$").unwrap(); + // let exp = regex.captures_iter(&expr.expr); + // for expr in exp { + // // let regex = Regex::new(r#"^(\w)$"#).unwrap(); + // // let mut atom = regex.captures_iter(atom?.get(0).unwrap().as_str()); + // // for atom in atom { + // // } + // for atom in expr.iter() { + // if let Some(atom) = atom { + // println!("{:?}", atom); + // }; + // } + // } + println!("{}", expr); Ok(()) } diff --git a/src/printer.rs b/src/printer.rs new file mode 100644 index 0000000..b5d8cff --- /dev/null +++ b/src/printer.rs @@ -0,0 +1,52 @@ +use crate::Value; + +pub fn print(value: &Value) -> String { + match value { + Value::List(v) => { + let strings: Vec = v.iter().map(print).collect(); + let mut string = strings.join(" "); + if v.len() < 2 { + return string; + } + let end = string.len() + 1; + string.insert_str(0, "("); + string.insert_str(end, ")"); + // dbg!(&string); + string + } + Value::String(s) => { + let mut s = s.clone(); + let length = s.len() + 1; + s.insert(0, '"'); + s.insert(length, '"'); + s.to_string() + } + Value::Number(n) => n.to_string(), + Value::Nil => "nil".to_string(), + Value::Function => "fn".to_string(), + Value::True => "t".to_string(), + Value::Symbol(s) => s.0.clone(), + Value::Keyword(k) => { + let mut string = k.0.clone(); + string.insert_str(0, ":"); + string + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::reader::read; + use pretty_assertions::assert_eq; + use std::fs::read_to_string; + + #[test] + fn test_print() { + let string = read_to_string("./test_presentation.lisp").unwrap(); + let values = read(&string); + let string = r#"((slide :background (image :source "~/pics/frodo.jpg" :fit crop) (text "This is frodo" :font-size 50)) (slide (video :source "~/vids/test/chosensmol.mp4" :fit fill)) (song :author "Jordan Feliz" :ccli 97987 :font "Quicksand" :font-size 80 :title "The River" :background (image :source "./coolbg.jpg") (text "I'm going down to the river") (text "Down to the river") (text "Down to the river to pray ay ay!")) (song :author "Jordan Feliz" :ccli 97987 :font "Quicksand" :font-size 80 :title "The River" :background (video :source "./coolerbg.mkv" :fit cover) :verse-order (v1 c1 v2 c1) (v1 "I'm going down to the river") (c1 "Down to the river") (v2 "Down to the river to pray ay ay!")) (load "./10000-reasons.lisp"))"#.to_string(); + let print = print(&values); + assert_eq!(print, string) + } +} diff --git a/src/reader.rs b/src/reader.rs index 62e2137..95f579a 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use crate::types::Symbol; +use crate::Keyword; use crate::Value; use color_eyre::{eyre::eyre, Result}; use lazy_static::lazy_static; @@ -14,68 +15,84 @@ struct Reader { impl Reader { fn next(&mut self) -> Option<&String> { - let token = self.position; self.position += 1; - self.tokens.get(token) + self.tokens.get(self.position - 1) } fn peek(&self) -> Option<&String> { - let index = self.position; - self.tokens.get(index) + self.tokens.get(self.position) } fn read_form(&mut self) -> Value { - let Some(first) = self.peek() else { + let Some(token) = self.peek() else { return Value::Nil; }; - let mut value = Value::Nil; - if first.as_str() == "(" { - value = self.read_list(); - } else { - if let Some(atom) = self.read_atom() { - value = atom + let val; + match &token[..] { + "(" => { + val = self.read_list(")"); } - }; - value + "[" => { + val = self.read_list("]"); + } + _ => { + val = self.read_atom().unwrap_or(Value::Nil); + } + } + val } fn read_atom(&mut self) -> Option { let Some(token) = self.next() else { return None; }; - if token != "(" { + if token != "(" && token != ")" { Some(Value::from(token)) } else { None } } - fn read_list(&mut self) -> Value { + fn read_list(&mut self, end: &str) -> Value { let mut values = vec![]; - let length = self.tokens.len(); - let position = self.position; - while self.tokens.len() >= position { - if self.peek().unwrap().as_str() == ")" { + self.next(); + loop { + let Some(token) = self.peek() else { + return Value::Nil; + }; + if token == end { break; } values.push(self.read_form()); } + self.next(); Value::from(values) } } pub fn read(str: &str) -> Value { let mut reader = tokenize(str); - let value = reader.read_form(); - value + let mut values = vec![]; + loop { + let length = reader.tokens.len(); + if reader.position <= length { + if reader.peek().is_some_and(|s| &s[..] == "(") { + values.push(reader.read_form()); + } else { + reader.next(); + } + } else { + break; + } + } + Value::from(values) } fn tokenize(str: &str) -> Reader { lazy_static! { - static ref RE: Regex = Regex::new( - r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]+)"### - ) - .unwrap(); + static ref RE: Regex = + Regex::new(r#"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"#) + .unwrap(); } let mut res = vec![]; @@ -85,6 +102,7 @@ fn tokenize(str: &str) -> Reader { } res.push(String::from(&cap[1])); } + // dbg!(&res); Reader { tokens: res, position: 0, @@ -110,15 +128,119 @@ mod test { String::from("world"), String::from(")"), ]; + // let string = get_test_string(); let reader = tokenize("(hello world)"); + // print!("{:?}", reader); assert_eq!(test_vec, reader.tokens) } #[test] fn test_read() { - let test_values = Value::List(vec![Value::Symbol(Symbol(String::from("hello")))]); - // let values = read(&get_test_string()); - // let values = read("(hello world)"); - // assert_eq!(test_values, values) + let test_values = Value::List(vec![ + Value::List(vec![ + Value::Symbol(Symbol(String::from("slide"))), + Value::Keyword(Keyword(String::from("background"))), + Value::List(vec![ + Value::Symbol(Symbol(String::from("image"))), + Value::Keyword(Keyword(String::from("source"))), + Value::String(String::from("~/pics/frodo.jpg")), + Value::Keyword(Keyword(String::from("fit"))), + Value::Symbol(Symbol(String::from("crop"))), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("text"))), + Value::String(String::from("This is frodo")), + Value::Keyword(Keyword(String::from("font-size"))), + Value::Number(50), + ]), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("slide"))), + Value::List(vec![ + Value::Symbol(Symbol(String::from("video"))), + Value::Keyword(Keyword(String::from("source"))), + Value::String(String::from("~/vids/test/chosensmol.mp4")), + Value::Keyword(Keyword(String::from("fit"))), + Value::Symbol(Symbol(String::from("fill"))), + ]), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("song"))), + Value::Keyword(Keyword(String::from("author"))), + Value::String(String::from("Jordan Feliz")), + Value::Keyword(Keyword(String::from("ccli"))), + Value::Number(97987), + Value::Keyword(Keyword(String::from("font"))), + Value::String(String::from("Quicksand")), + Value::Keyword(Keyword(String::from("font-size"))), + Value::Number(80), + Value::Keyword(Keyword(String::from("title"))), + Value::String(String::from("The River")), + Value::Keyword(Keyword(String::from("background"))), + Value::List(vec![ + Value::Symbol(Symbol(String::from("image"))), + Value::Keyword(Keyword(String::from("source"))), + Value::String(String::from("./coolbg.jpg")), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("text"))), + Value::String(String::from("I'm going down to the river")), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("text"))), + Value::String(String::from("Down to the river")), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("text"))), + Value::String(String::from("Down to the river to pray ay ay!")), + ]), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("song"))), + Value::Keyword(Keyword(String::from("author"))), + Value::String(String::from("Jordan Feliz")), + Value::Keyword(Keyword(String::from("ccli"))), + Value::Number(97987), + Value::Keyword(Keyword(String::from("font"))), + Value::String(String::from("Quicksand")), + Value::Keyword(Keyword(String::from("font-size"))), + Value::Number(80), + Value::Keyword(Keyword(String::from("title"))), + Value::String(String::from("The River")), + Value::Keyword(Keyword(String::from("background"))), + Value::List(vec![ + Value::Symbol(Symbol(String::from("video"))), + Value::Keyword(Keyword(String::from("source"))), + Value::String(String::from("./coolerbg.mkv")), + Value::Keyword(Keyword(String::from("fit"))), + Value::Symbol(Symbol(String::from("cover"))), + ]), + Value::Keyword(Keyword(String::from("verse-order"))), + Value::List(vec![ + Value::Symbol(Symbol(String::from("v1"))), + Value::Symbol(Symbol(String::from("c1"))), + Value::Symbol(Symbol(String::from("v2"))), + Value::Symbol(Symbol(String::from("c1"))), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("v1"))), + Value::String(String::from("I'm going down to the river")), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("c1"))), + Value::String(String::from("Down to the river")), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("v2"))), + Value::String(String::from("Down to the river to pray ay ay!")), + ]), + ]), + Value::List(vec![ + Value::Symbol(Symbol(String::from("load"))), + Value::String(String::from("./10000-reasons.lisp")), + ]), + ]); + let values = read(&get_test_string()); + assert_eq!(test_values, values) } } diff --git a/src/types.rs b/src/types.rs index c9dedd5..00f3029 100644 --- a/src/types.rs +++ b/src/types.rs @@ -23,6 +23,7 @@ pub enum Value { #[default] Nil, Function, + True, Symbol(Symbol), Keyword(Keyword), } @@ -49,8 +50,14 @@ impl From<&str> for Value { Value::Number(parse_result) } else if let Some(s) = s.strip_prefix(":") { Value::Keyword(Keyword(s.to_string())) + } else if s == "t".to_owned() { + Value::True } else if s.starts_with(r#"""#) { - Value::String(s) + Value::String(s.replace('"', "")) + } else if s == "fn".to_owned() { + Value::Function + } else if s == "defun".to_owned() { + Value::Function } else if s == "nil".to_owned() { Value::Nil } else {