Skip to content

Instantly share code, notes, and snippets.

@hayatoito
Last active June 19, 2017 03:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hayatoito/844de9fcadae3ef8a597122e174a4909 to your computer and use it in GitHub Desktop.
Save hayatoito/844de9fcadae3ef8a597122e174a4909 to your computer and use it in GitHub Desktop.
Calc
mod calc {
use combine::*;
use combine::char::{digit, spaces};
use std;
enum Expr {
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
Num(i64),
}
impl Expr {
fn eval(&self) -> i64 {
match *self {
Expr::Add(ref l, ref r) => l.eval() + r.eval(),
Expr::Sub(ref l, ref r) => l.eval() - r.eval(),
Expr::Mul(ref l, ref r) => l.eval() * r.eval(),
Expr::Div(ref l, ref r) => l.eval() / r.eval(),
Expr::Num(n) => n,
}
}
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Expr::Add(ref l, ref r) => write!(f, "(+ {} {})", l, r),
Expr::Sub(ref l, ref r) => write!(f, "(- {} {})", l, r),
Expr::Mul(ref l, ref r) => write!(f, "(* {} {})", l, r),
Expr::Div(ref l, ref r) => write!(f, "(/ {} {})", l, r),
Expr::Num(n) => n.fmt(f),
}
}
}
fn expr<I>(input: I) -> ParseResult<Expr, I>
where
I: Stream<Item = char>,
{
let number = many1(digit()).skip(spaces()).map(|s: String| {
Expr::Num(s.parse::<i64>().unwrap())
});
let factor = number.or(between(
token('(').skip(spaces()),
token(')').skip(spaces()),
parser(expr),
));
let term = chainl1(
factor,
token('*').or(token('/')).skip(spaces()).map(|op| {
move |l, r| match op {
'*' => Expr::Mul(Box::new(l), Box::new(r)),
'/' => Expr::Div(Box::new(l), Box::new(r)),
_ => unreachable!(),
}
}),
);
chainl1(
term,
token('+').or(token('-')).skip(spaces()).map(|op| {
move |l, r| match op {
'+' => Expr::Add(Box::new(l), Box::new(r)),
'-' => Expr::Sub(Box::new(l), Box::new(r)),
_ => unreachable!(),
}
}),
).parse_stream(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calc_test() {
let sexp = |s| parser(expr).parse(s).unwrap().0.to_string();
let eval = |s| parser(expr).parse(s).unwrap().0.eval();
assert_eq!(sexp("1"), "1");
assert_eq!(eval("1"), 1);
assert_eq!(sexp("(1)"), "1");
assert_eq!(eval("(1)"), 1);
assert_eq!(sexp("1 + 2"), "(+ 1 2)");
assert_eq!(eval("1 + 2"), 3);
assert_eq!(sexp("6 * 2"), "(* 6 2)");
assert_eq!(eval("6 * 2"), 12);
assert_eq!(sexp("6 / 2"), "(/ 6 2)");
assert_eq!(eval("6 / 2"), 3);
assert_eq!(sexp("1+2 + 3"), "(+ (+ 1 2) 3)");
assert_eq!(eval("1+2 + 3"), 6);
assert_eq!(sexp("1 - 2 + 3"), "(+ (- 1 2) 3)");
assert_eq!(eval("1 - 2 + 3"), 2);
assert_eq!(sexp("1 - 2 - 3"), "(- (- 1 2) 3)");
assert_eq!(eval("1 - 2 - 3"), -4);
assert_eq!(sexp("1 + 2 * 3"), "(+ 1 (* 2 3))");
assert_eq!(eval("1 + 2 * 3"), 7);
assert_eq!(sexp("1 * 2 + 3 * 4"), "(+ (* 1 2) (* 3 4))");
assert_eq!(eval("1 * 2 + 3 * 4"), 14);
assert_eq!(sexp("(1 + 2) * 3"), "(* (+ 1 2) 3)");
assert_eq!(eval("(1 + 2) * 3"), 9);
assert_eq!(sexp("1 - (2 + 3) * 4"), "(- 1 (* (+ 2 3) 4))");
assert_eq!(eval("1 - (2 + 3) * 4"), -19);
assert_eq!(sexp("(1 + 2) * (3 + 4)"), "(* (+ 1 2) (+ 3 4))");
assert_eq!(eval("(1 + 2) * (3 + 4)"), 21);
assert_eq!(sexp("(1 - (2 + 3)) * (4 + 5)"), "(* (- 1 (+ 2 3)) (+ 4 5))");
assert_eq!(eval("(1 - (2 + 3)) * (4 + 5)"), -36);
}
#[test]
fn parse_error_test() {
let parse_error = |s| parser(expr).parse(s).is_err();
assert!(parse_error(""));
assert!(parse_error("a"));
assert!(parse_error("+"));
assert!(parse_error(")"));
assert!(parse_error("()"));
assert!(parse_error("((1)"));
assert!(parse_error("* 1"));
assert!(parse_error("1 ++ 2"));
assert!(parse_error("1 + - 2"));
assert!(parse_error("1 + 2 +"));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment