From f1083009a4caae123f2a4359a0eda535f42456ae Mon Sep 17 00:00:00 2001 From: Roman Godmaire Date: Mon, 6 May 2024 21:51:22 -0400 Subject: [PATCH] refactor: split parser and interpreter into separate crates --- Cargo.lock | 19 +++- Cargo.toml | 15 +-- mute-interpreter/Cargo.toml | 11 ++ {src => mute-interpreter/src}/env/core.rs | 3 +- {src => mute-interpreter/src}/env/io.rs | 3 +- {src => mute-interpreter/src}/env/mod.rs | 2 +- mute-interpreter/src/error.rs | 28 +++++ {src => mute-interpreter/src}/evaluator.rs | 8 +- mute-interpreter/src/lib.rs | 15 +++ {src => mute-interpreter/src}/macros.rs | 0 mute-interpreter/src/node.rs | 110 ++++++++++++++++++++ mute-parser/Cargo.toml | 10 ++ {src => mute-parser/src}/error.rs | 28 ----- {src/parser => mute-parser/src}/lexer.rs | 12 +-- src/parser/mod.rs => mute-parser/src/lib.rs | 49 ++++----- {src => mute-parser/src}/node.rs | 26 ----- src/lib.rs | 18 ---- src/{bin/cli.rs => main.rs} | 36 +++++-- 18 files changed, 265 insertions(+), 128 deletions(-) create mode 100644 mute-interpreter/Cargo.toml rename {src => mute-interpreter/src}/env/core.rs (99%) rename {src => mute-interpreter/src}/env/io.rs (96%) rename {src => mute-interpreter/src}/env/mod.rs (98%) create mode 100644 mute-interpreter/src/error.rs rename {src => mute-interpreter/src}/evaluator.rs (97%) create mode 100644 mute-interpreter/src/lib.rs rename {src => mute-interpreter/src}/macros.rs (100%) create mode 100644 mute-interpreter/src/node.rs create mode 100644 mute-parser/Cargo.toml rename {src => mute-parser/src}/error.rs (52%) rename {src/parser => mute-parser/src}/lexer.rs (95%) rename src/parser/mod.rs => mute-parser/src/lib.rs (67%) rename {src => mute-parser/src}/node.rs (73%) delete mode 100644 src/lib.rs rename src/{bin/cli.rs => main.rs} (68%) diff --git a/Cargo.lock b/Cargo.lock index 278c195..1feca3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,8 +323,25 @@ name = "mute" version = "0.1.0" dependencies = [ "clap", - "rstest", + "mute-interpreter", + "mute-parser", "rustyline", +] + +[[package]] +name = "mute-interpreter" +version = "0.1.0" +dependencies = [ + "mute-parser", + "rstest", + "thiserror", +] + +[[package]] +name = "mute-parser" +version = "0.1.0" +dependencies = [ + "rstest", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index ce2eb80..cd44a38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,15 @@ version = "0.1.0" authors = ["Raine Godmaire "] edition = "2021" -[[bin]] -name = "mute" -path = "src/bin/cli.rs" +[workspace] +members = [ + ".", + "mute-parser", + "mute-interpreter", +] [dependencies] clap = { version = "4.5.4", features = ["derive"] } rustyline = "14.0.0" -thiserror = "1.0.48" - -[dev-dependencies] -rstest = "0.18.2" +mute-parser = { path = "./mute-parser" } +mute-interpreter = { path = "./mute-interpreter" } diff --git a/mute-interpreter/Cargo.toml b/mute-interpreter/Cargo.toml new file mode 100644 index 0000000..a5aeb2b --- /dev/null +++ b/mute-interpreter/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "mute-interpreter" +version = "0.1.0" +edition = "2021" + +[dependencies] +thiserror = "1.0.48" +mute-parser = { path = "../mute-parser" } + +[dev-dependencies] +rstest = "0.18.2" diff --git a/src/env/core.rs b/mute-interpreter/src/env/core.rs similarity index 99% rename from src/env/core.rs rename to mute-interpreter/src/env/core.rs index bb9d5f8..b497b2c 100644 --- a/src/env/core.rs +++ b/mute-interpreter/src/env/core.rs @@ -1,10 +1,9 @@ use std::borrow::Borrow; use std::collections::HashMap; -use crate::error::Error; +use crate::{Error, Node}; use crate::evaluator::{eval, eval_node}; use crate::macros::arg_count; -use crate::node::Node; pub(super) fn core() -> HashMap { [ diff --git a/src/env/io.rs b/mute-interpreter/src/env/io.rs similarity index 96% rename from src/env/io.rs rename to mute-interpreter/src/env/io.rs index 2150f29..5768df7 100644 --- a/src/env/io.rs +++ b/mute-interpreter/src/env/io.rs @@ -1,9 +1,8 @@ use std::borrow::Borrow; use std::collections::HashMap; -use crate::error::Error; use crate::macros::arg_count; -use crate::node::Node; +use crate::{Error, Node}; pub(super) fn io() -> HashMap { [ diff --git a/src/env/mod.rs b/mute-interpreter/src/env/mod.rs similarity index 98% rename from src/env/mod.rs rename to mute-interpreter/src/env/mod.rs index 0a0ed58..6c27822 100644 --- a/src/env/mod.rs +++ b/mute-interpreter/src/env/mod.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use crate::node::Node; +use crate::Node; mod core; mod io; diff --git a/mute-interpreter/src/error.rs b/mute-interpreter/src/error.rs new file mode 100644 index 0000000..31d47f7 --- /dev/null +++ b/mute-interpreter/src/error.rs @@ -0,0 +1,28 @@ +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("could not find symbol '{0}' in environment")] + NotInEnv(String), + #[error("expression does not have a valid operator")] + InvalidOperator, + #[error("expected symbol")] + ExpectedSymbol, + #[error("expected list, vector, or map")] + ExpectedCollection, + #[error("expected list")] + ExpectedList, + #[error("expected number")] + ExpectedNumber, + #[error("expected string")] + ExpectedString, + #[error("expected {0} arguments, got {1}")] + MismatchedArgCount(usize, usize), + #[error("system error {0}")] + SystemError(String), + + #[error("please file a bug report")] + Unreachable, +} diff --git a/src/evaluator.rs b/mute-interpreter/src/evaluator.rs similarity index 97% rename from src/evaluator.rs rename to mute-interpreter/src/evaluator.rs index cbc5c10..f021e23 100644 --- a/src/evaluator.rs +++ b/mute-interpreter/src/evaluator.rs @@ -4,7 +4,7 @@ use crate::Result; use crate::env::Environment; use crate::error::Error; -use crate::node::Node; +use crate::Node; pub fn eval(env: &Environment, ast: Vec) -> Result> { let mut exprs = Vec::new(); @@ -80,7 +80,7 @@ pub fn eval_node(env: &Environment, ast_node: Node) -> Result { #[cfg(test)] mod test { - use crate::parser; + use mute_parser::parse_str; use super::*; use rstest::rstest; @@ -204,7 +204,7 @@ mod test { fn test_evaluator(#[case] input: &str, #[case] expected: &str) { dbg!(input); let env = Environment::default(); - let ast = parser::parse_str(input).unwrap(); + let ast = parse_str(input).unwrap(); let res = eval(&env, ast) .unwrap() .into_iter() @@ -221,7 +221,7 @@ mod test { #[case("{:a}")] fn test_evaluator_fail(#[case] input: &str) { let env = Environment::default(); - let ast = parser::parse_str(input).unwrap(); + let ast = parse_str(input).unwrap(); let res = eval(&env, ast); assert!(res.is_err()) diff --git a/mute-interpreter/src/lib.rs b/mute-interpreter/src/lib.rs new file mode 100644 index 0000000..c82ee0e --- /dev/null +++ b/mute-interpreter/src/lib.rs @@ -0,0 +1,15 @@ +// Crate Exports +mod env; +mod error; +mod evaluator; +mod macros; +mod node; + +pub use env::Environment; +pub use error::{Error, Result}; +pub use node::Node; + +pub fn eval(env: &Environment, ast: Vec) -> Result> { + let ast = ast.into_iter().map(Into::into).collect(); + evaluator::eval(env, ast) +} diff --git a/src/macros.rs b/mute-interpreter/src/macros.rs similarity index 100% rename from src/macros.rs rename to mute-interpreter/src/macros.rs diff --git a/mute-interpreter/src/node.rs b/mute-interpreter/src/node.rs new file mode 100644 index 0000000..38fb8a2 --- /dev/null +++ b/mute-interpreter/src/node.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; + +use crate::Environment; +use crate::Result; + +#[derive(Debug, Clone)] +pub enum Node { + List(Vec), + + Symbol(String), + Keyword(String), + Int(i64), + Int128(i128), + Float(f64), + String(String), + Boolean(bool), + Nil, + + Vector(Vec), + Map(HashMap), + Function { + params: Vec, + env: Environment, + body: Box, + }, + NativeFunc(fn(env: &Environment, args: Vec) -> Result), + Special(fn(env: &Environment, args: Vec) -> Result), + + Void, +} + +impl From for Node { + fn from(value: mute_parser::Node) -> Self { + match value { + mute_parser::Node::List(list) => Node::List(list.into_iter().map(Into::into).collect()), + mute_parser::Node::Symbol(symbol) => Node::Symbol(symbol), + mute_parser::Node::Keyword(keyword) => Node::Keyword(keyword), + mute_parser::Node::Int(int) => Node::Int(int), + mute_parser::Node::Int128(int) => Node::Int128(int), + mute_parser::Node::Float(float) => Node::Float(float), + mute_parser::Node::String(string) => Node::String(string), + mute_parser::Node::Boolean(boolean) => Node::Boolean(boolean), + mute_parser::Node::Nil => Node::Nil, + mute_parser::Node::Vector(vector) => { + Node::Vector(vector.into_iter().map(Into::into).collect()) + } + mute_parser::Node::Map(map) => { + Node::Map(map.into_iter().map(|(k, v)| (k, v.into())).collect()) + } + } + } +} + +impl std::fmt::Display for Node { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Node::List(list) => { + let s = list + .iter() + .map(|elem| elem.to_string()) + .reduce(|lhs, rhs| format!("{lhs} {rhs}")) + .unwrap_or_default(); + + write!(f, "({s})") + } + + Node::Int(val) => write!(f, "{}", val), + Node::Int128(val) => write!(f, "{}", val), + Node::Float(val) => write!(f, "{}", val), + Node::Boolean(true) => write!(f, "true"), + Node::Boolean(false) => write!(f, "false"), + Node::Symbol(val) => write!(f, "{}", val), + Node::Keyword(val) => write!(f, ":{}", val), + Node::String(val) => write!(f, "{}", val), + Node::Nil => write!(f, "()"), + + Node::Vector(vec) => { + let s = vec + .iter() + .map(|elem| elem.to_string()) + .reduce(|lhs, rhs| format!("{lhs} {rhs}")) + .unwrap_or_default(); + + write!(f, "[{s}]") + } + Node::Map(map) => { + let res = map + .iter() + .map(|(k, v)| format!("{k}: {v}")) + .reduce(|lhs, rhs| format!("{lhs}, {rhs}")) + .unwrap_or_default(); + + write!(f, "{{{res}}}") + } + + Node::Function { + params: _, + env: _, + body: _, + } => { + write!(f, "#") + } + + Node::NativeFunc(func) => write!(f, "{func:?}"), + Node::Special(func) => write!(f, "{func:?}"), + + Node::Void => write!(f, ""), + } + } +} diff --git a/mute-parser/Cargo.toml b/mute-parser/Cargo.toml new file mode 100644 index 0000000..6574c5b --- /dev/null +++ b/mute-parser/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "mute-parser" +version = "0.1.0" +edition = "2021" + +[dependencies] +thiserror = "1.0.48" + +[dev-dependencies] +rstest = "0.18.2" diff --git a/src/error.rs b/mute-parser/src/error.rs similarity index 52% rename from src/error.rs rename to mute-parser/src/error.rs index c206c0b..450e0d1 100644 --- a/src/error.rs +++ b/mute-parser/src/error.rs @@ -4,34 +4,6 @@ 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")] - InvalidOperator, - #[error("expected symbol")] - ExpectedSymbol, - #[error("expected list, vector, or map")] - ExpectedCollection, - #[error("expected list")] - ExpectedList, - #[error("expected number")] - ExpectedNumber, - #[error("expected string")] - ExpectedString, - #[error("expected {0} arguments, got {1}")] - MismatchedArgCount(usize, usize), - #[error("system error {0}")] - SystemError(String), - - #[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")] diff --git a/src/parser/lexer.rs b/mute-parser/src/lexer.rs similarity index 95% rename from src/parser/lexer.rs rename to mute-parser/src/lexer.rs index 133afc8..5de445f 100644 --- a/src/parser/lexer.rs +++ b/mute-parser/src/lexer.rs @@ -1,6 +1,6 @@ use std::{iter::Peekable, str::Chars}; -use crate::error::ParserError; +use crate::Error; #[derive(Debug, PartialEq, PartialOrd)] pub enum Token { @@ -42,7 +42,7 @@ pub enum Token { Nil, } -pub fn read(input: &str) -> Result, ParserError> { +pub fn read(input: &str) -> Result, Error> { let mut input = input.chars().peekable(); let mut tokens = Vec::new(); @@ -53,7 +53,7 @@ pub fn read(input: &str) -> Result, ParserError> { Ok(tokens) } -fn next_token(input: &mut Peekable) -> Result, ParserError> { +fn next_token(input: &mut Peekable) -> Result, Error> { let tok = match input.next() { Some(tok) => tok, None => return Ok(None), @@ -129,13 +129,13 @@ fn next_token(input: &mut Peekable) -> Result, ParserError> Some(tok) => tok, None => return Ok(None), }, - c => Err(ParserError::UnexpectedChar(c))?, + c => Err(Error::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 { @@ -147,7 +147,7 @@ fn read_string(input: &mut Peekable) -> Result { } Some(_) => (), - None => Err(ParserError::UnterminatedString)?, + None => Err(Error::UnterminatedString)?, } raw_str.push(input.next().unwrap()) diff --git a/src/parser/mod.rs b/mute-parser/src/lib.rs similarity index 67% rename from src/parser/mod.rs rename to mute-parser/src/lib.rs index d711fb5..8f411fa 100644 --- a/src/parser/mod.rs +++ b/mute-parser/src/lib.rs @@ -1,26 +1,30 @@ +// Crate exports +mod error; +mod lexer; +mod node; + +pub use error::{Error, Result}; +pub use node::Node; + +// Actual imports use std::{iter::Peekable, vec::IntoIter}; -mod lexer; use lexer::Token; -use crate::error::ParserError; -use crate::{Error, Node}; +type TokenIter = Peekable>; -pub fn parse_str(input: &str) -> crate::Result> { - let mut tokens = lexer::read(input) - .map_err(Error::ParserError)? - .into_iter() - .peekable(); +pub fn parse_str(input: &str) -> Result> { + let mut tokens: TokenIter = lexer::read(input)?.into_iter().peekable(); let mut ast = Vec::new(); - while let Some(node) = next_statement(&mut tokens).map_err(Error::ParserError)? { + while let Some(node) = next_statement(&mut tokens)? { ast.push(node) } Ok(ast) } -fn next_statement(tokens: &mut Peekable>) -> Result, ParserError> { +fn next_statement(tokens: &mut TokenIter) -> Result> { let tok = match tokens.next() { Some(tok) => tok, None => return Ok(None), @@ -28,13 +32,13 @@ fn next_statement(tokens: &mut Peekable>) -> Result let node = match tok { Token::LeftParen => read_list(tokens, Token::RightParen)?, - Token::RightParen => Err(ParserError::UnopenedParenthesis)?, + Token::RightParen => Err(Error::UnopenedParenthesis)?, Token::LeftBracket => read_list(tokens, Token::RightBracket)?, - Token::RightBracket => Err(ParserError::UnopenedBracket)?, + Token::RightBracket => Err(Error::UnopenedBracket)?, Token::LeftBrace => read_list(tokens, Token::RightBrace)?, - Token::RightBrace => Err(ParserError::UnopenedBrace)?, + Token::RightBrace => Err(Error::UnopenedBrace)?, Token::WeirdSign => read_quote(tokens, "splice-unquote")?, Token::Apostrophe => read_quote(tokens, "quote")?, @@ -69,12 +73,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 TokenIter, 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())], - _ => Err(ParserError::Unreachable)?, + _ => Err(Error::Unreachable)?, }; loop { @@ -91,10 +95,10 @@ fn read_list(tokens: &mut Peekable>, closer: Token) -> Result list.push(node), None => match closer { - Token::RightParen => Err(ParserError::UnclosedParenthesis)?, - Token::RightBracket => Err(ParserError::UnclosedBracket)?, - Token::RightBrace => Err(ParserError::UnclosedBrace)?, - _ => Err(ParserError::Unreachable)?, + Token::RightParen => Err(Error::UnclosedParenthesis)?, + Token::RightBracket => Err(Error::UnclosedBracket)?, + Token::RightBrace => Err(Error::UnclosedBrace)?, + _ => Err(Error::Unreachable)?, }, } } @@ -102,13 +106,10 @@ fn read_list(tokens: &mut Peekable>, closer: Token) -> Result>, - quote_type: &str, -) -> Result { +fn read_quote(tokens: &mut TokenIter, quote_type: &str) -> Result { let follower_node = match next_statement(tokens)? { Some(node) => node, - None => Err(ParserError::NoFollowerNode)?, + None => Err(Error::NoFollowerNode)?, }; Ok(Node::List(vec![ diff --git a/src/node.rs b/mute-parser/src/node.rs similarity index 73% rename from src/node.rs rename to mute-parser/src/node.rs index 940daf8..8a3aa60 100644 --- a/src/node.rs +++ b/mute-parser/src/node.rs @@ -1,9 +1,5 @@ use std::collections::HashMap; -use crate::Result; - -use crate::env::Environment; - #[derive(Debug, Clone)] pub enum Node { List(Vec), @@ -19,15 +15,6 @@ pub enum Node { Vector(Vec), Map(HashMap), - Function { - params: Vec, - env: Environment, - body: Box, - }, - NativeFunc(fn(env: &Environment, args: Vec) -> Result), - Special(fn(env: &Environment, args: Vec) -> Result), - - Void, } impl std::fmt::Display for Node { @@ -71,19 +58,6 @@ impl std::fmt::Display for Node { write!(f, "{{{res}}}") } - - Node::Function { - params: _, - env: _, - body: _, - } => { - write!(f, "#") - } - - Node::NativeFunc(func) => write!(f, "{func:?}"), - Node::Special(func) => write!(f, "{func:?}"), - - Node::Void => write!(f, ""), } } } diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index a2f0ef3..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -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) -> Result> { - evaluator::eval(env, input) -} diff --git a/src/bin/cli.rs b/src/main.rs similarity index 68% rename from src/bin/cli.rs rename to src/main.rs index cdd5a76..c45ea53 100644 --- a/src/bin/cli.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use clap::{Parser, Subcommand}; use rustyline::{error::ReadlineError, DefaultEditor}; -use mute::{eval, parse, Environment, Node, Result}; +use mute_interpreter::{eval, Environment}; +use mute_parser::parse_str; #[derive(Debug, Parser)] #[clap(version, author, about)] @@ -29,7 +30,14 @@ fn main() { Command::Run { file } => { let input = std::fs::read_to_string(file).unwrap(); let env = Environment::default(); - let res = run(&env, &input); + let ast = match parse_str(&input) { + Ok(ast) => ast, + Err(err) => { + println!("error: {}", err); + return; + } + }; + let res = eval(&env, ast); match res { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Err(err) => println!("{}", err), @@ -37,7 +45,14 @@ fn main() { } Command::RunCmd { command } => { let env = Environment::default(); - let res = run(&env, &command); + let ast = match parse_str(&command) { + Ok(ast) => ast, + Err(err) => { + println!("error: {}", err); + return; + } + }; + let res = eval(&env, ast); match res { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Err(err) => println!("{}", err), @@ -47,11 +62,6 @@ fn main() { } } -fn run(env: &Environment, command: &str) -> Result> { - let ast = parse(command)?; - eval(env, ast) -} - fn repl() { let env = Environment::default(); let mut rl = DefaultEditor::new().unwrap(); @@ -65,7 +75,15 @@ fn repl() { Ok(line) => { rl.add_history_entry(line.as_str()).unwrap(); - match run(&env, &line) { + let ast = match parse_str(&line) { + Ok(ast) => ast, + Err(err) => { + println!("error: {}", err); + return; + } + }; + let res = eval(&env, ast); + match res { Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Err(err) => println!("error: {}", err), }