From 36f9b08396623b35f33fe7e2a7cb6be541158df8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 16:36:40 +0000 Subject: [PATCH] feat(bash): implement `let` builtin and fix `declare -i` arithmetic evaluation `let` builtin evaluates arithmetic expressions with assignment side effects and returns exit code 1 when last expression is zero, 0 otherwise. `declare -i` now uses `evaluate_arithmetic_with_assign` instead of plain `parse().unwrap_or(0)`, so `declare -i x=5+3` correctly yields 8. Adds 11 tests for `let` (basic, multiple, exit codes, increment, compound assign, no-args) and `declare -i` (basic, expressions, variable refs). --- crates/bashkit/src/interpreter/mod.rs | 22 ++++- .../tests/spec_cases/bash/arithmetic.test.sh | 88 +++++++++++++++++++ specs/009-implementation-status.md | 13 +-- 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index cae06153..64e05061 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -2616,6 +2616,24 @@ impl Interpreter { .await; } + // Handle `let` - evaluate arithmetic expressions with assignment + if name == "let" { + let mut last_val = 0i64; + for arg in &args { + last_val = self.evaluate_arithmetic_with_assign(arg); + } + // let returns 1 if last expression is 0, 0 otherwise + let exit_code = if last_val == 0 { 1 } else { 0 }; + let mut result = ExecResult { + stdout: String::new(), + stderr: String::new(), + exit_code, + control_flow: ControlFlow::None, + }; + result = self.apply_redirections(result, &command.redirects).await?; + return Ok(result); + } + // Handle `unset` with array element syntax: unset 'arr[key]' if name == "unset" { for arg in &args { @@ -3735,8 +3753,8 @@ impl Interpreter { } } } else if is_integer { - // Try to evaluate as integer - let int_val: i64 = value.parse().unwrap_or(0); + // Evaluate as arithmetic expression + let int_val = self.evaluate_arithmetic_with_assign(value); self.variables .insert(var_name.to_string(), int_val.to_string()); } else { diff --git a/crates/bashkit/tests/spec_cases/bash/arithmetic.test.sh b/crates/bashkit/tests/spec_cases/bash/arithmetic.test.sh index 0ee8378f..3ca493fe 100644 --- a/crates/bashkit/tests/spec_cases/bash/arithmetic.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/arithmetic.test.sh @@ -405,3 +405,91 @@ echo $((5 >= 3)); echo $((5 >= 5)); echo $((4 >= 5)) 1 0 ### end + +### let_basic +# let evaluates arithmetic and assigns +let x=5+3 +echo $x +### expect +8 +### end + +### let_multiple +# let evaluates multiple expressions +let a=2 b=3 c=a+b +echo $a $b $c +### expect +2 3 5 +### end + +### let_exit_zero +# let returns 0 when last expression is non-zero +let x=5 +echo $? +### expect +0 +### end + +### let_exit_one +# let returns 1 when last expression is zero +let x=0 +echo $? +### expect +1 +### end + +### let_increment +# let with increment operators +x=5; let x++ +echo $x +### expect +6 +### end + +### let_compound_assign +# let with compound assignment +x=10; let x+=5 +echo $x +### expect +15 +### end + +### let_no_args +# let with no arguments returns 1 +let 2>/dev/null +echo $? +### expect +1 +### end + +### declare_i_basic +# declare -i evaluates arithmetic on assignment +declare -i x=5+3 +echo $x +### expect +8 +### end + +### declare_i_expression +# declare -i with complex expression +declare -i x=2*3+4 +echo $x +### expect +10 +### end + +### declare_i_variable_ref +# declare -i referencing other variables +a=5; b=3; declare -i x=a+b +echo $x +### expect +8 +### end + +### declare_i_plain_number +# declare -i with plain number +declare -i x=42 +echo $x +### expect +42 +### end diff --git a/specs/009-implementation-status.md b/specs/009-implementation-status.md index 1f0e0645..5a5567b6 100644 --- a/specs/009-implementation-status.md +++ b/specs/009-implementation-status.md @@ -103,22 +103,23 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See ## Spec Test Coverage -**Total spec test cases:** 1214 (1209 pass, 5 skip) +**Total spec test cases:** 1277 (1272 pass, 5 skip) | Category | Cases | In CI | Pass | Skip | Notes | |----------|-------|-------|------|------|-------| -| Bash (core) | 853 | Yes | 848 | 5 | `bash_spec_tests` in CI | +| Bash (core) | 859 | Yes | 854 | 5 | `bash_spec_tests` in CI | | AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g | | Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect | | Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E | | JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env | -| **Total** | **1214** | **Yes** | **1209** | **5** | | +| Python | 57 | Yes | 57 | 0 | embedded Python (Monty) | +| **Total** | **1277** | **Yes** | **1272** | **5** | | ### Bash Spec Tests Breakdown | File | Cases | Notes | |------|-------|-------| -| arithmetic.test.sh | 57 | includes logical, bitwise, compound assign, increment/decrement | +| arithmetic.test.sh | 68 | includes logical, bitwise, compound assign, increment/decrement, `let` builtin, `declare -i` arithmetic | | arrays.test.sh | 27 | indices, `${arr[@]}` / `${arr[*]}`, negative indexing `${arr[-1]}` | | background.test.sh | 4 | | | bash-command.test.sh | 34 | bash/sh re-invocation | @@ -206,7 +207,7 @@ Features that may be added in the future (not intentionally excluded): ### Implemented -**92 core builtins + 3 feature-gated = 95 total** +**93 core builtins + 3 feature-gated = 96 total** `echo`, `printf`, `cat`, `nl`, `cd`, `pwd`, `true`, `false`, `exit`, `test`, `[`, `export`, `set`, `unset`, `local`, `source`, `.`, `read`, `shift`, `break`, @@ -214,7 +215,7 @@ Features that may be added in the future (not intentionally excluded): `basename`, `dirname`, `mkdir`, `rm`, `cp`, `mv`, `touch`, `chmod`, `chown`, `ln`, `wc`, `sort`, `uniq`, `cut`, `tr`, `paste`, `column`, `diff`, `comm`, `date`, `wait`, `curl`, `wget`, `timeout`, `command`, `getopts`, -`type`, `which`, `hash`, `declare`, `typeset`, `kill`, +`type`, `which`, `hash`, `declare`, `typeset`, `let`, `kill`, `time` (keyword), `whoami`, `hostname`, `uname`, `id`, `ls`, `rmdir`, `find`, `xargs`, `tee`, `:` (colon), `eval`, `readonly`, `times`, `bash`, `sh`, `od`, `xxd`, `hexdump`, `strings`,