From 78d59e447de1eb6478b2e9a3af5854d2665cf552 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 24 Feb 2026 14:45:24 +0000 Subject: [PATCH] fix(bash): negative array indexing ${arr[-1]} Convert negative i64 to correct usize by computing array_length + negative_index, for both read (${arr[-1]}) and write (arr[-1]=val) paths. https://claude.ai/code/session_012rzB3FRw7yoQWCG1mxyW7J --- crates/bashkit/src/interpreter/mod.rs | 30 +++++++++++++++---- .../tests/spec_cases/bash/arrays.test.sh | 30 +++++++++++++++++++ specs/009-implementation-status.md | 8 ++--- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 916433af..568a6c65 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -2169,9 +2169,18 @@ impl Interpreter { arr.insert(key, value); } } else { - // Indexed array: use numeric index - let index: usize = - self.evaluate_arithmetic(index_str).try_into().unwrap_or(0); + // Indexed array: use numeric index (supports negative) + let raw_idx = self.evaluate_arithmetic(index_str); + let index = if raw_idx < 0 { + let len = self + .arrays + .get(&assignment.name) + .and_then(|a| a.keys().max().map(|m| m + 1)) + .unwrap_or(0) as i64; + (len + raw_idx).max(0) as usize + } else { + raw_idx as usize + }; let arr = self.arrays.entry(assignment.name.clone()).or_default(); if assignment.append { let existing = arr.get(&index).cloned().unwrap_or_default(); @@ -4029,8 +4038,19 @@ impl Interpreter { result.push_str(value); } } else { - // ${arr[n]} - get specific element - let idx: usize = self.evaluate_arithmetic(index).try_into().unwrap_or(0); + // ${arr[n]} - get specific element (supports negative indexing) + let raw_idx = self.evaluate_arithmetic(index); + let idx = if raw_idx < 0 { + // Negative index: count from end + let len = self + .arrays + .get(name) + .map(|a| a.keys().max().map(|m| m + 1).unwrap_or(0)) + .unwrap_or(0) as i64; + (len + raw_idx).max(0) as usize + } else { + raw_idx as usize + }; if let Some(arr) = self.arrays.get(name) { if let Some(value) = arr.get(&idx) { result.push_str(value); diff --git a/crates/bashkit/tests/spec_cases/bash/arrays.test.sh b/crates/bashkit/tests/spec_cases/bash/arrays.test.sh index 728d3a5c..825187b0 100644 --- a/crates/bashkit/tests/spec_cases/bash/arrays.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/arrays.test.sh @@ -182,3 +182,33 @@ printf 'hello\nworld\n' | mapfile -t; echo ${MAPFILE[0]}; echo ${MAPFILE[1]} hello world ### end + +### array_negative_index_last +# Negative index gets last element +arr=(a b c d e); echo "${arr[-1]}" +### expect +e +### end + +### array_negative_index_second_last +# Negative index gets second-to-last +arr=(a b c d e); echo "${arr[-2]}" +### expect +d +### end + +### array_negative_index_first +# Negative index wrapping to first +arr=(a b c); echo "${arr[-3]}" +### expect +a +### end + +### array_negative_all_values +# ${arr[@]} with negative indexing for assignment +arr=(10 20 30 40 50) +arr[-1]=99 +echo "${arr[@]}" +### expect +10 20 30 40 99 +### end diff --git a/specs/009-implementation-status.md b/specs/009-implementation-status.md index 0ff20b71..69992f9a 100644 --- a/specs/009-implementation-status.md +++ b/specs/009-implementation-status.md @@ -103,23 +103,23 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See ## Spec Test Coverage -**Total spec test cases:** 1170 (1165 pass, 5 skip) +**Total spec test cases:** 1174 (1169 pass, 5 skip) | Category | Cases | In CI | Pass | Skip | Notes | |----------|-------|-------|------|------|-------| -| Bash (core) | 809 | Yes | 804 | 5 | `bash_spec_tests` in CI | +| Bash (core) | 813 | Yes | 808 | 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** | **1170** | **Yes** | **1165** | **5** | | +| **Total** | **1174** | **Yes** | **1169** | **5** | | ### Bash Spec Tests Breakdown | File | Cases | Notes | |------|-------|-------| | arithmetic.test.sh | 57 | includes logical, bitwise, compound assign, increment/decrement | -| arrays.test.sh | 20 | includes indices, `${arr[@]}` / `${arr[*]}` expansion | +| arrays.test.sh | 27 | indices, `${arr[@]}` / `${arr[*]}`, negative indexing `${arr[-1]}` | | background.test.sh | 4 | | | bash-command.test.sh | 34 | bash/sh re-invocation | | brace-expansion.test.sh | 21 | {a,b,c}, {1..5}, for-loop brace expansion |