arithmetic_parser_kma/
lib.rs1pub use pest::iterators::Pairs;
2pub use pest::pratt_parser::PrattParser;
3pub use pest::Parser;
4pub use pest_derive::Parser;
5pub use std::io::{self, BufRead};
6pub use thiserror::*;
7
8#[derive(pest_derive::Parser)]
9#[grammar = "./calc.pest"]
10pub struct MyParser;
11
12lazy_static::lazy_static! {
13 static ref PRATT_PARSER: PrattParser<Rule> = {
14 use pest::pratt_parser::{Assoc::*, Op};
15 use Rule::*;
16
17 PrattParser::new()
19 .op(Op::infix(add, Left) | Op::infix(subtract, Left))
21 .op(Op::infix(multiply, Left) | Op::infix(divide, Left))
22 .op(Op::prefix(unary_minus))
23 };
24}
25
26#[derive(Debug)]
27pub enum Expr {
28 Number(f64),
29 UnaryMinus(Box<Expr>),
30 BinOp {
31 lhs: Box<Expr>,
32 op: Op,
33 rhs: Box<Expr>,
34 },
35 Unreachable,
36}
37
38pub fn parse_expr(pairs: Pairs<Rule>) -> Expr {
48 PRATT_PARSER
49 .map_primary(|primary| match primary.as_rule() {
50 Rule::number => Expr::Number(primary.as_str().parse::<f64>().unwrap()),
51 Rule::expr => parse_expr(primary.into_inner()),
52 _ => Expr::Unreachable,
53 })
54 .map_infix(|lhs, op, rhs| {
55 let op = match op.as_rule() {
56 Rule::add => Op::Add,
57 Rule::subtract => Op::Subtract,
58 Rule::multiply => Op::Multiply,
59 Rule::divide => Op::Divide,
60 _ => Op::Invalid,
61 };
62 Expr::BinOp {
63 lhs: Box::new(lhs),
64 op,
65 rhs: Box::new(rhs),
66 }
67 })
68 .map_prefix(|op, rhs| match op.as_rule() {
69 Rule::unary_minus => Expr::UnaryMinus(Box::new(rhs)),
70 _ => Expr::Unreachable,
71 })
72 .parse(pairs)
73}
74
75pub fn eval_expr(expr: Expr) -> Result<f64, MyError> {
85 match expr {
86 Expr::Number(n) => Ok(n),
87 Expr::UnaryMinus(expr) => Ok(-(eval_expr(*expr)?)),
88 Expr::BinOp { lhs, op, rhs } => match op {
89 Op::Add => Ok(eval_expr(*lhs)? + eval_expr(*rhs)?),
90 Op::Subtract => Ok(eval_expr(*lhs)? - eval_expr(*rhs)?),
91 Op::Multiply => Ok(eval_expr(*lhs)? * eval_expr(*rhs)?),
92 Op::Divide => Ok(eval_expr(*lhs)? / eval_expr(*rhs)?),
93 Op::Invalid => Err(MyError::UnreachableError)
94 },
95 Expr::Unreachable => Err(MyError::UnreachableError),
96 }
97}
98
99pub fn eval_expr_from_string(s: &str) -> Result<f64, MyError> {
109 let pairs = MyParser::parse(Rule::equation, s);
110 match pairs {
111 Ok(mut pairs_) => {
112 let expr = parse_expr(pairs_.next().unwrap().into_inner());
113 Ok(eval_expr(expr)?)
114 }
115 Err(e) => Err(MyError::ParseError(e.to_string())),
116 }
117}
118
119#[derive(Error, Debug)]
121pub enum MyError {
122 #[error("io error")]
123 IOError(io::Error),
124
125 #[error("parse error")]
126 ParseError(String),
127
128 #[error("unknown error")]
129 Unknown,
130
131 #[error("cli error")]
132 CLIError(String),
133
134 #[error("ureachale error")]
135 UnreachableError,
136}
137
138#[derive(Debug)]
140pub enum Op {
141 Add,
142 Subtract,
143 Multiply,
144 Divide,
145 Invalid,
146}
147
148pub mod cli {
150 use super::*;
151 use clap::Parser as ParserClap;
152 use std::fs;
153
154 #[derive(ParserClap, Debug)]
156 #[command(author, version, about, long_about = None)]
157 struct Args {
158 #[arg(short, long)]
160 console: bool,
161
162 #[arg(short, long)]
164 file: Option<String>,
165 }
166
167 pub fn run() -> Result<(), MyError> {
169 let args = Args::parse();
170
171 if args.console && args.file.is_some() {
172 return Err(MyError::CLIError(
173 "Cannot use console mode and file mode at the same time.".to_string(),
174 ));
175 }
176
177 if args.console {
178 println!("Welcome to Arithmetic expression calculator parser.\nType you expression below and press enter.\nTo exit enter ':q'.");
179 for line in io::stdin().lock().lines() {
180 match line {
181 Ok(line) => {
182 if line.contains(":q") {
183 return Ok(());
184 }
185 let result = eval_expr_from_string(&line);
186 match result {
187 Ok(result) => println!("{} = {}", &line, result),
188 Err(e) => eprintln!("{}", MyError::ParseError(e.to_string())),
189 }
190 }
191 Err(e) => {
192 eprintln!("{}", MyError::IOError(e));
193 }
194 }
195 }
196 return Ok(());
197 }
198
199 match args.file {
200 Some(file) => {
201 let content = fs::read_to_string(&file);
202 match content {
203 Ok(content) => {
204 let mut outs: Vec<String> = Vec::new();
205
206 for line in content.lines() {
207 let result = eval_expr_from_string(line)?;
208 outs.push(format!("{} = {}", &line, result));
209 }
210 match fs::write(file + ".out", outs.join("\n")) {
211 Ok(_) => (),
212 Err(e) => return Err(MyError::IOError(e)),
213 }
214 }
215 Err(e) => {
216 return Err(MyError::IOError(e));
217 }
218 }
219 }
220 None => {
221 return Err(MyError::CLIError("No file specified.".to_string()));
222 }
223 }
224
225 Ok(())
226 }
227}