Repository navigation
Conversation
Parse `(?<=...)` and `(?<!...)`, which the README listed as not supported yet, instead of throwing "Invalid capture group name". This finishes support the code already anticipated: the Group type has had an unused `lookBehind` field since the TypeScript conversion, and the lookbehind round-trip tests were present but commented out. A lookbehind is tokenized like a lookahead (`followedBy` or `notFollowedBy`, `remember: false`) with `lookBehind: true`, and goes through the same lookahead branches in the tokenizer. Code that treats lookaheads as zero-width therefore handles lookbehinds the same way. Code that needs to tell them apart checks `lookBehind`, which the type now documents; the README says to check it. Lookbehinds do not count as capture groups, and `=` and `!` can never start a group name, so named groups `(?<name>...)` are unaffected. The `=` or `!` must be literal: `(?<\x3Da)` still throws. Like other zero-width tokens, a quantified lookbehind such as `(?<=a)*` is not rejected, although JavaScript rejects it. `reconstruct` emits `(?<=` and `(?<!` for these groups, so a lookbehind round-trips unchanged. Tests: the commented-out lookaround round-trip tests are enabled and extended (alternation, repetition, back-references, modifier groups, nesting, empty lookbehinds, a neighbouring named group). Parse tests cover both forms, nesting both ways, alternation and empty lookbehinds; reference tests show a lookbehind is not counted as a capture group while a group inside one is; error tests cover unterminated, unbalanced and escaped lookbehind syntax. README: document the lookbehind token shape and what may follow `(?`. Also correct the GROUP examples, which showed `stack` as an object rather than an array, and move named groups off the not-supported list with a note that they are not yet counted when numbering back-references such as `\1`. `\k<name>` back-references stay on the list: `\k` is still read as the literal `k`. Refs fent#13 Refs fent#57
fent
requested changes
Oct 30, 2026
fent
left a comment
Owner
There was a problem hiding this comment.
Hello, thanks again for contributing!
Looks good, just added a few comments in the readme
| "stack": [{ | ||
| "type": ret.types.GROUP, | ||
| "stack": { "type": ret.types.CHAR, "value": 97 }, | ||
| "stack": [{ "type": ret.types.CHAR, "value": 97 }], |
| ### GROUP | ||
|
|
||
| Groups contain tokens that are inside of a parenthesis. If the group begins with `?` followed by another character, it's a special type of group. A ':' tells the group not to be remembered when `exec` is used. '=' means the previous token matches only if followed by this group, and '!' means the previous token matches only if NOT followed. | ||
| Groups contain tokens that are inside of a parenthesis. If the group begins with `?` followed by another character, it's a special type of group. A ':' tells the group not to be remembered when `exec` is used. '=' means the previous token matches only if followed by this group, and '!' means the previous token matches only if NOT followed. '<=' means the next token matches only if preceded by this group, and '<!' means it matches only if NOT preceded by it. They set `followedBy` or `notFollowedBy` like a lookahead, plus `lookBehind: true`. Check `lookBehind` before treating `followedBy` or `notFollowedBy` as a lookahead. |
Owner
There was a problem hiding this comment.
This paragraph is getting a bit long, do you mind making it more into a list?
| * `(?<=)` and `(?<!)`: [Negative lookbehind assertions](https://github.com/tc39/proposal-regexp-lookbehind) | ||
| * `\k<group>`: [Named group back-references](https://github.com/tc39/proposal-regexp-named-groups) | ||
|
|
||
| Named groups `(?<name>...)` are parsed, but they are not yet counted when numbering back-references such as `\1`. |
Owner
There was a problem hiding this comment.
Could you put place this as part of the previous bullet point?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #13, #57.
What this adds
ret now parses
(?<=...)and(?<!...), which the README listed as not supported yet. Before, they threw "Invalid capture group name".This finishes something the code already anticipated: the
Grouptype has had an unusedlookBehindfield since the TypeScript conversion (#24), and the lookbehind round-trip tests intest/reconstruct-test.tswere there but commented out.Token shape
A lookbehind is tokenized like the matching lookahead, plus
lookBehind: true:I reused
followedBy/notFollowedByinstead of adding new flags so that code which already handles lookaheads keeps working: randexp, for example, emits nothing for a group withfollowedByornotFollowedBy, so it generates nothing for a lookbehind either. Code that needs to tell them apart checkslookBehind; the README says so and the type documents it. If you'd rather have separate flags, I can change it.reconstructemits(?<=and(?<!, so a lookbehind round-trips unchanged.Details
(?<!(a))(b)\2is a reference to group 2).=and!can never start a group name, so named groups are unaffected. The=/!must be literal:(?<\x3Da)still throws.(?<=a)*is not rejected, although JavaScript rejects it (ret already accepts^*and\b+, which JavaScript also rejects).README
(?.stackas an object instead of an array.\1.\k<name>stays on the list:\kis still read as the literalk.Testing
tsc, eslint and coverage (100%) clean; CI green on Node 20, 22 and 24 on my fork.