Repository: reubeno/brush (rust)

Issue:
The for loop without in does not iterate correctly
```console
$ brush --version
brush 0.2.14 (git:dd1d39c)

$ brush -c 'for i do echo $i; done' - a b c
(nothing)

$ bash -c 'for i do echo $i; done' - a b c
a
b
c
```


```console
Note: There is a `;` before `do`

$ brush -c 'for i; do echo $i; done' - a b c
(nothing)

$ bash -c 'for i; do echo $i; done' - a b c
a
b
c
```

Pull request description:
fix(for): correct semantics for "for" without "in"
When `in` is not specified with a `for` clause, we need to enumerate positional arguments instead (i.e., $@).

This scenario was getting parsed correctly, but not correctly interpreted.

Resolves #342. Thanks to @ko1nksm for identifying the problem and filing a bug report on it!

Patch:
diff --git a/brush-core/src/interp.rs b/brush-core/src/interp.rs
index 8e8df57..45e17c7 100644
--- a/brush-core/src/interp.rs
+++ b/brush-core/src/interp.rs
@@ -555,54 +555,62 @@ impl Execute for ast::ForClauseCommand {
     ) -> Result<ExecutionResult, error::Error> {
         let mut result = ExecutionResult::success();
 
+        // If we were given explicit words to iterate over, then expand them all, with splitting
+        // enabled.
+        let mut expanded_values = vec![];
         if let Some(unexpanded_values) = &self.values {
-            // Expand all values, with splitting enabled.
-            let mut expanded_values = vec![];
             for value in unexpanded_values {
                 let mut expanded = expansion::full_expand_and_split_word(shell, value).await?;
                 expanded_values.append(&mut expanded);
             }
+        } else {
+            // Otherwise, we use the current positional parameters.
+            expanded_values.extend_from_slice(&shell.positional_parameters);
+        }
 
-            for value in expanded_values {
-                if shell.options.print_commands_and_arguments {
+        for value in expanded_values {
+            if shell.options.print_commands_and_arguments {
+                if let Some(unexpanded_values) = &self.values {
                     shell.trace_command(std::format!(
                         "for {} in {}",
                         self.variable_name,
                         unexpanded_values.iter().join(" ")
                     ))?;
+                } else {
+                    shell.trace_command(std::format!("for {}", self.variable_name,))?;
                 }
+            }
 
-                // Update the variable.
-                shell.env.update_or_add(
-                    &self.variable_name,
-                    ShellValueLiteral::Scalar(value),
-                    |_| Ok(()),
-                    EnvironmentLookup::Anywhere,
-                    EnvironmentScope::Global,
-                )?;
+            // Update the variable.
+            shell.env.update_or_add(
+                &self.variable_name,
+                ShellValueLiteral::Scalar(value),
+                |_| Ok(()),
+                EnvironmentLookup::Anywhere,
+                EnvironmentScope::Global,
+            )?;
 
-                result = self.body.0.execute(shell, params).await?;
-                if result.exit_shell || result.return_from_function_or_script {
-                    break;
-                }
+            result = self.body.0.execute(shell, params).await?;
+            if result.exit_shell || result.return_from_function_or_script {
+                break;
+            }
 
-                if let Some(continue_count) = &result.continue_loop {
-                    if *continue_count == 0 {
-                        result.continue_loop = None;
-                    } else {
-                        result.continue_loop = Some(*continue_count - 1);
-                        break;
-                    }
-                }
-                if let Some(break_count) = &result.break_loop {
-                    if *break_count == 0 {
-                        result.break_loop = None;
-                    } else {
-                        result.break_loop = Some(*break_count - 1);
-                    }
+            if let Some(continue_count) = &result.continue_loop {
+                if *continue_count == 0 {
+                    result.continue_loop = None;
+                } else {
+                    result.continue_loop = Some(*continue_count - 1);
                     break;
                 }
             }
+            if let Some(break_count) = &result.break_loop {
+                if *break_count == 0 {
+                    result.break_loop = None;
+                } else {
+                    result.break_loop = Some(*break_count - 1);
+                }
+                break;
+            }
         }
 
         shell.last_exit_status = result.exit_code;


Test patch:
diff --git a/brush-shell/tests/cases/compound_cmds/for.yaml b/brush-shell/tests/cases/compound_cmds/for.yaml
index 427c1f6..85312d3 100644
--- a/brush-shell/tests/cases/compound_cmds/for.yaml
+++ b/brush-shell/tests/cases/compound_cmds/for.yaml
@@ -4,10 +4,36 @@ cases:
     stdin: |
       for f in 1 2 3; do echo $f; done
 
+  - name: "Multi-line for loop"
+    stdin: |
+      for f in 1 2 3; do
+        echo $f
+      done
+
   - name: "Empty for loop"
     stdin: |
       for f in; do echo $f; done
 
+  - name: "for loop without in"
+    stdin: |
+      set -- a b c
+
+      echo "Loop 1"
+      for f; do echo $f; done
+
+      echo "Loop 2: no semicolon"
+      for f do echo $f; done
+
+  - name: "for loop without in but spaces"
+    stdin: |
+      set -- a "b c" d
+
+      echo "Loop 1"
+      for f; do echo $f; done
+
+      echo "Loop 2: no semicolon"
+      for f do echo $f; done
+
   - name: "Break in for loop"
     stdin: |
       for f in 1 2 3; do