Full ECMA-262 regex literal grammar sample - #99
Conversation
Ports the ECMA-262 regex grammar started in PR #11 and finishes it: - Adds the missing DecimalDigits rule and removes the unfinished set-notation/modifiers sections that referenced undefined rules (including a duplicate Atom rule that would clobber the real one) - Excludes backslash from PatternCharacter so escapes parse via AtomEscape, and excludes `/` and line terminators per the lexical grammar for regex literals - Fixes the quantifier rule to reject {1,2,} like Unicode mode - Restricts IdentityEscape to SyntaxCharacter / `/` (strict mode) - Adds modern syntax: \u{...} escapes, \p{...} property escapes, and ES2025 pattern modifiers (?ims-ims:...) - Wraps Pattern in a RegExpLiteral rule that parses /pattern/flags, keeping the perf/compare.civet input working test/regex.civet validates 25 real-world literals (each cross-checked against the JS engine via eval) and 22 strict-mode rejections. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greptile SummaryThis PR replaces the 45-line placeholder regex grammar with a 189-line full ECMA-262 Pattern grammar (strict Unicode mode), adding a new test file that cross-checks every accepted literal against the real JS engine and confirms all 22 rejected literals throw parse errors.
Confidence Score: 3/5The grammar is a well-thought-out rewrite that substantially improves on the original, but the CodePoint rule silently accepts code points above 0x10FFFF that JS engines reject in Unicode mode, and the test suite has no invalid cases exercising that path. The CodePoint rule accepts hex strings of arbitrary value, meaning /\u{110000}/u and friends parse without error even though they are SyntaxError in every Unicode-aware JS engine. The grammar header documents other known omissions but does not mention this one, and the invalid test array has no entry that would expose the gap. The rest of the grammar change looks correct and is well-covered by tests. The CodePoint rule in samples/regex.hera (lines 124-125) and the invalid array in test/regex.civet (around line 43) both need attention to close the out-of-range code-point gap. Important Files Changed
Reviews (1): Last reviewed commit: "Replace WIP regex sample with full ECMA-..." | Re-trigger Greptile |
| CodePoint | ||
| /[0-9a-fA-F]+/ |
There was a problem hiding this comment.
CodePoint has no upper-bound — accepts \u{110000} and beyond
The rule /[0-9a-fA-F]+/ matches any-length hex string, so \u{110000} (0x110000 > 0x10FFFF) and \u{1FFFFF} parse successfully even though both are SyntaxError in JS Unicode mode. This is not one of the context-dependent errors the header disclaims (those are: nonexistent back-references, duplicate names/flags, [z-a] ranges, and {3,1} bounds). Code-point range is a purely local static check on a single token value.
The parallel in this repo's own url.hera is instructive — DecimalOctet uses an explicit regex to stay within 0-255 rather than just /[0-9]+/. A constrained pattern like /[0-9a-fA-F]{1,5}|10[0-9a-fA-F]{4}/ would enforce the ceiling. Alternatively, at minimum, add /\\u{110000}/u to the invalid array in test/regex.civet to document and guard the gap.
| UnicodePropertyName | ||
| /[A-Za-z_]+/ |
There was a problem hiding this comment.
UnicodePropertyName excludes digits, deviating from ECMA-262
ECMA-262 defines UnicodePropertyNameCharacter as AsciiLetter | $ | _ | DecimalDigit, so digits are technically legal in property names (though no currently-assigned Unicode property name happens to use one). The UnicodePropertyValue rule already includes digits (/[A-Za-z0-9_]+/), so the asymmetry is easy to miss. Aligning both rules removes the spec deviation at no practical cost.
| UnicodePropertyName | |
| /[A-Za-z_]+/ | |
| UnicodePropertyName | |
| /[A-Za-z0-9_]+/ |
| invalid := [ | ||
| '//' // a comment, not a regex | ||
| '/+/' | ||
| '/a**/' | ||
| '/^*/' | ||
| '/(/' | ||
| '/a)/' | ||
| '/[a/' | ||
| '/a{1,2,}/' // Annex B treats {1,2,} as literal characters | ||
| '/a{/' // Annex B | ||
| '/]/' // Annex B | ||
| '/\\q/' // Annex B identity escape | ||
| '/\\01/' // Annex B legacy octal | ||
| '/[\\B]/' | ||
| '/\\x1/' | ||
| '/\\u12/' | ||
| '/\\k/' | ||
| '/(?<1a>x)/' | ||
| '/(?<name>x/' | ||
| '/(?=x/' | ||
| '/(?i_:x)/' | ||
| '/a\nb/' // literal line terminator | ||
| '/a/z' // invalid flag |
There was a problem hiding this comment.
Missing invalid test cases for out-of-range
CodePoint
The 22 rejection cases cover Annex-B constructs, malformed escapes, and structural errors, but none exercise a \u{...} escape whose numeric value exceeds 0x10FFFF. Because the cross-check via new Function only runs for items in the valid array, the CodePoint upper-bound gap goes undetected. Adding entries like '/\\u{110000}/u' and '/\\u{1FFFFF}/u' would either catch the grammar gap or document it explicitly once the rule is tightened.
The CodePoint rule accepted any-length hex, so out-of-range escapes
like \u{110000} parsed even though they are SyntaxErrors in Unicode
mode. Bound the rule while still permitting arbitrary leading zeros
(\u{0000000041} is valid JS), and add accept/reject test coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
This could come in handy when / if we want to do fancier things with regexes in Hera |
edemaine
left a comment
There was a problem hiding this comment.
Looka like a nice realistic example! And potentially useful, as you say.
| # | ||
| # This grammar is purely syntactic. Context-dependent early errors are not | ||
| # checked: backreferences to nonexistent groups, duplicate group names or | ||
| # flags, out-of-order ranges like [z-a], or bounds like {3,1}. Annex B |
There was a problem hiding this comment.
GPT says: Maybe mention pattern modifier early errors here too. The grammar accepts forms like (?ii:x), (?i-i:x), and (?-:x), while ECMA-262 rejects them via modifier early-error rules. That seems consistent with the “purely syntactic” scope, but it’s not quite covered by “duplicate flags” as written.
perf/compare.civet compiles each sample with the previous hera release (0.9.0), which predates the ::any annotations regex.hera needs to break type-inference cycles. Skip samples the previous release cannot parse instead of aborting the whole benchmark. Also extend the grammar header's early-error disclaimer to cover pattern modifier combinations like (?ii:x), (?i-i:x), and (?-:x), which are accepted syntactically but rejected by ECMA-262 early errors (review feedback from @edemaine). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #99 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 9 9
Lines 1783 1783
Branches 300 300
=========================================
Hits 1783 1783 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Ports the ECMA-262 regex grammar from #11 onto current main, fixed up so it compiles and parses accurately.
What changed from the #11 version
PatternCharacternot excluding\— escapes never parsed viaAtomEscape; a trailing lone backslash was silently acceptedDecimalDigitsrule (was an undefined reference)[lookahead ∉ ClassReservedDouble] SourceCharacter but not ...) referencing undefined rules, and the trailing modifiersAtomrule duplicated (and would clobber) the realAtomQuantifierPrefixto reject{1,2,}(was( "," DecimalDigits )? ","?)IdentityEscaperestricted to SyntaxCharacter //,\0requires no following digit, Annex B forms (a{, lone],\q, legacy octal) rejectedModernized
\u{...}code point escapes,\p{...}/\P{...}property escapes (hera compiles terminals with theuflag, so these work natively)(?ims-ims:...)— now standard, integrated intoAtom(subsumes(?:)\k<name>, lookbehind kept from starting on optimizer #11The start rule is now
RegExpLiteral(/pattern/flagsper the lexical grammar — unescaped/only inside classes, no line terminators,//is not a regex), which keeps theperf/compare.civetinput/foo[abc](?:bar)\d+/gparsing.Set-notation (
v-flagClassSetExpression) syntax is left as a TODO; the flag itself is accepted.Testing
test/regex.civet: 25 valid literals — every accepted literal is cross-checked against the JS engine vianew Function— and 22 rejections (each a SyntaxError in Unicode mode). Recursive rules carry::anyannotations so the generated parser passes the stricttsc -p tsconfig.parsers.jsoncheck.Note:
pnpm test:typed-parser-samplescurrently fails on main locally oninference.fixture.hera($C/$Soverload arity); unrelated to this change.🤖 Generated with Claude Code