Rework error handling and main

main
mat ess 2023-07-19 23:03:15 -04:00
parent 2ccc3e8ef1
commit 8af9855a27
9 changed files with 116 additions and 113 deletions

53
Cargo.lock generated
View File

@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.71" version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]] [[package]]
name = "ascii-canvas" name = "ascii-canvas"
@ -207,9 +207,9 @@ dependencies = [
[[package]] [[package]]
name = "lalrpop" name = "lalrpop"
version = "0.19.9" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f34313ec00c2eb5c3c87ca6732ea02dcf3af99c3ff7a8fb622ffb99c9d860a87" checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
dependencies = [ dependencies = [
"ascii-canvas", "ascii-canvas",
"bit-set", "bit-set",
@ -219,9 +219,9 @@ dependencies = [
"itertools", "itertools",
"lalrpop-util", "lalrpop-util",
"petgraph", "petgraph",
"pico-args 0.4.2", "pico-args",
"regex", "regex",
"regex-syntax 0.6.29", "regex-syntax",
"string_cache", "string_cache",
"term", "term",
"tiny-keccak", "tiny-keccak",
@ -230,9 +230,9 @@ dependencies = [
[[package]] [[package]]
name = "lalrpop-util" name = "lalrpop-util"
version = "0.19.9" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c1f7869c94d214466c5fd432dfed12c379fd87786768d36455892d46b18edd" checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
dependencies = [ dependencies = [
"regex", "regex",
] ]
@ -281,8 +281,9 @@ dependencies = [
"anyhow", "anyhow",
"lalrpop", "lalrpop",
"lalrpop-util", "lalrpop-util",
"pico-args 0.5.0", "pico-args",
"regex", "regex",
"thiserror",
] ]
[[package]] [[package]]
@ -339,12 +340,6 @@ dependencies = [
"siphasher", "siphasher",
] ]
[[package]]
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]] [[package]]
name = "pico-args" name = "pico-args"
version = "0.5.0" version = "0.5.0"
@ -359,18 +354,18 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.56" version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.26" version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -403,15 +398,9 @@ checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax 0.7.1", "regex-syntax",
] ]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.1" version = "0.7.1"
@ -471,9 +460,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.15" version = "2.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -493,18 +482,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.40" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.40" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -4,10 +4,11 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[build-dependencies] [build-dependencies]
lalrpop = "0.19.9" lalrpop = "0.20.0"
[dependencies] [dependencies]
anyhow = "1.0.71" anyhow = "1.0.72"
lalrpop-util = { version = "0.19.9", features = ["lexer"] } lalrpop-util = { version = "0.20.0", features = ["lexer"] }
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] } pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
regex = "1" regex = "1"
thiserror = "1.0.43"

View File

@ -1,45 +1,60 @@
use std::path::PathBuf; use std::{ffi::OsString, path::PathBuf};
use pico_args::Arguments; use pico_args::Arguments;
use thiserror::Error;
#[derive(Debug)] pub const USAGE: &str = "
USAGE:
mul [-h|--help] (parse <FILE> | evaluate <FILE> | repl)
FLAGS:
-h, --help Prints help information (this text)
COMMANDS:
parse <FILE> Parse the given file and print a serialized AST to stdout
evaluate, eval <FILE> Evaluate the given file
repl Start the Read-Evaluate-Print-Loop
";
#[derive(Debug, Error)]
pub enum Error { pub enum Error {
ArgsError(pico_args::Error), #[error("Problem while parsing arguments, {0}")]
ArgsError(#[from] pico_args::Error),
#[error("Invalid command '{0}'\n{USAGE}")]
InvalidCommand(String), InvalidCommand(String),
PrintUsage, #[error("Unrecognized arguments {0:?}\n{USAGE}")]
} UnrecognizedArguments(Vec<OsString>),
impl From<pico_args::Error> for Error {
fn from(value: pico_args::Error) -> Self {
Error::ArgsError(value)
}
} }
pub enum Command { pub enum Command {
Parse { source: PathBuf }, Parse { path: PathBuf },
Run { source: PathBuf }, Evaluate { path: PathBuf },
Repl, Repl,
} }
impl Command { impl Command {
pub fn from_environment() -> Result<Self, Error> { pub fn from_environment() -> Result<Option<Self>, Error> {
let mut args = Arguments::from_env(); let mut args = Arguments::from_env();
if args.contains(["-h", "--help"]) { if args.contains(["-h", "--help"]) {
return Err(Error::PrintUsage); return Ok(None);
} }
let cmd = match args.subcommand()?.as_deref() { let cmd = match args.subcommand()?.as_deref() {
Some("parse") => { Some("parse") => {
let source = args.free_from_str()?; let path = args.free_from_str()?;
Command::Parse { source } Command::Parse { path }
} }
Some("run") => { Some("evaluate") | Some("eval") => {
let source = args.free_from_str()?; let path = args.free_from_str()?;
Command::Run { source } Command::Evaluate { path }
} }
Some("repl") => Command::Repl, Some("repl") | None => Command::Repl,
Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())), Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())),
None => return Err(Error::PrintUsage),
}; };
Ok(cmd) let remaining = args.finish();
if remaining.is_empty() {
Ok(Some(cmd))
} else {
Err(Error::UnrecognizedArguments(remaining))
}
} }
} }

View File

