refactor: split parser and interpreter into separate crates

This commit is contained in:
Roman Godmaire 2024-05-06 21:51:22 -04:00
parent e49dbab859
commit f1083009a4
18 changed files with 265 additions and 128 deletions

19
Cargo.lock generated
View file

@ -323,8 +323,25 @@ name = "mute"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"rstest", "mute-interpreter",
"mute-parser",
"rustyline", "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", "thiserror",
] ]

View file

@ -4,14 +4,15 @@ version = "0.1.0"
authors = ["Raine Godmaire <its@raine.ing>"] authors = ["Raine Godmaire <its@raine.ing>"]
edition = "2021" edition = "2021"
[[bin]] [workspace]
name = "mute" members = [
path = "src/bin/cli.rs" ".",
"mute-parser",
"mute-interpreter",
]
[dependencies] [dependencies]
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
rustyline = "14.0.0" rustyline = "14.0.0"
thiserror = "1.0.48" mute-parser = { path = "./mute-parser" }
mute-interpreter = { path = "./mute-interpreter" }
[dev-dependencies]
rstest = "0.18.2"

View file

@ -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"

View file

@ -1,10 +1,9 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use crate::error::Error; use crate::{Error, Node};
use crate::evaluator::{eval, eval_node}; use crate::evaluator::{eval, eval_node};
use crate::macros::arg_count; use crate::macros::arg_count;
use crate::node::Node;
pub(super) fn core() -> HashMap<String, Node> { pub(super) fn core() -> HashMap<String, Node> {
[ [

View file

@ -1,9 +1,8 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use crate::error::Error;
use crate::macros::arg_count; use crate::macros::arg_count;
use crate::node::Node; use crate::{Error, Node};
pub(super) fn io() -> HashMap<String, Node> { pub(super) fn io() -> HashMap<String, Node> {
[ [

View file

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::node::Node; use crate::Node;
mod core; mod core;
mod io; mod io;

View file

@ -0,0 +1,28 @@
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[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,
}

View file

@ -4,7 +4,7 @@ use crate::Result;
use crate::env::Environment; use crate::env::Environment;
use crate::error::Error; use crate::error::Error;
use crate::node::Node; use crate::Node;
pub fn eval(env: &Environment, ast: Vec<Node>) -> Result<Vec<Node>> { pub fn eval(env: &Environment, ast: Vec<Node>) -> Result<Vec<Node>> {
let mut exprs = Vec::new(); let mut exprs = Vec::new();
@ -80,7 +80,7 @@ pub fn eval_node(env: &Environment, ast_node: Node) -> Result<Node> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::parser; use mute_parser::parse_str;
use super::*; use super::*;
use rstest::rstest; use rstest::rstest;
@ -204,7 +204,7 @@ mod test {
fn test_evaluator(#[case] input: &str, #[case] expected: &str) { fn test_evaluator(#[case] input: &str, #[case] expected: &str) {
dbg!(input); dbg!(input);
let env = Environment::default(); let env = Environment::default();
let ast = parser::parse_str(input).unwrap(); let ast = parse_str(input).unwrap();
let res = eval(&env, ast) let res = eval(&env, ast)
.unwrap() .unwrap()
.into_iter() .into_iter()
@ -221,7 +221,7 @@ mod test {
#[case("{:a}")] #[case("{:a}")]
fn test_evaluator_fail(#[case] input: &str) { fn test_evaluator_fail(#[case] input: &str) {
let env = Environment::default(); let env = Environment::default();
let ast = parser::parse_str(input).unwrap(); let ast = parse_str(input).unwrap();
let res = eval(&env, ast); let res = eval(&env, ast);
assert!(res.is_err()) assert!(res.is_err())

View file

@ -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<mute_parser::Node>) -> Result<Vec<Node>> {
let ast = ast.into_iter().map(Into::into).collect();
evaluator::eval(env, ast)
}

View file

@ -0,0 +1,110 @@
use std::collections::HashMap;
use crate::Environment;
use crate::Result;
#[derive(Debug, Clone)]
pub enum Node {
List(Vec<Node>),
Symbol(String),
Keyword(String),
Int(i64),
Int128(i128),
Float(f64),
String(String),
Boolean(bool),
Nil,
Vector(Vec<Node>),
Map(HashMap<String, Node>),
Function {
params: Vec<String>,
env: Environment,
body: Box<Node>,
},
NativeFunc(fn(env: &Environment, args: Vec<Node>) -> Result<Node>),
Special(fn(env: &Environment, args: Vec<Node>) -> Result<Node>),
Void,
}
impl From<mute_parser::Node> 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, "#<function>")
}
Node::NativeFunc(func) => write!(f, "{func:?}"),
Node::Special(func) => write!(f, "{func:?}"),
Node::Void => write!(f, ""),
}
}
}

10
mute-parser/Cargo.toml Normal file
View file

@ -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"

View file

@ -4,34 +4,6 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum 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")] #[error("closing parenthesis does not have matching open parenthesis")]
UnopenedParenthesis, UnopenedParenthesis,
#[error("unclosed list")] #[error("unclosed list")]

View file

@ -1,6 +1,6 @@
use std::{iter::Peekable, str::Chars}; use std::{iter::Peekable, str::Chars};
use crate::error::ParserError; use crate::Error;
#[derive(Debug, PartialEq, PartialOrd)] #[derive(Debug, PartialEq, PartialOrd)]
pub enum Token { pub enum Token {
@ -42,7 +42,7 @@ pub enum Token {
Nil, Nil,
} }
pub fn read(input: &str) -> Result<Vec<Token>, ParserError> { pub fn read(input: &str) -> Result<Vec<Token>, Error> {
let mut input = input.chars().peekable(); let mut input = input.chars().peekable();
let mut tokens = Vec::new(); let mut tokens = Vec::new();
@ -53,7 +53,7 @@ pub fn read(input: &str) -> Result<Vec<Token>, ParserError> {
Ok(tokens) Ok(tokens)
} }
fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>, ParserError> { fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>, Error> {
let tok = match input.next() { let tok = match input.next() {
Some(tok) => tok, Some(tok) => tok,
None => return Ok(None), None => return Ok(None),
@ -129,13 +129,13 @@ fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>, ParserError>
Some(tok) => tok, Some(tok) => tok,
None => return Ok(None), None => return Ok(None),
}, },
c => Err(ParserError::UnexpectedChar(c))?, c => Err(Error::UnexpectedChar(c))?,
}; };
Ok(Some(tok)) Ok(Some(tok))
} }
fn read_string(input: &mut Peekable<Chars>) -> Result<Token, ParserError> { fn read_string(input: &mut Peekable<Chars>) -> Result<Token, Error> {
let mut raw_str = Vec::new(); let mut raw_str = Vec::new();
loop { loop {
@ -147,7 +147,7 @@ fn read_string(input: &mut Peekable<Chars>) -> Result<Token, ParserError> {
} }
Some(_) => (), Some(_) => (),
None => Err(ParserError::UnterminatedString)?, None => Err(Error::UnterminatedString)?,
} }
raw_str.push(input.next().unwrap()) raw_str.push(input.next().unwrap())

View file

@ -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}; use std::{iter::Peekable, vec::IntoIter};
mod lexer;
use lexer::Token; use lexer::Token;
use crate::error::ParserError; type TokenIter = Peekable<IntoIter<Token>>;
use crate::{Error, Node};
pub fn parse_str(input: &str) -> crate::Result<Vec<Node>> { pub fn parse_str(input: &str) -> Result<Vec<Node>> {
let mut tokens = lexer::read(input) let mut tokens: TokenIter = lexer::read(input)?.into_iter().peekable();
.map_err(Error::ParserError)?
.into_iter()
.peekable();
let mut ast = Vec::new(); 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) ast.push(node)
} }
Ok(ast) Ok(ast)
} }
fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>, ParserError> { fn next_statement(tokens: &mut TokenIter) -> Result<Option<Node>> {
let tok = match tokens.next() { let tok = match tokens.next() {
Some(tok) => tok, Some(tok) => tok,
None => return Ok(None), None => return Ok(None),
@ -28,13 +32,13 @@ fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>
let node = match tok { let node = match tok {
Token::LeftParen => read_list(tokens, Token::RightParen)?, 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::LeftBracket => read_list(tokens, Token::RightBracket)?,
Token::RightBracket => Err(ParserError::UnopenedBracket)?, Token::RightBracket => Err(Error::UnopenedBracket)?,
Token::LeftBrace => read_list(tokens, Token::RightBrace)?, 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::WeirdSign => read_quote(tokens, "splice-unquote")?,
Token::Apostrophe => read_quote(tokens, "quote")?, Token::Apostrophe => read_quote(tokens, "quote")?,
@ -69,12 +73,12 @@ fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>
Ok(Some(node)) Ok(Some(node))
} }
fn read_list(tokens: &mut Peekable<IntoIter<Token>>, closer: Token) -> Result<Node, ParserError> { fn read_list(tokens: &mut TokenIter, closer: Token) -> Result<Node> {
let mut list = match closer { let mut list = match closer {
Token::RightParen => Vec::new(), Token::RightParen => Vec::new(),
Token::RightBracket => vec![Node::Symbol("vector".into())], Token::RightBracket => vec![Node::Symbol("vector".into())],
Token::RightBrace => vec![Node::Symbol("hashmap".into())], Token::RightBrace => vec![Node::Symbol("hashmap".into())],
_ => Err(ParserError::Unreachable)?, _ => Err(Error::Unreachable)?,
}; };
loop { loop {
@ -91,10 +95,10 @@ fn read_list(tokens: &mut Peekable<IntoIter<Token>>, closer: Token) -> Result<No
match next_statement(tokens)? { match next_statement(tokens)? {
Some(node) => list.push(node), Some(node) => list.push(node),
None => match closer { None => match closer {
Token::RightParen => Err(ParserError::UnclosedParenthesis)?, Token::RightParen => Err(Error::UnclosedParenthesis)?,
Token::RightBracket => Err(ParserError::UnclosedBracket)?, Token::RightBracket => Err(Error::UnclosedBracket)?,
Token::RightBrace => Err(ParserError::UnclosedBrace)?, Token::RightBrace => Err(Error::UnclosedBrace)?,
_ => Err(ParserError::Unreachable)?, _ => Err(Error::Unreachable)?,
}, },
} }
} }
@ -102,13 +106,10 @@ fn read_list(tokens: &mut Peekable<IntoIter<Token>>, closer: Token) -> Result<No
Ok(Node::List(list)) Ok(Node::List(list))
} }
fn read_quote( fn read_quote(tokens: &mut TokenIter, quote_type: &str) -> Result<Node> {
tokens: &mut Peekable<IntoIter<Token>>,
quote_type: &str,
) -> Result<Node, ParserError> {
let follower_node = match next_statement(tokens)? { let follower_node = match next_statement(tokens)? {
Some(node) => node, Some(node) => node,
None => Err(ParserError::NoFollowerNode)?, None => Err(Error::NoFollowerNode)?,
}; };
Ok(Node::List(vec![ Ok(Node::List(vec![

View file

@ -1,9 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::Result;
use crate::env::Environment;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Node { pub enum Node {
List(Vec<Node>), List(Vec<Node>),
@ -19,15 +15,6 @@ pub enum Node {
Vector(Vec<Node>), Vector(Vec<Node>),
Map(HashMap<String, Node>), Map(HashMap<String, Node>),
Function {
params: Vec<String>,
env: Environment,
body: Box<Node>,
},
NativeFunc(fn(env: &Environment, args: Vec<Node>) -> Result<Node>),
Special(fn(env: &Environment, args: Vec<Node>) -> Result<Node>),
Void,
} }
impl std::fmt::Display for Node { impl std::fmt::Display for Node {
@ -71,19 +58,6 @@ impl std::fmt::Display for Node {
write!(f, "{{{res}}}") write!(f, "{{{res}}}")
} }
Node::Function {
params: _,
env: _,
body: _,
} => {
write!(f, "#<function>")
}
Node::NativeFunc(func) => write!(f, "{func:?}"),
Node::Special(func) => write!(f, "{func:?}"),
Node::Void => write!(f, ""),
} }
} }
} }

View file

@ -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<Vec<Node>> {
parser::parse_str(input)
}
pub fn eval(env: &Environment, input: Vec<Node>) -> Result<Vec<Node>> {
evaluator::eval(env, input)
}

View file

@ -1,7 +1,8 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use rustyline::{error::ReadlineError, DefaultEditor}; 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)] #[derive(Debug, Parser)]
#[clap(version, author, about)] #[clap(version, author, about)]
@ -29,7 +30,14 @@ fn main() {
Command::Run { file } => { Command::Run { file } => {
let input = std::fs::read_to_string(file).unwrap(); let input = std::fs::read_to_string(file).unwrap();
let env = Environment::default(); 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 { match res {
Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")),
Err(err) => println!("{}", err), Err(err) => println!("{}", err),
@ -37,7 +45,14 @@ fn main() {
} }
Command::RunCmd { command } => { Command::RunCmd { command } => {
let env = Environment::default(); 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 { match res {
Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")), Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")),
Err(err) => println!("{}", err), Err(err) => println!("{}", err),
@ -47,11 +62,6 @@ fn main() {
} }
} }
fn run(env: &Environment, command: &str) -> Result<Vec<Node>> {
let ast = parse(command)?;
eval(env, ast)
}
fn repl() { fn repl() {
let env = Environment::default(); let env = Environment::default();
let mut rl = DefaultEditor::new().unwrap(); let mut rl = DefaultEditor::new().unwrap();
@ -65,7 +75,15 @@ fn repl() {
Ok(line) => { Ok(line) => {
rl.add_history_entry(line.as_str()).unwrap(); 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}")), Ok(expressions) => expressions.into_iter().for_each(|expr| println!("{expr}")),
Err(err) => println!("error: {}", err), Err(err) => println!("error: {}", err),
} }