refactor: extract CLI to binary
Also start getting rid of anyhow
This commit is contained in:
parent
e9c350e925
commit
7e4166386f
6 changed files with 96 additions and 45 deletions
|
@ -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"
|
||||||
|
|
|
@ -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) => {
|
34
src/error.rs
34
src/error.rs
|
@ -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
18
src/lib.rs
Normal 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)
|
||||||
|
}
|
|
@ -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())
|
||||||
|
|
|
@ -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![
|
||||||
|
|
Loading…
Reference in a new issue