calc/
calc.rs

1//! An extremely simple command-line calculator.
2
3use flexi_parse::group::Group;
4use flexi_parse::group::Parentheses;
5use flexi_parse::parse;
6use flexi_parse::parse_string;
7use flexi_parse::pretty_unwrap;
8use flexi_parse::token;
9use flexi_parse::Parse;
10use flexi_parse::ParseStream;
11use flexi_parse::Punct;
12use flexi_parse::Result;
13
14use std::env;
15
16enum Expr {
17    Num(f64),
18    Neg(Punct!["-"], Box<Expr>),
19    Mul(Box<Expr>, Punct!["*"], Box<Expr>),
20    Div(Box<Expr>, Punct!["/"], Box<Expr>),
21    Mod(Box<Expr>, Punct!["%"], Box<Expr>),
22    Add(Box<Expr>, Punct!["+"], Box<Expr>),
23    Sub(Box<Expr>, Punct!["-"], Box<Expr>),
24}
25
26impl Expr {
27    fn eval(&self) -> f64 {
28        match self {
29            Expr::Num(num) => *num,
30            Expr::Neg(_, expr) => -expr.eval(),
31            Expr::Mul(left, _, right) => left.eval() * right.eval(),
32            Expr::Div(left, _, right) => left.eval() / right.eval(),
33            Expr::Mod(left, _, right) => left.eval() % right.eval(),
34            Expr::Add(left, _, right) => left.eval() + right.eval(),
35            Expr::Sub(left, _, right) => left.eval() - right.eval(),
36        }
37    }
38}
39
40impl Parse for Expr {
41    fn parse(input: ParseStream) -> Result<Self> {
42        let mut expr = factor(input)?;
43        loop {
44            if input.peek(Punct!["+"]) {
45                expr = Expr::Add(Box::new(expr), input.parse()?, Box::new(factor(input)?));
46            } else if input.peek(Punct!["-"]) {
47                expr = Expr::Sub(Box::new(expr), input.parse()?, Box::new(factor(input)?));
48            } else {
49                break;
50            }
51        }
52        Ok(expr)
53    }
54}
55
56fn factor(input: ParseStream) -> Result<Expr> {
57    let mut expr: Expr = unary(input)?;
58    loop {
59        if input.peek(Punct!["*"]) {
60            expr = Expr::Mul(Box::new(expr), input.parse()?, Box::new(unary(input)?));
61        } else if input.peek(Punct!["/"]) {
62            expr = Expr::Div(Box::new(expr), input.parse()?, Box::new(unary(input)?));
63        } else if input.peek(Punct!["%"]) {
64            expr = Expr::Mod(Box::new(expr), input.parse()?, Box::new(unary(input)?));
65        } else {
66            break;
67        }
68    }
69    Ok(expr)
70}
71
72fn unary(input: ParseStream) -> Result<Expr> {
73    if input.peek(Punct!["-"]) {
74        Ok(Expr::Neg(input.parse()?, Box::new(unary(input)?)))
75    } else {
76        primary(input)
77    }
78}
79
80#[allow(clippy::cast_precision_loss)]
81fn primary(input: ParseStream) -> Result<Expr> {
82    let lookahead = input.lookahead();
83    if lookahead.peek(token::LitFloat) {
84        Ok(Expr::Num(input.parse::<token::LitFloat>()?.value()))
85    } else if lookahead.peek(token::LitInt) {
86        Ok(Expr::Num(input.parse::<token::LitInt>()?.value() as f64))
87    } else if lookahead.peek(token::LeftParen) {
88        let group: Group<Parentheses> = input.parse()?;
89        parse(group.into_token_stream())
90    } else {
91        Err(lookahead.error())
92    }
93}
94
95fn main() {
96    let expr: Expr = pretty_unwrap(parse_string(env::args().nth(1).expect("expect expression")));
97    println!("{}", expr.eval());
98}
99
100#[cfg(test)]
101mod tests {
102    use super::Expr;
103
104    use flexi_parse::parse_string;
105    use flexi_parse::pretty_unwrap;
106
107    #[test]
108    #[allow(clippy::float_cmp)]
109    fn test_features() {
110        let expr: Expr = pretty_unwrap(parse_string(
111            "
112                (
113                    (
114                        (4 - 1) + 5
115                    ) / (2.5 + -0.5) * 2
116                ) % 3
117            "
118            .to_string(),
119        ));
120        assert_eq!(expr.eval(), 2.0);
121    }
122}