Skip to content

Commit

Permalink
Use rustyline for improved REPL
Browse files Browse the repository at this point in the history
  • Loading branch information
twolodzko committed Oct 31, 2023
1 parent c0ff224 commit 647e6f1
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 47 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 8 additions & 13 deletions Makefile → Justfile
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
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 \
'../../rusch run-all.scm' \
'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
Expand Down
13 changes: 13 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rustyline::error::ReadlineError;
use std::fmt;

#[derive(PartialEq, Debug, Clone)]
Expand Down Expand Up @@ -42,6 +43,7 @@ where
#[derive(PartialEq, Debug, Clone)]
pub enum ReadError {
EndOfInput,
Interrupted,
Unexpected(char),
Missing(String),
IoError(String),
Expand All @@ -51,10 +53,21 @@ 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),
IoError(msg) => msg.fmt(f),
}
}
}

impl From<ReadlineError> for ReadError {
fn from(value: ReadlineError) -> Self {
match value {
ReadlineError::Eof => ReadError::EndOfInput,
ReadlineError::Interrupted => ReadError::Interrupted,
other => ReadError::IoError(other.to_string()),
}
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod envir;
mod errors;
pub mod errors;
pub mod eval;
mod list;
pub mod parser;
Expand Down
34 changes: 22 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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<Sexpr>) {
match eval(sexpr, env) {
Ok(result) => {
Expand All @@ -19,24 +21,32 @@ fn eval_and_print(sexpr: &Sexpr, env: &mut Env<Sexpr>) {
}

fn main() {
let args = &mut env::args().skip(1);

let args: Vec<String> = 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),
}
Expand Down
40 changes: 24 additions & 16 deletions src/reader.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -20,6 +21,10 @@ impl StringReader {
cache: s.chars().collect::<Vec<_>>().into_iter().peekable(),
}
}

fn empty() -> StringReader {
StringReader::from("")
}
}

impl Reader for StringReader {
Expand Down Expand Up @@ -77,42 +82,45 @@ impl Reader for FileReader {
}

pub struct StdinReader {
iter: StringReader,
reader: DefaultEditor,
buffer: StringReader,
}

impl StdinReader {
pub fn new() -> Result<Self, ReadError> {
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<StringReader, ReadError> {
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<StringReader, ReadError> {
match self.reader.readline("> ") {
Ok(line) => Ok(StringReader::from(&format!("{}\n", line))),
Err(err) => Err(err.into()),
}
}
}

impl Reader for StdinReader {
fn next(&mut self) -> Result<char, ReadError> {
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,
}
}
}

fn peek(&mut self) -> Result<char, ReadError> {
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,
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/scheme.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
mod numbers;
mod procedures;
mod special_forms;
mod utils;

use crate::eval::eval_but_last;
use crate::scheme::numbers::*;
use crate::scheme::procedures::*;
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([
Expand Down

0 comments on commit 647e6f1

Please sign in to comment.