Minor error handling fixed
[evpf.git] / src / main.rs
index c911ca2..51ea94e 100644 (file)
@@ -1,7 +1,31 @@
-use std::io;
-use std::io::Write;
 use regex::Regex;
+use colored::*;
+use std::env;
+use rustyline::Editor;
+use std::path::PathBuf;
 
+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 HELP_TEXT : [&str ; 4] = 
+                               ["Type a postfix expression to evaluate.",
+                                "Example: 4 5 + 12 -",
+                                "Supported operators: +, -, *, /",
+                                "Type q, Q to quit"
+                                ];
+                                
+const HOMEDIR_NOT_FOUND : &'static str = "User home directory not found \
+                               (missing environment?).";
+                               
+const HISTORY_FILE_NOT_FOUND : &'static str = "History file not found.";
+
+const SAVE_HISTORY_ERROR : &'static str = "Unable to save command history!";
+
+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,
@@ -21,13 +45,13 @@ fn get_result (t : &mut Vec<Expression>, op : Operator) -> Result<f32,String> {
        // if nothing - panic with error
        let n1 = t.pop ();
        if n1.is_none () { 
-               return Err ("Illegal expression".to_string());
+               return Err (ILLEGAL_EXP.to_string());
        }
        // pop the stack for the first operand
        // if nothing - panic with error
        let n2 = t.pop ();
        if n2.is_none () {
-               return Err ("Illegal expression!".to_string());
+               return Err (ILLEGAL_EXP.to_string());
        }
        
        let num1 = n1.unwrap().value;
@@ -97,8 +121,7 @@ fn evaluate (expr : &str, match_num : &regex::Regex) -> Result<f32,String> {
                          }     
                        // if word doesn't match either operator or number then panic.                  
                        else {
-                               return Err ("Error parsing expression! \
-                                Must be a number or operator (+,-,* or /)".to_string());
+                               return Err (ERR_PARSING_NOT_MATCH.to_string());
                        }
                }
        }
@@ -106,7 +129,7 @@ fn evaluate (expr : &str, match_num : &regex::Regex) -> Result<f32,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
-               return Err ("Postfix expression incomplete!".to_string());
+               return Err (ERR_POSTFIX_INCOMPLETE.to_string());
        } else { 
        // stack has only one item which is the result so display it
                let res = ops[0].value;
@@ -114,43 +137,144 @@ fn evaluate (expr : &str, match_num : &regex::Regex) -> Result<f32,String> {
        }
 }
 
-fn main() {
+// Single command mode - command line arguments mode - evaluate the expression
+// given in the command line and quit
+fn run_command_line (args : &Vec<String>, match_num : &regex::Regex) {
+       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! ("{}", res.unwrap());
+               println! ("{}", restxt.green ());
+       } else {
+       // print the error in purple
+               let errtxt = format! ("{}: {}", ERROR, 
+                                                                               res.unwrap_err());
+               eprintln! ("{}", errtxt.purple ());
+       }
+}
+
+// get the history file name as string - home dir + .evpfhistory
+fn get_history_file () -> Result<String,String> {
+       // get the environment variable HOME
+       let home_path = env::var ("HOME");
+       // if not found, return an error
+       if home_path.is_err () {
+               return Err (HOMEDIR_NOT_FOUND.to_string());
+       }
+       // build the path for the history file i.e. homedir + .evpfhistory in
+       // platform independent way
+       let mut hist_file = PathBuf::new ();
+       hist_file.push (home_path.unwrap());
+       hist_file.push (".evpfhistory");        
+       
+       // if cannot convert to string return error 
+       if hist_file.to_str ().is_none () {
+               return Err (HOMEDIR_NOT_FOUND.to_string());
+       }
+       
+       // return the history file path as a string
+       let hist_file_path = String::from (hist_file.to_str().unwrap());
+       return Ok (hist_file_path);
+}
+
+// Interactive mode - display the prompt and evaluate expressions entered into
+// the prompt - until user quits
+fn run_interactive_mode (match_num : &regex::Regex) {
        // get a line from input and evaluate it 
        let mut expr = String::new ();
-       // regular expression to match a number 
-       let match_num = Regex::new (r"^\d+?\.*?\d*?$").unwrap ();
+
+       let mut rl = Editor::<()>::new ();
+       
+       // load the history file
+       let hist_file = get_history_file ();
+       // if unable to load the history file, display the appropriate error
+       if hist_file.is_err () {
+               eprintln! ("{}", &hist_file.unwrap_err());
+       } else {
+               if rl.load_history (&hist_file.unwrap ()).is_err () {
+                       eprintln! ("{}", HISTORY_FILE_NOT_FOUND.purple());
+               }
+       }
        // loop until a blank line is received  
        loop {
-               expr.clear ();  
-               print!("evpf>");
-               io::stdout().flush ().expect ("Error flushing!");
-               // read a line of text
-               io::stdin().read_line (&mut expr).expect ("Error reading line!");
-               // trim the text
-               let expr = expr.trim ();
+               expr.clear ();
+               
+               let line = rl.readline ("evpf> ");
+               if line.is_err () {
+                       break;
+               }
+               let hist = line.unwrap ();
+               rl.add_history_entry (&hist);
+               expr.push_str (&hist);
 
                if expr == "q" || expr == "Q" {
                // quit if the expression is q or Qs 
                        break;
                } else if expr == "?" || expr == "h" || expr == "H" {
                // display help text
-                       println! ("Type an expression in postfix style to evaluate.");
-                       println! ("Example: 4 5 + 12 -");
-                       println! ("Supported operators: +, -, *, /");
-                       println! ("Type q, Q to quit");
+                       for text in HELP_TEXT.iter() {
+                               println! ("{}", text.cyan() );
+                       }
+
                        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 () {
-                       println! ("Result: {}", res.unwrap());
+                       let restxt = format! ("{}", res.unwrap());
+                       println! ("{}", restxt.green ());
+               } else {
+               // print the error in purple
+                       let errtxt = format! ("{}: {}", ERROR, 
+                                                                                       res.unwrap_err());
+                       eprintln! ("{}", errtxt.purple());
+                       eprintln! ("{}", ERROR_HELP.purple());
                }
-               else {
-                       eprintln! ("Error: {}", res.unwrap_err());
-                       eprintln! ("Type ? or h or H for help");
+       }
+       // save the history 
+       let hist_file = get_history_file ();
+       if ! hist_file.is_err () {
+               if rl.save_history (&hist_file.unwrap()).is_err () {
+                       eprintln! ("{}", SAVE_HISTORY_ERROR);
                }
+       } else {
+               eprintln! ("{}", &hist_file.unwrap_err());
+       }
+}
+
+fn main() {
+       // collect the command line arguments - if any 
+       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      
+               run_command_line (&args, &match_num);
+               
+       } else {
+               // if arguments are not provided run in interactive mode - 
+               // display a prompt and get the expression 
+               // repeat until the user quits
+               run_interactive_mode (&match_num);
+
        }
 }