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

View File

@ -4,10 +4,11 @@ version = "0.1.0"
edition = "2021"
[build-dependencies]
lalrpop = "0.19.9"
lalrpop = "0.20.0"
[dependencies]
anyhow = "1.0.71"
lalrpop-util = { version = "0.19.9", features = ["lexer"] }
anyhow = "1.0.72"
lalrpop-util = { version = "0.20.0", features = ["lexer"] }
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
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 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 {
ArgsError(pico_args::Error),
#[error("Problem while parsing arguments, {0}")]
ArgsError(#[from] pico_args::Error),
#[error("Invalid command '{0}'\n{USAGE}")]
InvalidCommand(String),
PrintUsage,
}
impl From<pico_args::Error> for Error {
fn from(value: pico_args::Error) -> Self {
Error::ArgsError(value)
}
#[error("Unrecognized arguments {0:?}\n{USAGE}")]
UnrecognizedArguments(Vec<OsString>),
}
pub enum Command {
Parse { source: PathBuf },
Run { source: PathBuf },
Parse { path: PathBuf },
Evaluate { path: PathBuf },
Repl,
}
impl Command {
pub fn from_environment() -> Result<Self, Error> {
pub fn from_environment() -> Result<Option<Self>, Error> {
let mut args = Arguments::from_env();
if args.contains(["-h", "--help"]) {
return Err(Error::PrintUsage);
return Ok(None);
}
let cmd = match args.subcommand()?.as_deref() {
Some("parse") => {
let source = args.free_from_str()?;
Command::Parse { source }
let path = args.free_from_str()?;
Command::Parse { path }
}
Some("run") => {
let source = args.free_from_str()?;
Command::Run { source }
Some("evaluate") | Some("eval") => {
let path = args.free_from_str()?;
Command::Evaluate { path }
}
Some("repl") => Command::Repl,
Some("repl") | None => Command::Repl,
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::io::{stdin, stdout, BufRead, Write};
use std::path::Path;
use std::process::exit;
use mul::cli::{Command, Error};
use mul::phase::parse::{parse_program, parse_repl_line};
use anyhow::{anyhow, Context, Result};
fn main() -> Result<(), Error> {
let command = Command::from_environment()?;
use mul::cli::{Command, USAGE};
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 {
Command::Parse { source } => parse(source.as_path()),
Command::Run { source } => run(source.as_path()),
Command::Repl => repl(),
}
Command::Parse { 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(())
}
fn parse(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);
});
println!("{program:#?}");
Command::Evaluate { path } => {
todo!()
}
Command::Repl => repl(),
}
}
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() {
fn repl() -> Result<()> {
let mut stdin = stdin().lock();
let mut stdout = stdout().lock();
println!("welcome to M U L");
loop {
let mut line = String::new();
print!("> ");
stdout.flush().unwrap();
stdout.flush()?;
stdin
.read_line(&mut line)
.expect("Failed to read from stdin");
.context("Failed to read from stdin")?;
if line.trim().is_empty() {
println!();
continue;
}
if !line.trim_end().ends_with(';') {
line.push(';');
}
let parsed = match parse_repl_line(&line) {
let parsed = match parse_interactive_entry(&line) {
Ok(parsed) => parsed,
Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break,
Err(lalrpop_util::ParseError::UnrecognizedEof { .. }) => break,
Err(e) => {
eprintln!("{e}");
continue;
}
};
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;
// |
// |
// 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 crate::syntax::{self, Line};
use crate::syntax::{InteractiveEntry, Program};
lalrpop_mod!(parser, "/phase/parse/parser.rs");
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)
}
pub fn parse_repl_line(input: &str) -> Result<Line> {
parser::LineParser::new().parse(input)
pub fn parse_interactive_entry(input: &'_ str) -> Result<'_, InteractiveEntry> {
parser::InteractiveEntryParser::new().parse(input)
}

View File

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

View File

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