diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 346fd3a8..6120bd91 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -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. diff --git a/crates/bashkit/tests/spec_cases/bash/arithmetic-base-expansion.test.sh b/crates/bashkit/tests/spec_cases/bash/arithmetic-base-expansion.test.sh new file mode 100644 index 00000000..50c9d534 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/bash/arithmetic-base-expansion.test.sh @@ -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