From 94e5e8e9236e2aadb4f8065ccec4f84ef1d7bce2 Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Thu, 22 Jun 2017 13:06:37 -0400
Subject: [PATCH] Convert @[] to @()

---
 README.md                        | 18 ++++++++---------
 examples/arrays.ion              | 10 +++++-----
 examples/for.ion                 |  2 +-
 src/parser/pipelines.rs          | 33 +++++++++-----------------------
 src/parser/shell_expand/words.rs |  8 ++++----
 src/parser/statements.rs         | 22 ++++++++++-----------
 6 files changed, 39 insertions(+), 54 deletions(-)

diff --git a/README.md b/README.md
index 613701ca..edbe64fb 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Syntax and feature decisions for Ion are made based upon three specific measurem
 
 While Ion's foundations are heavily influenced by POSIX shell syntax, it does offer some critical features and differentiations that you won't find in a POSIX shell. The similarities only exist because POSIX syntax already had some good ideas, but it also came with a number of bad design decisions that have lead to inflexibility, and so we have taken the good ideas and implemented even better ideas on top of them, and as a replacement to the bad parts. Hence, while syntax may look familiar, it is not, nor will it ever, be compliant with POSIX.
 
-In example, we have carried a lot of the same basic features such as strings (**$string**) and process expansions that return strings (**$(command args...)***), but we have also implemented support for first class arrays (**@array**) and array-based process expansions (**@[command args..]**), rather than compounding the string variables, and utilize the distinction between the two types to implement methods (**$join(array)**, **@split(string)**) and slicing (**$string[..5]**, **@array[..5]**). In addition, we implement better syntax for redirecting/piping stderr (**^>**, **^|**), and both stderr/stdout (**&>**, **&|**); as well as dropping the **do** keyword, and using the **end** keyword to end a block.
+In example, we have carried a lot of the same basic features such as strings (**$string**) and process expansions that return strings (**$(command args...)***), but we have also implemented support for first class arrays (**@array**) and array-based process expansions (**@(command args..)**), rather than compounding the string variables, and utilize the distinction between the two types to implement methods (**$join(array)**, **@split(string)**) and slicing (**$string[..5]**, **@array[..5]**). In addition, we implement better syntax for redirecting/piping stderr (**^>**, **^|**), and both stderr/stdout (**&>**, **&|**); as well as dropping the **do** keyword, and using the **end** keyword to end a block.
 
 # Features
 
@@ -31,7 +31,7 @@ Below is an overview of features that Ion has either already implemented, or aim
         - [x] Nested Braces
     - [x] Process Expansions
         - [x] String-based Command Substitution (**$()**)
-        - [x] Array-based Command Substitution (**@[]**)
+        - [x] Array-based Command Substitution (**@()**)
     - [ ] Arithmetic Expansions
 - [x] Flow Control
     - [x] For Loops
@@ -258,7 +258,7 @@ Whereas the standard command substitution syntax will create a single string fro
 a whitespace-delimited vector of values from the output of the command.
 
 ```ion
-let word_split_process = @[echo one two three]
+let word_split_process = @(echo one two three)
 ```
 
 ### Using Arrays
@@ -286,9 +286,9 @@ 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]
+echo @(echo 1 2 3)[0]
+echo @(echo 1 2 3)[1]
+echo @(echo 1 2 3)[2]
 ```
 
 #### Slice by Range
@@ -460,7 +460,7 @@ Command substitution allows the user to execute commands within a subshell, and
 output used as the substitution for the expansion. There are two methods of performing command substitution: string and
 array-based command substitution. String-based command substitutions are the standard, and they are created by wrapping
 the external command between **$(** and **)**. Array-based command substitution is denoted by wrapping the command
-between **@[** and **]**. The first merely captures the result as a single string, precisely as it was written, while
+between **@(** and **)**. The first merely captures the result as a single string, precisely as it was written, while
 the second splits the data recieved into words delimited by whitespaces.
 
 Try comparing the following:
@@ -472,7 +472,7 @@ end
 ```
 
 ```ion
-for i in @[echo 1 2 3]
+for i in @(echo 1 2 3)
     echo $i
 end
 ```
@@ -490,7 +490,7 @@ echo $(echo one two three)[..3]
 You may slice the array returned to obtained a specific set of elements:
 
 ```ion
-echo @[grep "model name" /proc/cpuinfo | head -1][3..5]
+echo @(grep "model name" /proc/cpuinfo | head -1)[3..5]
 ```
 
 ### Functions
diff --git a/examples/arrays.ion b/examples/arrays.ion
index 5a652f9d..82d595f1 100644
--- a/examples/arrays.ion
+++ b/examples/arrays.ion
@@ -11,12 +11,12 @@ end
 
 # Array Command Substitution
 
-let array_process = @[echo a  b c d    e]
+let array_process = @(echo a  b c d    e)
 for i in @array_process
     echo $i
 end
 
-for i in @[echo a  b c d    e]
+for i in @(echo a  b c d    e)
     echo $i
 end
 
