Skip to content

Commit 4d7818c

Browse files
chaliyclaude
andauthored
feat(bash): implement let builtin and fix declare -i arithmetic (#250)
## Summary - Implement `let` builtin for evaluating arithmetic expressions with assignment side effects - Fix `declare -i` to use arithmetic evaluation instead of `parse().unwrap_or(0)`, so `declare -i x=5+3` correctly yields 8 - Add 11 spec tests covering `let` (basic, multiple, exit codes, increment, compound assign, no-args) and `declare -i` (basic, expressions, variable refs, plain numbers) ## Test plan - [x] `cargo test --all-features` passes - [x] `cargo clippy` clean - [x] `cargo fmt --check` clean - [x] Spec tests verify `let` assignment, exit codes, and `declare -i` arithmetic Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2d5b74e commit 4d7818c

File tree

3 files changed

+115
-8
lines changed

3 files changed

+115
-8
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,6 +2616,24 @@ impl Interpreter {
26162616
.await;
26172617
}
26182618

2619+
// Handle `let` - evaluate arithmetic expressions with assignment
2620+
if name == "let" {
2621+
let mut last_val = 0i64;
2622+
for arg in &args {
2623+
last_val = self.evaluate_arithmetic_with_assign(arg);
2624+
}
2625+
// let returns 1 if last expression is 0, 0 otherwise
2626+
let exit_code = if last_val == 0 { 1 } else { 0 };
2627+
let mut result = ExecResult {
2628+
stdout: String::new(),
2629+
stderr: String::new(),
2630+
exit_code,
2631+
control_flow: ControlFlow::None,
2632+
};
2633+
result = self.apply_redirections(result, &command.redirects).await?;
2634+
return Ok(result);
2635+
}
2636+
26192637
// Handle `unset` with array element syntax: unset 'arr[key]'
26202638
if name == "unset" {
26212639
for arg in &args {
@@ -3735,8 +3753,8 @@ impl Interpreter {
37353753
}
37363754
}
37373755
} else if is_integer {
3738-
// Try to evaluate as integer
3739-
let int_val: i64 = value.parse().unwrap_or(0);
3756+
// Evaluate as arithmetic expression
3757+
let int_val = self.evaluate_arithmetic_with_assign(value);
37403758
self.variables
37413759
.insert(var_name.to_string(), int_val.to_string());
37423760
} else {

crates/bashkit/tests/spec_cases/bash/arithmetic.test.sh

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,91 @@ echo $((5 >= 3)); echo $((5 >= 5)); echo $((4 >= 5))
405405
1
406406
0
407407
### end
408+
409+
### let_basic
410+
# let evaluates arithmetic and assigns
411+
let x=5+3
412+
echo $x
413+
### expect
414+
8
415+
### end
416+
417+
### let_multiple
418+
# let evaluates multiple expressions
419+
let a=2 b=3 c=a+b
420+
echo $a $b $c
421+
### expect
422+
2 3 5
423+
### end
424+
425+
### let_exit_zero
426+
# let returns 0 when last expression is non-zero
427+
let x=5
428+
echo $?
429+
### expect
430+
0
431+
### end
432+
433+
### let_exit_one
434+
# let returns 1 when last expression is zero
435+
let x=0
436+
echo $?
437+
### expect
438+
1
439+
### end
440+
441+
### let_increment
442+
# let with increment operators
443+
x=5; let x++
444+
echo $x
445+
### expect
446+
6
447+
### end
448+
449+
### let_compound_assign
450+
# let with compound assignment
451+
x=10; let x+=5
452+
echo $x
453+
### expect
454+
15
455+
### end
456+
457+
### let_no_args
458+
# let with no arguments returns 1
459+
let 2>/dev/null
460+
echo $?
461+
### expect
462+
1
463+
### end
464+
465+
### declare_i_basic
466+
# declare -i evaluates arithmetic on assignment
467+
declare -i x=5+3
468+
echo $x
469+
### expect
470+
8
471+
### end
472+
473+
### declare_i_expression
474+
# declare -i with complex expression
475+
declare -i x=2*3+4
476+
echo $x
477+
### expect
478+
10
479+
### end
480+
481+
### declare_i_variable_ref
482+
# declare -i referencing other variables
483+
a=5; b=3; declare -i x=a+b
484+
echo $x
485+
### expect
486+
8
487+
### end
488+
489+
### declare_i_plain_number
490+
# declare -i with plain number
491+
declare -i x=42
492+
echo $x
493+
### expect
494+
42
495+
### end

specs/009-implementation-status.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,23 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
103103

104104
## Spec Test Coverage
105105

106-
**Total spec test cases:** 1214 (1209 pass, 5 skip)
106+
**Total spec test cases:** 1277 (1272 pass, 5 skip)
107107

108108
| Category | Cases | In CI | Pass | Skip | Notes |
109109
|----------|-------|-------|------|------|-------|
110-
| Bash (core) | 853 | Yes | 848 | 5 | `bash_spec_tests` in CI |
110+
| Bash (core) | 859 | Yes | 854 | 5 | `bash_spec_tests` in CI |
111111
| AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g |
112112
| Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect |
113113
| Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E |
114114
| JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
115-
| **Total** | **1214** | **Yes** | **1209** | **5** | |
115+
| Python | 57 | Yes | 57 | 0 | embedded Python (Monty) |
116+
| **Total** | **1277** | **Yes** | **1272** | **5** | |
116117

117118
### Bash Spec Tests Breakdown
118119

119120
| File | Cases | Notes |
120121
|------|-------|-------|
121-
| arithmetic.test.sh | 57 | includes logical, bitwise, compound assign, increment/decrement |
122+
| arithmetic.test.sh | 68 | includes logical, bitwise, compound assign, increment/decrement, `let` builtin, `declare -i` arithmetic |
122123
| arrays.test.sh | 27 | indices, `${arr[@]}` / `${arr[*]}`, negative indexing `${arr[-1]}` |
123124
| background.test.sh | 4 | |
124125
| bash-command.test.sh | 34 | bash/sh re-invocation |
@@ -206,15 +207,15 @@ Features that may be added in the future (not intentionally excluded):
206207

207208
### Implemented
208209

209-
**92 core builtins + 3 feature-gated = 95 total**
210+
**93 core builtins + 3 feature-gated = 96 total**
210211

211212
`echo`, `printf`, `cat`, `nl`, `cd`, `pwd`, `true`, `false`, `exit`, `test`, `[`,
212213
`export`, `set`, `unset`, `local`, `source`, `.`, `read`, `shift`, `break`,
213214
`continue`, `return`, `grep`, `sed`, `awk`, `jq`, `sleep`, `head`, `tail`,
214215
`basename`, `dirname`, `mkdir`, `rm`, `cp`, `mv`, `touch`, `chmod`, `chown`, `ln`, `wc`,
215216
`sort`, `uniq`, `cut`, `tr`, `paste`, `column`, `diff`, `comm`, `date`,
216217
`wait`, `curl`, `wget`, `timeout`, `command`, `getopts`,
217-
`type`, `which`, `hash`, `declare`, `typeset`, `kill`,
218+
`type`, `which`, `hash`, `declare`, `typeset`, `let`, `kill`,
218219
`time` (keyword), `whoami`, `hostname`, `uname`, `id`, `ls`, `rmdir`, `find`, `xargs`, `tee`,
219220
`:` (colon), `eval`, `readonly`, `times`, `bash`, `sh`,
220221
`od`, `xxd`, `hexdump`, `strings`,

0 commit comments

Comments
 (0)