Added command line argument option to run as a single command
[evpf.git] / src / main.rs
index dcf215f..2154ba5 100644 (file)
 use std::io;
+use std::io::Write;
 use regex::Regex;
+use ansi_term::Colour;
+use ansi_term::Style;
+use std::env;
 
-// describe the operators
-enum Operator {
-       NUM, ADD, SUB, MUL, DIV,
+const ILLEGAL_EXP : &'static str = "Illegal Expression";
+const ERR_PARSING_NOT_MATCH : &'static str = "Error parsing expression! \
+                                Must be a number or operator (+,-,* or /)";
+const ERR_POSTFIX_INCOMPLETE : &'static str = "Postfix expression incomplete!";
+const ERR_FLUSHING : &'static str = "Error flushing!";
+const ERR_READING_LINE : &'static str = "Error reading line!";
+const HELP_TEXT : [&str ; 4] = 
+                               ["Type an expression in postfix style to evaluate.",
+                                "Example: 4 5 + 12 -",
+                                "Supported operators: +, -, *, /",
+                                "Type q, Q to quit"
+                                ];
+
+const RESULT : &'static str = "Result";
+const ERROR : &'static str = "Error";
+const ERROR_HELP : &'static str = "Type ? or h or H for help";
+
+// Describe an operator - one of add, subtract, multiply or divide
+enum Operator { 
+       ADD,
+       SUB,
+       MUL,
+       DIV,
 }
 
 // structure to hold an expression in the stack
 struct Expression {
        value : f32,
-       operator : Operator,
 }
 
 // function to compute a result by popping the stack and 
 // pushing back the result
-fn get_result (t : &mut Vec::<Expression>) {
-       // pop the stack to get the operator
-       // if nothing - panic with error
-       let op = match t.pop () {
-               Some(x) => x,
-               None => panic! ("Illegal expression!"),
-       };
+fn get_result (t : &mut Vec<Expression>, op : Operator) -> Result<f32,String> {
        // pop the stack for last operand 
        // if nothing - panic with error
-       let n1 = match t.pop () { 
-               Some (x) => x,
-               None => panic! ("Illegal expression!"),
-       };
+       let n1 = t.pop ();
+       if n1.is_none () { 
+               return Err (ILLEGAL_EXP.to_string());
+       }
        // pop the stack for the first operand
        // if nothing - panic with error
-       let n2 = match t.pop () {
-               Some (x) => x,
-               None => panic! ("Illegal expression!"),
-       };
-
-       let mut res : f32 = 0.0;
+       let n2 = t.pop ();
+       if n2.is_none () {
+               return Err (ILLEGAL_EXP.to_string());
+       }
+       
+       let num1 = n1.unwrap().value;
+       let num2 = n2.unwrap().value;
        
        // depending on the operation, set the result
-       match op.operator {
-               Operator::ADD => res = n1.value + n2.value,
-               Operator::SUB => res = n2.value - n1.value,
-               Operator::MUL => res = n1.value * n2.value,
-               Operator::DIV => res = n2.value / n1.value,
-               _       => panic! ("Illegal operator in expression!"),
+       match op {
+               Operator::ADD => {                              
+                                                       t.push (Expression{value: num2 + num1});
+                                                       Ok (num2 + num1)
+                                                },
+               Operator::SUB => {                              
+                                                       t.push (Expression{value: num2 - num1});
+                                                       Ok (num2 + num1)
+                                                },
+               Operator::MUL => {                              
+                                                       t.push (Expression{value: num2 * num1});
+                                                       Ok (num2 * num1)
+                                                },
+               Operator::DIV => {                              
+                                                       t.push (Expression{value: num2 / num1});
+                                                       Ok (num2 / num1)
+                                                },
        }
-       // push the result back to the stack
-       t.push (Expression {value : res, operator : Operator::NUM });
 }
 
 // evaluation function
-fn evaluate (expr : &str) {
+fn evaluate (expr : &str, match_num : &regex::Regex) -> Result<f32,String> {
        let mut ops = Vec::<Expression>::new ();
        // tokenize the individual words by splitting at whitespace
        let words = expr.split_whitespace ();
-       // regular expression to match a number 
-       let match_num = Regex::new (r"^\d+?.*?\d*?$").unwrap ();
+
        // iterate over the words
        for word in words {
                match word {
                // if the word matches one of +, -, *, / then push it on the stack
                // and immediately evaluate the expression by popping the operator 
                // and the last two operands and push the result back on to the stack           
-               "+" => {ops.push (Expression{ value: 0.0, operator: Operator::ADD });
-                          get_result (&mut ops) },
-               "-" => {ops.push (Expression{ value: 0.0, operator: Operator::SUB });
-                          get_result (&mut ops) },
-               "*" => {ops.push (Expression{ value: 0.0, operator: Operator::MUL});
-                          get_result (&mut ops)},
-               "/" => {ops.push (Expression{ value: 0.0, operator: Operator::DIV});
-                          get_result (&mut ops) },
+               "+" => {
+                               let m = get_result (&mut ops, Operator::ADD);
+                               if ! m.is_ok () {
+                                       return Err (m.unwrap_err().to_string());
+                               }
+                          },
+               "-" => {
+                               let m = get_result (&mut ops, Operator::SUB);
+                               if ! m.is_ok () {
+                                       return Err (m.unwrap_err().to_string());
+                               }
+                          },
+               "*" => {
+                               let m = get_result (&mut ops, Operator::MUL);
+                               if ! m.is_ok () {
+                                       return Err (m.unwrap_err().to_string());
+                               }
+                          },
+               "/" => {
+                               let m = get_result (&mut ops, Operator::DIV); 
+                               if ! m.is_ok () {
+                                       return Err (m.unwrap_err().to_string());
+                               }
+                          },
                        // if word matches a number, push it on to the stack
                _ =>  if match_num.is_match (word) {
-                               let num : f32 = word.parse ().unwrap ();
-                               ops.push (Expression {value: num, operator: Operator::NUM });
-                       }
+                               let num = word.parse ().unwrap ();
+                               ops.push (Expression { value: num });
+                         }     
                        // if word doesn't match either operator or number then panic.                  
                        else {
-                               panic! ("Error parsing expression!
-                                Must be a number or operator (+,-,* or /)");
-                       }               
+                               return Err (ERR_PARSING_NOT_MATCH.to_string());
+                       }
                }
        }
+       
+       if ops.len () > 1 {
        // if the stack has more than one value, it means that the postfix
        // expression is not complete - so display the stack status
-       if ops.len () > 1 {
-               println! ("Postfix Expression not complete. Current stack: ");
-               for exp in ops {
-                       println! ("{}", exp.value);
-               }
-       }
+               return Err (ERR_POSTFIX_INCOMPLETE.to_string());
+       } else { 
        // stack has only one item which is the result so display it
-       else {
-               println! ("Result: {}", ops[0].value);
+               let res = ops[0].value;
+               return Ok (res);
        }
-       println! ();    
 }
 
 fn main() {
-       // get a line from input and evaluate it 
-       let mut expr = String::new ();
-       // loop until a blank line is received
-       loop {
-               expr.clear ();  
-               println!("Enter an expression (postfix) (blank to quit): ");
-               // read a line of text
-               io::stdin().read_line (&mut expr).expect ("Error reading line!");
-               // trim the text
-               let expr = expr.trim ();
-               // quit if the expression is blank
-               if expr == "" { 
-                       break;
+
+       let args : Vec<String> = env::args().collect ();
+       // regular expression to match a number 
+       let match_num = Regex::new (r"^\d+?\.*?\d*?$").unwrap ();
+       
+       if args.len () > 1 {
+       // if arguments are provided run in command line mode - i.e. print the 
+       // result and exit      
+               let mut expr = String::new ();
+               let mut i = 0;
+               // create the expression string to evaluate
+               for arg in args.iter() {
+                       if i > 0 {
+                               expr.push_str (&arg);
+                               expr.push_str (" ");
+                       }
+                       i += 1;
                }
+               // evaluate the result 
+               let res = evaluate (&expr, &match_num);
+               // if Result is OK then print the result in green
+               if res.is_ok () {
+                       let restxt = format! ("{}: {}", RESULT, 
+                                                                                       res.unwrap());
+                       println! ("{}", Colour::Green.paint (restxt));
+               } else {
+               // print the error in purple
+                       let errtxt = format! ("{}: {}", ERROR, 
+                                                                                       res.unwrap_err());
+                       eprintln! ("{}", Colour::Purple.paint (errtxt));
+               }               
+               
+       } else {
+               // if arguments are not provided run in interactive mode - 
+               // display a prompt and get the expression 
+               // repeat until the user quits
                
-               evaluate (&expr);
+               // get a line from input and evaluate it 
+               let mut expr = String::new ();
+
+               // loop until a blank line is received  
+               loop {
+                       expr.clear ();  
+                       print!("{}", Style::new().bold().paint("evpf>"));
+                       io::stdout().flush ().expect (ERR_FLUSHING);
+                       // read a line of text
+                       io::stdin().read_line (&mut expr).expect (ERR_READING_LINE);
+                       // trim the text
+                       let expr = expr.trim ();
+
+                       if expr == "q" || expr == "Q" {
+                       // quit if the expression is q or Qs 
+                               break;
+                       } else if expr == "?" || expr == "h" || expr == "H" {
+                       // display help text
+                               for text in HELP_TEXT.iter() {
+                                       println! ("{}", Colour::Cyan.paint(*text));
+                               }
+
+                               continue;
+                       } else if expr == "" {
+                       // continue without proceeding
+                               continue;
+                       }
+                       
+                       // Evaluate result
+                       let res = evaluate (&expr, &match_num);
+                       
+                       // if Result is OK then print the result in green
+                       if res.is_ok () {
+                               let restxt = format! ("{}: {}", RESULT, 
+                                                                                               res.unwrap());
+                               println! ("{}", Colour::Green.paint (restxt));
+                       } else {
+                       // print the error in purple
+                               let errtxt = format! ("{}: {}", ERROR, 
+                                                                                               res.unwrap_err());
+                               eprintln! ("{}", Colour::Purple.paint (errtxt));
+                               eprintln! ("{}", Colour::Purple.paint (ERROR_HELP));
+                       }
+               }
        }
 }