-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 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.",
+ ["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 RESULT : &'static str = "Result";
const ERROR : &'static str = "Error";
const ERROR_HELP : &'static str = "Type ? or h or H for help";
}
}
-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 : ®ex::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 ());
+ }
+}
- let args : Vec<String> = env::args().collect ();
- // regular expression to match a number
- let match_num = Regex::new (r"^\d+?\.*?\d*?$").unwrap ();
+// 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 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 (" ");
+ // 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 : ®ex::Regex) {
+ // get a line from input and evaluate it
+ let mut expr = String::new ();
+
+ 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 ();
+
+ 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
+ for text in HELP_TEXT.iter() {
+ println! ("{}", text.cyan() );
}
- i += 1;
+
+ continue;
+ } else if expr == "" {
+ // continue without proceeding
+ continue;
}
- // evaluate the result
+
+ // 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());
+ 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! ("{}", errtxt.purple());
+ eprintln! ("{}", ERROR_HELP.purple());
+ }
+ }
+ // 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
-
- // get a line from input and evaluate it
- let mut expr = String::new ();
-
- // loop until a blank line is received
- loop {
- expr.clear ();
- print!("{}", "evpf>".bold() );
- 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! ("{}", text.cyan() );
- }
+ run_interactive_mode (&match_num);
- 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! ("{}", restxt.green ());
- } else {
- // print the error in purple
- let errtxt = format! ("{}: {}", ERROR,
- res.unwrap_err());
- eprintln! ("{}", errtxt.purple());
- eprintln! ("{}", ERROR_HELP.purple());
- }
- }
}
}