You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(regex): close every D-447 gap — named groups, \A \z \Z, class algebra
The regex-equivalence audit's remaining gaps, all landed or settled:
- Named groups `(?<name>e)`: a capturing group sharing Java's numbering,
duplicate names rejected, name→index bindings on the Program, and
`(.group m "name")` on Matcher.
- `\A \z \Z` anchors; `\Z` honours a final `\n` / `\r` / `\r\n` (Java
parity), all three MULTILINE-independent.
- Character-class ALGEBRA: nested unions `[a[b]]` and `&&` intersection
(`[a-z&&[^m-p]]`), via a recursive class parser over the existing
bitmap+RangeSet representation. This is also what `[[:alpha:]]` IS in
Java — the D-447 row called it "POSIX classes", but Java has no POSIX
bracket classes; it parses them as a nested union (oracle-verified:
clj matches "a" in "ab1"). The row's framing was a mis-diagnosis.
- Empty alternatives and groups: `()` and `a|` are legal Java (match
empty) and were misrouted to the unsupported signal.
- Lookbehind had already landed; the row was stale on it.
Backreferences are PERMANENTLY DECLINED as AD-060, not deferred: ADR-0031's
Pike-NFA linear-time guarantee is the property that lets untrusted patterns
run without a ReDoS budget, a backreference forces a backtracking engine,
and RE2 rejects them for exactly this reason.
The signal split deferred from the data-vs-feature sweep lands with it:
a MALFORMED pattern raises the CATCHABLE `regex_pattern_invalid` (Java
PatternSyntaxException parity), routed through one shared
`regex_value.raiseCompileError` used by every compile surface — re-pattern,
regex literals, Pattern/compile+matches, String .matches/.split/replace,
clojure.string/split — while a valid-in-Java feature cljw declines stays
the uncatchable unsupported signal.
Corpus-backed per the row's own mandate: 15 clj-byte-matched goldens in
regex_equivalence.txt covering every landed feature plus the catchable
invalid cases; pin e2e regex_parity_gaps.sh asserts the catchable/
uncatchable split the corpus cannot express. compile.zig's header now
states the true surface (the row's depth-1 doc-fix item). D-447 discharged.
Smell-audited: 2: the row said "POSIX classes" and the first instinct was to
implement POSIX classes — the oracle said Java doesn't have them, so what
landed is what Java actually does. Also caught my own stale unit test
asserting the pre-split error for "(" and updated it to the new contract
rather than weakening the contract to fit the test.
Copy file name to clipboardExpand all lines: .dev/accepted_divergences.yaml
+22Lines changed: 22 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -945,3 +945,25 @@ accepted:
945
945
be wrong.
946
946
derives_from: "ADR-0181 (doc coverage is a ledger of decisions) + .claude/rules/clj_attribution.md (the two header variants) + EPL-1.0 §7 redistribution."
947
947
pin: "scripts/check_clj_attribution.sh --gate (the variant-1 file set must equal legal/NOTICE's list, both directions) + test/diff/clj_corpus/set_arity.txt (17 golden pairs incl. every arity and the :doc/:arglists reads)."
948
+
- id: "AD-060"
949
+
area: "regex"
950
+
summary: |-
951
+
Backreferences (`\1` … `\9`, `\k<name>`) raise the UNCATCHABLE
952
+
unsupported-feature error at pattern-compile time; Java compiles and
953
+
matches them. This is the RE2 posture, permanent by design, not a
954
+
pending gap: cljw's matcher is a Pike-NFA (ADR-0031) whose worst case is
955
+
linear in the input, which is the property that lets the playground run
956
+
untrusted patterns without a ReDoS budget. A backreference makes
957
+
matching NP-hard in general and forces a backtracking engine — RE2
958
+
rejects backreferences for exactly this reason. The raise is the
959
+
unsupported-feature signal (not the catchable invalid-pattern error),
960
+
because the pattern is VALID Java that cljw deliberately declines,
961
+
distinct from a malformed pattern the user should catch and handle
962
+
(regex_pattern_invalid, which IS catchable, matching Java's
963
+
PatternSyntaxException). Every other D-447 gap closed 2026-08-05:
964
+
lookbehind, named groups `(?<name>…)` (+ `.group m "name"`), `\A \z \Z`,
965
+
nested class unions `[a[b]]` / `&&` intersection, empty
966
+
alternatives/groups.
967
+
example: '(re-pattern "(a)\\1") => cljw UNCATCHABLE "this regex feature … is not supported" / clj #"(a)\1" compiles and (re-matches #"(a)\1" "aa") => ["aa" "a"]'
968
+
derives_from: "ADR-0031 (Pike-NFA, non-backtracking invariant — linear-time matching is the security property, F-002 finished form)."
969
+
pin: "test/e2e/regex_parity_gaps.sh — backref compile raises the uncatchable unsupported signal and a (catch Throwable …) does NOT swallow it."
Copy file name to clipboardExpand all lines: .dev/debt.yaml
+27-5Lines changed: 27 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -1354,11 +1354,6 @@ active:
1354
1354
category: "perf"
1355
1355
barrier: "none hard — O-023 already ships the effective form. The 正しい姿 unification is a deliberate refactor that touches the gate-green O-023 path; a transducer stack (map's fn) ≠ a pred vector, so the unified representation needs design. Take up alongside a reduce-heavy bench that the current `[xform coll]` descriptor under-serves, or when the lazy-walk collapse is revisited with a real workload. Not blocking; the existing fused-reduce is correct + fast."
1356
1356
last_reviewed: "2026-06-15"
1357
-
- id: "D-447"
1358
-
status: "open (2026-06-15, regex equivalence audit / ADR-0147). Regex parity gaps cljw's Pike-NFA matcher does NOT support — all currently RAISE NotImplemented (never silently wrong; the 48-golden `test/diff/clj_corpus/regex_equivalence.txt` deliberately excludes them; reluctant quantifiers `*? +? ?? {n,m}?` LANDED 2026-06-16 — split-priority swap pairing with the cut-on-match leftmost-first fix + a dedicated matchFull loop for the re-matches full-span case, corpus regex_equivalence): lookbehind `(?<=)`/`(?<!)`, named groups `(?<name>)`, backreferences `\\1`, `\\A \\z \\Z`, POSIX `[[:class:]]`. (Lookahead `(?=)`/`(?!)` and all four flags `(?imsx)` DO work.) NOT campaign-blocking — addressed only when a real workload needs them OR opportunistically alongside the ADR-0147 regex-perf engine work (reluctant quantifiers are a cheap split-priority swap that pairs naturally with the matcher changes; backrefs would need a backtracking fallback cljw rejects per ADR-0031, so likely stay a documented gap / RE2 posture). When a gap closes, MOVE its goldens into the equivalence corpus in the same cycle (corpus-backed discharge, anti-D-177). Also a depth-1 doc fix: `compile.zig:22-23` claims 'lookaround' unsupported but lookahead works."
1359
-
category: "clj-parity"
1360
-
barrier: "none — each gap is a bounded matcher/compiler feature. Reluctant quantifiers DONE 2026-06-16 (the cheap split-priority swap). Remaining: take up opportunistically when a lib/workload needs one. Backrefs conflict with the non-backtracking invariant (ADR-0031) — likely an accepted gap, not a fix. lookbehind/named-groups/`\\A\\z\\Z`/POSIX-classes are bounded compiler features."
1361
-
last_reviewed: "2026-06-15"
1362
1357
- id: "D-462"
1363
1358
status: "PARTIAL (2026-06-18 — java.time LOCAL FAMILY WIRED; only ZonedDateTime + arithmetic methods residual). Instant, Duration, LocalDateTime, LocalDate, LocalTime are all wired as `.typed_instance` values (per-Runtime descriptors; timestamp.zig/date.zig model — NO new NaN-box tag): statics (of/now/parse/ofEpoch*) + readers + (str) ISO-grounded vs clj + value `=` + cross-refs (LocalDateTime.toLocalDate/toLocalTime). Shared civil + ISO date/time format/parse helpers live in runtime/time/instant.zig; the typed_instance print form is a `temporal_print` enum on TypeDescriptor; value-wrap files in the compat_tiers `wrap:` slot (G3-clean). e2e: phase15_java_time_{instant,duration,local_date_time,local_date}.sh (clj-grounded, incl. negative/pre-1970/1900-non-leap/ns-precision edges). pr-str divergence (bare toString vs clj `#object[…]`) = AD-042. RESIDUAL: (1) ZonedDateTime DEFERRED — needs a bundled IANA tz database for named zones (scope decision, user/ADR-owned; see compat_tiers status); (3) LocalDateTime.now is UTC-based (no zone DB). RESIDUAL (2) RESOLVED (2026-06-20): a verify-sweep (anti-D-177, 24 methods × clj oracle) found the 2026-06-18 'arithmetic NOT implemented' claim was STALE — plus*/minus*/plusWeeks/plusMonths/plusYears/isBefore/isAfter/isEqual (LocalDate), plus*/minus*/isBefore/isEqual (LocalDateTime/LocalTime), plusSeconds/plusMillis/plusNanos/minus*/isBefore (Instant), multipliedBy/negated/toMinutes/between (Duration) ALL already worked + matched clj. The ONLY genuinely-missing methods were LocalDate.atStartOfDay (→ LocalDateTime at midnight) + LocalDate.atTime (h m / h m s / h m s ns → LocalDateTime), now IMPLEMENTED in local_date_value.zig (atStartOfDayFn/atTimeFn → local_date_time_value.make(rt, epoch_day, nano_of_day); out-of-range time fields raise like JVM DateTimeException, message differs per AD-007). clj-verified incl. ns-precision + toLocalDate round-trip; e2e phase15_java_time_local_date_arith.sh (at-time case). The original UNWIRED finding (static-method class resolution + empty method_tables + compat_tiers over-claim) is fully resolved for the local family. ROW NOW OPEN ONLY FOR ZonedDateTime (residual 1, user/ADR-owned tz-DB) + the LDT.now-UTC note (residual 3)."
1364
1359
category: "clj-parity"
@@ -2696,6 +2691,33 @@ standing:
2696
2691
barrier: "open after the D-440 arc completes (R4 + R5 must first rewrite the bulk of 'Phase N' citations to gap-area terms). Premature adoption = ~70 dangling citations with no 1:1 redirect. Trigger: D-440 R4/R5 done + a user nod to re-architect the planning axis (it is depth-4, the project's organizing metaphor)."
2697
2692
last_reviewed: "2026-06-15"
2698
2693
discharged:
2694
+
- id: "D-447"
2695
+
status: |-
2696
+
DISCHARGED 2026-08-05 — every named gap closed or settled, corpus-backed.
2697
+
IMPLEMENTED: named groups `(?<name>e)` (shared Java numbering, duplicate
2698
+
names rejected, `.group m "name"` on Matcher); `\A \z \Z` anchors (\Z
2699
+
honours a final \n / \r / \r\n); nested class unions `[a[b]]` and `&&`
2700
+
intersection (which is what `[[:alpha:]]` actually IS in Java — the row's
2701
+
"POSIX classes" framing was a mis-diagnosis: Java has no POSIX bracket
2702
+
classes, it parses them as a nested union, verified on the oracle);
2703
+
empty alternatives/groups `()` `a|` (were misrouted to NotImplemented).
2704
+
Lookbehind had already landed (the row was stale). SETTLED: backreferences
2705
+
are PERMANENTLY DECLINED as AD-060 — the RE2 posture; ADR-0031's Pike-NFA
2706
+
linear-time guarantee is the security property, and a backref forces
2707
+
backtracking. SIGNAL SPLIT landed with it (the task-3 deferral): a
2708
+
MALFORMED pattern now raises the CATCHABLE `regex_pattern_invalid`
2709
+
(Java PatternSyntaxException parity) via the shared
2710
+
`regex_value.raiseCompileError` used by every compile surface (re-pattern,
appended to regex_equivalence.txt; pin e2e regex_parity_gaps.sh (3 cases
2715
+
incl. the catchable/uncatchable split). The compile.zig header doc now
2716
+
states the true surface (the row's depth-1 doc-fix item).
2717
+
category: clj-parity
2718
+
barrier: none — discharged
2719
+
last_reviewed: "2026-08-05"
2720
+
2699
2721
- id: "D-446"
2700
2722
status: "PARTIAL (2026-06-18 — MID/UNDER/over-arity sweep COMPLETE; only multidim residual open). An empirical both-sides arity probe (193 common clojure.core fns × arities 0-7, BOTH runtimes catch clojure.lang.ArityException; harness + data in private/arity_sweep/) found 22 over-strict fns (cljw REJECTS an arity clj accepts; ZERO over-lenient — the 0-arg class from the 2026-06-16 pass below stays clean). 14 fixed bug→fix (F-011): n-ary map/mapv/mapcat/list* (declare-d top-level -map-n-step + -spread, D-147 no-named-local-fn), bit-and-not variadic fold, resolve 2-arg env form (a local named in env → nil) [commit 982890aa]; the 8 typed array ctors gained the 2-arg (X-array size init-or-seq) form (uniform -array-from2 — value init fills, sequential init copies; the byte/short/char NUMBER-init divergence vs clj IS recorded as AD-036 deriving from AD-019, Devil-advocate-forked) [array commit]. Mechanised: test/diff/clj_corpus/arity_envelopes.txt (22 cases, all clj-byte-matched) + e2e phase14_arity_parity.sh (35/0). REMAINING OPEN SCOPE OF THIS ROW = multidim array indexing: (aget a i j …) and (aset a i j … v) + the typed aset-* multidim forms diverge at arity 3+/4+ (8 aset-* fns; aget is a builtin the ns-publics fn? sweep did not enumerate) — see barrier. EARLIER 0-ARG PASS (2026-06-16, kept for history): a fresh-context subagent probed the 0-ARG boundary (the not= precedent) + 22 over-arity + 20 extra-0-arg; found 10 divergences all aligned to clj — cljw-LENIENT = < > <= >= distinct? every-pred some-fn now throw; cljw-STRICT into + conj! now return clj values. ORIGINAL (2026-06-15, user-directed): arity-divergence audit — enumerate cljw fn arities, diff vs clj, classify each bug→fix or accepted→AD-NNN; big-bang-then-closed (clj_diff_sweep Discipline 2)."
0 commit comments