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