diff --git a/Cargo.lock b/Cargo.lock index 5917d7e..51cfb67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index e3fa5d3..f895764 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/cli.rs b/src/cli.rs index 8761b84..7d9f634 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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 | evaluate | repl) + +FLAGS: + -h, --help Prints help information (this text) + +COMMANDS: + parse Parse the given file and print a serialized AST to stdout + evaluate, eval 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 for Error { - fn from(value: pico_args::Error) -> Self { - Error::ArgsError(value) - } + #[error("Unrecognized arguments {0:?}\n{USAGE}")] + UnrecognizedArguments(Vec), } pub enum Command { - Parse { source: PathBuf }, - Run { source: PathBuf }, + Parse { path: PathBuf }, + Evaluate { path: PathBuf }, Repl, } impl Command { - pub fn from_environment() -> Result { + pub fn from_environment() -> Result, 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)) + } } } diff --git a/src/main.rs b/src/main.rs index 608ffa3..c4ad52a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::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(()) + } + Command::Evaluate { path } => { + todo!() + } Command::Repl => repl(), } - 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:#?}"); -} - -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(()) } diff --git a/src/phase.rs b/src/phase.rs index ea86848..222657c 100644 --- a/src/phase.rs +++ b/src/phase.rs @@ -1 +1,7 @@ pub mod parse; +// | +// | +// syntax +// | +// v +pub mod evaluate; diff --git a/src/phase/evaluate.rs b/src/phase/evaluate.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/phase/evaluate.rs @@ -0,0 +1 @@ + diff --git a/src/phase/parse.rs b/src/phase/parse.rs index d288f7a..533878e 100644 --- a/src/phase/parse.rs +++ b/src/phase/parse.rs @@ -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, &'static str>>; -pub fn parse_program(input: &str) -> Result { +pub fn parse_program(input: &'_ str) -> Result<'_, Program> { parser::ProgramParser::new().parse(input) } -pub fn parse_repl_line(input: &str) -> Result { - parser::LineParser::new().parse(input) +pub fn parse_interactive_entry(input: &'_ str) -> Result<'_, InteractiveEntry> { + parser::InteractiveEntryParser::new().parse(input) } diff --git a/src/phase/parse/parser.lalrpop b/src/phase/parse/parser.lalrpop index e1bfe11..4aff32d 100644 --- a/src/phase/parse/parser.lalrpop +++ b/src/phase/parse/parser.lalrpop @@ -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(<>), - ";"? => Line::Expression(<>), +pub InteractiveEntry: InteractiveEntry = { + Item => InteractiveEntry::Item(<>), + Binding => InteractiveEntry::Binding(<>), + ";"? => InteractiveEntry::Expression(<>), } Item: Item = { diff --git a/src/syntax.rs b/src/syntax.rs index 28068ea..1e05b7c 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,14 +1,20 @@ +//! Syntax elements. + +/// Identifiers. pub type Name = String; +/// The rough equivalent of a module. pub type Program = Vec; +/// 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,