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]
name = "mute"
version = "0.1.0"
author = "Raine Godmaire <its@raine.ing>"
authors = ["Raine Godmaire <its@raine.ing>"]
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"

View file

@ -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<Vec<Node>> {
let env = env::Environment::new();
let ast = parser::parse_str(command)?;
evaluator::eval(&env, ast)
fn run(env: &Environment, command: &str) -> Result<Vec<Node>> {
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) => {

View file

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

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 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<Vec<Token>> {
pub fn read(input: &str) -> Result<Vec<Token>, ParserError> {
let mut input = input.chars().peekable();
let mut tokens = Vec::new();
@ -52,7 +52,7 @@ pub fn read(input: &str) -> Result<Vec<Token>> {
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() {
Some(tok) => tok,
None => return Ok(None),
@ -128,13 +128,13 @@ fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>> {
Some(tok) => tok,
None => return Ok(None),
},
_ => bail!("illegal token"),
c => Err(ParserError::UnexpectedChar(c))?,
};
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();
loop {
@ -146,7 +146,7 @@ fn read_string(input: &mut Peekable<Chars>) -> Result<Token> {
}
Some(_) => (),
None => bail!("unbalanced string"),
None => Err(ParserError::UnterminatedString)?,
}
raw_str.push(input.next().unwrap())

View file

@ -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<Vec<Node>> {
let mut tokens = lexer::read(input)?.into_iter().peekable();
pub fn parse_str(input: &str) -> crate::Result<Vec<Node>> {
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<IntoIter<Token>>) -> Result<Option<Node>> {
fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>, ParserError> {
let tok = match tokens.next() {
Some(tok) => tok,
None => return Ok(None),
@ -26,13 +28,13 @@ fn next_statement(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Option<Node>
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<IntoIter<Token>>) -> Result<Option<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 {
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<IntoIter<Token>>, closer: Token) -> Result<No
match next_statement(tokens)? {
Some(node) => 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<IntoIter<Token>>, closer: Token) -> Result<No
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)? {
Some(node) => node,
None => bail!("quote does not have a valid follower node"),
None => Err(ParserError::NoFollowerNode)?,
};
Ok(Node::List(vec![