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 parse;
|
||||||
pub mod print;
|
pub mod print;
|
||||||
pub mod eval;
|
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