diff --git a/examples/arrays.ion b/examples/arrays.ion index 5280cddd8afa807c6b65f80d031137e5484e780d..cf4ce3895bdd2d9d64ff638271db5ace5f5b8e75 100644 --- a/examples/arrays.ion +++ b/examples/arrays.ion @@ -1,3 +1,5 @@ +# Standard Arrays + let array = [1 [2 3] 4 [5 6]] for i in @array echo $i @@ -7,6 +9,8 @@ for i in [1 [2 3] 4 [5 6]] echo $i end +# Array Command Substitution + let array_process = @[echo a b c d e] for i in @array_process echo $i @@ -16,11 +20,30 @@ for i in @[echo a b c d e] echo $i end +# Array Concatenation + let new_array = [@array @array_process] for i in @new_array echo $i end +# As Command Arguments + echo @array echo @array_process echo @new_array + +# Slice by ID + +let array = [ 1 2 3 ] +echo @array[0] +echo @array[1] +echo @array[2] + +echo [ 1 2 3 ][0] +echo [ 1 2 3 ][1] +echo [ 1 2 3 ][2] + +echo @[echo 1 2 3][0] +echo @[echo 1 2 3][1] +echo @[echo 1 2 3][2] diff --git a/examples/arrays.out b/examples/arrays.out index 0e888b876689d80e77c53eddb0b29237e8220080..194f2c62b3166c8be284ab0b51c7d70add7c4675 100644 --- a/examples/arrays.out +++ b/examples/arrays.out @@ -34,3 +34,12 @@ e 1 2 3 4 5 6 a b c d e 1 2 3 4 5 6 a b c d e +1 +2 +3 +1 +2 +3 +1 +2 +3 diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 63d90f5bf782f6cafb81a0aa100743adf59ee5db..a77ec4bd2ee01946ea0bbef5b5d5ee20b9918b45 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -25,7 +25,8 @@ pub fn expand_string<'a>(original: &'a str, vars: &Variables, dir_stack: &Direct array: &|array: &str, index: Index| { match vars.get_array(array) { Some(array) => match index { - Index::All => Some(array.to_owned()) + Index::All => Some(array.to_owned()), + Index::ID(id) => array.get(id).map(|x| vec![x.to_owned()]) }, None => None } diff --git a/src/parser/shell_expand/mod.rs b/src/parser/shell_expand/mod.rs index 2e0c6fcb5968d991305c08c9a98208e23abf257d..01c378696524b29a05fc13c3c4cb461ef3bb616b 100644 --- a/src/parser/shell_expand/mod.rs +++ b/src/parser/shell_expand/mod.rs @@ -66,10 +66,17 @@ fn expand_brace(current: &mut String, expanders: &mut Vec<Vec<String>>, } fn expand_array(elements: &[&str], expand_func: &ExpanderFunctions) -> Vec<String> { - elements.iter().flat_map(|element| expand_string(element, expand_func, false)) + elements.iter() + .flat_map(|element| expand_string(element, expand_func, false)) .collect() } +fn array_nth(elements: &[&str], expand_func: &ExpanderFunctions, id: usize) -> String { + elements.iter() + .flat_map(|element| expand_string(element, expand_func, false)) + .nth(id).unwrap_or_default() +} + #[allow(cyclomatic_complexity)] /// Performs shell expansions to an input string, efficiently returning the final expanded form. /// Shells must provide their own batteries for expanding tilde and variable words. @@ -93,22 +100,32 @@ pub fn expand_string(original: &str, expand_func: &ExpanderFunctions, reverse_qu for word in token_buffer.drain(..) { match word { WordToken::Array(elements, index) => { - let expanded = match index { - Index::All => expand_array(&elements, expand_func) + match index { + Index::All => { + let expanded = expand_array(&elements, expand_func); + current.push_str(&expanded.join(" ")); + }, + Index::ID(id) => { + let expanded = array_nth(&elements, expand_func, id); + current.push_str(&expanded); + }, }; - current.push_str(&expanded.join(" ")); }, WordToken::ArrayVariable(array, _, index) => { if let Some(array) = (expand_func.array)(array, index) { current.push_str(&array.join(" ")); } }, - // TODO: Handle Indexing WordToken::ArrayProcess(command, quoted, index) => { let quoted = if reverse_quoting { !quoted } else { quoted }; match index { Index::All => { expand_process(&mut current, command, quoted, expand_func); + }, + Index::ID(id) => { + let mut temp = String::new(); + expand_process(&mut temp, command, quoted, expand_func); + current.push_str(temp.split_whitespace().nth(id).unwrap_or_default()); } } }, @@ -150,7 +167,8 @@ pub fn expand_string(original: &str, expand_func: &ExpanderFunctions, reverse_qu match token_buffer[0].clone() { WordToken::Array(elements, index) => { return match index { - Index::All => expand_array(&elements, expand_func) + Index::All => expand_array(&elements, expand_func), + Index::ID(id) => vec![array_nth(&elements, expand_func, id)], }; }, WordToken::ArrayVariable(array, quoted, index) => { @@ -165,9 +183,13 @@ pub fn expand_string(original: &str, expand_func: &ExpanderFunctions, reverse_qu match index { Index::All => { expand_process(&mut output, command, quoted, expand_func); + return output.split_whitespace().map(String::from).collect::<Vec<String>>(); + }, + Index::ID(id) => { + expand_process(&mut output, command, quoted, expand_func); + return vec![output.split_whitespace().nth(id).unwrap_or_default().to_owned()]; } } - return output.split_whitespace().map(String::from).collect::<Vec<String>>(); } _ => () } @@ -176,10 +198,16 @@ pub fn expand_string(original: &str, expand_func: &ExpanderFunctions, reverse_qu for word in token_buffer.drain(..) { match word { WordToken::Array(elements, index) => { - let expanded = match index { - Index::All => expand_array(&elements, expand_func) + match index { + Index::All => { + let expanded = expand_array(&elements, expand_func); + output.push_str(&expanded.join(" ")); + }, + Index::ID(id) => { + let expanded = array_nth(&elements, expand_func, id); + output.push_str(&expanded); + }, }; - output.push_str(&expanded.join(" ")); }, WordToken::ArrayVariable(array, _, index) => { if let Some(array) = (expand_func.array)(array, index) { @@ -191,6 +219,11 @@ pub fn expand_string(original: &str, expand_func: &ExpanderFunctions, reverse_qu match index { Index::All => { expand_process(&mut output, command, quoted, expand_func); + }, + Index::ID(id) => { + let mut temp = String::new(); + expand_process(&mut temp, command, quoted, expand_func); + output.push_str(temp.split_whitespace().nth(id).unwrap_or_default()); } } }, diff --git a/src/parser/shell_expand/words.rs b/src/parser/shell_expand/words.rs index 657ca6998aa35c2a73b3976b6d7aa878ae9427c3..77ca8742ff977faa2ad9afcaa599a29481273eeb 100644 --- a/src/parser/shell_expand/words.rs +++ b/src/parser/shell_expand/words.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + // Bit Twiddling Guide: // var & FLAG != 0 checks if FLAG is enabled // var & FLAG == 0 checks if FLAG is disabled @@ -13,7 +15,23 @@ const DQUOTE: u8 = 4; #[derive(Debug, PartialEq, Copy, Clone)] pub enum Index { // TODO: Ranged and ID - All + All, + ID(usize), +} + +pub enum IndexError { + Invalid +} + +impl FromStr for Index { + type Err = IndexError; + fn from_str(data: &str) -> Result<Index, IndexError> { + if let Ok(index) = data.parse::<usize>() { + return Ok(Index::ID(index)) + } + + Ok(Index::All) + } } #[derive(Debug, PartialEq, Clone)] @@ -119,6 +137,26 @@ impl<'a> WordIterator<'a> { WordToken::Variable(&self.data[start..], self.flags & DQUOTE != 0) } + fn read_index<I>(&mut self, iterator: &mut I) -> Index + where I: Iterator<Item = u8> + { + self.read += 1; + let start = self.read; + while let Some(character) = iterator.next() { + if let b']' = character { + let index = match self.data[start..self.read].parse::<Index>() { + Ok(index) => index, + Err(_) => Index::All + }; + self.read += 1; + return index + } + self.read += 1; + } + + panic!() + } + /// Contains the logic for parsing array variable syntax fn array_variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8> @@ -127,8 +165,14 @@ impl<'a> WordIterator<'a> { self.read += 1; while let Some(character) = iterator.next() { match character { - // TODO: Detect Index // TODO: ArrayFunction + b'[' => { + return WordToken::ArrayVariable ( + &self.data[start..self.read], + self.flags & DQUOTE != 0, + self.read_index(iterator) + ); + }, // Only alphanumerical and underscores are allowed in variable names 0...47 | 58...64 | 91...94 | 96 | 123...127 => { return WordToken::ArrayVariable(&self.data[start..self.read], self.flags & DQUOTE != 0, Index::All); @@ -195,10 +239,22 @@ impl<'a> WordIterator<'a> { }, b']' if self.flags & SQUOTE == 0 => { if level == 0 { - // TODO: Detect Index - let output = &self.data[start..self.read]; + let array_process_contents = &self.data[start..self.read]; self.read += 1; - return WordToken::ArrayProcess(output, self.flags & DQUOTE != 0, Index::All); + return if let Some(&b'[') = self.data.as_bytes().get(self.read) { + let _ = iterator.next(); + WordToken::ArrayProcess ( + array_process_contents, + self.flags & DQUOTE != 0, + self.read_index(iterator) + ) + } else { + WordToken::ArrayProcess ( + array_process_contents, + self.flags & DQUOTE != 0, + Index::All + ) + } } else { level -= 1; } @@ -279,7 +335,14 @@ impl<'a> WordIterator<'a> { if level == 0 { elements.push(&self.data[start..self.read]); self.read += 1; - return WordToken::Array(elements, Index::All); + + return if let Some(&b'[') = self.data.as_bytes().get(self.read) { + let _ = iterator.next(); + WordToken::Array(elements, self.read_index(iterator)) + } else { + WordToken::Array(elements, Index::All) + + } } else { level -= 1; } diff --git a/src/shell/assignments.rs b/src/shell/assignments.rs index 66d706599e74bb9f092465e452b983bd42ea0ccd..61708022d5517e43c39ef8e3600bc80a4bab1b90 100644 --- a/src/shell/assignments.rs +++ b/src/shell/assignments.rs @@ -49,7 +49,8 @@ pub fn let_assignment<'a>(original: &'a str, vars: &mut Variables, dir_stack: &D array: &|array: &str, index: Index| { match vars.get_array(array) { Some(array) => match index { - Index::All => Some(array.to_owned()) + Index::All => Some(array.to_owned()), + Index::ID(id) => array.get(id).map(|x| vec![x.to_owned()]) }, None => None }