optipack 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -2
- package/bin/optipack.js +12 -0
- package/example/manifest.json +27 -0
- package/example/scripts/drops.js +24 -0
- package/example/scripts/main.js +67 -0
- package/example/scripts/util.js +30 -0
- package/package.json +34 -6
- package/src/cli.js +180 -0
- package/src/config.js +141 -0
- package/src/diff.js +108 -0
- package/src/hotpath.js +158 -0
- package/src/lint.js +150 -0
- package/src/pack-walker.js +50 -0
- package/src/pass-utils.js +419 -0
- package/src/passes/b1-cache-getter-chains.js +406 -0
- package/src/passes/b2-closure-lift.js +316 -0
- package/src/passes/b3-string-build.js +167 -0
- package/src/passes/b4-dynprop-hoist.js +177 -0
- package/src/passes/b5-null-chain-flatten.js +137 -0
- package/src/passes/index.js +43 -0
- package/src/passes/r1-cross-tick-cache.js +192 -0
- package/src/passes/r2-json-fold.js +201 -0
- package/src/passes/r3-try-catch-strip.js +61 -0
- package/src/passes/r4-getter-cache-cross-barrier.js +29 -0
- package/src/passes/s1-const-fold.js +130 -0
- package/src/passes/s2-dead-code.js +183 -0
- package/src/passes/s3-bool-simplify.js +82 -0
- package/src/passes/s4-for-of-to-indexed.js +140 -0
- package/src/passes/s5-array-methods-to-loops.js +357 -0
- package/src/passes/s6-destructure-to-direct.js +186 -0
- package/src/passes/s7-hoist-invariant-members.js +327 -0
- package/src/pipeline.js +186 -0
- package/src/pragma.js +115 -0
- package/src/report.js +98 -0
- package/test/array-methods-to-loops/expected.js +58 -0
- package/test/array-methods-to-loops/expects.json +6 -0
- package/test/array-methods-to-loops/input.js +23 -0
- package/test/array-methods-to-loops/scenario.json +22 -0
- package/test/array-methods-to-loops-bail/expected.js +10 -0
- package/test/array-methods-to-loops-bail/input.js +24 -0
- package/test/bool-simplify/expected.js +14 -0
- package/test/bool-simplify/expects.json +11 -0
- package/test/bool-simplify/input.js +18 -0
- package/test/cache-getter-chains/expected.js +20 -0
- package/test/cache-getter-chains/expects.json +11 -0
- package/test/cache-getter-chains/input.js +20 -0
- package/test/cache-getter-chains/scenario.json +35 -0
- package/test/closure-lift/expected.js +58 -0
- package/test/closure-lift/expects.json +1 -0
- package/test/closure-lift/input.js +50 -0
- package/test/closure-lift/scenario.json +9 -0
- package/test/const-fold/expected.js +12 -0
- package/test/const-fold/expects.json +1 -0
- package/test/const-fold/input.js +12 -0
- package/test/const-fold-bail/expected.js +7 -0
- package/test/const-fold-bail/expects.json +3 -0
- package/test/const-fold-bail/input.js +7 -0
- package/test/cross-tick-cache/config.json +1 -0
- package/test/cross-tick-cache/expected.js +38 -0
- package/test/cross-tick-cache/expects.json +6 -0
- package/test/cross-tick-cache/input.js +13 -0
- package/test/cross-tick-cache/scenario.json +33 -0
- package/test/dead-code/expected.js +14 -0
- package/test/dead-code/expects.json +5 -0
- package/test/dead-code/input.js +23 -0
- package/test/destructure-to-direct/expected.js +37 -0
- package/test/destructure-to-direct/expects.json +11 -0
- package/test/destructure-to-direct/input.js +28 -0
- package/test/destructure-to-direct/scenario.json +22 -0
- package/test/destructure-to-direct-bail/expected.js +36 -0
- package/test/destructure-to-direct-bail/expects.json +11 -0
- package/test/destructure-to-direct-bail/input.js +25 -0
- package/test/differential.test.mjs +267 -0
- package/test/dynprop-hoist/expected.js +24 -0
- package/test/dynprop-hoist/expects.json +11 -0
- package/test/dynprop-hoist/input.js +25 -0
- package/test/dynprop-hoist/scenario.json +24 -0
- package/test/example-report-snapshot.json +107 -0
- package/test/example.test.mjs +115 -0
- package/test/for-of-to-indexed/expected.js +59 -0
- package/test/for-of-to-indexed/expects.json +11 -0
- package/test/for-of-to-indexed/input.js +43 -0
- package/test/for-of-to-indexed-bail/expected.js +25 -0
- package/test/for-of-to-indexed-bail/expects.json +11 -0
- package/test/for-of-to-indexed-bail/input.js +29 -0
- package/test/gen-expected.mjs +33 -0
- package/test/getter-cache-cross-barrier/config.json +1 -0
- package/test/getter-cache-cross-barrier/expected.js +10 -0
- package/test/getter-cache-cross-barrier/expects.json +6 -0
- package/test/getter-cache-cross-barrier/input.js +11 -0
- package/test/getter-cache-cross-barrier/scenario.json +28 -0
- package/test/harness/fixture.js +117 -0
- package/test/harness/setup.js +198 -0
- package/test/hoist-invariant-members/expected.js +37 -0
- package/test/hoist-invariant-members/expects.json +11 -0
- package/test/hoist-invariant-members/input.js +34 -0
- package/test/hoist-invariant-members/scenario.json +35 -0
- package/test/hoist-invariant-members-bail/expected.js +19 -0
- package/test/hoist-invariant-members-bail/expects.json +5 -0
- package/test/hoist-invariant-members-bail/input.js +22 -0
- package/test/infra.test.mjs +260 -0
- package/test/json-fold/config.json +1 -0
- package/test/json-fold/expected.js +14 -0
- package/test/json-fold/expects.json +12 -0
- package/test/json-fold/input.js +17 -0
- package/test/json-fold-bail/config.json +1 -0
- package/test/json-fold-bail/expected.js +10 -0
- package/test/json-fold-bail/expects.json +5 -0
- package/test/json-fold-bail/input.js +14 -0
- package/test/loader-hooks.mjs +38 -0
- package/test/mock/@minecraft/server.mjs +274 -0
- package/test/null-chain-flatten/expected.js +22 -0
- package/test/null-chain-flatten/expects.json +5 -0
- package/test/null-chain-flatten/input.js +22 -0
- package/test/null-chain-flatten/scenario.json +24 -0
- package/test/string-build/expected.js +29 -0
- package/test/string-build/expects.json +5 -0
- package/test/string-build/input.js +29 -0
- package/test/string-build/scenario.json +27 -0
- package/test/string-build-bail/expected.js +10 -0
- package/test/string-build-bail/input.js +21 -0
- package/test/transforms.test.mjs +43 -0
- package/test/try-catch-strip/config.json +1 -0
- package/test/try-catch-strip/expected.js +57 -0
- package/test/try-catch-strip/expects.json +1 -0
- package/test/try-catch-strip/input.js +34 -0
- package/test/try-catch-strip/scenario.json +28 -0
- package/test/update-example-snapshot.mjs +19 -0
package/src/report.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report accumulation and serialization.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function newFileReport(relPath, beforeBytes) {
|
|
6
|
+
return {
|
|
7
|
+
path: relPath,
|
|
8
|
+
hotFunctions: 0,
|
|
9
|
+
transforms: {},
|
|
10
|
+
bails: [],
|
|
11
|
+
lint: [],
|
|
12
|
+
residualAllocs: { closures: 0, arrays: 0, objects: 0 },
|
|
13
|
+
bytes: { before: beforeBytes, after: 0 },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Report {
|
|
18
|
+
constructor(level) {
|
|
19
|
+
this.level = level;
|
|
20
|
+
this.files = [];
|
|
21
|
+
this.warnings = [];
|
|
22
|
+
this.totals = { transforms: 0, bails: 0, warnings: 0 };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
addFile(fr) {
|
|
26
|
+
this.files.push(fr);
|
|
27
|
+
this.totals.transforms += Object.values(fr.transforms).reduce((a, b) => a + b, 0);
|
|
28
|
+
this.totals.bails += fr.bails.length;
|
|
29
|
+
this.totals.warnings += this.warningsForFile(fr.path);
|
|
30
|
+
return fr;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
warningsForFile(p) {
|
|
34
|
+
return this.warnings.filter((w) => w.path === p).length;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
warn(entry) {
|
|
38
|
+
this.warnings.push(entry);
|
|
39
|
+
this.totals.warnings++;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
finish() {
|
|
43
|
+
// Recompute totals defensively (warnings are global, files are fixed).
|
|
44
|
+
this.totals.transforms = this.files.reduce(
|
|
45
|
+
(a, f) => a + Object.values(f.transforms).reduce((x, y) => x + y, 0), 0
|
|
46
|
+
);
|
|
47
|
+
this.totals.bails = this.files.reduce((a, f) => a + f.bails.length, 0);
|
|
48
|
+
this.totals.warnings = this.warnings.length;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toJSON() {
|
|
53
|
+
return {
|
|
54
|
+
level: this.level,
|
|
55
|
+
files: this.files,
|
|
56
|
+
warnings: this.warnings,
|
|
57
|
+
totals: { transforms: this.totals.transforms, bails: this.totals.bails, warnings: this.totals.warnings },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
serialize() {
|
|
62
|
+
return JSON.stringify(this.finish().toJSON(), null, 2) + '\n';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Shared per-file pass context handed to every pass. */
|
|
67
|
+
export class PassContext {
|
|
68
|
+
constructor({ fileName, pragmas, hot, fileReport, warningsSink, config }) {
|
|
69
|
+
this.fileName = fileName;
|
|
70
|
+
this.pragmas = pragmas;
|
|
71
|
+
this.hot = hot; // HotInfo or null
|
|
72
|
+
this.fileReport = fileReport;
|
|
73
|
+
this.warningsSink = warningsSink; // (entry) => void
|
|
74
|
+
this.config = config;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
applied(id) {
|
|
78
|
+
this.fileReport.transforms[id] = (this.fileReport.transforms[id] || 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
bail(line, code, reason) {
|
|
82
|
+
if (!line) return; // generated nodes have no source line to report
|
|
83
|
+
this.fileReport.bails.push({ line, transform: code, reason });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
warn(line, transform, message) {
|
|
87
|
+
this.warningsSink({ path: this.fileName, line: line ?? 0, transform, message });
|
|
88
|
+
this.fileReport.warnings = (this.fileReport.warnings || 0) + 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isDisabled(path, ...ids) {
|
|
92
|
+
return this.pragmas ? this.pragmas.isDisabled(path, ...ids) : false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isHot(node) {
|
|
96
|
+
return this.hot ? this.hot.isHot(node) : false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// S5: array-method inlining inside a hot tick handler.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
const tags = ['a', 'bb', 'ccc'];
|
|
4
|
+
function onTick() {
|
|
5
|
+
const _opArr = tags;
|
|
6
|
+
for (let _opI = 0; _opI < _opArr.length; _opI++) {
|
|
7
|
+
const t = _opArr[_opI];
|
|
8
|
+
world.sendMessage('tag:' + t);
|
|
9
|
+
}
|
|
10
|
+
const _opArr2 = tags;
|
|
11
|
+
const _opAcc2 = [];
|
|
12
|
+
for (let _opI2 = 0; _opI2 < _opArr2.length; _opI2++) {
|
|
13
|
+
const t = _opArr2[_opI2];
|
|
14
|
+
_opAcc2.push(t + '!');
|
|
15
|
+
}
|
|
16
|
+
const doubled = _opAcc2;
|
|
17
|
+
world.setDynamicProperty('doubled', doubled.join(','));
|
|
18
|
+
const _opArr3 = tags;
|
|
19
|
+
const _opAcc3 = [];
|
|
20
|
+
for (let _opI3 = 0; _opI3 < _opArr3.length; _opI3++) {
|
|
21
|
+
const t = _opArr3[_opI3];
|
|
22
|
+
if (t.length > 1) _opAcc3.push(t);
|
|
23
|
+
}
|
|
24
|
+
const longs = _opAcc3;
|
|
25
|
+
world.setDynamicProperty('longs', longs.length);
|
|
26
|
+
const _opArr4 = tags;
|
|
27
|
+
let _opRes4 = false;
|
|
28
|
+
for (let _opI4 = 0; _opI4 < _opArr4.length; _opI4++) {
|
|
29
|
+
const t = _opArr4[_opI4];
|
|
30
|
+
if (t === 'bb') {
|
|
31
|
+
_opRes4 = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const hasBb = _opRes4;
|
|
36
|
+
const _opArr5 = tags;
|
|
37
|
+
let _opRes5 = true;
|
|
38
|
+
for (let _opI5 = 0; _opI5 < _opArr5.length; _opI5++) {
|
|
39
|
+
const t = _opArr5[_opI5];
|
|
40
|
+
if (!(t.length < 5)) {
|
|
41
|
+
_opRes5 = false;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const allShort = _opRes5;
|
|
46
|
+
const _opArr6 = tags;
|
|
47
|
+
let _opRes6;
|
|
48
|
+
for (let _opI6 = 0; _opI6 < _opArr6.length; _opI6++) {
|
|
49
|
+
const t = _opArr6[_opI6];
|
|
50
|
+
if (t.length === 3) {
|
|
51
|
+
_opRes6 = t;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const found = _opRes6;
|
|
56
|
+
world.setDynamicProperty('checks', String(hasBb) + String(allShort) + String(found));
|
|
57
|
+
}
|
|
58
|
+
system.runInterval(onTick, 20);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// S5: array-method inlining inside a hot tick handler.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
const tags = ['a', 'bb', 'ccc'];
|
|
5
|
+
|
|
6
|
+
function onTick() {
|
|
7
|
+
tags.forEach((t) => {
|
|
8
|
+
world.sendMessage('tag:' + t);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const doubled = tags.map((t) => t + '!');
|
|
12
|
+
world.setDynamicProperty('doubled', doubled.join(','));
|
|
13
|
+
|
|
14
|
+
const longs = tags.filter((t) => t.length > 1);
|
|
15
|
+
world.setDynamicProperty('longs', longs.length);
|
|
16
|
+
|
|
17
|
+
const hasBb = tags.some((t) => t === 'bb');
|
|
18
|
+
const allShort = tags.every((t) => t.length < 5);
|
|
19
|
+
const found = tags.find((t) => t.length === 3);
|
|
20
|
+
world.setDynamicProperty('checks', String(hasBb) + String(allShort) + String(found));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
system.runInterval(onTick, 20);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// S5 bails: non-inline callbacks, this/arguments, optional chaining.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
const _opLift = t => world.sendMessage(t);
|
|
4
|
+
function handler(t) {
|
|
5
|
+
world.sendMessage(t);
|
|
6
|
+
}
|
|
7
|
+
function optionalTick(tags) {
|
|
8
|
+
tags?.forEach(_opLift);
|
|
9
|
+
}
|
|
10
|
+
system.runInterval(() => optionalTick(['a']), 20);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// S5 bails: non-inline callbacks, this/arguments, optional chaining.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
function handler(t) {
|
|
5
|
+
world.sendMessage(t);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function onTick(tags) {
|
|
9
|
+
tags.forEach(handler);
|
|
10
|
+
|
|
11
|
+
const obj = {
|
|
12
|
+
run: function () {
|
|
13
|
+
tags.forEach((t) => this.log(t));
|
|
14
|
+
return 1;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
return obj;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function optionalTick(tags) {
|
|
21
|
+
tags?.forEach((t) => world.sendMessage(t));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
system.runInterval(() => optionalTick(['a']), 20);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// S3: boolean simplification shapes.
|
|
2
|
+
export function f(x, y) {
|
|
3
|
+
const a = !!(x > 0);
|
|
4
|
+
return !!(y > 1);
|
|
5
|
+
}
|
|
6
|
+
export function g(x) {
|
|
7
|
+
return x > 0;
|
|
8
|
+
}
|
|
9
|
+
export function h(x) {
|
|
10
|
+
return x !== 3;
|
|
11
|
+
}
|
|
12
|
+
export function b(x) {
|
|
13
|
+
return x === true; // bails: x not statically boolean-typed
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// S3: boolean simplification shapes.
|
|
2
|
+
export function f(x, y) {
|
|
3
|
+
const a = x > 0 ? true : false;
|
|
4
|
+
if (y > 1) {
|
|
5
|
+
return true;
|
|
6
|
+
} else {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function g(x) {
|
|
11
|
+
return (x > 0) === true;
|
|
12
|
+
}
|
|
13
|
+
export function h(x) {
|
|
14
|
+
return (x !== 3) !== false;
|
|
15
|
+
}
|
|
16
|
+
export function b(x) {
|
|
17
|
+
return x === true; // bails: x not statically boolean-typed
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// B1: repeated host chains cached within barrier-free regions.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
function onTick(player) {
|
|
4
|
+
const _opC = player.location.x;
|
|
5
|
+
// Two uses in one statement: cached with one const before the statement.
|
|
6
|
+
world.sendMessage('x=' + _opC + ' again=' + _opC);
|
|
7
|
+
|
|
8
|
+
// Two uses across barrier-free statements: cached across both.
|
|
9
|
+
const _opC2 = player.location.y;
|
|
10
|
+
const first = _opC2;
|
|
11
|
+
const second = _opC2 + 1;
|
|
12
|
+
world.sendMessage('y=' + (first + second));
|
|
13
|
+
|
|
14
|
+
// Uses split by a call barrier: bails.
|
|
15
|
+
const a = player.location.z;
|
|
16
|
+
world.sendMessage('mid');
|
|
17
|
+
const b = player.location.z;
|
|
18
|
+
world.sendMessage('z=' + (a + b));
|
|
19
|
+
}
|
|
20
|
+
system.runInterval(onTick, 20);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// B1: repeated host chains cached within barrier-free regions.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
function onTick(player) {
|
|
5
|
+
// Two uses in one statement: cached with one const before the statement.
|
|
6
|
+
world.sendMessage('x=' + player.location.x + ' again=' + player.location.x);
|
|
7
|
+
|
|
8
|
+
// Two uses across barrier-free statements: cached across both.
|
|
9
|
+
const first = player.location.y;
|
|
10
|
+
const second = player.location.y + 1;
|
|
11
|
+
world.sendMessage('y=' + (first + second));
|
|
12
|
+
|
|
13
|
+
// Uses split by a call barrier: bails.
|
|
14
|
+
const a = player.location.z;
|
|
15
|
+
world.sendMessage('mid');
|
|
16
|
+
const b = player.location.z;
|
|
17
|
+
world.sendMessage('z=' + (a + b));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
system.runInterval(onTick, 20);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reads": {
|
|
3
|
+
"location:p1": [
|
|
4
|
+
{
|
|
5
|
+
"y": 64,
|
|
6
|
+
"z": 2,
|
|
7
|
+
"x": 1
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"location:p2": [
|
|
11
|
+
{
|
|
12
|
+
"y": 70,
|
|
13
|
+
"z": 4,
|
|
14
|
+
"x": 3
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"entry": "scripts/input.js",
|
|
19
|
+
"steps": [
|
|
20
|
+
{
|
|
21
|
+
"n": 5,
|
|
22
|
+
"op": "tick"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"state": {
|
|
26
|
+
"players": [
|
|
27
|
+
{
|
|
28
|
+
"id": "p1"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "p2"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// B2: closure lifting inside a hot handler.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
const _opLift = () => {
|
|
4
|
+
world.sendMessage('tick');
|
|
5
|
+
};
|
|
6
|
+
const _opLift2 = () => world.sendMessage(PREFIX + ':2');
|
|
7
|
+
const _opCap = 7;
|
|
8
|
+
const _opLift3 = () => world.sendMessage('n=' + _opCap);
|
|
9
|
+
let _opCell;
|
|
10
|
+
const _opLift4 = suffix => world.sendMessage(_opCell.tag + suffix);
|
|
11
|
+
const _opLift5 = () => setup(world.getAllPlayers()[0]);
|
|
12
|
+
const _opLift6 = () => reassigned(world.getAllPlayers()[0]);
|
|
13
|
+
const PREFIX = 'op';
|
|
14
|
+
function onTick() {
|
|
15
|
+
// Captures nothing: lifted.
|
|
16
|
+
const announce = _opLift;
|
|
17
|
+
announce();
|
|
18
|
+
|
|
19
|
+
// Captures a module-level const: lifted.
|
|
20
|
+
const announce2 = _opLift2;
|
|
21
|
+
announce2();
|
|
22
|
+
|
|
23
|
+
// Captures a per-invocation const primitive: lifted with the value inlined
|
|
24
|
+
// as a module const (_opCap).
|
|
25
|
+
const local = 7;
|
|
26
|
+
const announce3 = _opLift3;
|
|
27
|
+
announce3();
|
|
28
|
+
|
|
29
|
+
// Captures a never-reassigned live local, called synchronously: lifted via
|
|
30
|
+
// a module-level cell assigned at the creation point.
|
|
31
|
+
const ctxObj = {
|
|
32
|
+
tag: 'x'
|
|
33
|
+
};
|
|
34
|
+
_opCell = ctxObj;
|
|
35
|
+
const announce4 = _opLift4;
|
|
36
|
+
announce4('!4');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Escaping arrow (registration) capturing a live local: bails — each
|
|
40
|
+
// registration needs its own capture and the invocation count is unprovable.
|
|
41
|
+
function setup(player) {
|
|
42
|
+
const cb = () => world.sendMessage('hi ' + player.name);
|
|
43
|
+
system.runInterval(cb, 20);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Reassigned capture: bails.
|
|
47
|
+
function reassigned(player) {
|
|
48
|
+
let n = player.location.y;
|
|
49
|
+
const cb = () => world.sendMessage('n=' + n);
|
|
50
|
+
cb();
|
|
51
|
+
n = 9;
|
|
52
|
+
cb();
|
|
53
|
+
}
|
|
54
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
55
|
+
system.runInterval(onTick, 20);
|
|
56
|
+
system.runInterval(_opLift5, 20);
|
|
57
|
+
system.runInterval(_opLift6, 20);
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"transforms": {"closure-lift": 6}, "bails": [{"transform": "B2", "reasonContains": "escapes as a value"}, {"transform": "B2", "reasonContains": "reassigned"}]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// B2: closure lifting inside a hot handler.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
const PREFIX = 'op';
|
|
5
|
+
|
|
6
|
+
function onTick() {
|
|
7
|
+
// Captures nothing: lifted.
|
|
8
|
+
const announce = () => {
|
|
9
|
+
world.sendMessage('tick');
|
|
10
|
+
};
|
|
11
|
+
announce();
|
|
12
|
+
|
|
13
|
+
// Captures a module-level const: lifted.
|
|
14
|
+
const announce2 = () => world.sendMessage(PREFIX + ':2');
|
|
15
|
+
announce2();
|
|
16
|
+
|
|
17
|
+
// Captures a per-invocation const primitive: lifted with the value inlined
|
|
18
|
+
// as a module const (_opCap).
|
|
19
|
+
const local = 7;
|
|
20
|
+
const announce3 = () => world.sendMessage('n=' + local);
|
|
21
|
+
announce3();
|
|
22
|
+
|
|
23
|
+
// Captures a never-reassigned live local, called synchronously: lifted via
|
|
24
|
+
// a module-level cell assigned at the creation point.
|
|
25
|
+
const ctxObj = { tag: 'x' };
|
|
26
|
+
const announce4 = (suffix) => world.sendMessage(ctxObj.tag + suffix);
|
|
27
|
+
announce4('!4');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Escaping arrow (registration) capturing a live local: bails — each
|
|
31
|
+
// registration needs its own capture and the invocation count is unprovable.
|
|
32
|
+
function setup(player) {
|
|
33
|
+
const cb = () => world.sendMessage('hi ' + player.name);
|
|
34
|
+
system.runInterval(cb, 20);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Reassigned capture: bails.
|
|
38
|
+
function reassigned(player) {
|
|
39
|
+
let n = player.location.y;
|
|
40
|
+
const cb = () => world.sendMessage('n=' + n);
|
|
41
|
+
cb();
|
|
42
|
+
n = 9;
|
|
43
|
+
cb();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
47
|
+
system.runInterval(onTick, 20);
|
|
48
|
+
system.runInterval(() => setup(world.getAllPlayers()[0]), 20);
|
|
49
|
+
system.runInterval(() => reassigned(world.getAllPlayers()[0]), 20);
|
|
50
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entry": "scripts/input.js",
|
|
3
|
+
"reads": { "location:p1": [{ "x": 1, "y": 64, "z": 2 }], "name:p1": ["Alice"] },
|
|
4
|
+
"state": { "players": [{ "id": "p1" }, { "id": "p2" }] },
|
|
5
|
+
"steps": [
|
|
6
|
+
{ "op": "fire", "event": "world.afterEvents.worldLoad", "payload": {} },
|
|
7
|
+
{ "op": "tick", "n": 45 }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// S1: constant folding shapes.
|
|
2
|
+
export const a = 3;
|
|
3
|
+
export const b = "xyz";
|
|
4
|
+
export const c = "string";
|
|
5
|
+
export const d = false;
|
|
6
|
+
export const e = false;
|
|
7
|
+
export const f = 10;
|
|
8
|
+
export const g = "a1b";
|
|
9
|
+
export const h = 'fallback';
|
|
10
|
+
export const i = 5;
|
|
11
|
+
export const j = i + 1; // never folded: crosses an identifier
|
|
12
|
+
export const k = Math.floor(1.5); // never folded: function call
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"transforms": {"const-fold": 12}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// S1: constant folding shapes.
|
|
2
|
+
export const a = 1 + 2;
|
|
3
|
+
export const b = 'x' + 'y' + 'z';
|
|
4
|
+
export const c = typeof 'hello';
|
|
5
|
+
export const d = !true;
|
|
6
|
+
export const e = 10 > 5 && 3 < 2;
|
|
7
|
+
export const f = 2 * 3 + 4;
|
|
8
|
+
export const g = `a${1}b`;
|
|
9
|
+
export const h = null ?? 'fallback';
|
|
10
|
+
export const i = 5;
|
|
11
|
+
export const j = i + 1; // never folded: crosses an identifier
|
|
12
|
+
export const k = Math.floor(1.5); // never folded: function call
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"level": "risky"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// R1: cross-tick caches (risky, opt-in).
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
const __opR1Cache = new WeakMap();
|
|
4
|
+
function __opR1Component(obj, key) {
|
|
5
|
+
let m = __opR1Cache.get(obj);
|
|
6
|
+
if (m === undefined) {
|
|
7
|
+
m = new Map();
|
|
8
|
+
__opR1Cache.set(obj, m);
|
|
9
|
+
}
|
|
10
|
+
let v = m.get(key);
|
|
11
|
+
if (v === undefined) {
|
|
12
|
+
v = obj.getComponent(key);
|
|
13
|
+
m.set(key, v);
|
|
14
|
+
}
|
|
15
|
+
return v;
|
|
16
|
+
}
|
|
17
|
+
function __opR1Field(obj, key) {
|
|
18
|
+
let m = __opR1Cache.get(obj);
|
|
19
|
+
if (m === undefined) {
|
|
20
|
+
m = new Map();
|
|
21
|
+
__opR1Cache.set(obj, m);
|
|
22
|
+
}
|
|
23
|
+
let v = m.get(key);
|
|
24
|
+
if (v === undefined) {
|
|
25
|
+
v = obj[key];
|
|
26
|
+
m.set(key, v);
|
|
27
|
+
}
|
|
28
|
+
return v;
|
|
29
|
+
}
|
|
30
|
+
function onTick(player) {
|
|
31
|
+
const health = __opR1Component(player, "minecraft:health");
|
|
32
|
+
const id = __opR1Field(player, "id");
|
|
33
|
+
const typeId = __opR1Field(player, "typeId");
|
|
34
|
+
if (health) {
|
|
35
|
+
world.sendMessage(id + ':' + typeId + ':' + health.currentValue);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
system.runInterval(() => onTick(world.getAllPlayers()[0]), 20);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// R1: cross-tick caches (risky, opt-in).
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
function onTick(player) {
|
|
5
|
+
const health = player.getComponent('minecraft:health');
|
|
6
|
+
const id = player.id;
|
|
7
|
+
const typeId = player.typeId;
|
|
8
|
+
if (health) {
|
|
9
|
+
world.sendMessage(id + ':' + typeId + ':' + health.currentValue);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
system.runInterval(() => onTick(world.getAllPlayers()[0]), 20);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reads": {
|
|
3
|
+
"component:p1:minecraft:health": [
|
|
4
|
+
{
|
|
5
|
+
"currentValue": 20
|
|
6
|
+
}
|
|
7
|
+
],
|
|
8
|
+
"location:p1": [
|
|
9
|
+
{
|
|
10
|
+
"y": 64,
|
|
11
|
+
"z": 2,
|
|
12
|
+
"x": 1
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"entry": "scripts/input.js",
|
|
17
|
+
"steps": [
|
|
18
|
+
{
|
|
19
|
+
"n": 5,
|
|
20
|
+
"op": "tick"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"state": {
|
|
24
|
+
"players": [
|
|
25
|
+
{
|
|
26
|
+
"id": "p1"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "p2"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|