arithmetic_parser_kma/
lib.rs

1pub 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        // Precedence is defined lowest to highest
18        PrattParser::new()
19            // Addition and subtract have equal precedence
20            .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
38/// Parse the input pairs into an abstract syntax tree representing the expression.
39///
40/// # Arguments
41///
42/// * `pairs` - A sequence of tokens representing the input expression.
43///
44/// # Returns
45///
46/// An abstract syntax tree representing the parsed expression.
47pub 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
75/// Evaluate the given expression and return the result.
76///
77/// # Arguments
78///
79/// * `expr` - An abstract syntax tree representing the expression to be evaluated.
80///
81/// # Returns
82///
83/// The numerical result of the evaluated expression.
84pub 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
99/// Evaluate an expression from a string input and return the result.
100///
101/// # Arguments
102///
103/// * `s` - A string representing the expression to be evaluated.
104///
105/// # Returns
106///
107/// A Result containing the numerical result of the evaluated expression, or an error if parsing fails.
108pub 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/// MyError is an error type used to return errors from the parser.
120#[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/// Op describes a mathematical operation.
139#[derive(Debug)]
140pub enum Op {
141    Add,
142    Subtract,
143    Multiply,
144    Divide,
145    Invalid,
146}
147
148/// cli ia a modul with cli implementation.
149pub mod cli {
150    use super::*;
151    use clap::Parser as ParserClap;
152    use std::fs;
153
154    /// Parser CLI.
155    #[derive(ParserClap, Debug)]
156    #[command(author, version, about, long_about = None)]
157    struct Args {
158        /// Defines console mode.
159        #[arg(short, long)]
160        console: bool,
161
162        /// Defines file mode.
163        #[arg(short, long)]
164        file: Option<String>,
165    }
166
167    /// run runs the programm.
168    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}