#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", deny(clippy, clippy_pedantic))]
#![deny(missing_docs)]
extern crate rl_sys;
#[cfg(test)]
extern crate sodium_sys;
use rl_sys::readline;
use std::fmt;
use std::io::{self, Write};
pub use self::ReplErr::{Read, Eval, Print};
pub mod version;
pub trait ReplEnv {
fn preamble(&self) -> bool;
fn colorize(&self) -> bool;
fn prompt(&self) -> &str;
}
pub trait Repl<T, U, V>
where U: fmt::Display,
V: ReplEnv
{
fn preamble(&self, &V) -> &Self;
fn read(&self, String, &V) -> Result<T, U>;
fn eval(&self, T, &V) -> Result<T, U>;
fn print(&self, T, &V) -> Result<T, U>;
fn break_loop(&self, &T, &V) -> bool;
fn _loop(&self, env: &V) {
if env.preamble() {
self.preamble(env);
}
let green = "\x1b[32;1m";
let reset = "\x1b[0m";
let mut p = String::new();
let prompt = env.prompt();
if env.colorize() {
p.push_str(green);
p.push_str(prompt);
p.push_str(reset);
} else {
p.push_str(prompt);
}
loop {
match readline::readline(&p) {
Ok(None) => break,
Ok(Some(line)) => {
match self.read(line, env)
.and_then(|r| self.eval(r, env))
.and_then(|r| self.print(r, env)) {
Err(e) => {
io::stderr().write_fmt(format_args!("{}", e)).unwrap_or(());
}
Ok(ref o) if self.break_loop(o, env) => break,
Ok(_) => {}
}
}
Err(e) => io::stderr().write_fmt(format_args!("{}", e)).unwrap_or(()),
};
}
}
}
#[derive(Debug)]
pub enum ReplErr {
Read(String),
Eval(String),
Print(String),
}
impl fmt::Display for ReplErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let x = match *self {
Read(ref x) | Eval(ref x) | Print(ref x) => x,
};
write!(f, "{}", x)
}
}
#[cfg(test)]
mod test {
use super::{Repl, ReplEnv};
use self::Exp::{Cmd, Strn, Exit, Nil};
use self::CmdType::{Open, Close};
use self::ReplErr::{Read, Eval};
use std::default::Default;
use std::fmt;
use std::io::{self, Write};
pub struct MyRepl;
#[derive(Default)]
pub struct MyReplEnv {
prompt: String,
}
impl ReplEnv for MyReplEnv {
fn preamble(&self) -> bool {
false
}
fn colorize(&self) -> bool {
false
}
fn prompt(&self) -> &str {
&self.prompt
}
}
pub enum CmdType {
Open,
Close,
}
pub enum Exp {
Cmd(CmdType),
Strn(String),
Exit,
Nil,
}
pub enum ReplErr {
Read(&'static str),
Eval(&'static str), }
impl fmt::Display for ReplErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let x = match *self {
Read(ref x) | Eval(ref x) => x,
};
write!(f, "{}", x)
}
}
pub type MyReplResult = Result<Exp, ReplErr>;
impl Repl<Exp, ReplErr, MyReplEnv> for MyRepl {
fn preamble(&self, _: &MyReplEnv) -> &MyRepl {
self
}
fn read(&self, input: String, _: &MyReplEnv) -> MyReplResult {
match &input[..] {
"open" | "close" | "exit" => Ok(Strn(input)),
_ => Err(Read("invalid input")),
}
}
fn eval(&self, cmd: Exp, _: &MyReplEnv) -> MyReplResult {
match cmd {
Strn(ref x) if *x == "open" => Ok(Cmd(Open)),
Strn(ref x) if *x == "close" => Ok(Cmd(Close)),
Strn(ref x) if *x == "exit" => Ok(Exit),
_ => Err(Eval("invalid ast")),
}
}
fn print(&self, cmd: Exp, _: &MyReplEnv) -> MyReplResult {
match cmd {
Cmd(Open) => {
io::stdout().write(b"opening...").unwrap_or(1);
Ok(Nil)
}
Cmd(Close) => {
io::stdout().write(b"closing...").unwrap_or(1);
Ok(Nil)
}
Exit => {
io::stdout().write(b"exiting...").unwrap_or(1);
Ok(Exit)
}
_ => Ok(Nil),
}
}
fn break_loop(&self, cmd: &Exp, _: &MyReplEnv) -> bool {
match *cmd {
Exit => true,
_ => false,
}
}
}
#[test]
fn test_read() {
let repl = MyRepl;
let env: MyReplEnv = Default::default();
assert!(repl.read("open".to_owned(), &env).is_ok());
assert!(repl.read("close".to_owned(), &env).is_ok());
assert!(repl.read("exit".to_owned(), &env).is_ok());
assert!(repl.read("blah".to_owned(), &env).is_err());
}
#[test]
fn test_eval() {
let repl = MyRepl;
let env: MyReplEnv = Default::default();
assert!(repl.eval(Strn("open".to_owned()), &env).is_ok());
assert!(repl.eval(Strn("close".to_owned()), &env).is_ok());
assert!(repl.eval(Strn("exit".to_owned()), &env).is_ok());
assert!(repl.eval(Nil, &env).is_err());
}
#[test]
fn test_print() {
let repl = MyRepl;
let env: MyReplEnv = Default::default();
assert!(repl.print(Cmd(Open), &env).is_ok());
assert!(repl.print(Cmd(Close), &env).is_ok());
assert!(repl.print(Exit, &env).is_ok());
assert!(repl.print(Nil, &env).is_ok());
}
#[test]
fn test_break_loop() {
let repl = MyRepl;
let env: MyReplEnv = Default::default();
assert!(repl.break_loop(&Exit, &env));
assert!(!repl.break_loop(&Cmd(Open), &env));
}
#[test]
fn test_read_eval_print() {
let repl = MyRepl;
let env: MyReplEnv = Default::default();
assert!(repl.read("open".to_owned(), &env)
.and_then(|r| repl.eval(r, &env))
.and_then(|r| repl.print(r, &env))
.is_ok());
assert!(repl.read("close".to_owned(), &env)
.and_then(|r| repl.eval(r, &env))
.and_then(|r| repl.print(r, &env))
.is_ok());
assert!(repl.read("blah".to_owned(), &env)
.and_then(|r| repl.eval(r, &env))
.and_then(|r| repl.print(r, &env))
.is_err());
}
}