Skip to content

Commit 0707eaf

Browse files
Merge branch 'weekend/tpc-1575-chakra-parse' into weekend/all-combined
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents e699808 + cd10aa9 commit 0707eaf

5 files changed

Lines changed: 163 additions & 29 deletions

File tree

Apps/Playground/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ set(DEPENDENCIES
1515
set(SCRIPTS
1616
"Scripts/cube_texture_polyfill.js"
1717
"Scripts/dom_polyfill.js"
18+
"Scripts/es2019_transpile.js"
1819
"Scripts/experience.js"
1920
"Scripts/fetch_polyfill.js"
2021
"Scripts/file_polyfill.js"

Apps/Playground/Scripts/config.json

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,7 +1517,7 @@
15171517
"playgroundId": "#8NTR5X#8",
15181518
"replace": "//options//, roundtrip = true;",
15191519
"excludeFromAutomaticTesting": true,
1520-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
1520+
"reason": "Chakra parse fails on ES2022 syntax not handled by ES2019 polyfill (e.g. private class fields).",
15211521
"referenceImage": "glTFSerializerTextureExport.png"
15221522
},
15231523
{
@@ -2292,7 +2292,7 @@
22922292
"title": "Material Plugin",
22932293
"playgroundId": "#22HT5Z#10",
22942294
"excludeFromAutomaticTesting": true,
2295-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
2295+
"reason": "Chakra parse fails on ES2022 syntax not handled by ES2019 polyfill (e.g. private class fields).",
22962296
"referenceImage": "materialPlugin.png"
22972297
},
22982298
{
@@ -2377,7 +2377,7 @@
23772377
"playgroundId": "#PCMH7A#2",
23782378
"renderCount": 120,
23792379
"excludeFromAutomaticTesting": true,
2380-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
2380+
"reason": "Chakra parse fails on ES2022 syntax not handled by ES2019 polyfill (e.g. private class fields).",
23812381
"referenceImage": "fluidBoxSphere.png"
23822382
},
23832383
{
@@ -2762,8 +2762,6 @@
27622762
"title": "custom-handling-of-materials-for-render-target-pass",
27632763
"playgroundId": "#FIVL25#21",
27642764
"renderCount": 60,
2765-
"excludeFromAutomaticTesting": true,
2766-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
27672765
"referenceImage": "custom-handling-of-materials-for-render-target-pass.png"
27682766
},
27692767
{
@@ -2857,7 +2855,7 @@
28572855
"renderCount": 2,
28582856
"replace": "//options//, modelIndex = 2;",
28592857
"excludeFromAutomaticTesting": true,
2860-
"reason": "Test crashes or hangs on Babylon Native",
2858+
"reason": "ES2020+ ?. parsed via ES2019 polyfill; runtime TypeError from .stop() on optional null field.",
28612859
"referenceImage": "computeMaxExtents-BoxAnimated.png"
28622860
},
28632861
{
@@ -2866,7 +2864,7 @@
28662864
"renderCount": 2,
28672865
"replace": "//options//, modelIndex = 4; animationIndex = 0;",
28682866
"excludeFromAutomaticTesting": true,
2869-
"reason": "Test crashes or hangs on Babylon Native",
2867+
"reason": "ES2020+ ?. parsed via ES2019 polyfill; runtime TypeError from .stop() on optional null field.",
28702868
"referenceImage": "computeMaxExtents-Fox0.png"
28712869
},
28722870
{
@@ -2875,7 +2873,7 @@
28752873
"renderCount": 2,
28762874
"replace": "//options//, modelIndex = 4; animationIndex = 1;",
28772875
"excludeFromAutomaticTesting": true,
2878-
"reason": "Test crashes or hangs on Babylon Native",
2876+
"reason": "ES2020+ ?. parsed via ES2019 polyfill; runtime TypeError from .stop() on optional null field.",
28792877
"referenceImage": "computeMaxExtents-Fox1.png"
28802878
},
28812879
{
@@ -2884,7 +2882,7 @@
28842882
"renderCount": 2,
28852883
"replace": "//options//, modelIndex = 5;",
28862884
"excludeFromAutomaticTesting": true,
2887-
"reason": "Test crashes or hangs on Babylon Native",
2885+
"reason": "ES2020+ ?. parsed via ES2019 polyfill; runtime TypeError from .stop() on optional null field.",
28882886
"referenceImage": "computeMaxExtents-MorphStressTest.png"
28892887
},
28902888
{
@@ -2997,7 +2995,7 @@
29972995
"title": "FrameGraph nrge custom rendering",
29982996
"playgroundId": "#1QCA2M#35",
29992997
"excludeFromAutomaticTesting": true,
3000-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
2998+
"reason": "Parse repaired via ES2019 polyfill; runtime fails (asset/URL load issue in FrameGraph snippet).",
30012999
"referenceImage": "FrameGraph-nrge-custom-rendering.png"
30023000
},
30033001
{
@@ -3011,14 +3009,14 @@
30113009
"title": "FrameGraph nrge glow layer",
30123010
"playgroundId": "#IG8NRC#84",
30133011
"excludeFromAutomaticTesting": true,
3014-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3012+
"reason": "Parse repaired via ES2019 polyfill; runtime fails: cubemap files not defined (asset gap).",
30153013
"referenceImage": "FrameGraph-nrge-glow-layer.png"
30163014
},
30173015
{
30183016
"title": "FrameGraph nrge highlight layer",
30193017
"playgroundId": "#QZYNMK#3",
30203018
"excludeFromAutomaticTesting": true,
3021-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3019+
"reason": "Parse repaired via ES2019 polyfill; runtime fails (asset/URL load issue in FrameGraph snippet).",
30223020
"referenceImage": "FrameGraph-nrge-highlight-layer.png"
30233021
},
30243022
{
@@ -3076,7 +3074,7 @@
30763074
"title": "FrameGraph nrge rig camera",
30773075
"playgroundId": "#ATL1CS#19",
30783076
"excludeFromAutomaticTesting": true,
3079-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3077+
"reason": "Parse repaired via ES2019 polyfill; runtime fails (asset/URL load issue in FrameGraph snippet).",
30803078
"referenceImage": "FrameGraph-nrge-rig-camera.png"
30813079
},
30823080
{
@@ -3103,7 +3101,7 @@
31033101
"title": "FrameGraph nrge volumetric lighting",
31043102
"playgroundId": "#3VH0AC#2",
31053103
"excludeFromAutomaticTesting": true,
3106-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3104+
"reason": "Parse repaired via ES2019 polyfill; runtime hangs (FrameGraph volumetric lighting).",
31073105
"referenceImage": "FrameGraph-nrge-volumetric-lighting.png"
31083106
},
31093107
{
@@ -3130,14 +3128,14 @@
31303128
"title": "FrameGraph nrge transmission",
31313129
"playgroundId": "#ZNTBN2#10",
31323130
"excludeFromAutomaticTesting": true,
3133-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3131+
"reason": "Chakra parse fails on ES2022 syntax not handled by ES2019 polyfill (e.g. private class fields).",
31343132
"referenceImage": "FrameGraph-nrge-transmission.png"
31353133
},
31363134
{
31373135
"title": "FrameGraph nrge selection outline layer",
31383136
"playgroundId": "#ADUC74#1",
31393137
"excludeFromAutomaticTesting": true,
3140-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3138+
"reason": "Parse repaired via ES2019 polyfill; runtime fails (asset/URL load issue in FrameGraph snippet).",
31413139
"referenceImage": "FrameGraph-nrge-selection-outline-layer.png"
31423140
},
31433141
{
@@ -3245,7 +3243,7 @@
32453243
"title": "FrameGraph volumetric lighting",
32463244
"playgroundId": "#3VH0AC",
32473245
"excludeFromAutomaticTesting": true,
3248-
"reason": "Test crashes or hangs on Babylon Native",
3246+
"reason": "Parse repaired via ES2019 polyfill; runtime TypeError (FrameGraph volumetric lighting).",
32493247
"referenceImage": "FrameGraph-volumetric-lighting.png"
32503248
},
32513249
{
@@ -3266,14 +3264,14 @@
32663264
"title": "FrameGraph selection outline",
32673265
"playgroundId": "#E1F0GP#4",
32683266
"excludeFromAutomaticTesting": true,
3269-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3267+
"reason": "Parse repaired via ES2019 polyfill; runtime fails: Unknown error opening URL.",
32703268
"referenceImage": "FrameGraph-selection-outline.png"
32713269
},
32723270
{
32733271
"title": "Render target texture with clustered lights",
32743272
"playgroundId": "#1QCA2M#11",
32753273
"excludeFromAutomaticTesting": true,
3276-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3274+
"reason": "Parse repaired via ES2019 polyfill; runtime fails: texture data size mismatch (clustered lighting gap).",
32773275
"referenceImage": "Render-target-texture-with-clustered-lights.png"
32783276
},
32793277
{
@@ -3399,14 +3397,14 @@
33993397
"title": "Sponza Clustered Lighting",
34003398
"playgroundId": "#CSCJO2#17",
34013399
"excludeFromAutomaticTesting": true,
3402-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3400+
"reason": "Parse repaired via ES2019 polyfill; runtime fails: texture data size mismatch (clustered lighting gap).",
34033401
"referenceImage": "sponza-clustered-lighting.png"
34043402
},
34053403
{
34063404
"title": "Sponza Clustered Lighting (2 viewports)",
34073405
"playgroundId": "#CSCJO2#20",
34083406
"excludeFromAutomaticTesting": true,
3409-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3407+
"reason": "Parse repaired via ES2019 polyfill; runtime fails: texture data size mismatch (clustered lighting gap).",
34103408
"referenceImage": "sponza-clustered-lighting-viewports.png"
34113409
},
34123410
{
@@ -3487,28 +3485,28 @@
34873485
"title": "Atmosphere Day",
34883486
"playgroundId": "#VO1Z0C#39",
34893487
"excludeFromAutomaticTesting": true,
3490-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3488+
"reason": "Requires babylonjs-addons (ADDONS.Atmosphere); not shipped by Babylon Native.",
34913489
"referenceImage": "atmosphere-day.png"
34923490
},
34933491
{
34943492
"title": "Atmosphere Day (Planet Origin)",
34953493
"playgroundId": "#VO1Z0C#40",
34963494
"excludeFromAutomaticTesting": true,
3497-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3495+
"reason": "Requires babylonjs-addons (ADDONS.Atmosphere); not shipped by Babylon Native.",
34983496
"referenceImage": "atmosphere-day.png"
34993497
},
35003498
{
35013499
"title": "Atmosphere Day (Ray Marching)",
35023500
"playgroundId": "#VO1Z0C#41",
35033501
"excludeFromAutomaticTesting": true,
3504-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3502+
"reason": "Requires babylonjs-addons (ADDONS.Atmosphere); not shipped by Babylon Native.",
35053503
"referenceImage": "atmosphere-day-ray-marching.png"
35063504
},
35073505
{
35083506
"title": "Atmosphere Sunset",
35093507
"playgroundId": "#VO1Z0C#42",
35103508
"excludeFromAutomaticTesting": true,
3511-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
3509+
"reason": "Requires babylonjs-addons (ADDONS.Atmosphere); not shipped by Babylon Native.",
35123510
"referenceImage": "atmosphere-sunset.png"
35133511
},
35143512
{
@@ -4199,7 +4197,7 @@
41994197
"renderCount": 180,
42004198
"useLargeWorldRendering": true,
42014199
"excludeFromAutomaticTesting": true,
4202-
"reason": "Test crashes or hangs on Babylon Native",
4200+
"reason": "Parse repaired via ES2019 polyfill; runtime fails: 'HK' (Havok) global not defined.",
42034201
"referenceImage": "Havok-FloatingOrigin-Multi-Region.png"
42044202
},
42054203
{
@@ -4602,7 +4600,7 @@
46024600
"title": "Selection outline layer with instances",
46034601
"playgroundId": "#UR9706#0",
46044602
"excludeFromAutomaticTesting": true,
4605-
"reason": "Pixel comparison fails (more than 20% pixels differ)",
4603+
"reason": "Parse repaired via ES2019 polyfill; runtime fails (asset/URL load issue).",
46064604
"referenceImage": "Selection-outline-layer-with-instances.png"
46074605
},
46084606
{
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// es2019_transpile.js
2+
//
3+
// Lightweight regex-based ES2020+ -> ES2019 syntax repair for engines that
4+
// lack optional chaining (?.), nullish coalescing (??), and numeric
5+
// separators (1_000_000).
6+
//
7+
// This is a SYNTAX REPAIR, not a full transpile. It rewrites parse-time
8+
// failing tokens to the closest legal ES2019 expressions so that the host
9+
// engine accepts the code. Where native ES2020+ semantics differ from the
10+
// rewritten form, the rewritten form runs the "happy path" -- assumes the
11+
// target value is present. If the target is null at runtime the rewritten
12+
// code throws a TypeError where native ?. would have short-circuited to
13+
// undefined. This is an intentional trade-off: it produces valid syntax
14+
// (the prerequisite for the test running at all) while preserving the
15+
// common case behaviour. Tests that rely on the nullish-short-circuit
16+
// semantics may surface runtime errors that were previously hidden by
17+
// the parse failure -- a strict improvement for debuggability.
18+
//
19+
// Transforms applied:
20+
// * 1_000_000 -> 1000000 (strip numeric separators)
21+
// * a?.b -> a.b (optional chaining -> required chaining)
22+
// * a?.[x] -> a[x]
23+
// * a?.() -> a()
24+
// * a ?? b -> (a != null ? a : b)
25+
//
26+
// Not handled (still cause parse failures on Chakra):
27+
// * Logical assignment ||= &&= ??=
28+
// * Class private fields #name
29+
// * BigInt literals 1n
30+
//
31+
// String/regex/comment literals containing ? . sequences are not skipped,
32+
// but in Babylon snippet code such occurrences are rare.
33+
//
34+
// On engines with native ES2020+ support (V8, JSC) this code is never
35+
// invoked because the caller only retries with this transform after
36+
// initial eval throws a SyntaxError.
37+
//
38+
// Public API: top-level global function __bnTranspileES2019(code) -> string.
39+
// Chakra in Babylon Native does not define `self` or `globalThis`, so we
40+
// install via a top-level `var` declaration which becomes a property of
41+
// the script-global object across all our supported engines.
42+
var __bnTranspileES2019 = (function () {
43+
"use strict";
44+
45+
function stripNumericSeparators(code) {
46+
// Decimal / float: 1_000_000, 1.234_5, 1_000e3
47+
var out = code.replace(/\b(\d[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?)\b/g, function (m) {
48+
return m.indexOf("_") === -1 ? m : m.replace(/_/g, "");
49+
});
50+
// Hex / octal / binary: 0xFFFF_FFFF, 0o7_5, 0b1010_0101
51+
out = out.replace(/\b(0[xXoObB][0-9A-Fa-f_]+)\b/g, function (m) {
52+
return m.indexOf("_") === -1 ? m : m.replace(/_/g, "");
53+
});
54+
return out;
55+
}
56+
57+
function transformLogicalAssignment(code) {
58+
// a ||= b -> (a || (a = b))
59+
// a &&= b -> (a && (a = b))
60+
// a ??= b -> (a != null ? a : (a = b))
61+
// Only matches simple LHS (identifier, optional .prop / [idx] chain) to keep
62+
// the rewrite syntactically safe; complex LHS expressions are left alone.
63+
var lhs = "(?:[A-Za-z_$][\\w$]*)(?:\\.[A-Za-z_$][\\w$]*|\\[[^\\]\\n]*\\])*";
64+
var rhs = "[^;\\n]+";
65+
var prev = null, out = code, guard = 0;
66+
while (prev !== out && guard++ < 10) {
67+
prev = out;
68+
out = out
69+
.replace(new RegExp("(" + lhs + ")\\s*\\?\\?=\\s*(" + rhs + ")", "g"),
70+
"$1 = ($1 != null ? $1 : ($2))")
71+
.replace(new RegExp("(" + lhs + ")\\s*\\|\\|=\\s*(" + rhs + ")", "g"),
72+
"$1 = ($1 || ($2))")
73+
.replace(new RegExp("(" + lhs + ")\\s*&&=\\s*(" + rhs + ")", "g"),
74+
"$1 = ($1 && ($2))");
75+
}
76+
return out;
77+
}
78+
79+
function transformOptionalChaining(code) {
80+
var prev = null, out = code, guard = 0;
81+
while (prev !== out && guard++ < 50) {
82+
prev = out;
83+
out = out
84+
.replace(/\?\.(?=\()/g, "")
85+
.replace(/\?\.(?=\[)/g, "")
86+
.replace(/\?\./g, ".");
87+
}
88+
return out;
89+
}
90+
91+
function transformNullishCoalescing(code) {
92+
var prev = null, out = code, guard = 0;
93+
while (prev !== out && guard++ < 50) {
94+
prev = out;
95+
out = out.replace(
96+
/(\b[A-Za-z_$][\w$.]*(?:\[[^\]]*\])?)\s*\?\?\s*(\b[A-Za-z_$][\w$.]*(?:\[[^\]]*\])?|"[^"]*"|'[^']*'|\d+(?:\.\d+)?|true|false|null|undefined)/g,
97+
"($1 != null ? $1 : $2)"
98+
);
99+
}
100+
return out;
101+
}
102+
103+
function transpileES2019(code) {
104+
if (typeof code !== "string") { return code; }
105+
var out = code;
106+
out = stripNumericSeparators(out);
107+
out = transformLogicalAssignment(out);
108+
out = transformOptionalChaining(out);
109+
out = transformNullishCoalescing(out);
110+
return out;
111+
}
112+
113+
return transpileES2019;
114+
})();

Apps/Playground/Scripts/validation_native.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@
4545
done(false);
4646
}
4747

48+
// Run `eval(src)` directly. If the host engine throws a SyntaxError
49+
// (e.g. Chakra rejecting ?. ?? or numeric separators) and the
50+
// __bnTranspileES2019 helper is available, retry once with an ES2019
51+
// syntax-repaired version of the source. Re-throws on any other error.
52+
function evalWithFallback(src, test) {
53+
try {
54+
return eval(src);
55+
} catch (e) {
56+
if (e instanceof SyntaxError && typeof __bnTranspileES2019 === "function") {
57+
const repaired = __bnTranspileES2019(src);
58+
if (repaired !== src) {
59+
const title = test && test.title ? test.title : "(unknown)";
60+
console.log("Retrying '" + title + "' after ES2019 syntax repair (host engine lacks ES2020+ parse support).");
61+
return eval(repaired);
62+
}
63+
}
64+
throw e;
65+
}
66+
}
67+
4868
// Per-run counters surfaced as a final summary line on exit.
4969
let ranCount = 0;
5070
let passedCount = 0;
@@ -344,7 +364,7 @@
344364
}
345365
}
346366

347-
currentScene = eval(code + "\r\ncreateScene(engine)");
367+
currentScene = evalWithFallback(code + "\r\ncreateScene(engine)", test);
348368

349369
if (currentScene.then) {
350370
// Handle if createScene returns a promise
@@ -415,7 +435,7 @@
415435
}
416436
}
417437

418-
currentScene = eval(scriptToRun + test.functionToCall + "(engine)");
438+
currentScene = evalWithFallback(scriptToRun + test.functionToCall + "(engine)", test);
419439
processCurrentScene(test, renderImage, done, compareFunction);
420440
}
421441
catch (e) {

0 commit comments

Comments
 (0)