refactor: extract CLI to binary

Also start getting rid of anyhow
This commit is contained in:
Roman Godmaire 2024-05-05 11:16:18 -04:00
parent e9c350e925
commit 7e4166386f
6 changed files with 96 additions and 45 deletions

View file

@ -1,10 +1,12 @@
[package] [package]
name = "mute" name = "mute"
version = "0.1.0" version = "0.1.0"
author = "Raine Godmaire <its@raine.ing>" authors = ["Raine Godmaire <its@raine.ing>"]
edition = "2021" 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] [dependencies]
anyhow = "1.0.75" anyhow = "1.0.75"

View file

@ -1,14 +1,8 @@
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use node::Node;
use rustyline::{error::ReadlineError, DefaultEditor}; use rustyline::{error::ReadlineError, DefaultEditor};
mod env; use mute::{eval, parse, Environment, Node};
mod error;
mod evaluator;
mod macros;
mod node;
mod parser;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[clap(version, author, about)] #[clap(version, author, about)]
@ -35,14 +29,16 @@ fn main() {
match args.command { match args.command {
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 res = run(&input); let env = Environment::new();
let res = run(&env, &input);
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),
} }
} }
Command::RunCommand { command } => { Command::RunCommand { command } => {
let res = run(&command); let env = Environment::new();
let res = run(&env, &command);
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),
@ -52,14 +48,13 @@ fn main() {
} }
} }
fn run(command: &str) -> Result<Vec<Node>> { fn run(env: &Environment, command: &str) -> Result<Vec<Node>> {
let env = env::Environment::new(); let ast = parse(command)?;
let ast = parser::parse_str(command)?; eval(&env, ast)
evaluator::eval(&env, ast)
} }
fn repl() { fn repl() {
let env = env::Environment::new(); let env = Environment::new();
let mut rl = DefaultEditor::new().unwrap(); let mut rl = DefaultEditor::new().unwrap();
println!("Mute -- REPL"); println!("Mute -- REPL");
@ -71,12 +66,9 @@ fn repl() {
Ok(line) => { Ok(line) => {
rl.add_history_entry(line.as_str()).unwrap(); rl.add_history_entry(line.as_str()).unwrap();
let ast = parser::parse_str(&line).unwrap(); match run(&env, &line) {
let res = evaluator::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!("{}", err), Err(err) => println!("error: {}", err),
} }
} }
Err(ReadlineError::Interrupted) => { Err(ReadlineError::Interrupted) => {

View file

@ -1,7 +1,12 @@
use thiserror::Error; use thiserror::Error;
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")] #[error("could not find symbol '{0}' in environment")]
NotInEnv(String), NotInEnv(String),
#[error("expression does not have a valid operator")] #[error("expression does not have a valid operator")]
@ -18,4 +23,33 @@ pub enum Error {
ExpectedString, ExpectedString,
#[error("expected {0} arguments, got {1}")] #[error("expected {0} arguments, got {1}")]
MismatchedArgCount(usize, usize), 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,
} }

18
src/lib.rs Normal file
View file

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

View file

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

View file

@ -1,24 +1,26 @@
use std::{iter::Peekable, vec::IntoIter}; use std::{iter::Peekable, vec::IntoIter};
use anyhow::{bail, Result};
mod lexer; mod lexer;
use lexer::Token; use lexer::Token;
use crate::node::Node; use crate::error::ParserError;
use crate::{Error, Node};
pub fn parse_str(input: &str) -> Result<Vec<Node>> { pub fn parse_str(input: &str) -> crate::Result<Vec<Node>> {
let mut tokens = lexer::read(input)?.into_iter().peekable(); let mut tokens = lexer::read(input)
.map_err(|err| Error::ParserError(err))?
.into_iter()
.peekable();
let mut ast = Vec::new(); 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) ast.push(node)
} }
Ok(ast) Ok(ast)
} }
fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>> { fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>, ParserError> {
let tok = match tokens.next() { let tok = match tokens.next() {
Some(tok) => tok, Some(tok) => tok,
None => return Ok(None), None => return Ok(None),
@ -26,13 +28,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 => bail!("closing parenthsis does not have matching open parenthesis"), Token::RightParen => Err(ParserError::UnopenedParenthesis)?,
Token::LeftBracket => read_list(tokens, Token::RightBracket)?, 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::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::WeirdSign => read_quote(tokens, "splice-unquote")?,
Token::Apostrophe => read_quote(tokens, "quote")?, Token::Apostrophe => read_quote(tokens, "quote")?,
@ -66,12 +68,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> { fn read_list(tokens: &mut Peekable<IntoIter<Token>>, closer: Token) -> Result<Node, ParserError> {
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())],
_ => bail!("unreachable"), _ => Err(ParserError::Unreachable)?,
}; };
loop { loop {
@ -88,10 +90,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 => bail!("unclosed list"), Token::RightParen => Err(ParserError::UnclosedParenthesis)?,
Token::RightBracket => bail!("unclosed vector"), Token::RightBracket => Err(ParserError::UnclosedBracket)?,
Token::RightBrace => bail!("unclosed hashmap"), Token::RightBrace => Err(ParserError::UnclosedBrace)?,
_ => bail!("unreachable"), _ => Err(ParserError::Unreachable)?,
}, },
} }
} }
@ -99,10 +101,13 @@ fn read_list(tokens: &mut Peekable<IntoIter<Token>>, closer: Token) -> Result<No
Ok(Node::List(list)) Ok(Node::List(list))
} }
fn read_quote(tokens: &mut Peekable<IntoIter<Token>>, quote_type: &str) -> Result<Node> { fn read_quote(
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 => bail!("quote does not have a valid follower node"), None => Err(ParserError::NoFollowerNode)?,
}; };
Ok(Node::List(vec![ Ok(Node::List(vec![