Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7516,10 +7516,67 @@ impl Interpreter {
return String::new();
}

// Check for parameter expansion operators (%, %%, #, ##, :-, etc.)
// If present, handle expansion with the operator applied.
let has_operator = inner.contains("%%")
|| inner.contains('%')
|| (inner.contains('#') && !inner.starts_with('#'))
|| inner.contains(":-");
if has_operator {
return self.expand_param_op_in_arithmetic(inner);
}

// ${var} — plain variable
self.expand_variable(inner)
}

/// Expand a parameter expansion with operators inside arithmetic context.
/// Handles common cases like ${var%%-*}, ${var##prefix}, etc.
fn expand_param_op_in_arithmetic(&self, inner: &str) -> String {
// ${var%%pattern} — remove longest suffix
if let Some(pos) = inner.find("%%") {
let name = &inner[..pos];
let pattern = &inner[pos + 2..];
let value = self.expand_variable(name);
return self.remove_pattern(&value, pattern, false, true);
}
// ${var%pattern} — remove shortest suffix
if let Some(pos) = inner.find('%') {
let name = &inner[..pos];
let pattern = &inner[pos + 1..];
let value = self.expand_variable(name);
return self.remove_pattern(&value, pattern, false, false);
}
// ${var##pattern} — remove longest prefix
if let Some(pos) = inner.find("##") {
let name = &inner[..pos];
let pattern = &inner[pos + 2..];
let value = self.expand_variable(name);
return self.remove_pattern(&value, pattern, true, true);
}
// ${var#pattern} — remove shortest prefix (but not ${#var} length)
if let Some(pos) = inner.find('#')
&& pos > 0
{
let name = &inner[..pos];
let pattern = &inner[pos + 1..];
let value = self.expand_variable(name);
return self.remove_pattern(&value, pattern, true, false);
}
// ${var:-default}
if let Some(pos) = inner.find(":-") {
let name = &inner[..pos];
let default = &inner[pos + 2..];
let value = self.expand_variable(name);
if value.is_empty() {
return default.to_string();
}
return value;
}
// Fallback
self.expand_variable(inner)
}

/// Parse and evaluate a simple arithmetic expression with depth tracking.
/// THREAT[TM-DOS-026]: `arith_depth` prevents stack overflow from deeply nested expressions.
/// Parse an arithmetic atom: unary operators, parenthesized expressions, and literals.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
### arithmetic_base_with_suffix_removal
# 10#${var%%-*} should expand then convert
last="0003-assistant.md"
echo $(( 10#${last%%-*} ))
### expect
3
### end

### arithmetic_base_with_prefix_removal
# 10#${var##0} should expand then convert
val="007"
echo $(( 10#${val##0} ))
### expect
7
### end

### arithmetic_base_with_expansion_plus
# 10#${var%%-*} + 1 in arithmetic
seq="0041-user.md"
echo $(( 10#${seq%%-*} + 1 ))
### expect
42
### end

### arithmetic_base_simple_var
# 10#${var} without operators (verify no regression)
x="0099"
echo $(( 10#${x} ))
### expect
99
### end
Loading