Skip to content

Commit 21af1fb

Browse files
chaliyclaude
andauthored
feat(bash): implement FUNCNAME special variable (#248)
## Summary - Implement `FUNCNAME` bash array variable that reflects the function call stack - `FUNCNAME[0]` is the current function, `FUNCNAME[1]` is the caller, etc. - At top level, `FUNCNAME` is empty (length 0) - Array is set from the call stack before function body execution and restored after ## Test plan - [x] 5 new spec tests: basic name, call stack, nesting depth, empty outside functions, restored after return - [x] All 1204 spec tests pass (1199 pass, 5 skip) - [x] `cargo clippy` and `cargo fmt` clean Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1d67da0 commit 21af1fb

File tree

4 files changed

+69
-4
lines changed

4 files changed

+69
-4
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,13 +2431,30 @@ impl Interpreter {
24312431
positional: args.clone(),
24322432
});
24332433

2434+
// Set FUNCNAME array from call stack (index 0 = current, 1 = caller, ...)
2435+
let funcname_arr: HashMap<usize, String> = self
2436+
.call_stack
2437+
.iter()
2438+
.rev()
2439+
.enumerate()
2440+
.map(|(i, f)| (i, f.name.clone()))
2441+
.collect();
2442+
let prev_funcname = self.arrays.insert("FUNCNAME".to_string(), funcname_arr);
2443+
24342444
// Execute function body
24352445
let mut result = self.execute_command(&func_def.body).await?;
24362446

24372447
// Pop call frame and function counter
24382448
self.call_stack.pop();
24392449
self.counters.pop_function();
24402450

2451+
// Restore previous FUNCNAME (or set from remaining stack)
2452+
if self.call_stack.is_empty() {
2453+
self.arrays.remove("FUNCNAME");
2454+
} else if let Some(prev) = prev_funcname {
2455+
self.arrays.insert("FUNCNAME".to_string(), prev);
2456+
}
2457+
24412458
// Handle return - convert Return control flow to exit code
24422459
if let ControlFlow::Return(code) = result.control_flow {
24432460
result.exit_code = code;

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,47 @@ echo "after outer: $x"
159159
in outer: local_val
160160
after outer: global
161161
### end
162+
163+
### func_funcname_basic
164+
# FUNCNAME[0] is current function name
165+
myfunc() { echo "${FUNCNAME[0]}"; }
166+
myfunc
167+
### expect
168+
myfunc
169+
### end
170+
171+
### func_funcname_call_stack
172+
# FUNCNAME array reflects call stack
173+
inner() { echo "${FUNCNAME[@]}"; }
174+
outer() { inner; }
175+
outer
176+
### expect
177+
inner outer
178+
### end
179+
180+
### func_funcname_depth
181+
# FUNCNAME array length matches nesting depth
182+
a() { echo "${#FUNCNAME[@]}"; }
183+
b() { a; }
184+
c() { b; }
185+
c
186+
### expect
187+
3
188+
### end
189+
190+
### func_funcname_empty_outside
191+
# FUNCNAME is empty outside functions
192+
echo "${#FUNCNAME[@]}"
193+
### expect
194+
0
195+
### end
196+
197+
### func_funcname_restored
198+
# FUNCNAME is cleared after function returns
199+
f() { echo "in: ${FUNCNAME[0]}"; }
200+
f
201+
echo "out: ${#FUNCNAME[@]}"
202+
### expect
203+
in: f
204+
out: 0
205+
### end

specs/009-implementation-status.md

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

104104
## Spec Test Coverage
105105

106-
**Total spec test cases:** 1199 (1194 pass, 5 skip)
106+
**Total spec test cases:** 1204 (1199 pass, 5 skip)
107107

108108
| Category | Cases | In CI | Pass | Skip | Notes |
109109
|----------|-------|-------|------|------|-------|
110-
| Bash (core) | 838 | Yes | 833 | 5 | `bash_spec_tests` in CI |
110+
| Bash (core) | 843 | Yes | 838 | 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** | **1199** | **Yes** | **1194** | **5** | |
115+
| **Total** | **1204** | **Yes** | **1199** | **5** | |
116116

117117
### Bash Spec Tests Breakdown
118118

@@ -136,7 +136,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
136136
| errexit.test.sh | 8 | set -e tests |
137137
| fileops.test.sh | 21 | |
138138
| find.test.sh | 10 | file search |
139-
| functions.test.sh | 17 | local dynamic scoping, nested writes |
139+
| functions.test.sh | 22 | local dynamic scoping, nested writes, FUNCNAME call stack |
140140
| getopts.test.sh | 9 | POSIX option parsing, combined flags, silent mode |
141141
| globs.test.sh | 12 | for-loop glob expansion, recursive `**` |
142142
| headtail.test.sh | 14 | |

supply-chain/config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,10 @@ criteria = "safe-to-deploy"
962962
version = "0.8.9"
963963
criteria = "safe-to-deploy"
964964

965+
[[exemptions.regex-syntax]]
966+
version = "0.8.10"
967+
criteria = "safe-to-deploy"
968+
965969
[[exemptions.reqwest]]
966970
version = "0.13.2"
967971
criteria = "safe-to-deploy"

0 commit comments

Comments
 (0)