From 69e0edac24e042241f0649491eba3e63a0351112 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sat, 26 Sep 2026 10:40:32 +0100 Subject: [PATCH] Implement generic REPL --- src/lib.rs | 1 + src/repl.rs | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 src/repl.rs diff --git a/src/lib.rs b/src/lib.rs index 5fe3b8e..2a06ac5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,4 @@ pub mod lex; pub mod parse; pub mod print; pub mod eval; +pub mod repl; diff --git a/src/repl.rs b/src/repl.rs new file mode 100644 index 0000000..875e48e --- /dev/null +++ b/src/repl.rs @@ -0,0 +1,250 @@ +use crate::eval::Evaluator; + +use std::io::BufRead; +use std::io::Write; + +pub struct Repl { + input: I, + output: O, + evaluator: E, +} + +impl Repl, std::io::BufWriter, E> +where + I: std::io::Read, + O: std::io::Write, + E: Evaluator, + E::Expr: std::fmt::Display + std::str::FromStr, + E::Error: std::fmt::Display, + ::Err: std::fmt::Display +{ + pub fn new(input: I, output: O, evaluator: E) -> Self { + Self { + input: std::io::BufReader::new(input), + output: std::io::BufWriter::new(output), + evaluator, + } + } + + pub fn run(&mut self) -> std::io::Result<()> { + let mut line = String::new(); + loop { + write!(self.output, "> ")?; + self.output.flush()?; + + line.clear(); + match self.input.read_line(&mut line)? { + 0 => { + writeln!(self.output, "")?; + self.output.flush()?; + break Ok(()) + } + _ => self.handle(&line)?, + } + } + } + + fn handle(&mut self, line: &str) -> std::io::Result<()> { + match line.parse::() { + Ok(input) => { + match self.evaluator.eval(input) { + Ok(result) => self.print_expr(&result)?, + Err(error) => self.print_error(&error)?, + } + } + Err(error) => self.print_error(&error)?, + } + Ok(()) + } + + fn print_expr(&mut self, expr: &E::Expr) -> std::io::Result<()> { + writeln!(self.output, "{}", expr) + } + + fn print_error( + &mut self, error: &T) -> Result<(), std::io::Error> + { + writeln!(self.output, "ERROR: {}", error) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + enum Expr { + Foo, + Bar, + } + + enum Error { + Read, + Eval, + } + + struct Eval; + + struct ErrorReader; + + impl std::str::FromStr for Expr { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.trim() { + "foo" => Ok(Expr::Foo), + "bar" => Ok(Expr::Bar), + _ => Err(Error::Read), + } + } + } + + impl std::fmt::Display for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expr::Foo => write!(f, "foo"), + Expr::Bar => write!(f, "bar"), + } + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Read => write!(f, "read"), + Error::Eval => write!(f, "eval"), + } + } + } + + impl Evaluator for Eval { + type Expr = Expr; + type Error = Error; + + fn eval(&mut self, input: Expr) -> Result { + match input { + Expr::Foo => Ok(Expr::Bar), + _ => Err(Error::Eval), + } + } + } + + impl ErrorReader { + pub fn error() -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::Other, "test") + } + } + + impl std::io::Read for ErrorReader { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + Err(ErrorReader::error()) + } + } + + #[test] + fn repl_prints_prompt() { + let reader = std::io::Cursor::new(b""); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + { + let mut repl = Repl::new(reader, &mut writer, evaluator); + repl.run().unwrap(); + } + let output = String::from_utf8( + writer.into_inner().unwrap()).unwrap(); + assert_eq!(output, "> \n"); + } + + #[test] + fn repl_reads_expression_evals_and_prints_result() { + let reader = std::io::Cursor::new(b"foo\n"); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + { + let mut repl = Repl::new(reader, &mut writer, evaluator); + repl.run().unwrap(); + } + let output = String::from_utf8( + writer.into_inner().unwrap()).unwrap(); + assert_eq!(output, "> bar\n> \n"); + } + + #[test] + fn read_errors_are_reported() { + let reader = std::io::Cursor::new(b"baz\n"); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + { + let mut repl = Repl::new(reader, &mut writer, evaluator); + repl.run().unwrap(); + } + let output = String::from_utf8( + writer.into_inner().unwrap()).unwrap(); + assert_eq!(output, "> ERROR: read\n> \n"); + } + + #[test] + fn eval_errors_are_reported() { + let reader = std::io::Cursor::new(b"bar\n"); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + { + let mut repl = Repl::new(reader, &mut writer, evaluator); + repl.run().unwrap(); + } + let output = String::from_utf8( + writer.into_inner().unwrap()).unwrap(); + assert_eq!(output, "> ERROR: eval\n> \n"); + } + + #[test] + fn interaction_can_have_many_lines() { + let reader = std::io::Cursor::new(b"foo\nfoo\nfoo\n"); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + { + let mut repl = Repl::new(reader, &mut writer, evaluator); + repl.run().unwrap(); + } + let output = String::from_utf8( + writer.into_inner().unwrap()).unwrap(); + assert_eq!(output, "> bar\n> bar\n> bar\n> \n"); + } + + #[test] + fn errors_are_recovered_from() { + let reader = std::io::Cursor::new(b"foo\nbar\nfoo\nbaz\nfoo\n"); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + { + let mut repl = Repl::new(reader, &mut writer, evaluator); + repl.run().unwrap(); + } + let output = String::from_utf8( + writer.into_inner().unwrap()).unwrap(); + assert_eq!( + output, + "> bar\n> ERROR: eval\n> bar\n> ERROR: read\n> bar\n> \n"); + } + + #[test] + fn run_returns_ok_when_exiting_normally() + { + let reader = std::io::Cursor::new(b""); + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + let mut repl = Repl::new(reader, &mut writer, evaluator); + let result = repl.run(); + assert!(matches!(result, Ok(()))); + } + + #[test] + fn run_returns_error_when_exiting_abnormally() + { + let reader = ErrorReader {}; + let mut writer = std::io::BufWriter::new(Vec::new()); + let evaluator = Eval {}; + let mut repl = Repl::new(reader, &mut writer, evaluator); + let result = repl.run(); + assert!(matches!(result, Err(_))); + } +}