diff --git a/src/builtins/calc.rs b/src/builtins/calc.rs index 6a32b8522e7f01de7fa232cbd060b58957a6452b..49eb612461b488254753f272c31526b4648b32be 100644 --- a/src/builtins/calc.rs +++ b/src/builtins/calc.rs @@ -473,7 +473,7 @@ pub fn parse(tokens: &[Token]) -> Result<String, CalcError> { d_expr(tokens).map(|answer| answer.value.to_string()) } -fn eval(input: &str) -> Result<String, CalcError> { +pub fn eval(input: &str) -> Result<String, CalcError> { tokenize(input).and_then(|x| parse(&x)) } diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 903f5ab314988cf43f6145f8c2eeed774e0ae868..bbff0c003e4901269062ff715ceace15cbecdeb0 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,11 +1,11 @@ pub mod source; pub mod variables; pub mod functions; +pub mod calc; mod test; mod time; mod echo; -mod calc; mod set; use self::variables::{alias, drop_alias, drop_variable}; diff --git a/src/parser/shell_expand/mod.rs b/src/parser/shell_expand/mod.rs index 2f33e745c36ee2539e95a99b46f873114392c223..4218beab3bf5b1b414bc1700e54f892f89c6d271 100644 --- a/src/parser/shell_expand/mod.rs +++ b/src/parser/shell_expand/mod.rs @@ -14,6 +14,8 @@ use self::ranges::parse_range; pub use self::words::{WordIterator, WordToken, Select, Index, Range}; use shell::variables::Variables; +use ::builtins::calc; + use std::io::{self, Write}; use types::*; @@ -269,6 +271,7 @@ pub fn expand_tokens<'a>(token_buffer: &[WordToken], expand_func: &'a ExpanderFu } } }, + WordToken::Arithmetic(s) => expand_arithmetic(&mut output, s, &expand_func), } } @@ -477,6 +480,7 @@ pub fn expand_tokens<'a>(token_buffer: &[WordToken], expand_func: &'a ExpanderFu slice_string(&mut output, &expanded, index); }, + WordToken::Arithmetic(s) => expand_arithmetic(&mut output, s, expand_func), } } //the is_glob variable can probably be removed, I'm not entirely sure if empty strings are valid in any case- maarten @@ -488,6 +492,47 @@ pub fn expand_tokens<'a>(token_buffer: &[WordToken], expand_func: &'a ExpanderFu expanded_words } +/// Expand a string inside an arithmetic expression, for example: +/// ```ignore +/// x * 5 + y => 22 +/// ``` +/// if `x=5` and `y=7` +fn expand_arithmetic(output : &mut String , input : &str, expander : &ExpanderFunctions) { + let mut intermediate = String::with_capacity(input.as_bytes().len()); + let mut varbuf = String::new(); + let flush = |var : &mut String, out : &mut String| { + if ! var.is_empty() { + // We have reached the end of a potential variable, so we expand it and push + // it onto the result + let res = (expander.variable)(&var, false); + match res { + Some(v) => out.push_str(&v), + None => out.push_str(&var), + } + var.clear(); + } + }; + for c in input.bytes() { + match c { + 48...57 | 65...90 | 95 | 97...122 => { + varbuf.push(c as char); + }, + _ => { + flush(&mut varbuf, &mut intermediate); + intermediate.push(c as char); + } + } + } + flush(&mut varbuf, &mut intermediate); + match calc::eval(&intermediate) { + Ok(s) => output.push_str(&s), + Err(e) => { + let err_string : String = e.into(); + output.push_str(&err_string); + } + } +} + // TODO: Write Nested Brace Tests #[cfg(test)] @@ -589,4 +634,14 @@ mod test { } } } + + #[test] + fn arith_expression() { + let line = "$((A * A - (A + A)))"; + let expected = Array::from_vec(vec!["-1".to_owned()]); + assert_eq!(expected, expand_string(line, &functions!(), false)); + let line = "$((3 * 10 - 27))"; + let expected = Array::from_vec(vec!["3".to_owned()]); + assert_eq!(expected, expand_string(line, &functions!(), false)); + } } diff --git a/src/parser/shell_expand/words.rs b/src/parser/shell_expand/words.rs index ab22f788959c1a6b7b4edc160c39480d9f26c010..93c8e1bc0712346c03e32a44ff8e4d480edf3884 100644 --- a/src/parser/shell_expand/words.rs +++ b/src/parser/shell_expand/words.rs @@ -344,6 +344,7 @@ pub enum WordToken<'a> { Process(&'a str, bool, Select), StringMethod(&'a str, &'a str, &'a str, Select), ArrayMethod(ArrayMethod<'a>), + Arithmetic(&'a str) //Glob(&'a str), } @@ -853,6 +854,30 @@ impl<'a> WordIterator<'a> { false } } + + fn arithmetic_expression<I : Iterator<Item=u8>>(&mut self, iter : &mut I) -> WordToken<'a> { + let mut paren : i8 = 0; + let start = self.read; + while let Some(character) = iter.next() { + match character { + b'(' => paren += 1, + b')' => { + if paren == 0 { + // Skip the incoming ); we have validated this syntax so it should be OK + let _ = iter.next(); + let output = &self.data[start..self.read]; + self.read += 2; + return WordToken::Arithmetic(output) + } else { + paren -= 1; + } + }, + _ => () + } + self.read += 1; + } + panic!("ion: fatal syntax error: unterminated arithmetic expression"); + } } @@ -940,7 +965,12 @@ impl<'a> Iterator for WordIterator<'a> { match iterator.next() { Some(b'(') => { self.read += 2; - return if self.flags.contains(EXPAND_PROCESSES) { + return if self.data.as_bytes()[self.read] == b'(' { + // Pop the incoming left paren + let _ = iterator.next(); + self.read += 1; + Some(self.arithmetic_expression(&mut iterator)) + } else if self.flags.contains(EXPAND_PROCESSES) { Some(self.process(&mut iterator)) } else { Some(WordToken::Normal(&self.data[start..self.read],glob)) @@ -1214,4 +1244,16 @@ mod tests { ]; compare(input, expected); } + + #[test] + fn test_arithmetic() { + let input = "echo $((foo bar baz bing 3 * 2))"; + let expected = vec![ + WordToken::Normal("echo", false), + WordToken::Whitespace(" "), + WordToken::Arithmetic("foo bar baz bing 3 * 2"), + ]; + compare(input, expected); + } + } diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 25c6439c37b9636134656c11f270e2bc878e3dc5..a3aa8ac0c2137e779050e4f6eb3f39135b46d9dc 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -18,6 +18,9 @@ bitflags! { const ARRAY = 64; const VARIAB = 128; const METHOD = 256; + /// Set while parsing through an inline arithmetic expression, e.g. $((foo * bar / baz)) + const MATHEXPR = 512; + const POST_MATHEXPR = 1024; } } @@ -30,6 +33,7 @@ pub enum StatementError<'a> { UnterminatedBracedVar, UnterminatedBrace, UnterminatedMethod, + UnterminatedArithmetic, ExpectedCommandButFound(&'static str) } @@ -59,6 +63,9 @@ pub fn check_statement<'a>(statement: Result<&str, StatementError<'a>>) -> State StatementError::UnterminatedMethod => { let _ = writeln!(stderr.lock(), "ion: syntax error: unterminated method"); } + StatementError::UnterminatedArithmetic => { + let _ = writeln!(stderr.lock(), "ion: syntax error: unterminated arithmetic subexpression"); + } StatementError::ExpectedCommandButFound(element) => { let _ = writeln!(stderr.lock(), "ion: expected command, but found {}", element); } @@ -76,6 +83,7 @@ pub struct StatementSplitter<'a> { array_process_level: u8, process_level: u8, brace_level: u8, + math_paren_level: i8, } impl<'a> StatementSplitter<'a> { @@ -87,7 +95,8 @@ impl<'a> StatementSplitter<'a> { array_level: 0, array_process_level: 0, process_level: 0, - brace_level: 0 + brace_level: 0, + math_paren_level: 0, } } } @@ -103,6 +112,7 @@ impl<'a> Iterator for StatementSplitter<'a> { for character in self.data.bytes().skip(self.read) { self.read += 1; match character { + _ if self.flags.contains(POST_MATHEXPR) => (), 0...47 | 58...64 | 91...94 | 96 | 123...124 | 126...127 if self.flags.contains(VBRACE) => { // If we are just ending the braced section continue as normal if error.is_none() { @@ -141,14 +151,24 @@ impl<'a> Iterator for StatementSplitter<'a> { self.brace_level -= 1; } }, + b'(' if self.flags.contains(MATHEXPR) => { + self.math_paren_level += 1; + } b'(' if !self.flags.intersects(COMM_1 | VARIAB | ARRAY) => { if error.is_none() && !self.flags.intersects(SQUOTE | DQUOTE) { error = Some(StatementError::InvalidCharacter(character as char, self.read)) } }, b'(' if self.flags.contains(COMM_1) => { - self.process_level += 1; self.flags -= VARIAB | ARRAY; + if self.data.as_bytes()[self.read] == b'(' { + self.flags -= COMM_1; + self.flags |= MATHEXPR; + // The next character will always be a left paren in this branch; + self.math_paren_level = -1; + } else { + self.process_level += 1; + } }, b'(' if self.flags.intersects(VARIAB | ARRAY) => { self.flags -= VARIAB | ARRAY; @@ -165,6 +185,25 @@ impl<'a> Iterator for StatementSplitter<'a> { }, b']' if !self.flags.contains(SQUOTE) && self.array_level != 0 => self.array_level -= 1, b']' if !self.flags.contains(SQUOTE) => self.array_process_level -= 1, + b')' if self.flags.contains(MATHEXPR) => { + if self.math_paren_level == 0 { + if self.data.as_bytes().len() <= self.read { + if error.is_none() { + error = Some(StatementError::UnterminatedArithmetic) + } + } else { + let next_character = self.data.as_bytes()[self.read] as char; + if next_character == ')' { + self.flags -= MATHEXPR; + self.flags |= POST_MATHEXPR; + } else if error.is_none() { + error = Some(StatementError::InvalidCharacter(next_character, self.read)); + } + } + } else { + self.math_paren_level -= 1; + } + }, b')' if !self.flags.contains(SQUOTE) && self.flags.contains(METHOD) => { self.flags ^= METHOD; }, @@ -230,6 +269,7 @@ impl<'a> Iterator for StatementSplitter<'a> { None if self.flags.contains(METHOD) => Some(Err(StatementError::UnterminatedMethod)), None if self.flags.contains(VBRACE) => Some(Err(StatementError::UnterminatedBracedVar)), None if self.brace_level != 0 => Some(Err(StatementError::UnterminatedBrace)), + None if self.flags.contains(MATHEXPR) => Some(Err(StatementError::UnterminatedArithmetic)), None => { let output = self.data[start..].trim(); match output.as_bytes()[0] { @@ -247,11 +287,11 @@ impl<'a> Iterator for StatementSplitter<'a> { #[test] fn syntax_errors() { - let command = "echo (echo one); echo $((echo one); echo ) two; echo $(echo one"; + let command = "echo (echo one); echo $( (echo one); echo ) two; echo $(echo one"; let results = StatementSplitter::new(command).collect::<Vec<Result<&str, StatementError>>>(); assert_eq!(results[0], Err(StatementError::InvalidCharacter('(', 6))); - assert_eq!(results[1], Err(StatementError::InvalidCharacter('(', 25))); - assert_eq!(results[2], Err(StatementError::InvalidCharacter(')', 42))); + assert_eq!(results[1], Err(StatementError::InvalidCharacter('(', 26))); + assert_eq!(results[2], Err(StatementError::InvalidCharacter(')', 43))); assert_eq!(results[3], Err(StatementError::UnterminatedSubshell)); assert_eq!(results.len(), 4); @@ -259,6 +299,11 @@ fn syntax_errors() { let results = StatementSplitter::new(command).collect::<Vec<Result<&str, StatementError>>>(); assert_eq!(results[0], Err(StatementError::ExpectedCommandButFound("redirection"))); assert_eq!(results.len(), 1); + + let command = "echo $((foo bar baz)"; + let results = StatementSplitter::new(command).collect::<Vec<_>>(); + assert_eq!(results[0], Err(StatementError::UnterminatedArithmetic)); + assert_eq!(results.len(), 1); } #[test]