Refactored the main() function
[evpf.git] / src / main.rs
1 use std::io;
2 use std::io::Write;
3 use regex::Regex;
4 use colored::*;
5 use std::env;
6
7 const ILLEGAL_EXP : &'static str = "Illegal Expression";
8 const ERR_PARSING_NOT_MATCH : &'static str = "Error parsing expression! \
9 Must be a number or operator (+,-,* or /)";
10 const ERR_POSTFIX_INCOMPLETE : &'static str = "Postfix expression incomplete!";
11 const ERR_FLUSHING : &'static str = "Error flushing!";
12 const ERR_READING_LINE : &'static str = "Error reading line!";
13 const HELP_TEXT : [&str ; 4] =
14 ["Type a postfix expression to evaluate.",
15 "Example: 4 5 + 12 -",
16 "Supported operators: +, -, *, /",
17 "Type q, Q to quit"
18 ];
19
20 const ERROR : &'static str = "Error";
21 const ERROR_HELP : &'static str = "Type ? or h or H for help";
22
23 // Describe an operator - one of add, subtract, multiply or divide
24 enum Operator {
25 ADD,
26 SUB,
27 MUL,
28 DIV,
29 }
30
31 // structure to hold an expression in the stack
32 struct Expression {
33 value : f32,
34 }
35
36 // function to compute a result by popping the stack and
37 // pushing back the result
38 fn get_result (t : &mut Vec<Expression>, op : Operator) -> Result<f32,String> {
39 // pop the stack for last operand
40 // if nothing - panic with error
41 let n1 = t.pop ();
42 if n1.is_none () {
43 return Err (ILLEGAL_EXP.to_string());
44 }
45 // pop the stack for the first operand
46 // if nothing - panic with error
47 let n2 = t.pop ();
48 if n2.is_none () {
49 return Err (ILLEGAL_EXP.to_string());
50 }
51
52 let num1 = n1.unwrap().value;
53 let num2 = n2.unwrap().value;
54
55 // depending on the operation, set the result
56 match op {
57 Operator::ADD => {
58 t.push (Expression{value: num2 + num1});
59 Ok (num2 + num1)
60 },
61 Operator::SUB => {
62 t.push (Expression{value: num2 - num1});
63 Ok (num2 + num1)
64 },
65 Operator::MUL => {
66 t.push (Expression{value: num2 * num1});
67 Ok (num2 * num1)
68 },
69 Operator::DIV => {
70 t.push (Expression{value: num2 / num1});
71 Ok (num2 / num1)
72 },
73 }
74 }
75
76 // evaluation function
77 fn evaluate (expr : &str, match_num : &regex::Regex) -> Result<f32,String> {
78 let mut ops = Vec::<Expression>::new ();
79 // tokenize the individual words by splitting at whitespace
80 let words = expr.split_whitespace ();
81
82 // iterate over the words
83 for word in words {
84 match word {
85 // if the word matches one of +, -, *, / then push it on the stack
86 // and immediately evaluate the expression by popping the operator
87 // and the last two operands and push the result back on to the stack
88 "+" => {
89 let m = get_result (&mut ops, Operator::ADD);
90 if ! m.is_ok () {
91 return Err (m.unwrap_err().to_string());
92 }
93 },
94 "-" => {
95 let m = get_result (&mut ops, Operator::SUB);
96 if ! m.is_ok () {
97 return Err (m.unwrap_err().to_string());
98 }
99 },
100 "*" => {
101 let m = get_result (&mut ops, Operator::MUL);
102 if ! m.is_ok () {
103 return Err (m.unwrap_err().to_string());
104 }
105 },
106 "/" => {
107 let m = get_result (&mut ops, Operator::DIV);
108 if ! m.is_ok () {
109 return Err (m.unwrap_err().to_string());
110 }
111 },
112 // if word matches a number, push it on to the stack
113 _ => if match_num.is_match (word) {
114 let num = word.parse ().unwrap ();
115 ops.push (Expression { value: num });
116 }
117 // if word doesn't match either operator or number then panic.
118 else {
119 return Err (ERR_PARSING_NOT_MATCH.to_string());
120 }
121 }
122 }
123
124 if ops.len () > 1 {
125 // if the stack has more than one value, it means that the postfix
126 // expression is not complete - so display the stack status
127 return Err (ERR_POSTFIX_INCOMPLETE.to_string());
128 } else {
129 // stack has only one item which is the result so display it
130 let res = ops[0].value;
131 return Ok (res);
132 }
133 }
134
135 // Single command mode - command line arguments mode - evaluate the expression
136 // given in the command line and quit
137 fn run_command_line (args : &Vec<String>, match_num : &regex::Regex) {
138 let mut expr = String::new ();
139 let mut i = 0;
140 // create the expression string to evaluate
141 for arg in args.iter() {
142 if i > 0 {
143 expr.push_str (&arg);
144 expr.push_str (" ");
145 }
146 i += 1;
147 }
148 // evaluate the result
149 let res = evaluate (&expr, &match_num);
150 // if Result is OK then print the result in green
151 if res.is_ok () {
152 let restxt = format! ("{}", res.unwrap());
153 println! ("{}", restxt.green ());
154 } else {
155 // print the error in purple
156 let errtxt = format! ("{}: {}", ERROR,
157 res.unwrap_err());
158 eprintln! ("{}", errtxt.purple ());
159 }
160 }
161
162 // Interactive mode - display the prompt and evaluate expressions entered into
163 // the prompt - until user quits
164 fn run_interactive_mode (match_num : &regex::Regex) {
165 // get a line from input and evaluate it
166 let mut expr = String::new ();
167
168 // loop until a blank line is received
169 loop {
170 expr.clear ();
171 print!("{}", "evpf>".bold() );
172 io::stdout().flush ().expect (ERR_FLUSHING);
173 // read a line of text
174 io::stdin().read_line (&mut expr).expect (ERR_READING_LINE);
175 // trim the text
176 let expr = expr.trim ();
177
178 if expr == "q" || expr == "Q" {
179 // quit if the expression is q or Qs
180 break;
181 } else if expr == "?" || expr == "h" || expr == "H" {
182 // display help text
183 for text in HELP_TEXT.iter() {
184 println! ("{}", text.cyan() );
185 }
186
187 continue;
188 } else if expr == "" {
189 // continue without proceeding
190 continue;
191 }
192
193 // Evaluate result
194 let res = evaluate (&expr, &match_num);
195
196 // if Result is OK then print the result in green
197 if res.is_ok () {
198 let restxt = format! ("{}", res.unwrap());
199 println! ("{}", restxt.green ());
200 } else {
201 // print the error in purple
202 let errtxt = format! ("{}: {}", ERROR,
203 res.unwrap_err());
204 eprintln! ("{}", errtxt.purple());
205 eprintln! ("{}", ERROR_HELP.purple());
206 }
207 }
208 }
209
210 fn main() {
211 // collect the command line arguments - if any
212 let args : Vec<String> = env::args().collect ();
213 // regular expression to match a number
214 let match_num = Regex::new (r"^\d+?\.*?\d*?$").unwrap ();
215
216 if args.len () > 1 {
217 // if arguments are provided run in command line mode - i.e. print the
218 // result and exit
219 run_command_line (&args, &match_num);
220
221 } else {
222 // if arguments are not provided run in interactive mode -
223 // display a prompt and get the expression
224 // repeat until the user quits
225 run_interactive_mode (&match_num);
226
227 }
228 }