diff --git a/src/builtins/calc.rs b/src/builtins/calc.rs new file mode 100644 index 0000000000000000000000000000000000000000..590a76a0af054a2c56c7810eacbf4c27ad136ffb --- /dev/null +++ b/src/builtins/calc.rs @@ -0,0 +1,316 @@ +use std::io::{self, Write}; +use std::error::Error; +use self::CalcError::*; + +#[derive(Debug, Clone)] +pub enum Token { + Plus, + Minus, + Divide, + Multiply, + Exponent, + OpenParen, + CloseParen, + Number(String), +} + +impl Token { + pub fn to_str(&self) -> &'static str { + match *self { + Token::Plus => "Plus", + Token::Minus => "Minus", + Token::Divide => "Divide", + Token::Multiply => "Multiply", + Token::Exponent => "Exponent", + Token::OpenParen => "OpenParen", + Token::CloseParen => "CloseParen", + Token::Number(_) => "Number", + } + } + + pub fn to_string(&self) -> String { + self.to_str().to_owned() + } +} + +#[derive(Debug)] +pub enum CalcError { + DivideByZero, + InvalidNumber(String), + InvalidOperator(char), + UnrecognizedToken(String), + UnexpectedToken(String, &'static str), + UnexpectedEndOfInput, + UnmatchedParenthesis, + IO(io::Error), +} + +impl From<CalcError> for String { + fn from(data: CalcError) -> String { + match data { + DivideByZero => String::from("calc: attempted to divide by zero"), + InvalidNumber(number) => ["calc: invalid number: ", &number].concat(), + InvalidOperator(character) => format!("calc: invalid operator: {}", character), + IO(error) => error.description().to_owned(), + UnrecognizedToken(token) => ["calc: unrecognized token: ", &token].concat(), + UnexpectedToken(token, kind) => ["calc: unexpected ", kind, " token: ", &token].concat(), + UnexpectedEndOfInput => String::from("calc: unexpected end of input"), + UnmatchedParenthesis => String::from("calc: unmatched parenthesis") + } + } +} + +#[derive(Clone,Debug)] +pub struct IntermediateResult { + value: f64, + tokens_read: usize, +} + +impl IntermediateResult { + fn new(value: f64, tokens_read: usize) -> Self { + IntermediateResult { + value: value, + tokens_read: tokens_read, + } + } +} + +pub trait OperatorFunctions { + fn is_operator(self) -> bool; + fn operator_type(self) -> Option<Token>; +} + +impl OperatorFunctions for char { + fn is_operator(self) -> bool { + self == '+' || + self == '-' || + self == '*' || + self == '/' || + self == '^' || + self == '(' || + self == ')' + } + + fn operator_type(self) -> Option<Token> { + match self { + '+' => Some(Token::Plus), + '-' => Some(Token::Minus), + '/' => Some(Token::Divide), + '*' => Some(Token::Multiply), + '^' => Some(Token::Exponent), + '(' => Some(Token::OpenParen), + ')' => Some(Token::CloseParen), + _ => None + } + } +} + +pub fn tokenize(input: &str) -> Result<Vec<Token>, CalcError> { + let mut tokens = Vec::with_capacity(input.len()); + + // TODO: Not this. Modify to use iterator + let chars: Vec<char> = input.chars().collect(); + + let input_length = chars.len(); + let mut current_pos = 0; + while current_pos < input_length { + let c = chars[current_pos]; + if c.is_digit(10) || c == '.' { + let token_string = consume_number(&chars[current_pos..]); + current_pos += token_string.len(); + tokens.push(Token::Number(token_string)); + } else if c.is_operator() { + tokens.push(c.operator_type().ok_or_else(|| InvalidOperator(c))?); + current_pos += 1; + } else if c.is_whitespace() { + current_pos += 1; + } else { + let token_string = consume_until_new_token(&chars[current_pos..]); + return Err(CalcError::UnrecognizedToken(token_string)); + } + } + Ok(tokens) +} + +fn consume_number(input: &[char]) -> String { + let mut number = String::with_capacity(input.len()); + let mut has_decimal_point = false; + for &c in input { + if c == '.' { + if has_decimal_point { + break; + } else { + number.push(c); + has_decimal_point = true; + } + } else if c.is_digit(10) { + number.push(c); + } else { + break; + } + } + number +} + +fn consume_until_new_token(input: &[char]) -> String { + input.iter() + .take_while(|c| !(c.is_whitespace() || c.is_operator() || c.is_digit(10))) + .cloned() + .collect() +} + +// Addition and subtraction +pub fn e_expr(token_list: &[Token]) -> Result<IntermediateResult, CalcError> { + let mut t1 = try!(t_expr(token_list)); + let mut index = t1.tokens_read; + + while index < token_list.len() { + match token_list[index] { + Token::Plus => { + let t2 = try!(t_expr(&token_list[index+1..])); + t1.value += t2.value; + t1.tokens_read += t2.tokens_read + 1; + } + Token::Minus => { + let t2 = try!(t_expr(&token_list[index+1..])); + t1.value -= t2.value; + t1.tokens_read += t2.tokens_read + 1; + } + Token::Number(ref n) => return Err(CalcError::UnexpectedToken(n.clone(),"operator")), + _ => break, + }; + index = t1.tokens_read; + } + Ok(t1) +} + +// Multiplication and division +pub fn t_expr(token_list: &[Token]) -> Result<IntermediateResult, CalcError> { + let mut f1 = try!(f_expr(token_list)); + let mut index = f1.tokens_read; + + while index < token_list.len() { + match token_list[index] { + Token::Multiply => { + let f2 = try!(f_expr(&token_list[index+1..])); + f1.value *= f2.value; + f1.tokens_read += f2.tokens_read + 1; + } + Token::Divide => { + let f2 = try!(f_expr(&token_list[index+1..])); + if f2.value == 0.0 { + return Err(CalcError::DivideByZero); + } else { + f1.value /= f2.value; + f1.tokens_read += f2.tokens_read + 1; + } + } + Token::Number(ref n) => return Err(CalcError::UnexpectedToken(n.clone(),"operator")), + _ => break, + } + index = f1.tokens_read; + } + Ok(f1) +} + +// Exponentiation +pub fn f_expr(token_list: &[Token]) -> Result<IntermediateResult, CalcError> { + let mut g1 = try!(g_expr(token_list)); + let mut index = g1.tokens_read; + let token_len = token_list.len(); + while index < token_len { + match token_list[index] { + Token::Exponent => { + let f = try!(f_expr(&token_list[index+1..])); + g1.value = g1.value.powf(f.value); + g1.tokens_read += f.tokens_read + 1; + } + Token::Number(ref n) => return Err(CalcError::UnexpectedToken(n.clone(),"operator")), + _ => break, + } + index = g1.tokens_read; + } + Ok(g1) +} + +// Numbers and parenthesized expressions +pub fn g_expr(token_list: &[Token]) -> Result<IntermediateResult, CalcError> { + if !token_list.is_empty() { + match token_list[0] { + Token::Number(ref n) => { + n.parse::<f64>() + .map_err(|_| CalcError::InvalidNumber(n.clone())) + .and_then(|num| Ok(IntermediateResult::new(num, 1))) + } + Token::Minus => { + if token_list.len() > 1 { + if let Token::Number(ref n) = token_list[1] { + n.parse::<f64>() + .map_err(|_| CalcError::InvalidNumber(n.clone())) + .and_then(|num| Ok(IntermediateResult::new(-1.0 * num, 2))) + } else { + Err(CalcError::UnexpectedToken(token_list[1].to_string(), "number")) + } + } else { + Err(CalcError::UnexpectedEndOfInput) + } + } + Token::OpenParen => { + let expr = e_expr(&token_list[1..]); + match expr { + Ok(ir) => { + let close_paren = ir.tokens_read + 1; + if close_paren < token_list.len() { + match token_list[close_paren] { + Token::CloseParen => Ok(IntermediateResult::new(ir.value, close_paren+1)), + _ => Err(CalcError::UnexpectedToken(token_list[close_paren].to_string(), ")")), + } + } else { + Err(CalcError::UnmatchedParenthesis) + } + } + Err(e) => Err(e), + } + } + _ => Err(CalcError::UnexpectedToken(token_list[0].to_string(), "number")) + } + } else { + Err(CalcError::UnexpectedEndOfInput) + } +} + +pub fn parse(tokens: &[Token]) -> Result<String, CalcError> { + e_expr(tokens).map(|answer| answer.value.to_string()) +} + +fn eval(input: &str) -> Result<String, CalcError> { + tokenize(input).and_then(|x| parse(&x)) +} + +pub fn calc(args: &[String]) -> Result<(), String> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + if !args.is_empty() { + let result = eval(&args.join(""))?; + writeln!(stdout, "{}", result).map_err(CalcError::IO)?; + } else { + let prompt = b"[]> "; + loop { + let _ = stdout.write(prompt).map_err(CalcError::IO)?; + let mut input = String::new(); + io::stdin().read_line(&mut input).map_err(CalcError::IO)?; + if input.is_empty() { + break; + } else { + match input.trim() { + "" => (), + "exit" => break, + s => { + writeln!(stdout, "{}", eval(s)?).map_err(CalcError::IO)?; + }, + } + } + } + } + Ok(()) +} diff --git a/src/builtins/echo.rs b/src/builtins/echo.rs index ee2fd5531a219e464d8d4322099ba1cfa180bafe..7dda02e90de39d22a8e18e7f930af267d30b0d4e 100644 --- a/src/builtins/echo.rs +++ b/src/builtins/echo.rs @@ -44,7 +44,7 @@ pub fn echo(args: &[String]) -> Result<(), io::Error> { "--no-newline" => flags |= NO_NEWLINE, "--no-spaces" => flags |= NO_SPACES, _ => { - if arg.starts_with("-") { + if arg.starts_with('-') { let arg = &arg[1..]; for argopt in arg.chars() { match argopt { diff --git a/src/builtins/functions.rs b/src/builtins/functions.rs index b95fbf9b02b2a448e0a91fe479f9df98b71a30ec..542a8277ca1de304436ffc855d83385a5793bf8f 100644 --- a/src/builtins/functions.rs +++ b/src/builtins/functions.rs @@ -8,7 +8,7 @@ fn print_functions(functions: &FnvHashMap<String, Function>) { let stdout = &mut stdout.lock(); let _ = writeln!(stdout, "# Functions"); for fn_name in functions.keys() { - let ref description = functions.get(fn_name).unwrap().description; + let description = &functions.get(fn_name).unwrap().description; if description.len() >= 1 { let _ = writeln!(stdout, " {} -- {}", fn_name, description); } else { diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 787c258a48bf74e2730ae54026a4374b3c425d2b..931fc761a3f7d499fd263b85b105e2ea33c7ad08 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,7 +1,8 @@ pub mod source; pub mod variables; pub mod functions; -pub mod echo; +mod echo; +mod calc; use self::variables::{alias, drop_alias, drop_variable, export_variable}; use self::functions::fn_; @@ -232,6 +233,23 @@ impl Builtin { } }); + commands.insert("calc", + Builtin { + name: "calc", + help: "Calculate a mathematical expression", + main: box |args: &[String], _: &mut Shell| -> i32 { + match calc::calc(&args[1..]) { + Ok(()) => SUCCESS, + Err(why) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + FAILURE + } + } + } + }); + commands.insert("true", Builtin { name: "true", diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 56cce713eb6371451d1113e728008d8c7ca5d60c..19ed0f1f8298fc86477343c93844ac66553171d8 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -1,4 +1,4 @@ -// TODO: +// TODO: // - Rewrite this in the same style as shell_expand::words. // - Validate syntax in methods @@ -120,7 +120,7 @@ impl<'a> Iterator for StatementSplitter<'a> { }, b'}' if self.flags & VBRACE != 0 => self.flags ^= VBRACE, b'(' if self.flags & (COMM_1 + VARIAB + ARRAY) == 0 => { - if error.is_none() { + if error.is_none() && self.flags & (SQUOTE + DQUOTE) == 0 { error = Some(StatementError::InvalidCharacter(character as char, self.read)) } }, @@ -147,7 +147,7 @@ impl<'a> Iterator for StatementSplitter<'a> { self.flags ^= METHOD; }, b')' if self.process_level == 0 && self.array_level == 0 && self.flags & SQUOTE == 0 => { - if error.is_none() { + if error.is_none() && self.flags & (SQUOTE + DQUOTE) == 0 { error = Some(StatementError::InvalidCharacter(character as char, self.read)) } }, diff --git a/src/shell/job.rs b/src/shell/job.rs index f4f1f2255fff521e04b9fd44824f39f85ed1498a..5d6639a328884001813140fb1292dc4a33d30d60 100644 --- a/src/shell/job.rs +++ b/src/shell/job.rs @@ -79,7 +79,7 @@ enum CommandType { impl<'a> From<&'a str> for CommandType { fn from(command: &'a str) -> CommandType { match command { - "help" | "history" | "echo" => CommandType::Builtin, + "help" | "history" | "echo" | "calc" => CommandType::Builtin, _ => CommandType::External } }