diff --git a/src/main.rs b/src/main.rs index 59bf590..d3571ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,9 +15,34 @@ fn main() -> io::Result<()> { let _ = io::stdout().flush(); let mut input = String::new(); + loop { + // Read line to compose program inpug + let mut line = String::new(); + io::stdin().read_line(&mut line)?; - io::stdin().read_line(&mut input)?; + // Append line to input + input.push_str(&line); - println!("{}", rep(&input.replace("\n", " "))); + // If there is nothing to evaluate skip rep + if input == "\n" { + continue; + } + + // Perform rep on whole available input + match rep(&input) { + Ok(output) => println!("{}", output), + Err((err, depth)) => { + if line == "\n" { + println!("ERROR: {}", err); + } else { + print!("user> {}", " ".repeat(depth)); + // Flush the prompt to appear before command + let _ = io::stdout().flush(); + continue; + } + } + } + break; + } } } diff --git a/src/printer.rs b/src/printer.rs index 5b1912e..e813d0f 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,30 +1,39 @@ +use crate::types::escape_str; use crate::types::MalType; +use crate::types::MalType::*; -pub fn pr_str(ast: &MalType) -> String { +pub fn pr_str(ast: &MalType, print_readably: bool) -> String { match ast { - MalType::Nil => "nil".to_string(), - MalType::Symbol(sym) => sym.to_string(), - MalType::Integer(val) => val.to_string(), - MalType::Bool(val) => val.to_string(), - MalType::List(el) => format!( + Nil => "nil".to_string(), + Symbol(sym) | Keyword(sym) => sym.to_string(), + Int(val) => val.to_string(), + Bool(val) => val.to_string(), + Str(str) => { + if print_readably { + escape_str(str) + } else { + str.to_string() + } + } + List(el) => format!( "({})", el.iter() - .map(|sub| pr_str(sub)) + .map(|e| pr_str(e, print_readably)) .collect::>() .join(" ") ), // This is truly horrible - MalType::Vector(el) => format!( + Vector(el) => format!( "[{}]", el.iter() - .map(|sub| pr_str(sub)) + .map(|e| pr_str(e, print_readably)) .collect::>() .join(" ") ), - MalType::Map(el) => format!( + Map(el) => format!( "{{{}}}", el.iter() - .map(|sub| vec![pr_str(sub.0), pr_str(sub.1)].join(" ")) + .map(|sub| vec![sub.0.to_string(), pr_str(sub.1, print_readably)].join(" ")) .collect::>() .join(" ") ), diff --git a/src/reader.rs b/src/reader.rs index f12e5d3..da499b3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,38 +1,43 @@ -use std::collections::{BTreeMap, VecDeque}; - -use crate::types::MalType; +// Specyfy components in "types" +use crate::types::*; +// By specifying enum variants it's possible to omit namespace +use crate::types::MalType::*; use regex::Regex; pub struct Reader { - tokens: VecDeque, + tokens: Vec, + ptr: usize, + depth: usize, } // TODO: 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 -const PAREN_ERROR: &str = - "Looks like you reached a dead end, did you perhaps miss any \")\" or left some extra \"(\"?"; - impl Reader { - fn new(tokens: VecDeque) -> Reader { - Reader { tokens } + fn new(tokens: Vec) -> Reader { + Reader { + tokens, + ptr: 0, + depth: 0, + } } - /// Returns the token at the current positioni - fn peek(&self) -> &str { - match self.tokens.get(0) { - Some(token) => token, - None => panic!("{}", PAREN_ERROR), + /// Returns the token at the current position + fn peek(&self) -> Result { + match self.tokens.get(self.ptr) { + Some(token) => Ok(token.to_string()), + None => Err("Unexpected EOF, Missing parenthesis?".to_string()), } } /// Returns the token at current position and increment current position // TODO: PLEASE USE THE PEEK FUNCTION - fn next(&mut self) -> String { - match self.tokens.pop_front() { - Some(token) => token, - None => panic!("{}", PAREN_ERROR), + fn next(&mut self) -> Result { + self.ptr += 1; + match self.tokens.get(self.ptr - 1) { + Some(token) => Ok(token.to_string()), + None => Err("Unexpected EOF, Missing parenthesis?".to_string()), } } @@ -41,35 +46,54 @@ impl Reader { /// Accumulates results into a MalList /// NOTE: `read_list` calls `read_form` -> enable recursion /// (lists can contains other lists) - fn read_list(&mut self, terminator: &str) -> Vec { - std::iter::from_fn(|| match self.peek() { - ")" | "]" | "}" => { - if terminator != self.peek() { - panic!("Unexpected token: {}", self.peek()) - } - self.next(); - None + fn read_list(&mut self, terminator: &str) -> MalRet { + self.depth += 1; + + self.next()?; + + let mut vector = Vec::new(); + loop { + let token = self.peek()?; + if token == terminator { + break; } - _ => Some(self.read_form()), - }) - .collect() + vector.push(self.read_form()?) + } + self.next()?; + let ret = match terminator { + ")" => Ok(List(vector)), + "]" => Ok(Vector(vector)), + "}" => make_map(vector), + _ => Err(format!("Unknown collection terminator: {}", terminator)), + }; + self.depth -= 1; + ret } /// Read atomic token and return appropriate scalar () - fn read_atom(&mut self) -> MalType { - let token = self.next(); - // parse the token as an integer - match token.parse::() { - // On success assign the value - Ok(value) => MalType::Integer(value), - // Otherwise assign the symbol - Err(_) => match token.as_str() { - ")" | "]" | "}" => panic!("Lone parenthesis {}", token), - "false" => MalType::Bool(false), - "true" => MalType::Bool(true), - "nil" => MalType::Nil, - _ => MalType::Symbol(token), - }, + fn read_atom(&mut self) -> MalRet { + let token = self.next()?; + let re_digits = Regex::new(r"^-?[0-9]+$").unwrap(); + match token.as_str() { + ")" | "]" | "}" => Err(format!("Lone parenthesis {}", token)), + "false" => Ok(Bool(false)), + "true" => Ok(Bool(true)), + "nil" => Ok(Nil), + _ => { + if re_digits.is_match(&token) { + Ok(Int(token.parse::().unwrap())) + } else if token.starts_with('\"') { + if token.ends_with('\"') { + Ok(Str(unescape_str(&token))) + } else { + Err("Unterminated string, expected \"".to_string()) + } + } else if token.starts_with(':') { + Ok(Keyword(token)) + } else { + Ok(Symbol(token)) + } + } } } @@ -78,31 +102,14 @@ impl Reader { /// Switch on the first character /// "(" -> call `read_list` /// otherwise -> call `read_atom` - fn read_form(&mut self) -> MalType { - match self.peek() { + fn read_form(&mut self) -> MalRet { + let token = self.peek()?; + // String slice containing the whole string + match &token[..] { // Consume "(" and parse list - "(" => { - self.next(); - MalType::List(self.read_list(")")) - } - "[" => { - self.next(); - MalType::Vector(self.read_list("]")) - } - "{" => { - self.next(); - // fallback to C mode for now 😎 - let list = self.read_list("}"); - if list.len() % 2 != 0 { - panic!("Missing Map element") - } - let mut map = BTreeMap::new(); - for i in (0..list.len()).step_by(2) { - map.insert(list[i].clone(), list[i + 1].clone()); - } - MalType::Map(map) - } - // read atomically + "(" => self.read_list(")"), + "[" => self.read_list("]"), + "{" => self.read_list("}"), _ => self.read_atom(), } } @@ -112,23 +119,36 @@ impl Reader { /// Create anew Reader with the tokens /// Call read_from with the reader instance /// TODO: catch errors -pub fn read_str(input: &str) -> MalType { - let ast = Reader::new(tokenize(input)).read_form(); - // pretty_print(&ast, 0); - ast +pub fn read_str(input: &str) -> Result { + let tokens = tokenize(input); + match tokens.len() { + 0 => Ok(Nil), + _ => { + let mut reader = Reader::new(tokens); + match reader.read_form() { + Err(err) => Err((err, reader.depth)), + Ok(any) => Ok(any), + } + } + } } /// Read a string and return a list of tokens in it (following regex in README) // Add error handling for strings that are not terminated -fn tokenize(input: &str) -> VecDeque { - let mut tokens = VecDeque::new(); +fn tokenize(input: &str) -> Vec { + let mut tokens = Vec::new(); - let re = - Regex::new(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"###) - .unwrap(); + let re = Regex::new( + r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*\n|[^\s\[\]{}('"`,;)]*)"###, + ) + .unwrap(); for match_str in re.captures_iter(input) { - if match_str[1].len() > 0 { - tokens.push_back(match_str[1].to_string()); + if !match_str[1].is_empty() { + // Drop comments + if match_str[1].starts_with(';') { + continue; + } + tokens.push(match_str[1].to_string()); } } diff --git a/src/step1_read_print.rs b/src/step1_read_print.rs index 07f43d7..c718e5e 100644 --- a/src/step1_read_print.rs +++ b/src/step1_read_print.rs @@ -6,12 +6,15 @@ use crate::printer::pr_str; use crate::reader::read_str; -use crate::types::MalType; +use crate::types::{MalErr, MalType}; #[allow(non_snake_case)] /// Read input and generate an ast -fn READ(input: &str) -> MalType { - read_str(input) +fn READ(input: &str) -> Result { + match read_str(input) { + Ok(ast) => Ok(ast), + Err((err, depth)) => Err((format!("Unexpected error during READ: {}", err), depth)), + } } #[allow(non_snake_case)] @@ -24,11 +27,11 @@ fn EVAL(ast: MalType) -> MalType { #[allow(non_snake_case)] /// Print out the result of the evaluation fn PRINT(input: MalType) -> String { - pr_str(&input) + pr_str(&input, true) } -pub fn rep(input: &str) -> String { - let ast = READ(input); +pub fn rep(input: &str) -> Result { + let ast = READ(input)?; let out = EVAL(ast); - PRINT(out /*&result*/) + Ok(PRINT(out)) } diff --git a/src/types.rs b/src/types.rs index cad429d..bf587c7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,16 +1,69 @@ // TODO: use enums for MalTypes -use std::collections::BTreeMap; +use std::collections::HashMap; // All Mal types should inherit from this -#[derive(Debug, Ord, Eq, PartialEq, PartialOrd, Clone)] +#[derive(Debug, Clone)] pub enum MalType { List(Vec), Vector(Vec), - // HashMap cannot implement Hash - Map(BTreeMap), + Map(HashMap), Symbol(String), - Integer(i32), + Keyword(String), + Str(String), + Int(isize), Bool(bool), Nil, } + +// Stolen, but this way it's easier to handle errors + +/* +#[derive(Debug)] +pub enum MalErr { + Str(String), // Messages to the user + // Val(MalType), + // Messages to the program +} TEMP TEMP */ +pub type MalErr = String; + +pub type MalArgs = Vec; +pub type MalRet = Result; + +use MalType::{Map, Symbol}; + +pub fn make_map(list: MalArgs) -> MalRet { + if list.len() % 2 != 0 { + return Err("Map length is odd: missing value".to_string()); + } + + let mut map = HashMap::new(); + + for i in (0..list.len()).step_by(2) { + match &list[i] { + Symbol(k) => { + let v = list[i + 1].clone(); + map.insert(k.to_string(), v); + } + _ => return Err(format!("Map key not valid: {:?}", list[i])), + } + } + Ok(Map(map)) +} + +pub fn escape_str(s: &str) -> String { + format!( + "\"{}\"", + String::from(s) + .replace('\\', "\\\\") + .replace('\n', "\\n") + .replace('\"', "\\\"") + ) +} + +pub fn unescape_str(s: &str) -> String { + String::from(&s[1..s.len() - 1]) + .replace("\\\\", "\\") + .replace("\\n", "\n") + .replace("\\\"", "\"") +}