@@ -44,9 +44,9 @@ 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]
+echo @(echo 1 2 3)[0]
+echo @(echo 1 2 3)[1]
+echo @(echo 1 2 3)[2]
 
 # Slice by Range
 
diff --git a/examples/for.ion b/examples/for.ion
index e5df5c47..606cb506 100644
--- a/examples/for.ion
+++ b/examples/for.ion
@@ -10,7 +10,7 @@ for i in 1 2 3 4 5
     echo $i
 end
 
-for i in @[echo 1 2 3 4 5]
+for i in @(echo 1 2 3 4 5)
     echo $i
 end
 
diff --git a/src/parser/pipelines.rs b/src/parser/pipelines.rs
index 27cad181..9a42c00b 100644
--- a/src/parser/pipelines.rs
+++ b/src/parser/pipelines.rs
@@ -143,17 +143,16 @@ pub fn collect(possible_error: &mut Option<&str>, args: &str) -> Pipeline {
                             index += 1;
                             continue
                         },
-                        b'[' if flags_ext.contains(ARRAY_CHAR_FOUND) => {
-                            array_process_levels += 1;
-                            flags |= ARRAY_PROCESS;
-                        },
                         b'['                      => array_levels += 1,
                         b']' if array_levels != 0 => array_levels -= 1,
-                        b']'                      => array_process_levels -= 1,
                         b'(' if flags_ext.contains(VAR_CHAR_FOUND) => {
                             flags |= PROCESS_TWO;
                             levels += 1;
                         },
