diff --git a/crates/bashkit/src/parser/mod.rs b/crates/bashkit/src/parser/mod.rs index 1354bfe..740e422 100644 --- a/crates/bashkit/src/parser/mod.rs +++ b/crates/bashkit/src/parser/mod.rs @@ -1078,6 +1078,13 @@ impl<'a> Parser<'a> { self.advance(); } Some(tokens::Token::LeftParen) => { + if saw_regex_op { + // Regex pattern starts with '(' — collect it + let pattern = self.collect_conditional_regex_pattern("("); + words.push(Word::literal(&pattern)); + saw_regex_op = false; + continue; + } words.push(Word::literal("(")); self.advance(); } diff --git a/crates/bashkit/tests/spec_cases/bash/control-flow.test.sh b/crates/bashkit/tests/spec_cases/bash/control-flow.test.sh index 9eb52d5..321f5b4 100644 --- a/crates/bashkit/tests/spec_cases/bash/control-flow.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/control-flow.test.sh @@ -271,3 +271,72 @@ ERR done BYE ### end + +### regex_match_basic +# [[ =~ ]] regex match returns correct exit code +[[ "hello123" =~ [0-9]+ ]]; echo $? +[[ "hello" =~ [0-9]+ ]]; echo $? +### expect +0 +1 +### end + +### regex_match_bash_rematch +# BASH_REMATCH populated with capture groups +x="hello123world" +[[ "$x" =~ ([0-9]+) ]] +echo "${BASH_REMATCH[0]}" +echo "${BASH_REMATCH[1]}" +### expect +123 +123 +### end + +### regex_match_multiple_groups +# Multiple capture groups in BASH_REMATCH +[[ "2024-01-15" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]] +echo "${BASH_REMATCH[0]}" +echo "${BASH_REMATCH[1]}" +echo "${BASH_REMATCH[2]}" +echo "${BASH_REMATCH[3]}" +### expect +2024-01-15 +2024 +01 +15 +### end + +### regex_match_nested_groups +# Nested capture groups +[[ "foo123bar" =~ (foo([0-9]+)bar) ]] +echo "${BASH_REMATCH[0]}" +echo "${BASH_REMATCH[1]}" +echo "${BASH_REMATCH[2]}" +### expect +foo123bar +foo123bar +123 +### end + +### regex_match_no_match_clears +# BASH_REMATCH cleared on no match +[[ "abc123" =~ ([0-9]+) ]] +echo "before: ${#BASH_REMATCH[@]}" +[[ "abc" =~ ([0-9]+) ]] +echo "after: ${#BASH_REMATCH[@]}" +### expect +before: 2 +after: 0 +### end + +### regex_match_in_conditional +# Regex match used in && chain +x="error: line 42" +if [[ "$x" =~ error:\ line\ ([0-9]+) ]]; then + echo "line ${BASH_REMATCH[1]}" +else + echo "no match" +fi +### expect +line 42 +### end diff --git a/specs/009-implementation-status.md b/specs/009-implementation-status.md index e7e2eff..0ff20b7 100644 --- a/specs/009-implementation-status.md +++ b/specs/009-implementation-status.md @@ -103,16 +103,16 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See ## Spec Test Coverage -**Total spec test cases:** 1161 (1156 pass, 5 skip) +**Total spec test cases:** 1170 (1165 pass, 5 skip) | Category | Cases | In CI | Pass | Skip | Notes | |----------|-------|-------|------|------|-------| -| Bash (core) | 800 | Yes | 795 | 5 | `bash_spec_tests` in CI | +| Bash (core) | 809 | Yes | 804 | 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** | **1161** | **Yes** | **1156** | **5** | | +| **Total** | **1170** | **Yes** | **1165** | **5** | | ### Bash Spec Tests Breakdown @@ -128,7 +128,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See | command-not-found.test.sh | 17 | unknown command handling | | conditional.test.sh | 17 | `[[ ]]` conditionals, `=~` regex, BASH_REMATCH | | command-subst.test.sh | 14 | includes backtick substitution (1 skipped) | -| control-flow.test.sh | 37 | if/elif/else, for, while, case, trap ERR | +| control-flow.test.sh | 43 | if/elif/else, for, while, case, trap ERR, `[[ =~ ]]` BASH_REMATCH | | cuttr.test.sh | 32 | cut and tr commands, `-z` zero-terminated | | date.test.sh | 38 | format specifiers, `-d` relative/compound/epoch, `-R`, `-I`, `%N` (2 skipped) | | diff.test.sh | 4 | line diffs | @@ -136,7 +136,7 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See | errexit.test.sh | 8 | set -e tests | | fileops.test.sh | 21 | | | find.test.sh | 10 | file search | -| functions.test.sh | 14 | | +| functions.test.sh | 17 | local dynamic scoping, nested writes | | getopts.test.sh | 9 | POSIX option parsing, combined flags, silent mode | | globs.test.sh | 12 | for-loop glob expansion, recursive `**` | | headtail.test.sh | 14 | |