@ -1,75 +1,57 @@
use std::fs::read_to_string; use std::fs::read_to_string;
use std::io::{stdin, stdout, BufRead, Write}; use std::io::{stdin, stdout, BufRead, Write};
use std::path::Path;
use std::process::exit;
use mul::cli::{Command, Error}; use anyhow::{anyhow, Context, Result};
use mul::phase::parse::{parse_program, parse_repl_line};
fn main() -> Result<(), Error> { use mul::cli::{Command, USAGE};
let command = Command::from_environment()?; use mul::phase::parse::{parse_interactive_entry, parse_program};
fn main() -> Result<()> {
let Some(command) = Command::from_environment()? else {
eprintln!("mul\n{USAGE}");
return Ok(())
};
match command { match command {
Command::Parse { source } => parse(source.as_path()), Command::Parse { path } => {
Command::Run { source } => run(source.as_path()), let source = read_to_string(&path)
.with_context(|| format!("failed to open {}", path.display()))?;
let program = parse_program(source.as_str())
.map_err(|e| anyhow!("{e}"))
.with_context(|| format!("failed to parse {}", path.display()))?;
println!("{program:#?}");
Ok(())
}
Command::Evaluate { path } => {
todo!()
}
Command::Repl => repl(), Command::Repl => repl(),
} }
Ok(())
} }
fn parse(file: &Path) { fn repl() -> Result<()> {
let content = read_to_string(file).expect("Failed to read file");
let program = parse_program(content.as_str()).unwrap_or_else(|e| {
eprintln!("{e}");
exit(1);
});
println!("{program:#?}");
}
fn run(file: &Path) {
let content = read_to_string(file).expect("Failed to read file");
let program = parse_program(content.as_str()).unwrap_or_else(|e| {
eprintln!("{e}");
exit(1);
});
// let v = interpreter.interpret(&program).unwrap_or_else(|e| {
// eprintln!("{e}");
// exit(1);
// });
// println!("{v:?}")
}
fn repl() {
let mut stdin = stdin().lock(); let mut stdin = stdin().lock();
let mut stdout = stdout().lock(); let mut stdout = stdout().lock();
println!("welcome to M U L");
loop { loop {
let mut line = String::new(); let mut line = String::new();
print!("> "); print!("> ");
stdout.flush().unwrap(); stdout.flush()?;
stdin stdin
.read_line(&mut line) .read_line(&mut line)
.expect("Failed to read from stdin"); .context("Failed to read from stdin")?;
if line.trim().is_empty() { if line.trim().is_empty() {
println!(); println!();
continue; continue;
} }
if !line.trim_end().ends_with(';') { let parsed = match parse_interactive_entry(&line) {
line.push(';');
}
let parsed = match parse_repl_line(&line) {
Ok(parsed) => parsed, Ok(parsed) => parsed,
Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break, Err(lalrpop_util::ParseError::UnrecognizedEof { .. }) => break,
Err(e) => { Err(e) => {
eprintln!("{e}"); eprintln!("{e}");
continue; continue;
} }
}; };
println!("{parsed:?}"); println!("{parsed:?}");
// match intepreter.interpret_statement(&parsed) {
// Ok(value) => println!("{value:?}"),
// Err(e) => {
// eprintln!("{e}");
// continue;
// }
// };
} }
Ok(())
} }

View File

@ -1 +1,7 @@
pub mod parse; pub mod parse;
// |
// |
// syntax
// |
// v
pub mod evaluate;

1
src/phase/evaluate.rs Normal file
View File

@ -0,0 +1 @@

View File

@ -1,15 +1,15 @@
use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError}; use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
use crate::syntax::{self, Line}; use crate::syntax::{InteractiveEntry, Program};
lalrpop_mod!(parser, "/phase/parse/parser.rs"); lalrpop_mod!(parser, "/phase/parse/parser.rs");
pub type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>; pub type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
pub fn parse_program(input: &str) -> Result<syntax::Program> { pub fn parse_program(input: &'_ str) -> Result<'_, Program> {
parser::ProgramParser::new().parse(input) parser::ProgramParser::new().parse(input)
} }
pub fn parse_repl_line(input: &str) -> Result<Line> { pub fn parse_interactive_entry(input: &'_ str) -> Result<'_, InteractiveEntry> {
parser::LineParser::new().parse(input) parser::InteractiveEntryParser::new().parse(input)
} }

View File

@ -3,7 +3,7 @@ use crate::syntax::{
Block, Block,
Expression, Expression,
Item, Item,
Line, InteractiveEntry,
Name, Name,
Program, Program,
}; };
@ -12,10 +12,10 @@ grammar;
pub Program: Program = Item*; pub Program: Program = Item*;
pub Line: Line = { pub InteractiveEntry: InteractiveEntry = {
Item => Line::Item(<>), Item => InteractiveEntry::Item(<>),
Binding => Line::Binding(<>), Binding => InteractiveEntry::Binding(<>),
<Expression> ";"? => Line::Expression(<>), <Expression> ";"? => InteractiveEntry::Expression(<>),
} }
Item: Item = { Item: Item = {

View File

@ -1,14 +1,20 @@
//! Syntax elements.
/// Identifiers.
pub type Name = String; pub type Name = String;
/// The rough equivalent of a module.
pub type Program = Vec<Item>; pub type Program = Vec<Item>;
/// A fragment of code entered in, e.g., the REPL.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Line { pub enum InteractiveEntry {
Item(Item), Item(Item),
Binding(Binding), Binding(Binding),
Expression(Expression), Expression(Expression),
} }
/// A top level definition.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Item { pub enum Item {
Fn { Fn {
@ -18,9 +24,11 @@ pub enum Item {
}, },
} }
/// Syntactic mapping from an identifier to an expression.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Binding(pub Name, pub Expression); pub struct Binding(pub Name, pub Expression);
/// Some individually addressable syntax node, either atomic or compound.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Expression { pub enum Expression {
Variable(Name), Variable(Name),
@ -37,6 +45,7 @@ pub enum Expression {
Integer(i64), Integer(i64),
} }
/// A sequence of bindings with a final result.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Block { pub struct Block {
pub bindings: Vec<Binding>, pub bindings: Vec<Binding>,