Skip to content

Commit 0e6f906

Browse files
authored
fix(interpreter): expand parameter operators inside arithmetic base# expressions (#1039)
## Summary - Add `expand_param_op_in_arithmetic` to handle `${var%%-*}`, `${var##prefix}`, etc. inside arithmetic - Detect parameter operators in `expand_brace_expr_in_arithmetic` and delegate to new handler - Fixes `$(( 10#${var%%-*} ))` returning 0 instead of the correct base-10 value ## Test plan - [x] Spec tests: 4 cases (suffix removal, prefix removal, arithmetic, plain var) - [x] Full test suite passes Closes #944
1 parent 7259df4 commit 0e6f906

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7610,10 +7610,67 @@ impl Interpreter {
76107610
return String::new();
76117611
}
76127612

7613+
// Check for parameter expansion operators (%, %%, #, ##, :-, etc.)
7614+
// If present, handle expansion with the operator applied.
7615+
let has_operator = inner.contains("%%")
7616+
|| inner.contains('%')
7617+
|| (inner.contains('#') && !inner.starts_with('#'))
7618+
|| inner.contains(":-");
7619+
if has_operator {
7620+
return self.expand_param_op_in_arithmetic(inner);
7621+
}
7622+
76137623
// ${var} — plain variable
76147624
self.expand_variable(inner)
76157625
}
76167626

7627+
/// Expand a parameter expansion with operators inside arithmetic context.
7628+
/// Handles common cases like ${var%%-*}, ${var##prefix}, etc.
7629+
fn expand_param_op_in_arithmetic(&self, inner: &str) -> String {
7630+
// ${var%%pattern} — remove longest suffix
7631+
if let Some(pos) = inner.find("%%") {
7632+
let name = &inner[..pos];
7633+
let pattern = &inner[pos + 2..];
7634+
let value = self.expand_variable(name);
7635+
return self.remove_pattern(&value, pattern, false, true);
7636+
}
7637+
// ${var%pattern} — remove shortest suffix
7638+
if let Some(pos) = inner.find('%') {
7639+
let name = &inner[..pos];
7640+
let pattern = &inner[pos + 1..];
7641+
let value = self.expand_variable(name);
7642+
return self.remove_pattern(&value, pattern, false, false);
7643+
}
7644+
// ${var##pattern} — remove longest prefix
7645+
if let Some(pos) = inner.find("##") {
7646+
let name = &inner[..pos];
7647+
let pattern = &inner[pos + 2..];
7648+
let value = self.expand_variable(name);
7649+
return self.remove_pattern(&value, pattern, true, true);
7650+
}
7651+
// ${var#pattern} — remove shortest prefix (but not ${#var} length)
7652+
if let Some(pos) = inner.find('#')
7653+
&& pos > 0
7654+
{
7655+
let name = &inner[..pos];
7656+
let pattern = &inner[pos + 1..];
7657+
let value = self.expand_variable(name);
7658+
return self.remove_pattern(&value, pattern, true, false);
7659+
}
7660+
// ${var:-default}
7661+
if let Some(pos) = inner.find(":-") {
7662+
let name = &inner[..pos];
7663+
let default = &inner[pos + 2..];
7664+
let value = self.expand_variable(name);
7665+
if value.is_empty() {
7666+
return default.to_string();
7667+
}
7668+
return value;
7669+
}
7670+
// Fallback
7671+
self.expand_variable(inner)
7672+
}
7673+
76177674
/// Parse and evaluate a simple arithmetic expression with depth tracking.
76187675
/// THREAT[TM-DOS-026]: `arith_depth` prevents stack overflow from deeply nested expressions.
76197676
/// Parse an arithmetic atom: unary operators, parenthesized expressions, and literals.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
### arithmetic_base_with_suffix_removal
2+
# 10#${var%%-*} should expand then convert
3+
last="0003-assistant.md"
4+
echo $(( 10#${last%%-*} ))
5+
### expect
6+
3
7+
### end
8+
9+
### arithmetic_base_with_prefix_removal
10+
# 10#${var##0} should expand then convert
11+
val="007"
12+
echo $(( 10#${val##0} ))
13+
### expect
14+
7
15+
### end
16+
17+
### arithmetic_base_with_expansion_plus
18+
# 10#${var%%-*} + 1 in arithmetic
19+
seq="0041-user.md"
20+
echo $(( 10#${seq%%-*} + 1 ))
21+
### expect
22+
42
23+
### end
24+
25+
### arithmetic_base_simple_var
26+
# 10#${var} without operators (verify no regression)
27+
x="0099"
28+
echo $(( 10#${x} ))
29+
### expect
30+
99
31+
### end

0 commit comments

Comments
 (0)