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