diff --git a/examples/array_ops.ion b/examples/array_ops.ion new file mode 100644 index 0000000000000000000000000000000000000000..b7337e8eef1885562b086f3838de46c03c946255 --- /dev/null +++ b/examples/array_ops.ion @@ -0,0 +1,17 @@ +let array = [ 4 4 5 5 5 6 6 6 6 ] +echo @array +let array ++= [ 1 2 3 ] +echo @array +let array ::= [ 1 2 3 ] +echo @array +let array \\= [ 4 5 6 ] +echo @array + +let array = [ 1 2 2 3 3 3 ] +echo @array +let array ++= 4 +echo @array +let array ::= 0 +echo @array +let array \\= 3 +echo @array diff --git a/examples/array_ops.out b/examples/array_ops.out new file mode 100644 index 0000000000000000000000000000000000000000..4304d5d07a939192afe9a8c14dad0ddec8dc2aeb --- /dev/null +++ b/examples/array_ops.out @@ -0,0 +1,8 @@ +4 4 5 5 5 6 6 6 6 +4 4 5 5 5 6 6 6 6 1 2 3 +1 2 3 4 4 5 5 5 6 6 6 6 1 2 3 +1 2 3 1 2 3 +1 2 2 3 3 3 +1 2 2 3 3 3 4 +0 1 2 2 3 3 3 4 +0 1 2 2 4 diff --git a/src/lib/builtins/exists.rs b/src/lib/builtins/exists.rs index 95a733ac619a445193eb47eefd14b38944e2ff15..e555196d5f97e7067c800dc321047139c0cf8025 100644 --- a/src/lib/builtins/exists.rs +++ b/src/lib/builtins/exists.rs @@ -150,7 +150,7 @@ fn function_is_defined(function: &str, shell: &Shell) -> bool { #[test] fn test_evaluate_arguments() { - use parser::assignments::{KeyBuf, Primitive}; + use lexers::assignments::{KeyBuf, Primitive}; let mut shell = shell::ShellBuilder::new().as_library(); // assert_eq!(evaluate_arguments(&[], &mut sink, &shell), Ok(false)); @@ -404,7 +404,7 @@ fn test_string_var_is_not_empty() { #[test] fn test_function_is_defined() { - use parser::assignments::{KeyBuf, Primitive}; + use lexers::assignments::{KeyBuf, Primitive}; let mut shell = shell::ShellBuilder::new().as_library(); // create a simple dummy function diff --git a/src/lib/parser/assignments/actions.rs b/src/lib/parser/assignments/actions.rs index 011b540319efd3dd6f7faa48ac0e3eda38796eab..795c6860f0fbd24f43da75c213826682e16c22a2 100644 --- a/src/lib/parser/assignments/actions.rs +++ b/src/lib/parser/assignments/actions.rs @@ -120,6 +120,7 @@ impl<'a> Action<'a> { #[cfg(test)] mod tests { use super::*; + use lexers::assignments::*; fn split(input: &str) -> (String, Operator, String) { let (keys, op, vals) = assignment_lexer(input); @@ -246,5 +247,47 @@ mod tests { "[four five]", )) ); + let (keys, op, values) = split("array ++= [one two three four five]"); + let actions = AssignmentActions::new(&keys, op, &values).collect::<Vec<_>>(); + assert_eq!(actions.len(), 1); + assert_eq!( + actions[0], + Ok(Action::UpdateArray( + Key { + name: "array", + kind: Primitive::Any, + }, + Operator::Concatenate, + "[one two three four five]", + )) + ); + let (keys, op, values) = split("array ::= [1 2 3 4 5]"); + let actions = AssignmentActions::new(&keys, op, &values).collect::<Vec<_>>(); + assert_eq!(actions.len(), 1); + assert_eq!( + actions[0], + Ok(Action::UpdateArray( + Key { + name: "array", + kind: Primitive::Any, + }, + Operator::ConcatenateHead, + "[1 2 3 4 5]", + )) + ); + let (keys, op, values) = split(r"array \\= [foo bar baz]"); + let actions = AssignmentActions::new(&keys, op, &values).collect::<Vec<_>>(); + assert_eq!(actions.len(), 1); + assert_eq!( + actions[0], + Ok(Action::UpdateArray( + Key { + name: "array", + kind: Primitive::Any, + }, + Operator::Filter, + "[foo bar baz]", + )) + ); } } diff --git a/src/lib/parser/statement/functions.rs b/src/lib/parser/statement/functions.rs index c12421b15c33f3632e5044a81e12353197340f3d..6d5cc8b142e7f89cf38a2a6c81f64263e54063c9 100644 --- a/src/lib/parser/statement/functions.rs +++ b/src/lib/parser/statement/functions.rs @@ -19,9 +19,8 @@ pub(crate) fn collect_arguments(args: KeyIterator) -> Result<Vec<KeyBuf>, TypeEr #[cfg(test)] mod tests { - use super::{ - super::super::assignments::{KeyBuf, Primitive}, *, - }; + use lexers::assignments::{KeyBuf, Primitive}; + use parser::statement::functions::{collect_arguments, parse_function}; #[test] fn function_parsing() { diff --git a/src/lib/parser/statement/parse.rs b/src/lib/parser/statement/parse.rs index bab71b16f3edb846164094a70e6412815ffc70c0..35750efc5cd619cadc9932414772aafa9fd2c052 100644 --- a/src/lib/parser/statement/parse.rs +++ b/src/lib/parser/statement/parse.rs @@ -223,7 +223,7 @@ pub(crate) fn parse(code: &str) -> Statement { mod tests { use self::pipelines::PipeItem; use super::*; - use parser::assignments::{KeyBuf, Primitive}; + use lexers::assignments::{KeyBuf, Primitive}; use shell::{flow_control::Statement, Job, JobKind}; #[test] diff --git a/src/lib/shell/assignments.rs b/src/lib/shell/assignments.rs index 57cf9ac18dc75898b41f86d7732af25fec67fd15..599e52fe604c8e9b9f477301046ea1581483edac 100644 --- a/src/lib/shell/assignments.rs +++ b/src/lib/shell/assignments.rs @@ -148,36 +148,96 @@ impl VariableStore for Shell { }; for action in actions_step1 { match action { - Ok(Action::UpdateArray(key, Operator::Equal, expression)) => { - match value_check(self, &expression, &key.kind) { - Ok(VariableType::Array(values)) => { - // When we changed the HISTORY_IGNORE variable, update the - // ignore patterns. This happens first because `set_array` - // consumes 'values' - if key.name == "HISTORY_IGNORE" { - self.update_ignore_patterns(&values); + Ok(Action::UpdateArray(key, operator, expression)) => { + match operator { + Operator::Equal => match value_check(self, &expression, &key.kind) { + Ok(VariableType::Array(values)) => { + // When we changed the HISTORY_IGNORE variable, update the + // ignore patterns. This happens first because `set_array` + // consumes 'values' + if key.name == "HISTORY_IGNORE" { + self.update_ignore_patterns(&values); + } + collected.insert(key.name, VariableType::Array(values)); + } + Ok(VariableType::Str(value)) => { + collected.insert(key.name, VariableType::Str(value)); + } + Ok(VariableType::HashMap(map)) => { + collected.insert(key.name, VariableType::HashMap(map)); + } + Err(why) => { + eprintln!("ion: assignment error: {}: {}", key.name, why); + return FAILURE; } - collected.insert(key.name, VariableType::Array(values)); + _ => (), } - Ok(VariableType::Str(value)) => { - collected.insert(key.name, VariableType::Str(value)); + Operator::Concatenate => match value_check(self, &expression, &key.kind) { + Ok(VariableType::Array(values)) => { + match self.variables.get_mut(key.name) { + Some(VariableType::Array(ref mut array)) => { + array.extend(values); + } + None => { + eprintln!("ion: assignment error: {}: cannot concatenate non-array variable", key.name); + return FAILURE; + } + _ => (), + } + } + Err(why) => { + eprintln!("ion: assignment error: {}: {}", key.name, why); + return FAILURE; + } + _ => (), } - Ok(VariableType::HashMap(map)) => { - collected.insert(key.name, VariableType::HashMap(map)); + Operator::ConcatenateHead => match value_check(self, &expression, &key.kind) { + Ok(VariableType::Array(values)) => { + match self.variables.get_mut(key.name) { + Some(VariableType::Array(ref mut array)) => { + for (index, value) in values.into_iter().enumerate() { + array.insert(index, value); + } + } + None => { + eprintln!("ion: assignment error: {}: cannot head concatenate non-array variable", key.name); + return FAILURE; + } + _ => (), + } + } + Err(why) => { + eprintln!("ion: assignment error: {}: {}", key.name, why); + return FAILURE; + } + _ => (), } - Err(why) => { - eprintln!("ion: assignment error: {}: {}", key.name, why); - return FAILURE; + Operator::Filter => match value_check(self, &expression, &key.kind) { + Ok(VariableType::Array(values)) => { + match self.variables.get_mut(key.name) { + Some(VariableType::Array(ref mut array)) => { + let mut iterator: Box<Iterator<Item=&String>> = Box::new(array.iter()); + for value in &values { + iterator = Box::new(iterator.filter(move |item| *item != value)); + } + *array = iterator.cloned().collect(); + } + None => { + eprintln!("ion: assignment error: {}: cannot head concatenate non-array variable", key.name); + return FAILURE; + } + _ => (), + } + } + Err(why) => { + eprintln!("ion: assignment error: {}: {}", key.name, why); + return FAILURE; + } + _ => (), } _ => (), } } - Ok(Action::UpdateArray(..)) => { - eprintln!( - "ion: arithmetic operators on array expressions aren't supported yet." - ); - return FAILURE; - } Ok(Action::UpdateString(key, operator, expression)) => { if ["HOME", "HOST", "PWD", "MWD", "SWD", "?"].contains(&key.name) { eprintln!("ion: not allowed to set {}", key.name); @@ -186,11 +246,53 @@ impl VariableStore for Shell { match value_check(self, &expression, &key.kind) { Ok(VariableType::Str(value)) => { - if operator == Operator::Equal { - collected.insert(key.name, VariableType::Str(value)); - continue; + match operator { + Operator::Equal => { + collected.insert(key.name, VariableType::Str(value)); + continue; + } + Operator::Concatenate => { + match self.variables.get_mut(key.name) { + Some(VariableType::Array(ref mut array)) => { + array.push(value); + } + None => { + eprintln!("ion: assignment error: {}: cannot concatenate non-array variable", key.name); + return FAILURE; + } + _ => (), + } + continue; + } + Operator::ConcatenateHead => { + match self.variables.get_mut(key.name) { + Some(VariableType::Array(ref mut array)) => { + array.insert(0, value); + } + None => { + eprintln!("ion: assignment error: {}: cannot head concatenate non-array variable", key.name); + return FAILURE; + } + _ => (), + } + continue; + } + Operator::Filter => { + match self.variables.get_mut(key.name) { + Some(VariableType::Array(ref mut array)) => { + *array = array.iter().filter(move |item| **item != value).cloned().collect(); + } + None => { + eprintln!("ion: assignment error: {}: cannot head concatenate non-array variable", key.name); + return FAILURE; + } + _ => (), + } + continue; + } + _ => (), } - match self.variables.lookup_any(key.name) { + match self.variables.get_ref(key.name) { Some(VariableType::Str(lhs)) => { let result = math(&lhs, &key.kind, operator, &value, |value| { collected.insert(key.name, VariableType::Str(unsafe { @@ -289,7 +391,7 @@ impl VariableStore for Shell { if let Primitive::Indexed(ref index_value, ref index_kind) = key.kind { match value_check(self, index_value, index_kind) { Ok(VariableType::Str(ref index)) => { - match self.variables.lookup_any_mut(key.name) { + match self.variables.get_mut(key.name) { Some(VariableType::HashMap(map)) => { map.entry(SmallString::from_str(index)).or_insert(VariableType::Str(value)); } diff --git a/src/lib/shell/variables/mod.rs b/src/lib/shell/variables/mod.rs index c4332c7c05b936e5b31748b639c2955181b4897f..c93f2b6fa77eff8a88c1894e4b9e5db833bcf0b6 100644 --- a/src/lib/shell/variables/mod.rs +++ b/src/lib/shell/variables/mod.rs @@ -232,27 +232,33 @@ impl Variables { self.scopes[self.current].namespace = namespace; } } + pub fn pop_scope(&mut self) { self.scopes[self.current].clear(); self.current -= 1; } + pub fn pop_scopes<'a>(&'a mut self, index: usize) -> impl Iterator<Item = Scope> + 'a { self.current = index; self.scopes.drain(index+1..) } + pub fn append_scopes(&mut self, scopes: Vec<Scope>) { self.scopes.drain(self.current+1..); self.current += scopes.len(); self.scopes.extend(scopes); } + pub fn scopes(&self) -> impl Iterator<Item = &Scope> { let amount = self.scopes.len() - self.current - 1; self.scopes.iter().rev().skip(amount) } + pub fn scopes_mut(&mut self) -> impl Iterator<Item = &mut Scope> { let amount = self.scopes.len() - self.current - 1; self.scopes.iter_mut().rev().skip(amount) } + pub fn index_scope_for_var(&self, name: &str) -> Option<usize> { let amount = self.scopes.len() - self.current - 1; for (i, scope) in self.scopes.iter().enumerate().rev().skip(amount) { @@ -262,10 +268,12 @@ impl Variables { } None } + pub fn shadow(&mut self, name: SmallString, value: VariableType) -> Option<VariableType> { self.scopes[self.current].insert(name, value) } - pub fn lookup_any(&self, mut name: &str) -> Option<&VariableType> { + + pub fn get_ref(&self, mut name: &str) -> Option<&VariableType> { let mut up_namespace: isize = 0; if name.starts_with("global::") { name = &name["global::".len()..]; @@ -291,7 +299,8 @@ impl Variables { } None } - pub fn lookup_any_mut(&mut self, name: &str) -> Option<&mut VariableType> { + + pub fn get_mut(&mut self, name: &str) -> Option<&mut VariableType> { if name.starts_with("super::") || name.starts_with("global::") { // Cannot mutate outer namespace return None; @@ -450,7 +459,7 @@ impl Variables { Some(("env", variable)) => env::var(variable).map(Into::into).ok().map(|s| T::from(VariableType::Str(s))), Some(("super", _)) | Some(("global", _)) | None => { // Otherwise, it's just a simple variable name. - match self.lookup_any(name) { + match self.get_ref(name) { Some(VariableType::Str(val)) => Some(T::from(VariableType::Str(val.clone()))), _ => env::var(name).ok().map(|s| T::from(VariableType::Str(s))), } @@ -488,22 +497,22 @@ impl Variables { } } } else if specified_type == TypeId::of::<types::Alias>() { - match self.lookup_any(name) { + match self.get_ref(name) { Some(VariableType::Alias(alias)) => Some(T::from(VariableType::Alias((*alias).clone()))), _ => None } } else if specified_type == TypeId::of::<types::Array>() { - match self.lookup_any(name) { + match self.get_ref(name) { Some(VariableType::Array(array)) => Some(T::from(VariableType::Array(array.clone()))), _ => None } } else if specified_type == TypeId::of::<types::HashMap>() { - match self.lookup_any(name) { + match self.get_ref(name) { Some(VariableType::HashMap(hash_map)) => Some(T::from(VariableType::HashMap(hash_map.clone()))), _ => None } } else if specified_type == TypeId::of::<Function>() { - match self.lookup_any(name) { + match self.get_ref(name) { Some(VariableType::Function(func)) => Some(T::from(VariableType::Function(func.clone()))), _ => None } @@ -514,7 +523,7 @@ impl Variables { pub fn set<T: Into<VariableType>>(&mut self, name: &str, var: T) { let var = var.into(); - match self.lookup_any_mut(&name) { + match self.get_mut(&name) { Some(VariableType::Str(ref mut str_)) => { if !name.is_empty() { if let VariableType::Str(var_str) = var {