+                        b'(' if flags_ext.contains(ARRAY_CHAR_FOUND) => {
+                            flags |= ARRAY_PROCESS;
+                            array_process_levels += 1;
+                        },
                         b'(' if flags_ext.intersects(VARIABLE | ARRAY) => {
                             flags |= METHOD;
                             flags_ext -= VARIABLE | ARRAY;
@@ -161,6 +160,7 @@ pub fn collect(possible_error: &mut Option<&str>, args: &str) -> Pipeline {
                         b')' if levels == 0 && flags.contains(METHOD) && !flags.contains(SINGLE_QUOTE) => {
                             flags -= METHOD;
                         }
+                        b')' if flags.contains(ARRAY_PROCESS) => array_process_levels -= 1,
                         b')' if flags.contains(PROCESS_TWO) => {
                             levels -= 0;
                             if levels == 0 { flags -= PROCESS_TWO; }
@@ -410,10 +410,10 @@ mod tests {
 
     #[test]
     fn nested_array_process() {
-        if let Statement::Pipeline(pipeline) = parse("echo @[echo one @[echo two] three]") {
+        if let Statement::Pipeline(pipeline) = parse("echo @(echo one @(echo two) three)") {
             let jobs = pipeline.jobs;
             assert_eq!("echo", jobs[0].args[0]);
-            assert_eq!("@[echo one @[echo two] three]", jobs[0].args[1]);
+            assert_eq!("@(echo one @(echo two) three)", jobs[0].args[1]);
         } else {
             assert!(false);
         }
@@ -445,10 +445,10 @@ mod tests {
 
     #[test]
     fn array_process() {
-        if let Statement::Pipeline(pipeline) = parse("echo @[seq 1 10 | head -1]") {
+        if let Statement::Pipeline(pipeline) = parse("echo @(seq 1 10 | head -1)") {
             let jobs = pipeline.jobs;
             assert_eq!("echo", jobs[0].args[0]);
-            assert_eq!("@[seq 1 10 | head -1]", jobs[0].args[1]);
+            assert_eq!("@(seq 1 10 | head -1)", jobs[0].args[1]);
             assert_eq!(2, jobs[0].args.len());
         } else {
             assert!(false);
@@ -755,19 +755,4 @@ mod tests {
             assert!(false);
         }
     }
-
-    // #[test]
-    // fn real_tests() {
-    //     // Real world scenarios where parsing has failed.
-    //     if let Statement::Pipeline(pipeline) = parse("awk -v x=$x '{ if (1) print $1 }' myfile") {
-    //         assert_eq!(1, pipeline.jobs.len());
-    //         assert_eq!("awk", &pipeline.clone().jobs[0].args[0]);
-    //         assert_eq!("-v", &pipeline.clone().jobs[0].args[1]);
-    //         assert_eq!("x=$x", &pipeline.clone().jobs[0].args[2]);
-    //         assert_eq!("'{ if (1) print $1 }'", &pipeline.clone().jobs[0].args[3]);
-    //         assert_eq!("myfile", &pipeline.clone().jobs[0].args[4]);
-    //     } {
-    //         assert!(false);
-    //     }
-    // }
 }
diff --git a/src/parser/shell_expand/words.rs b/src/parser/shell_expand/words.rs
index 93c8e1bc..43229fbf 100644
--- a/src/parser/shell_expand/words.rs
+++ b/src/parser/shell_expand/words.rs
@@ -691,11 +691,11 @@ impl<'a> WordIterator<'a> {
                 b'\'' if !self.flags.contains(DQUOTE) => self.flags ^= SQUOTE,
                 b'"'  if !self.flags.contains(SQUOTE) => self.flags ^= DQUOTE,
                 b'@'  if !self.flags.contains(SQUOTE) => {
-                    if self.data.as_bytes()[self.read+1] == b'[' {
+                    if self.data.as_bytes()[self.read+1] == b'(' {
                         level += 1;
                     }
                 },
-                b']' if !self.flags.contains(SQUOTE) => {
+                b')' if !self.flags.contains(SQUOTE) => {
                     if level == 0 {
                         let array_process_contents = &self.data[start..self.read];
                         self.read += 1;
@@ -943,7 +943,7 @@ impl<'a> Iterator for WordIterator<'a> {
                     },
                     b'@' if !self.flags.contains(SQUOTE) => {
                         match iterator.next() {
-                            Some(b'[') => {
+                            Some(b'(') => {
                                 self.read += 2;
                                 return if self.flags.contains(EXPAND_PROCESSES) {
                                     Some(self.array_process(&mut iterator))
@@ -1148,7 +1148,7 @@ mod tests {
 
     #[test]
     fn array_processes() {
-        let input = "@[echo one two three] @[echo one two three][0]";
+        let input = "@(echo one two three) @(echo one two three)[0]";
         let expected = vec![
             WordToken::ArrayProcess("echo one two three", false, Select::All),
             WordToken::Whitespace(" "),
diff --git a/src/parser/statements.rs b/src/parser/statements.rs
index a3aa8ac0..23f99753 100644
--- a/src/parser/statements.rs
+++ b/src/parser/statements.rs
@@ -170,21 +170,20 @@ impl<'a> Iterator for StatementSplitter<'a> {
                         self.process_level += 1;
                     }
                 },
+                b'(' if self.flags.contains(COMM_2) => {
+                    self.array_process_level += 1;
+                },
                 b'(' if self.flags.intersects(VARIAB | ARRAY) => {
                     self.flags -= VARIAB | ARRAY;
                     self.flags |= METHOD;
                 },
-                b'[' if self.flags.contains(COMM_2) => {
-                    self.array_process_level += 1;
-                },
                 b'[' if !self.flags.contains(SQUOTE) => self.array_level += 1,
-                b']' if self.array_process_level == 0 && self.array_level == 0 && !self.flags.contains(SQUOTE) => {
+                b']' if self.array_level == 0 && !self.flags.contains(SQUOTE) => {
                     if error.is_none() {
                         error = Some(StatementError::InvalidCharacter(character as char, self.read))
                     }
                 },
                 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 {
@@ -207,12 +206,13 @@ impl<'a> Iterator for StatementSplitter<'a> {
                 b')' if !self.flags.contains(SQUOTE) && self.flags.contains(METHOD) => {
                     self.flags ^= METHOD;
                 },
-                b')' if self.process_level == 0 && self.array_level == 0 && !self.flags.contains(SQUOTE) => {
+                b')' if self.process_level == 0 && self.array_process_level == 0 && !self.flags.contains(SQUOTE) => {
                     if error.is_none() && !self.flags.intersects(SQUOTE | DQUOTE) {
                         error = Some(StatementError::InvalidCharacter(character as char, self.read))
                     }
                 },
-                b')' if !self.flags.contains(SQUOTE) => self.process_level -= 1,
+                b')' if !self.flags.contains(SQUOTE) && self.process_level != 0 => self.process_level -= 1,
+                b')' if !self.flags.contains(SQUOTE) => self.array_process_level -= 1,
                 b';' if !self.flags.intersects(SQUOTE | DQUOTE) && self.process_level == 0 && self.array_process_level == 0 => {
                     return match error {
                         Some(error) => Some(Err(error)),
@@ -325,9 +325,9 @@ fn processes() {
 
 #[test]
 fn array_processes() {
-    let command = "echo @[echo one; sleep 1]; echo @[echo one; sleep 1]";
+    let command = "echo @(echo one; sleep 1); echo @(echo one; sleep 1)";
     for statement in StatementSplitter::new(command) {
-        assert_eq!(statement, Ok("echo @[echo one; sleep 1]"));
+        assert_eq!(statement, Ok("echo @(echo one; sleep 1)"));
     }
 }
 
@@ -372,12 +372,12 @@ fn nested_process() {
 
 #[test]
 fn nested_array_process() {
-    let command = "echo @[echo one @[echo two] three]";
+    let command = "echo @(echo one @(echo two) three)";
     let results = StatementSplitter::new(command).collect::<Vec<Result<&str, StatementError>>>();
     assert_eq!(results.len(), 1);
     assert_eq!(results[0], Ok(command));
 
-    let command = "echo @[echo @[echo one; echo two]; echo two]";
+    let command = "echo @(echo @(echo one; echo two); echo two)";
     let results = StatementSplitter::new(command).collect::<Vec<Result<&str, StatementError>>>();
     assert_eq!(results.len(), 1);
     assert_eq!(results[0], Ok(command));
-- 
GitLab