Add Pattern_groupindex_count/_at, closing the groupindex enumeration gap
README.md and docs/API.md both previously documented "no way to list every name a pattern defines without already knowing what to look for" as an accepted limitation of Pattern_groupindex_lookup being the only access to groupindex. It was not actually a hard constraint: the underlying GroupIndex struct (regexx.c) already stores every name and its group number in two parallel arrays, populated once at compile time; only a public accessor was missing. Pattern_groupindex_count returns the number of named groups; Pattern_groupindex_at(self, i, &name) for 0 <= i < count writes the i-th name and returns its 1-based group number, or returns -1 for an out-of-range i. Enumeration order is declaration order, verified against a real CPython 3.11 interpreter to match groupindex's own practical (insertion-order) iteration order, not just assumed. Verified directly (count/name/group-number correctness, matching the exact snippet now in USAGE.md's own output), full 3,252-case suite unaffected (3252/3252, this is a pure accessor addition touching no matching logic), clean AddressSanitizer/UndefinedBehaviorSanitizer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EjuMk8kY9SDus1wWe2K9xY
This commit is contained in:
+7
-3
@@ -29,7 +29,7 @@ struct Pattern {
|
||||
- `pattern`: the exact source text passed to `re_compile`, NUL-terminated, owned by the `Pattern` (freed by `Pattern_free`). Read-only for callers.
|
||||
- `flags`: the exact `flags` value passed to `re_compile`, including the encoding-mode bits (`BINARY`/`ASCII`/`UTF8`) and any leading global inline flags folded in during parsing (Section 2.6).
|
||||
- `groups`: the number of capturing groups, matching Python's `re.Pattern.groups` exactly (group 0, the whole match, is not counted).
|
||||
- `groupindex`: opaque; see `Pattern_groupindex_lookup` (3.11) for the only supported access to it.
|
||||
- `groupindex`: opaque; see `Pattern_groupindex_lookup`/`_count`/`_at` (3.11) for the supported access to it.
|
||||
- `program`: private, never dereference directly.
|
||||
|
||||
A `Pattern` is created only by `re_compile` (directly, or indirectly through the module level cache in `re_match`/`re_search`/etc.) and is freed only by `Pattern_free`.
|
||||
@@ -191,13 +191,17 @@ void Pattern_free(Pattern *self);
|
||||
|
||||
Frees a `Pattern` and everything it owns (the compiled program, the retained parse tree, `groupindex`, the copy of the pattern text). Do not call this while any `Match` produced from this `Pattern` is still in use (`Match.re`/`Match.lastgroup` borrow from it, Section 1.2); free every such `Match` with `Match_free` first, or simply free them in the reverse order they were created, which is always safe.
|
||||
|
||||
### 3.11 `Pattern_groupindex_lookup`
|
||||
### 3.11 `Pattern_groupindex_lookup` / `_count` / `_at`
|
||||
|
||||
```c
|
||||
int Pattern_groupindex_lookup(Pattern *self, const char *name);
|
||||
int Pattern_groupindex_count(Pattern *self);
|
||||
int Pattern_groupindex_at(Pattern *self, int i, const char **name);
|
||||
```
|
||||
|
||||
Looks up a named group in `re.Pattern.groupindex`; returns its 1-based group number, or `-1` if no group by that name exists in this pattern. This is the only supported access to `groupindex`'s content in this build: there is no enumeration function (no way to list every name a pattern defines without already knowing what to look for), unlike Python's `groupindex`, which is a full mapping object supporting iteration and `len()`. A caller that needs every name a pattern uses must track the names it compiled the pattern with itself.
|
||||
`Pattern_groupindex_lookup` looks up a named group in `re.Pattern.groupindex`; returns its 1-based group number, or `-1` if no group by that name exists in this pattern.
|
||||
|
||||
`Pattern_groupindex_count`/`Pattern_groupindex_at` enumerate `groupindex` as a whole (Python: `len(pattern.groupindex)`, `dict(pattern.groupindex).items()`): `_count` returns the number of named groups; `_at(self, i, &name)` for `0 <= i < count` writes a borrowed pointer (valid as long as `self` is) to the `i`-th name into `*name` and returns its 1-based group number, or returns `-1` and leaves `*name` untouched for an out-of-range `i`. Enumeration order is declaration order in the pattern source, which is not something Python's own `dict`-based `groupindex` guarantees at the language level but was verified to match in practice (insertion order) against a real CPython 3.11 interpreter (`(?P<c>c)(?P<a>a)(?P<b>b)`'s `groupindex.items()` lists `c`, `a`, `b`, not alphabetical order).
|
||||
|
||||
### 3.12-3.20 Module level functions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user