diff --git a/Cargo.toml b/Cargo.toml index 3c391ad..652f1b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "mute" version = "0.1.0" -author = "Raine Godmaire " +authors = ["Raine Godmaire "] edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "mute" +path = "src/bin/cli.rs" [dependencies] anyhow = "1.0.75" diff --git a/src/main.rs b/src/bin/cli.rs similarity index 76% rename from src/main.rs rename to src/bin/cli.rs index 89beb1a..b11ae88 100644 --- a/src/main.rs +++ b/src/bin/cli.rs @@ -1,14 +1,8 @@ use anyhow::Result; use clap::{Parser, Subcommand}; -use node::Node; use rustyline::{error::ReadlineError, DefaultEditor}; -mod env; -mod error; -mod evaluator; -mod macros; -mod node; -mod parser; +use mute::{eval, parse, Environment, Node}; #[derive(Debug, Parser)] #[clap(version, author, about)] @@ -35,14 +29,16 @@ fn main() { match args.command { Command::Run { file } => { let input = std::fs::read_to_string(file).unwrap(); - let res = run(&input); + let env = Environment::new(); + let res = run(&env, &input); match res { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Err(err) => println!("{}", err), } } Command::RunCommand { command } => { - let res = run(&command); + let env = Environment::new(); + let res = run(&env, &command); match res { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Err(err) => println!("{}", err), @@ -52,14 +48,13 @@ fn main() { } } -fn run(command: &str) -> Result> { - let env = env::Environment::new(); - let ast = parser::parse_str(command)?; - evaluator::eval(&env, ast) +fn run(env: &Environment, command: &str) -> Result> { + let ast = parse(command)?; + eval(&env, ast) } fn repl() { - let env = env::Environment::new(); + let env = Environment::new(); let mut rl = DefaultEditor::new().unwrap(); println!("Mute -- REPL"); @@ -71,12 +66,9 @@ fn repl() { Ok(line) => { rl.add_history_entry(line.as_str()).unwrap(); - let ast = parser::parse_str(&line).unwrap(); - let res = evaluator::eval(&env, ast); - - match res { + match run(&env, &line) { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), - Err(err) => println!("{}", err), + Err(err) => println!("error: {}", err), } } Err(ReadlineError::Interrupted) => { diff --git a/src/error.rs b/src/error.rs index a202d34..9dad68a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,12 @@ use thiserror::Error; +pub type Result = std::result::Result; + #[derive(Debug, Error)] pub enum Error { + #[error("ParserError: {0}")] + ParserError(ParserError), + #[error("could not find symbol '{0}' in environment")] NotInEnv(String), #[error("expression does not have a valid operator")] @@ -18,4 +23,33 @@ pub enum Error { ExpectedString, #[error("expected {0} arguments, got {1}")] MismatchedArgCount(usize, usize), + + #[error("please file a bug report")] + Unreachable, +} + +#[derive(Debug, Error)] +pub enum ParserError { + #[error("closing parenthesis does not have matching open parenthesis")] + UnopenedParenthesis, + #[error("unclosed list")] + UnclosedParenthesis, + #[error("closing bracket does not have matching open parenthesis")] + UnopenedBracket, + #[error("unclosed vector")] + UnclosedBracket, + #[error("closing brace does not have matching open parenthesis")] + UnopenedBrace, + #[error("unclosed hashmap")] + UnclosedBrace, + + #[error("unexpected character '{0}'")] + UnexpectedChar(char), + #[error("string is never closed")] + UnterminatedString, + #[error("the quote does not have a valid follower node")] + NoFollowerNode, + + #[error("please file a bug report")] + Unreachable, } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..383caa8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +mod env; +mod error; +mod evaluator; +mod macros; +mod node; +mod parser; + +pub use env::Environment; +pub use error::{Error, Result}; +pub use node::Node; + +pub fn parse(input: &str) -> Result> { + parser::parse_str(input) +} + +pub fn eval(env: &Environment, input: Vec) -> anyhow::Result> { + evaluator::eval(env, input) +} diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs index 3db6dbe..5735c02 100644 --- a/src/parser/lexer.rs +++ b/src/parser/lexer.rs @@ -1,6 +1,6 @@ use std::{iter::Peekable, str::Chars}; -use anyhow::{bail, Result}; +use crate::error::ParserError; #[derive(Debug, PartialEq, PartialOrd)] pub enum Token { @@ -41,7 +41,7 @@ pub enum Token { Nil, } -pub fn read(input: &str) -> Result> { +pub fn read(input: &str) -> Result, ParserError> { let mut input = input.chars().peekable(); let mut tokens = Vec::new(); @@ -52,7 +52,7 @@ pub fn read(input: &str) -> Result> { Ok(tokens) } -fn next_token(input: &mut Peekable) -> Result> { +fn next_token(input: &mut Peekable) -> Result, ParserError> { let tok = match input.next() { Some(tok) => tok, None => return Ok(None), @@ -128,13 +128,13 @@ fn next_token(input: &mut Peekable) -> Result> { Some(tok) => tok, None => return Ok(None), }, - _ => bail!("illegal token"), + c => Err(ParserError::UnexpectedChar(c))?, }; Ok(Some(tok)) } -fn read_string(input: &mut Peekable) -> Result { +fn read_string(input: &mut Peekable) -> Result { let mut raw_str = Vec::new(); loop { @@ -146,7 +146,7 @@ fn read_string(input: &mut Peekable) -> Result { } Some(_) => (), - None => bail!("unbalanced string"), + None => Err(ParserError::UnterminatedString)?, } raw_str.push(input.next().unwrap()) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b6f0444..865fd6f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,24 +1,26 @@ use std::{iter::Peekable, vec::IntoIter}; -use anyhow::{bail, Result}; - mod lexer; use lexer::Token; -use crate::node::Node; +use crate::error::ParserError; +use crate::{Error, Node}; -pub fn parse_str(input: &str) -> Result> { - let mut tokens = lexer::read(input)?.into_iter().peekable(); +pub fn parse_str(input: &str) -> crate::Result> { + let mut tokens = lexer::read(input) + .map_err(|err| Error::ParserError(err))? + .into_iter() + .peekable(); let mut ast = Vec::new(); - while let Some(node) = next_statement(&mut tokens)? { + while let Some(node) = next_statement(&mut tokens).map_err(|err| Error::ParserError(err))? { ast.push(node) } Ok(ast) } -fn next_statement(tokens: &mut Peekable>) -> Result> { +fn next_statement(tokens: &mut Peekable>) -> Result, ParserError> { let tok = match tokens.next() { Some(tok) => tok, None => return Ok(None), @@ -26,13 +28,13 @@ fn next_statement(tokens: &mut Peekable>) -> Result let node = match tok { Token::LeftParen => read_list(tokens, Token::RightParen)?, - Token::RightParen => bail!("closing parenthsis does not have matching open parenthesis"), + Token::RightParen => Err(ParserError::UnopenedParenthesis)?, Token::LeftBracket => read_list(tokens, Token::RightBracket)?, - Token::RightBracket => bail!("closing bracket does not have matching open bracket"), + Token::RightBracket => Err(ParserError::UnopenedBracket)?, Token::LeftBrace => read_list(tokens, Token::RightBrace)?, - Token::RightBrace => bail!("closing brace does not have matching open brace"), + Token::RightBrace => Err(ParserError::UnopenedBrace)?, Token::WeirdSign => read_quote(tokens, "splice-unquote")?, Token::Apostrophe => read_quote(tokens, "quote")?, @@ -66,12 +68,12 @@ fn next_statement(tokens: &mut Peekable>) -> Result Ok(Some(node)) } -fn read_list(tokens: &mut Peekable>, closer: Token) -> Result { +fn read_list(tokens: &mut Peekable>, closer: Token) -> Result { let mut list = match closer { Token::RightParen => Vec::new(), Token::RightBracket => vec![Node::Symbol("vector".into())], Token::RightBrace => vec![Node::Symbol("hashmap".into())], - _ => bail!("unreachable"), + _ => Err(ParserError::Unreachable)?, }; loop { @@ -88,10 +90,10 @@ fn read_list(tokens: &mut Peekable>, closer: Token) -> Result list.push(node), None => match closer { - Token::RightParen => bail!("unclosed list"), - Token::RightBracket => bail!("unclosed vector"), - Token::RightBrace => bail!("unclosed hashmap"), - _ => bail!("unreachable"), + Token::RightParen => Err(ParserError::UnclosedParenthesis)?, + Token::RightBracket => Err(ParserError::UnclosedBracket)?, + Token::RightBrace => Err(ParserError::UnclosedBrace)?, + _ => Err(ParserError::Unreachable)?, }, } } @@ -99,10 +101,13 @@ fn read_list(tokens: &mut Peekable>, closer: Token) -> Result>, quote_type: &str) -> Result { +fn read_quote( + tokens: &mut Peekable>, + quote_type: &str, +) -> Result { let follower_node = match next_statement(tokens)? { Some(node) => node, - None => bail!("quote does not have a valid follower node"), + None => Err(ParserError::NoFollowerNode)?, }; Ok(Node::List(vec![