Implement generic REPL
This commit is contained in:
1 parent
a756756a9f
commit
69e0edac24
2 files changed
+251
No files matched your search
@@ -6,3 +6,4 @@ pub mod lex;
|
||||
pub mod parse;
|
||||
pub mod print;
|
||||
pub mod eval;
|
||||
pub mod repl;
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
use crate::eval::Evaluator;
|
||||
|
||||
use std::io::BufRead;
|
||||
use std::io::Write;
|
||||
|
||||
pub struct Repl<I, O, E> {
|
||||
input: I,
|
||||
output: O,
|
||||
evaluator: E,
|
||||
}
|
||||
|
||||
impl<I, O, E> Repl<std::io::BufReader<I>, std::io::BufWriter<O>, 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,
|
||||
<E::Expr as std::str::FromStr>::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::<E::Expr>() {
|
||||
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<T: std::fmt::Display>(
|
||||
&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<Self, Self::Err> {
|
||||
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<Expr, Error> {
|
||||
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<usize> {
|
||||
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(_)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user