diff --git a/Cargo.toml b/Cargo.toml index a828888..faee960 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rustyline = "12.0.0" [profile.optimized] # see: https://doc.rust-lang.org/rustc/codegen-options/index.html diff --git a/Makefile b/Justfile similarity index 72% rename from Makefile rename to Justfile index ee107d5..82373a7 100644 --- a/Makefile +++ b/Justfile @@ -1,29 +1,23 @@ -BINARY := $(CURDIR)/rusch +BINARY := "$(pwd)/rusch" -rusch: src/* +test: lint unit-test integration-test + +rusch: cargo build --profile optimized - cp -f target/optimized/rusch $(BINARY) + cp -f target/optimized/rusch {{BINARY}} -.PHONY: repl repl: @ cargo run --quiet -.PHONY: test -test: lint unit-test integration-test - -.PHONY: lint lint: cargo clippy -.PHONY: unit-test unit-test: cargo test -.PHONY: integration-test integration-test: cd examples/the-little-schemer/ && cargo run -- run-all.scm -.PHONY: benchmark benchmark: rusch cd examples/the-little-schemer/ && \ hyperfine -m 200 --warmup 10 \ @@ -31,11 +25,12 @@ benchmark: rusch 'loco run-all.scm' \ 'gosch run-all.scm' -.PHONY: lines +install: + cargo install --profile optimized --path . + lines: @ find . -type f -name "*.rs" -exec awk '1;/#[cfg\(test\)]/{exit}' {} \; | grep . | wc -l -.PHONY: clean clean: rm -rf rusch rm -rf target diff --git a/src/errors.rs b/src/errors.rs index 436d6ca..4e9c690 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,4 @@ +use rustyline::error::ReadlineError; use std::fmt; #[derive(PartialEq, Debug, Clone)] @@ -42,6 +43,7 @@ where #[derive(PartialEq, Debug, Clone)] pub enum ReadError { EndOfInput, + Interrupted, Unexpected(char), Missing(String), IoError(String), @@ -51,6 +53,7 @@ impl fmt::Display for ReadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use ReadError::*; match self { + Interrupted => write!(f, "interrupted"), EndOfInput => write!(f, "end of input"), Unexpected(ch) => write!(f, "unexpected character '{}'", ch), Missing(msg) => write!(f, "missing {}", msg), @@ -58,3 +61,13 @@ impl fmt::Display for ReadError { } } } + +impl From for ReadError { + fn from(value: ReadlineError) -> Self { + match value { + ReadlineError::Eof => ReadError::EndOfInput, + ReadlineError::Interrupted => ReadError::Interrupted, + other => ReadError::IoError(other.to_string()), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 13ceb47..9c241d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ pub mod envir; -mod errors; +pub mod errors; pub mod eval; mod list; pub mod parser; diff --git a/src/main.rs b/src/main.rs index 5130141..60a3d6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ +use rusch::{ + envir::Env, + errors::ReadError, + eval::{eval, eval_file}, + parser::read_sexpr, + reader::StdinReader, + scheme::root_env, + types::Sexpr, +}; use std::env; -use rusch::envir::Env; -use rusch::eval::{eval, eval_file}; -use rusch::parser::read_sexpr; -use rusch::reader::StdinReader; -use rusch::scheme::root_env; -use rusch::types::Sexpr; - fn eval_and_print(sexpr: &Sexpr, env: &mut Env) { match eval(sexpr, env) { Ok(result) => { @@ -19,24 +21,32 @@ fn eval_and_print(sexpr: &Sexpr, env: &mut Env) { } fn main() { - let args = &mut env::args().skip(1); - + let args: Vec = env::args().collect(); let env = &mut root_env(); - if args.len() == 0 { + if args.len() < 2 { println!("Press ^C to exit.\n"); let reader = &mut StdinReader::new().unwrap(); loop { match read_sexpr(reader) { Ok(ref sexpr) => eval_and_print(sexpr, env), + Err(ReadError::Interrupted) => std::process::exit(0), Err(msg) => println!("Error: {}", msg), } } } - for arg in args { - match eval_file(&arg, env) { + if &args[1] == "-h" || &args[1] == "--help" { + println!( + "Usage: {} [FILE]...\n\nIf no files are given, opens REPL.", + args[0] + ); + return; + } + + for arg in args.iter().skip(1) { + match eval_file(arg, env) { Ok(_) => (), Err(msg) => panic!("{}", msg), } diff --git a/src/reader.rs b/src/reader.rs index 84129d0..06238bc 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,7 +1,8 @@ use crate::errors::ReadError; +use rustyline::{Config, DefaultEditor}; use std::fs::File; -use std::io; -use std::io::{BufRead, BufReader, Lines, Write}; + +use std::io::{BufRead, BufReader, Lines}; use std::iter::Peekable; use std::vec::IntoIter; @@ -20,6 +21,10 @@ impl StringReader { cache: s.chars().collect::>().into_iter().peekable(), } } + + fn empty() -> StringReader { + StringReader::from("") + } } impl Reader for StringReader { @@ -77,24 +82,27 @@ impl Reader for FileReader { } pub struct StdinReader { - iter: StringReader, + reader: DefaultEditor, + buffer: StringReader, } impl StdinReader { pub fn new() -> Result { + let config = Config::builder().auto_add_history(true).build(); + let reader = match DefaultEditor::with_config(config) { + Ok(editor) => editor, + Err(msg) => return Err(ReadError::IoError(msg.to_string())), + }; Ok(StdinReader { - iter: StdinReader::next_line()?, + reader, + buffer: StringReader::empty(), }) } - fn next_line() -> Result { - print!("> "); - let _ = io::stdout().flush(); - - let mut buffer = String::new(); - match io::stdin().read_line(&mut buffer) { - Ok(_) => Ok(StringReader::from(&buffer)), - Err(msg) => Err(ReadError::IoError(msg.to_string())), + fn next_line(&mut self) -> Result { + match self.reader.readline("> ") { + Ok(line) => Ok(StringReader::from(&format!("{}\n", line))), + Err(err) => Err(err.into()), } } } @@ -102,8 +110,8 @@ impl StdinReader { impl Reader for StdinReader { fn next(&mut self) -> Result { loop { - match self.iter.next() { - Err(ReadError::EndOfInput) => self.iter = StdinReader::next_line()?, + match self.buffer.next() { + Err(ReadError::EndOfInput) => self.buffer = self.next_line()?, result => return result, } } @@ -111,8 +119,8 @@ impl Reader for StdinReader { fn peek(&mut self) -> Result { loop { - match self.iter.peek() { - Err(ReadError::EndOfInput) => self.iter = StdinReader::next_line()?, + match self.buffer.peek() { + Err(ReadError::EndOfInput) => self.buffer = self.next_line()?, result => return result, } } diff --git a/src/scheme.rs b/src/scheme.rs index f29f5bf..6a16881 100644 --- a/src/scheme.rs +++ b/src/scheme.rs @@ -1,3 +1,8 @@ +mod numbers; +mod procedures; +mod special_forms; +mod utils; + use crate::eval::eval_but_last; use crate::scheme::numbers::*; use crate::scheme::procedures::*; @@ -5,11 +10,6 @@ use crate::scheme::special_forms::*; use crate::scheme::utils::eval_one_arg; use crate::types::{Env, Sexpr}; -mod numbers; -mod procedures; -mod special_forms; -mod utils; - pub fn root_env() -> Env { use crate::types::Sexpr::{Func, Tco}; Env::from([