Skip to content

Fix a lot ot test262 regexp/parser issues - #122

Merged
toshok merged 8 commits into
mainfrom
toshok/get-us-to-75percent-test-262
Aug 5, 2026
Merged

Fix a lot ot test262 regexp/parser issues#122
toshok merged 8 commits into
mainfrom
toshok/get-us-to-75percent-test-262

Conversation

@toshok

@toshok toshok commented Aug 5, 2026

Copy link
Copy Markdown
Owner

a bunch of test262 fixes that should get us to 75% passing 🤞

toshok and others added 7 commits August 5, 2026 10:23
The user's call: `with` is dynamic scope — every name in the block
resolves against a runtime object, the same compile-time-unknowable
bindings that put eval out of scope — so its tests leave the
denominator rather than sitting in it as foregone failures.  The
classifier skips language/statements/with/ wholesale plus any test
with a statement-position `with (`.  That scan is line-anchored, not
word-boundary: a bare \bwith\s*\( match drowns in assertion prose
("called with (undefined, ...)") and `with` as a method name
(arr.with(i, v)) — 19 false positives on the recorded run, including
Promise tests that pass today.  Anchored, the reclassification is
exact: 291 newly skipped, 275 of them prior fail-parse, and the 16
prior passes all inside the with/ tree itself — negative strict-mode
tests that passed only because the parser rejects `with`, which is not
a conformance win worth counting.

full-baseline.json reclassifies to 33085/45168 (73.25%) — computed
offline by replaying the classifier over the recorded CI run's rows,
so the ratchet agrees with what the next run will measure without
waiting for it.  Both lane expectations files drop their 14
now-skipped entries.  The CLI dispatch moves behind an import.meta
guard so the classifier is importable for exactly this kind of offline
replay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from probing the stage1 binary and inventorying the failing
tests, ahead of the test262-75% regexp work.  The vendored PCRE 8.32
was built without UCP, so every \p{...} dies at pcre16_compile; the
flag parser stops at gimuy (no s/d/v); patterns reach the engine raw;
and a rejected literal evaluates to null instead of throwing (the
"Cannot read property 'test' of null" signature, 592 tests).

Recommendation: translate in front of the engine rather than swap it —
expand \p and v-mode class sets into explicit ranges from
build-time-generated Unicode tables, sourced from
@unicode/unicode-17.0.0, the same data the suite's property-escapes
tests were generated from (node's own engine is two Unicode versions
behind the suite and would bake in wrong answers).  Staged: \p under
/u (~450), the v flag (~115), then s/d/RegExp.escape riders (~60);
named-groups and the prototype residue are exec-side and independent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pattern translator (ejs-regexp-unicode.c) expands \p{...}/\P{...}
into explicit code-point classes before pcre sees the pattern — pcre
has no ECMAScript property model, so the properties live in generated
tables (ejs-unicode-tables.h: 441 range sets, 23k ranges, Unicode 17)
built by gen-unicode-tables.mjs from @unicode/unicode-17.0.0, the same
data the test262 property-escapes tests are generated from.  Lone
names resolve binary properties and General_Category values with both
alias directions; =-form accepts gc/sc/scx only, so \p{ASCII=Yes} is
the SyntaxError the spec wants.  Expansion emits \uXXXX for BMP
endpoints and raw surrogate pairs for astral ones — JAVASCRIPT_COMPAT
forecloses \x{...}, and PCRE_UTF16 reads a pattern pair back as one
code point.  Outside /u, \p identity-escapes to "p" per Annex B
instead of dying in pcre's UCP-less build.

Regexp literals lower from the syntactic n.regex instead of acorn's
host-constructed n.value.  That value is null whenever the compiler's
engine rejects the pattern, and lower.ts's null-literal case caught it
first: a /v literal compiled to the VALUE null, surfacing as
"Cannot read property 'test' of null" at some later use.  Rebuilding
flags from value's booleans also silently dropped flags the value
object predates.  A bad pattern is now the runtime's own SyntaxError
at the literal's evaluation, and the constval folds in lower.ts and
integrate.ts learn that a regex literal is identified by .regex, not
by value — folding one would mint a fresh object per reference site.

Riders the suite runs forced out of hiding: RegExpBuiltinExec's two
fullUnicode stubs die (pcre's ovec is code-unit offsets whatever the
flags, so the spec's code-point conversions are the identity and the
capture substring is flag-independent); the utf8 converters pair
surrogates instead of aborting (a lone surrogate is U+FFFD — these
feed logging, where replacement beats SIGABRT — and U+FFFF no longer
trips an off-by-one); encodeURI encodes astral code points through a
real 1-4-byte encoder instead of truncating to 16 bits, without
sign-extending continuation bytes into "%FFFFFFC3".

test262 property-escapes: 164 -> 565 of 613.  The residue is pcre's
UTF-16 fidelity tail (lone-surrogate subjects, surrogate-endpoint
classes) and the v-only string properties — docs/regexp-plan.md
stage 2.  Stage ladder green (test-stage0/1); lane at 75.0%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v-mode classes are ClassSetExpressions, and pcre has no set algebra —
so the translator parses them outright and evaluates the sets at
translation time: union by juxtaposition (with a-b ranges), uniform
"--" difference and "&&" intersection chains (mixing without brackets
is the SyntaxError the spec wants, as is a range as a chain operand),
nested classes recursively, and ^ complement over the full code
space.  What pcre receives is the evaluated result — a plain range
class, or an alternation when strings are members.

Strings enter through \q{...|...} literals and the properties of
strings (\p{RGI_Emoji} and friends), whose sequences join the
generated tables: 8 properties, 9345 packed sequences, single-code-
point members folded into range sets.  Emission orders strings
longest-first (alternation must prefer the longer match), and an
empty-string member becomes an empty final alternative.  Negating
anything that may contain strings — [^...\q{...}...], \P{RGI_Emoji} —
is a SyntaxError.

Flag plumbing: v accepted (u+v rejected), unicodeSets getter,
PCRE_UTF16 and exec's fullUnicode ride either flag.  The flags getter
also moves to spec order — it emitted "y" before "u", so
/a/uy.flags round-tripped as "yu"-ordered nonsense — and picks up v
between them: gimuvy.

test262: RegExp/unicodeSets 114/114 (from 0 — the whole slice);
property-escapes 572/613 with the v-only string properties now
passing.  Stage ladder green; repo v test matches node byte-for-byte.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A full-suite run is ~50 minutes; losing one at 88% to a killed
process meant starting over.  --resume reads the rows the output file
already has, skips those tests, and appends — the run continues where
it died.  Composes with the same selection flags; a changed selection
simply re-runs whatever the file doesn't cover.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--script used to parse with sourceType "module" — the tester's harness
wrappers import their spec, and module was how import syntax parsed at
all — but a module parse is strict, so a sloppy script couldn't use
`yield` as an identifier (or anything else strict rejects).  Script
parses now use sourceType "script" with acorn's
allowImportExportEverywhere/allowAwaitOutsideFunction, which is the
contract --script always meant: script SEMANTICS, module syntax.  The
options are scoped to script parses only — on module parses they would
legalize `export` in nested positions the module grammar forbids
(caught by the lane's negative tests).  yield-identifier slice: 64/64.

s (dotAll): flag, getter, PCRE_DOTALL — and bare `.` now translates to
[^\n\r

] when dot-all is off, because JS excludes all four
LineTerminators where pcre excludes only \n.  The translator tracks
inline modifier groups ((?s:...), (?-s:...)) for the effective state,
since pcre applies the same semantics to any dot left bare.  dotall
slice 5/5.

d (hasIndices): flag, getter, and .indices arrays built from pcre's
ovector — installed with CreateDataProperty semantics (a setter on
Array.prototype.indices must not fire).  Along the way exec's capture
count moves from pcre16_exec's return (which stops at the highest
participating group, silently truncating /(a)(x)?/ results) to
PCRE_INFO_CAPTURECOUNT, with the ovector pre-filled to pcre's unset
marker.  The residue of the match-indices slice (7 tests) is named
groups, a separate lever.

RegExp.escape per ES2025: syntax chars backslashed, a leading ASCII
alphanumeric hex-escaped, control chars keeping their letter
spellings, punctuators/whitespace/lone surrogates hex-escaped, astral
input passing through whole.  escape slice 19/19.

Flags getter emits spec order dgimsuvy.  Lane expectations regenerate
596 -> 572 (76.0%); full stage ladder green including the stage2/3
byte-identity fixed point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Raising the floor took a second, flag-bearing workflow run: the report
only rewrote full-baseline.json under --update-baseline, so an
improving push printed "regenerate it" and made you dispatch CI again
for a file the run could have produced the first time.  The ratchet
already proves the direction — an improvement is by definition the
safe way for the file to move — so the report now rewrites it
unprompted whenever the run beats the committed floor (tolerance
carried), and every improving run's test262-full-results artifact
carries a ready-to-commit baseline.  Committing it stays the manual
act it was; --update-baseline narrows to the one move that should
stay deliberate, forcing the floor DOWN after an accepted regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@toshok toshok changed the title Toshok/get us to 75percent test 262 Fix a lof ot test262 regexp/parser issues Aug 5, 2026
@toshok toshok changed the title Fix a lof ot test262 regexp/parser issues Fix a lot ot test262 regexp/parser issues Aug 5, 2026
@toshok
toshok merged commit 305a449 into main Aug 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant