1use std::{str::FromStr, io::Write};
2
3use fn_bnf::{define, Any, Rule, While, Fail, errors::Unexpected};
4
5#[derive(Debug, PartialEq, Copy, Clone)]
6pub enum Token {
7 Number(f64),
8 Plus, Minus, Asterisk, Slash, Carat, Percent, Ans,
9 LeftParen, RightParen
10}
11
12define! {
13 grammar MathTokens<str> {
14 WhitespaceToken -> () = _ (' ', '\n', '\t');
16 Whitespace -> () = _ WhitespaceToken, _ WhitespaceToken.hoard();
17
18 pub LangTokens -> Vec<Token> = LangToken.consume_all()
19 .map_parsed(|v| v.into_iter().filter_map(|v| v).collect() );
20 LangToken -> Option<Token> =
21 Num : Plus : Minus : Asterisk : Slash : Percent : Carat
22 : LParen : RParen : Ans : _ Whitespace
23 : InvalidChar;
24 InvalidChar -> Token from(|_, n| n) = Any, Fail::new(Unexpected::new(arg_0));
26
27 Plus -> Token = '+'.map_parsed(|_| Token::Plus);
28 Minus -> Token = '-'.map_parsed(|_| Token::Minus);
29 Asterisk -> Token = '*'.map_parsed(|_| Token::Asterisk);
30 Slash -> Token = '/'.map_parsed(|_| Token::Slash);
31 Percent -> Token = '%'.map_parsed(|_| Token::Percent);
32 Carat -> Token = '^'.map_parsed(|_| Token::Carat);
33 LParen -> Token = '('.map_parsed(|_| Token::LeftParen);
34 RParen -> Token = ')'.map_parsed(|_| Token::RightParen);
35
36 Ans -> Token = "ans".map_parsed(|_| Token::Ans);
37
38 Num -> Token from(|n| Token::Number(n)) =
39 ("nan", "NaN").map_parsed(|_| f64::NAN) :
40 ("inf", "Infinity").map_parsed(|_| f64::INFINITY) :
41 Float;
42 Float -> f64 try_from(f64::from_str) = FloatTokens.spanned().map_parsed(|span| span.source);
43
44 FloatTokens -> () = _ UInt, _ FloatFract.attempt(), _ FloatExp.attempt();
45 FloatFract -> () = _ '.', _ UInt;
46 FloatExp -> () = _ ('e', 'E'), _ ('-', '+').attempt(), _ UInt;
47
48 UInt -> &'input str = While::from(char::is_ascii_digit);
49 }
50}
51
52define! {
53 grammar TokenMath<[Token]> {
54 pub Expr -> f64 from(parse_expr) = Prod, SumSuf.consume_all();
55
56 EOF -> () = Rule::<'input, [Token]>::prevent(Any);
57 Sum -> f64 from(parse_expr) = Prod, SumSuf.hoard();
58 SumSuf -> (&'input [Token], f64) = ([Token::Plus], [Token::Minus]), Prod;
59 Prod -> f64 from(parse_expr) = Exp, ProdSuf.hoard();
60 ProdSuf -> (&'input [Token], f64) = ([Token::Asterisk], [Token::Slash], [Token::Percent]), Exp;
61 Exp -> f64 from(parse_expr) = Neg, ExpSuf.hoard();
62 ExpSuf -> (&'input [Token], f64) = [Token::Carat], Neg;
63 Neg -> f64 from(|negative, num: f64| if negative {-num} else {num})
64 = [Token::Minus].attempt().map_parsed(|opt| opt.is_ok()), Atom;
65 Atom -> f64 = _ [Token::LeftParen], Sum, _ [Token::RightParen] : Number;
66 Number -> f64 try_from(|token: &Token| {
67 let Token::Number(n) = token else { return Err(Unexpected::<Token>::new(*token)); };
68 Ok(*n)
69 }) = Any;
70 }
71}
72
73fn parse_expr(mut lhs: f64, suffixes: Vec<(&[Token], f64)>) -> f64 {
74 for (op, rhs) in suffixes {
75 match op[0] {
76 Token::Plus => lhs += rhs,
77 Token::Minus => lhs -= rhs,
78 Token::Asterisk => lhs *= rhs,
79 Token::Slash => lhs /= rhs,
80 Token::Percent => lhs %= rhs,
81 Token::Carat => lhs = lhs.powf(rhs),
82 _ => unreachable!()
83 }
84 }
85 lhs
86}
87
88
89fn main() -> Result<(), std::io::Error> {
90 let mut lines = std::io::stdin().lines();
91 println!("Input a math expression below, `clear` to clear the console, or `exit` / `quit` to exit.");
92 println!("You can access the result of the last expression with `ans`.");
93 let mut last_ans = None;
94 'outer: loop {
95 print!("[?] ");
96 std::io::stdout().flush()?;
97 let Some(input) = lines.next().transpose()? else { break Ok(()) };
98 let input = input.trim_ascii();
99 if input.is_empty() { print!("\x1b[1A"); continue; }
100 match input {
101 "clear" => print!("\x1bc"),
102 "exit" | "quit" => break Ok(()),
103 _ => {
104 let (_, mut tokens) = match MathTokens::LangTokens.parse(&input) {
105 Ok(v) => v,
106 Err(err) => {
107 println!("[!] Failed to parse: {err}");
108 continue;
109 }
110 };
111 for ans in tokens.iter_mut().filter(|t| matches!(t, Token::Ans)) {
112 let Some(answer) = last_ans else {
113 println!("[!] No previous answer exists");
114 continue 'outer;
115 };
116 *ans = Token::Number(answer);
117 }
118 let (_, result) = match TokenMath::Expr.parse(tokens.as_ref()) {
119 Ok(v) => v,
120 Err(err) => {
121 println!("[!] Failed to parse: {err}");
122 continue;
123 }
124 };
125 last_ans = Some(result);
126 println!("[=] {result:.}")
127 }
128 }
129 }
130}