Skip to content

Commit

Permalink
fix(for): correct semantics for "for" without "in" (#348)
Browse files Browse the repository at this point in the history
  • Loading branch information
reubeno authored Jan 21, 2025
1 parent a36cb56 commit 925f829
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 30 deletions.
68 changes: 38 additions & 30 deletions brush-core/src/interp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 26 additions & 0 deletions brush-shell/tests/cases/compound_cmds/for.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 925f829

Please sign in to comment.