swf-toolkit 0.0.0-stage → 0.1.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 +125 -2
- package/README.tr.md +126 -0
- package/dist/abc/abc-file.d.ts +168 -0
- package/dist/abc/abc-file.js +656 -0
- package/dist/abc/analysis.d.ts +36 -0
- package/dist/abc/analysis.js +213 -0
- package/dist/abc/code.d.ts +59 -0
- package/dist/abc/code.js +379 -0
- package/dist/abc/constants.d.ts +80 -0
- package/dist/abc/constants.js +93 -0
- package/dist/abc/interner.d.ts +46 -0
- package/dist/abc/interner.js +168 -0
- package/dist/abc/model.d.ts +40 -0
- package/dist/abc/model.js +146 -0
- package/dist/abc/opcodes.d.ts +39 -0
- package/dist/abc/opcodes.js +196 -0
- package/dist/abc/pcode/assembler.d.ts +42 -0
- package/dist/abc/pcode/assembler.js +635 -0
- package/dist/abc/pcode/disassembler.d.ts +17 -0
- package/dist/abc/pcode/disassembler.js +147 -0
- package/dist/abc/pcode/format.d.ts +23 -0
- package/dist/abc/pcode/format.js +131 -0
- package/dist/abc/pcode/lexer.d.ts +34 -0
- package/dist/abc/pcode/lexer.js +183 -0
- package/dist/assets/assets.d.ts +26 -0
- package/dist/assets/assets.js +151 -0
- package/dist/assets/images.d.ts +31 -0
- package/dist/assets/images.js +238 -0
- package/dist/assets/png.d.ts +12 -0
- package/dist/assets/png.js +196 -0
- package/dist/assets/sounds.d.ts +37 -0
- package/dist/assets/sounds.js +236 -0
- package/dist/avm1/avm1.d.ts +55 -0
- package/dist/avm1/avm1.js +642 -0
- package/dist/avm1/project.d.ts +16 -0
- package/dist/avm1/project.js +81 -0
- package/dist/cjs/abc/abc-file.d.ts +168 -0
- package/dist/cjs/abc/abc-file.js +663 -0
- package/dist/cjs/abc/analysis.d.ts +36 -0
- package/dist/cjs/abc/analysis.js +221 -0
- package/dist/cjs/abc/code.d.ts +59 -0
- package/dist/cjs/abc/code.js +387 -0
- package/dist/cjs/abc/constants.d.ts +80 -0
- package/dist/cjs/abc/constants.js +97 -0
- package/dist/cjs/abc/interner.d.ts +46 -0
- package/dist/cjs/abc/interner.js +172 -0
- package/dist/cjs/abc/model.d.ts +40 -0
- package/dist/cjs/abc/model.js +155 -0
- package/dist/cjs/abc/opcodes.d.ts +39 -0
- package/dist/cjs/abc/opcodes.js +201 -0
- package/dist/cjs/abc/pcode/assembler.d.ts +42 -0
- package/dist/cjs/abc/pcode/assembler.js +640 -0
- package/dist/cjs/abc/pcode/disassembler.d.ts +17 -0
- package/dist/cjs/abc/pcode/disassembler.js +152 -0
- package/dist/cjs/abc/pcode/format.d.ts +23 -0
- package/dist/cjs/abc/pcode/format.js +141 -0
- package/dist/cjs/abc/pcode/lexer.d.ts +34 -0
- package/dist/cjs/abc/pcode/lexer.js +188 -0
- package/dist/cjs/assets/assets.d.ts +26 -0
- package/dist/cjs/assets/assets.js +159 -0
- package/dist/cjs/assets/images.d.ts +31 -0
- package/dist/cjs/assets/images.js +246 -0
- package/dist/cjs/assets/png.d.ts +12 -0
- package/dist/cjs/assets/png.js +202 -0
- package/dist/cjs/assets/sounds.d.ts +37 -0
- package/dist/cjs/assets/sounds.js +246 -0
- package/dist/cjs/avm1/avm1.d.ts +55 -0
- package/dist/cjs/avm1/avm1.js +649 -0
- package/dist/cjs/avm1/project.d.ts +16 -0
- package/dist/cjs/avm1/project.js +88 -0
- package/dist/cjs/compiler/ast.d.ts +285 -0
- package/dist/cjs/compiler/ast.js +3 -0
- package/dist/cjs/compiler/class-compiler.d.ts +34 -0
- package/dist/cjs/compiler/class-compiler.js +440 -0
- package/dist/cjs/compiler/codegen.d.ts +144 -0
- package/dist/cjs/compiler/codegen.js +1589 -0
- package/dist/cjs/compiler/lexer.d.ts +27 -0
- package/dist/cjs/compiler/lexer.js +254 -0
- package/dist/cjs/compiler/parser.d.ts +58 -0
- package/dist/cjs/compiler/parser.js +989 -0
- package/dist/cjs/compiler/program.d.ts +29 -0
- package/dist/cjs/compiler/program.js +36 -0
- package/dist/cjs/compiler/scope.d.ts +22 -0
- package/dist/cjs/compiler/scope.js +242 -0
- package/dist/cjs/compression/lzma.d.ts +37 -0
- package/dist/cjs/compression/lzma.js +575 -0
- package/dist/cjs/compression/swf-container.d.ts +19 -0
- package/dist/cjs/compression/swf-container.js +92 -0
- package/dist/cjs/decompiler/ast.d.ts +228 -0
- package/dist/cjs/decompiler/ast.js +455 -0
- package/dist/cjs/decompiler/cfg.d.ts +40 -0
- package/dist/cjs/decompiler/cfg.js +236 -0
- package/dist/cjs/decompiler/class-printer.d.ts +9 -0
- package/dist/cjs/decompiler/class-printer.js +173 -0
- package/dist/cjs/decompiler/method.d.ts +41 -0
- package/dist/cjs/decompiler/method.js +436 -0
- package/dist/cjs/decompiler/simulate.d.ts +55 -0
- package/dist/cjs/decompiler/simulate.js +591 -0
- package/dist/cjs/decompiler/structure.d.ts +48 -0
- package/dist/cjs/decompiler/structure.js +764 -0
- package/dist/cjs/errors.d.ts +11 -0
- package/dist/cjs/errors.js +25 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/index.js +82 -0
- package/dist/cjs/io/reader.d.ts +48 -0
- package/dist/cjs/io/reader.js +180 -0
- package/dist/cjs/io/writer.d.ts +37 -0
- package/dist/cjs/io/writer.js +175 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/project/pcode-project.d.ts +32 -0
- package/dist/cjs/project/pcode-project.js +210 -0
- package/dist/cjs/project/source-export.d.ts +12 -0
- package/dist/cjs/project/source-export.js +51 -0
- package/dist/cjs/project/source-import.d.ts +13 -0
- package/dist/cjs/project/source-import.js +73 -0
- package/dist/cjs/swf/swf.d.ts +63 -0
- package/dist/cjs/swf/swf.js +147 -0
- package/dist/cjs/swf/tag-codes.d.ts +77 -0
- package/dist/cjs/swf/tag-codes.js +109 -0
- package/dist/cjs/swf/tags.d.ts +118 -0
- package/dist/cjs/swf/tags.js +330 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +296 -0
- package/dist/compiler/ast.d.ts +285 -0
- package/dist/compiler/ast.js +2 -0
- package/dist/compiler/class-compiler.d.ts +34 -0
- package/dist/compiler/class-compiler.js +437 -0
- package/dist/compiler/codegen.d.ts +144 -0
- package/dist/compiler/codegen.js +1585 -0
- package/dist/compiler/lexer.d.ts +27 -0
- package/dist/compiler/lexer.js +249 -0
- package/dist/compiler/parser.d.ts +58 -0
- package/dist/compiler/parser.js +984 -0
- package/dist/compiler/program.d.ts +29 -0
- package/dist/compiler/program.js +32 -0
- package/dist/compiler/scope.d.ts +22 -0
- package/dist/compiler/scope.js +238 -0
- package/dist/compression/lzma.d.ts +37 -0
- package/dist/compression/lzma.js +569 -0
- package/dist/compression/swf-container.d.ts +19 -0
- package/dist/compression/swf-container.js +87 -0
- package/dist/decompiler/ast.d.ts +228 -0
- package/dist/decompiler/ast.js +441 -0
- package/dist/decompiler/cfg.d.ts +40 -0
- package/dist/decompiler/cfg.js +231 -0
- package/dist/decompiler/class-printer.d.ts +9 -0
- package/dist/decompiler/class-printer.js +170 -0
- package/dist/decompiler/method.d.ts +41 -0
- package/dist/decompiler/method.js +427 -0
- package/dist/decompiler/simulate.d.ts +55 -0
- package/dist/decompiler/simulate.js +586 -0
- package/dist/decompiler/structure.d.ts +48 -0
- package/dist/decompiler/structure.js +760 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +49 -0
- package/dist/io/reader.d.ts +48 -0
- package/dist/io/reader.js +176 -0
- package/dist/io/writer.d.ts +37 -0
- package/dist/io/writer.js +169 -0
- package/dist/project/pcode-project.d.ts +32 -0
- package/dist/project/pcode-project.js +203 -0
- package/dist/project/source-export.d.ts +12 -0
- package/dist/project/source-export.js +47 -0
- package/dist/project/source-import.d.ts +13 -0
- package/dist/project/source-import.js +70 -0
- package/dist/swf/swf.d.ts +63 -0
- package/dist/swf/swf.js +143 -0
- package/dist/swf/tag-codes.d.ts +77 -0
- package/dist/swf/tag-codes.js +105 -0
- package/dist/swf/tags.d.ts +118 -0
- package/dist/swf/tags.js +316 -0
- package/package.json +71 -4
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FunctionCompiler = exports.TOPLEVEL_NAMES = void 0;
|
|
4
|
+
const abc_file_js_1 = require("../abc/abc-file.js");
|
|
5
|
+
const analysis_js_1 = require("../abc/analysis.js");
|
|
6
|
+
const code_js_1 = require("../abc/code.js");
|
|
7
|
+
const constants_js_1 = require("../abc/constants.js");
|
|
8
|
+
const opcodes_js_1 = require("../abc/opcodes.js");
|
|
9
|
+
const lexer_js_1 = require("./lexer.js");
|
|
10
|
+
/** Names of top-level (public, unnamed package) classes and functions of the Flash runtime. */
|
|
11
|
+
exports.TOPLEVEL_NAMES = new Set([
|
|
12
|
+
'Object', 'Class', 'Function', 'Namespace', 'QName', 'Boolean', 'Number', 'int', 'uint', 'String', 'Array', 'Vector',
|
|
13
|
+
'Error', 'DefinitionError', 'EvalError', 'RangeError', 'ReferenceError', 'SecurityError', 'SyntaxError', 'TypeError',
|
|
14
|
+
'URIError', 'VerifyError', 'ArgumentError', 'UninitializedError', 'Date', 'Math', 'RegExp', 'XML', 'XMLList', 'JSON',
|
|
15
|
+
'void', 'trace', 'isNaN', 'isFinite', 'parseInt', 'parseFloat', 'escape', 'unescape', 'encodeURI', 'decodeURI',
|
|
16
|
+
'encodeURIComponent', 'decodeURIComponent', 'isXMLName', 'NaN', 'Infinity', 'undefined',
|
|
17
|
+
]);
|
|
18
|
+
const isIntLiteral = (v) => Number.isInteger(v) && v >= -0x80000000 && v <= 0x7fffffff && !Object.is(v, -0);
|
|
19
|
+
/** Compiles one function (method, accessor, constructor or closure). */
|
|
20
|
+
class FunctionCompiler {
|
|
21
|
+
scope;
|
|
22
|
+
fn;
|
|
23
|
+
options;
|
|
24
|
+
items = [];
|
|
25
|
+
nextLabelId = 0;
|
|
26
|
+
exceptions = [];
|
|
27
|
+
locals = new Map();
|
|
28
|
+
nextReg;
|
|
29
|
+
freeTemps = [];
|
|
30
|
+
loops = [];
|
|
31
|
+
activationReg = -1;
|
|
32
|
+
activationTraits = [];
|
|
33
|
+
needsActivation;
|
|
34
|
+
/** Pending label for the next loop/switch statement. */
|
|
35
|
+
pendingLabel;
|
|
36
|
+
scopeDepth = 0;
|
|
37
|
+
method;
|
|
38
|
+
constructor(scope, fn, options) {
|
|
39
|
+
this.scope = scope;
|
|
40
|
+
this.fn = fn;
|
|
41
|
+
this.options = options;
|
|
42
|
+
const { pool } = scope;
|
|
43
|
+
this.method = {
|
|
44
|
+
paramTypes: fn.params.map((p) => (p.type ? scope.resolveType(p.type) : 0)),
|
|
45
|
+
returnType: fn.returnType ? scope.resolveType(fn.returnType) : 0,
|
|
46
|
+
name: pool.string(options.name ?? fn.name ?? ''),
|
|
47
|
+
flags: 0,
|
|
48
|
+
optional: [],
|
|
49
|
+
paramNames: fn.params.map((p) => pool.string(p.name)),
|
|
50
|
+
};
|
|
51
|
+
const firstOpt = fn.params.findIndex((p) => p.init);
|
|
52
|
+
if (firstOpt >= 0) {
|
|
53
|
+
for (const p of fn.params.slice(firstOpt)) {
|
|
54
|
+
if (!p.init)
|
|
55
|
+
throw this.err('Required parameter after optional parameter', fn.pos);
|
|
56
|
+
this.method.optional.push(this.constantValue(p.init));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (fn.rest)
|
|
60
|
+
this.method.flags |= constants_js_1.MethodFlags.NEED_REST;
|
|
61
|
+
this.needsActivation = containsFunction(fn.body ?? []);
|
|
62
|
+
if (this.needsActivation)
|
|
63
|
+
this.method.flags |= constants_js_1.MethodFlags.NEED_ACTIVATION;
|
|
64
|
+
if (!fn.rest && usesArguments(fn.body ?? []))
|
|
65
|
+
this.method.flags |= constants_js_1.MethodFlags.NEED_ARGUMENTS;
|
|
66
|
+
// Registers: 0 = this, 1..n = params, then rest/arguments.
|
|
67
|
+
fn.params.forEach((p, i) => this.declare(p.name, i + 1, this.method.paramTypes[i]));
|
|
68
|
+
this.nextReg = fn.params.length + 1;
|
|
69
|
+
if (fn.rest)
|
|
70
|
+
this.declare(fn.rest.name, this.nextReg++, 0);
|
|
71
|
+
else if (this.method.flags & constants_js_1.MethodFlags.NEED_ARGUMENTS)
|
|
72
|
+
this.declare('arguments', this.nextReg++, 0);
|
|
73
|
+
}
|
|
74
|
+
err(msg, pos) {
|
|
75
|
+
return new lexer_js_1.CompileError(msg, pos?.line ?? 0, pos?.col ?? 0, this.scope.source);
|
|
76
|
+
}
|
|
77
|
+
// -------------------------------------------------------------------------
|
|
78
|
+
// Emission helpers
|
|
79
|
+
// -------------------------------------------------------------------------
|
|
80
|
+
newLabel() {
|
|
81
|
+
return this.nextLabelId++;
|
|
82
|
+
}
|
|
83
|
+
place(label) {
|
|
84
|
+
this.items.push({ type: 'label', id: label });
|
|
85
|
+
// Branch targets in AVM2 code must not be skipped by the verifier's
|
|
86
|
+
// backward-branch rule; a `label` op is only needed for backward targets.
|
|
87
|
+
}
|
|
88
|
+
emit(op, ...args) {
|
|
89
|
+
this.items.push({ type: 'op', op, args, targets: [], offset: -1 });
|
|
90
|
+
if (op === opcodes_js_1.Op.pushscope || op === opcodes_js_1.Op.pushwith)
|
|
91
|
+
this.scopeDepth++;
|
|
92
|
+
if (op === opcodes_js_1.Op.popscope)
|
|
93
|
+
this.scopeDepth--;
|
|
94
|
+
}
|
|
95
|
+
jump(op, label) {
|
|
96
|
+
this.items.push({ type: 'op', op, args: [], targets: [label], offset: -1 });
|
|
97
|
+
}
|
|
98
|
+
temp() {
|
|
99
|
+
return this.freeTemps.pop() ?? this.nextReg++;
|
|
100
|
+
}
|
|
101
|
+
release(reg) {
|
|
102
|
+
this.emit(opcodes_js_1.Op.kill, reg);
|
|
103
|
+
this.freeTemps.push(reg);
|
|
104
|
+
}
|
|
105
|
+
getReg(r) {
|
|
106
|
+
if (r <= 3)
|
|
107
|
+
this.emit(opcodes_js_1.Op.getlocal0 + r);
|
|
108
|
+
else
|
|
109
|
+
this.emit(opcodes_js_1.Op.getlocal, r);
|
|
110
|
+
}
|
|
111
|
+
setReg(r) {
|
|
112
|
+
if (r <= 3)
|
|
113
|
+
this.emit(opcodes_js_1.Op.setlocal0 + r);
|
|
114
|
+
else
|
|
115
|
+
this.emit(opcodes_js_1.Op.setlocal, r);
|
|
116
|
+
}
|
|
117
|
+
declare(name, reg, type) {
|
|
118
|
+
const v = { name, reg, slot: 0, type };
|
|
119
|
+
this.locals.set(name, v);
|
|
120
|
+
return v;
|
|
121
|
+
}
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
// Constant pool helpers
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
get pool() {
|
|
126
|
+
return this.scope.pool;
|
|
127
|
+
}
|
|
128
|
+
/** Runtime-resolved name: Multiname(name, open namespaces). */
|
|
129
|
+
mn(name) {
|
|
130
|
+
return this.pool.multiname({ kind: constants_js_1.MultinameKind.Multiname, name: this.pool.string(name), nsSet: this.scope.openNsSet });
|
|
131
|
+
}
|
|
132
|
+
/** Attribute multiname (`@name`). */
|
|
133
|
+
mnAttr(name) {
|
|
134
|
+
return this.pool.multiname({ kind: constants_js_1.MultinameKind.MultinameA, name: this.pool.string(name === '*' ? null : name), nsSet: this.scope.openNsSet });
|
|
135
|
+
}
|
|
136
|
+
/** Late-bound multiname for `obj[expr]`. */
|
|
137
|
+
mnLate(attr = false) {
|
|
138
|
+
return this.pool.multiname({ kind: attr ? constants_js_1.MultinameKind.MultinameLA : constants_js_1.MultinameKind.MultinameL, nsSet: this.scope.openNsSet });
|
|
139
|
+
}
|
|
140
|
+
/** Name with an explicit namespace (`ns::name`). */
|
|
141
|
+
mnQualified(ns, name, pos) {
|
|
142
|
+
if (ns === '*')
|
|
143
|
+
return this.pool.multiname({ kind: constants_js_1.MultinameKind.QName, ns: 0, name: this.pool.string(name) });
|
|
144
|
+
const nsIndex = this.scope.namespaceByName(ns);
|
|
145
|
+
if (nsIndex === undefined)
|
|
146
|
+
throw this.err(`Unknown namespace '${ns}'`, pos);
|
|
147
|
+
return this.pool.multiname({ kind: constants_js_1.MultinameKind.QName, ns: nsIndex, name: this.pool.string(name) });
|
|
148
|
+
}
|
|
149
|
+
publicQName(name) {
|
|
150
|
+
return this.pool.qname(this.pool.namespace(constants_js_1.NamespaceKind.PackageNamespace, ''), name);
|
|
151
|
+
}
|
|
152
|
+
/** Default value for optional parameters / slots. */
|
|
153
|
+
constantValue(e) {
|
|
154
|
+
const abc = this.scope.abc;
|
|
155
|
+
switch (e.k) {
|
|
156
|
+
case 'num':
|
|
157
|
+
if (isIntLiteral(e.v))
|
|
158
|
+
return { kind: constants_js_1.ConstantKind.Int, value: abc.int(e.v) };
|
|
159
|
+
return { kind: constants_js_1.ConstantKind.Double, value: abc.double(e.v) };
|
|
160
|
+
case 'str':
|
|
161
|
+
return { kind: constants_js_1.ConstantKind.Utf8, value: this.pool.string(e.v) };
|
|
162
|
+
case 'bool':
|
|
163
|
+
return e.v ? { kind: constants_js_1.ConstantKind.True, value: constants_js_1.ConstantKind.True } : { kind: constants_js_1.ConstantKind.False, value: constants_js_1.ConstantKind.False };
|
|
164
|
+
case 'null':
|
|
165
|
+
return { kind: constants_js_1.ConstantKind.Null, value: constants_js_1.ConstantKind.Null };
|
|
166
|
+
case 'undefined':
|
|
167
|
+
return { kind: constants_js_1.ConstantKind.Undefined, value: 0 };
|
|
168
|
+
case 'id':
|
|
169
|
+
if (e.name === 'NaN')
|
|
170
|
+
return { kind: constants_js_1.ConstantKind.Double, value: abc.double(NaN) };
|
|
171
|
+
if (e.name === 'Infinity')
|
|
172
|
+
return { kind: constants_js_1.ConstantKind.Double, value: abc.double(Infinity) };
|
|
173
|
+
break;
|
|
174
|
+
case 'unary':
|
|
175
|
+
if (e.op === '-' && e.e.k === 'id' && e.e.name === 'Infinity')
|
|
176
|
+
return { kind: constants_js_1.ConstantKind.Double, value: abc.double(-Infinity) };
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
throw this.err('Default value must be a compile-time constant', e.pos);
|
|
180
|
+
}
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// Function compilation
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
/** Compiles the function and installs it in the ABC. Returns the method index. */
|
|
185
|
+
compile() {
|
|
186
|
+
const { abc } = this.scope;
|
|
187
|
+
const fn = this.fn;
|
|
188
|
+
// Hoist `var` declarations and nested function declarations.
|
|
189
|
+
const hoisted = [];
|
|
190
|
+
collectVars(fn.body ?? [], hoisted);
|
|
191
|
+
for (const d of hoisted) {
|
|
192
|
+
if (!this.locals.has(d.name))
|
|
193
|
+
this.declare(d.name, this.nextReg++, d.type ? this.scope.resolveType(d.type) : 0);
|
|
194
|
+
}
|
|
195
|
+
// Move locals into the activation object when closures may capture them.
|
|
196
|
+
if (this.needsActivation) {
|
|
197
|
+
let slot = 1;
|
|
198
|
+
for (const v of this.locals.values()) {
|
|
199
|
+
v.slot = slot++;
|
|
200
|
+
this.activationTraits.push((0, abc_file_js_1.newTrait)({ kind: constants_js_1.TraitKind.Slot, name: this.publicQName(v.name), id: v.slot, typeName: v.type }));
|
|
201
|
+
}
|
|
202
|
+
this.activationReg = this.nextReg++;
|
|
203
|
+
}
|
|
204
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
205
|
+
this.emit(opcodes_js_1.Op.pushscope);
|
|
206
|
+
if (this.needsActivation) {
|
|
207
|
+
this.emit(opcodes_js_1.Op.newactivation);
|
|
208
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
209
|
+
this.setReg(this.activationReg);
|
|
210
|
+
this.emit(opcodes_js_1.Op.pushscope);
|
|
211
|
+
// Copy parameters (still in registers) into their slots.
|
|
212
|
+
for (const v of this.locals.values()) {
|
|
213
|
+
if (v.reg > 0 && v.reg <= fn.params.length + (fn.rest || this.method.flags & constants_js_1.MethodFlags.NEED_ARGUMENTS ? 1 : 0)) {
|
|
214
|
+
this.getReg(this.activationReg);
|
|
215
|
+
this.getReg(v.reg);
|
|
216
|
+
this.emit(opcodes_js_1.Op.setslot, v.slot);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (const v of this.locals.values())
|
|
220
|
+
v.reg = -1;
|
|
221
|
+
}
|
|
222
|
+
// Typed locals start with their type's default value (ASC does the same for int/uint/Number/Boolean),
|
|
223
|
+
// unless the declaration initialises them before any possible use.
|
|
224
|
+
const skipDefault = new Set();
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
for (const st of fn.body ?? []) {
|
|
227
|
+
if (st.k === 'var') {
|
|
228
|
+
for (const d of st.decls) {
|
|
229
|
+
if (d.init)
|
|
230
|
+
walkAllExprs([{ k: 'expr', e: d.init, pos: d.pos }], (x) => void (x.k === 'id' && seen.add(x.name)));
|
|
231
|
+
if (d.init && !seen.has(d.name))
|
|
232
|
+
skipDefault.add(d.name);
|
|
233
|
+
seen.add(d.name);
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
walkAllExprs([st], (x) => void (x.k === 'id' && seen.add(x.name)));
|
|
238
|
+
if (st.k !== 'expr')
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
for (const d of hoisted) {
|
|
242
|
+
const v = this.locals.get(d.name);
|
|
243
|
+
const def = this.defaultFor(v.type);
|
|
244
|
+
if (def !== undefined && !this.needsActivation && !skipDefault.has(d.name)) {
|
|
245
|
+
def();
|
|
246
|
+
this.setReg(v.reg);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Nested function declarations are initialised up front.
|
|
250
|
+
for (const s of fn.body ?? []) {
|
|
251
|
+
if (s.k === 'function') {
|
|
252
|
+
this.compileClosure(s.fn);
|
|
253
|
+
this.storeLocal(this.locals.get(s.fn.name));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
this.options.prologue?.(this);
|
|
257
|
+
const body = fn.body ?? [];
|
|
258
|
+
if (this.options.isConstructor && !hasSuperCall(body)) {
|
|
259
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
260
|
+
this.emit(opcodes_js_1.Op.constructsuper, 0);
|
|
261
|
+
}
|
|
262
|
+
for (const s of body)
|
|
263
|
+
this.stmt(s);
|
|
264
|
+
this.emit(opcodes_js_1.Op.returnvoid);
|
|
265
|
+
// Encode and install.
|
|
266
|
+
const issues = (0, analysis_js_1.verifyCode)(abc, this.items, this.exceptions);
|
|
267
|
+
if (issues.length) {
|
|
268
|
+
throw this.err(`Internal compiler error in ${this.options.name ?? fn.name ?? 'function'}: ${issues.map((i) => i.message).join('; ')}`, fn.pos);
|
|
269
|
+
}
|
|
270
|
+
const encoded = (0, code_js_1.encodeCode)(this.items, this.exceptions);
|
|
271
|
+
const limits = (0, analysis_js_1.computeLimits)(abc, this.items, this.exceptions);
|
|
272
|
+
const index = this.options.methodIndex ?? abc.methods.length;
|
|
273
|
+
if (this.options.methodIndex === undefined)
|
|
274
|
+
abc.methods.push(this.method);
|
|
275
|
+
else
|
|
276
|
+
abc.methods[index] = this.method;
|
|
277
|
+
const mb = {
|
|
278
|
+
method: index,
|
|
279
|
+
maxStack: Math.max(1, limits.maxStack),
|
|
280
|
+
localCount: Math.max(limits.registers, (0, analysis_js_1.minLocalCount)(this.method), this.nextReg),
|
|
281
|
+
initScopeDepth: this.options.initScopeDepth,
|
|
282
|
+
maxScopeDepth: this.options.initScopeDepth + Math.max(1, limits.maxScope),
|
|
283
|
+
code: encoded.code,
|
|
284
|
+
exceptions: encoded.exceptions,
|
|
285
|
+
traits: this.activationTraits,
|
|
286
|
+
};
|
|
287
|
+
const existing = abc.bodies.findIndex((b) => b.method === index);
|
|
288
|
+
if (existing >= 0)
|
|
289
|
+
abc.bodies[existing] = mb;
|
|
290
|
+
else
|
|
291
|
+
abc.bodies.push(mb);
|
|
292
|
+
return index;
|
|
293
|
+
}
|
|
294
|
+
/** Pushes the default value of a typed local, or returns undefined if none is needed. */
|
|
295
|
+
defaultFor(type) {
|
|
296
|
+
if (type === 0)
|
|
297
|
+
return undefined;
|
|
298
|
+
const name = this.scope.abc.qualifiedName(type);
|
|
299
|
+
switch (name) {
|
|
300
|
+
case 'int':
|
|
301
|
+
case 'uint':
|
|
302
|
+
return () => this.emit(opcodes_js_1.Op.pushbyte, 0);
|
|
303
|
+
case 'Number':
|
|
304
|
+
return () => this.emit(opcodes_js_1.Op.pushnan);
|
|
305
|
+
case 'Boolean':
|
|
306
|
+
return () => this.emit(opcodes_js_1.Op.pushfalse);
|
|
307
|
+
default:
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
compileClosure(def) {
|
|
312
|
+
const inner = new FunctionCompiler(this.scope, def, {
|
|
313
|
+
initScopeDepth: this.options.initScopeDepth + this.scopeDepth,
|
|
314
|
+
name: def.name ?? '',
|
|
315
|
+
});
|
|
316
|
+
const idx = inner.compile();
|
|
317
|
+
this.emit(opcodes_js_1.Op.newfunction, idx);
|
|
318
|
+
}
|
|
319
|
+
// -------------------------------------------------------------------------
|
|
320
|
+
// Variables
|
|
321
|
+
// -------------------------------------------------------------------------
|
|
322
|
+
lookupLocal(name) {
|
|
323
|
+
return this.locals.get(name);
|
|
324
|
+
}
|
|
325
|
+
loadLocal(v) {
|
|
326
|
+
if (v.reg >= 0)
|
|
327
|
+
this.getReg(v.reg);
|
|
328
|
+
else {
|
|
329
|
+
this.getReg(this.activationReg);
|
|
330
|
+
this.emit(opcodes_js_1.Op.getslot, v.slot);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/** Stores the value on top of the stack into a local (consumes it). */
|
|
334
|
+
storeLocal(v) {
|
|
335
|
+
if (v.type)
|
|
336
|
+
this.coerceTo(v.type);
|
|
337
|
+
if (v.reg >= 0)
|
|
338
|
+
this.setReg(v.reg);
|
|
339
|
+
else {
|
|
340
|
+
this.getReg(this.activationReg);
|
|
341
|
+
this.emit(opcodes_js_1.Op.swap);
|
|
342
|
+
this.emit(opcodes_js_1.Op.setslot, v.slot);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Emits the coercion ASC would insert when storing into a typed location. */
|
|
346
|
+
coerceTo(type) {
|
|
347
|
+
const name = this.scope.abc.qualifiedName(type);
|
|
348
|
+
switch (name) {
|
|
349
|
+
case '*':
|
|
350
|
+
return;
|
|
351
|
+
case 'int':
|
|
352
|
+
this.emit(opcodes_js_1.Op.convert_i);
|
|
353
|
+
return;
|
|
354
|
+
case 'uint':
|
|
355
|
+
this.emit(opcodes_js_1.Op.convert_u);
|
|
356
|
+
return;
|
|
357
|
+
case 'Number':
|
|
358
|
+
this.emit(opcodes_js_1.Op.convert_d);
|
|
359
|
+
return;
|
|
360
|
+
case 'Boolean':
|
|
361
|
+
this.emit(opcodes_js_1.Op.convert_b);
|
|
362
|
+
return;
|
|
363
|
+
case 'String':
|
|
364
|
+
this.emit(opcodes_js_1.Op.coerce_s);
|
|
365
|
+
return;
|
|
366
|
+
case 'Object':
|
|
367
|
+
return;
|
|
368
|
+
default:
|
|
369
|
+
this.emit(opcodes_js_1.Op.coerce, type);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// -------------------------------------------------------------------------
|
|
373
|
+
// Statements
|
|
374
|
+
// -------------------------------------------------------------------------
|
|
375
|
+
stmts(list) {
|
|
376
|
+
for (const s of list)
|
|
377
|
+
this.stmt(s);
|
|
378
|
+
}
|
|
379
|
+
stmt(s) {
|
|
380
|
+
switch (s.k) {
|
|
381
|
+
case 'empty':
|
|
382
|
+
case 'function': // hoisted
|
|
383
|
+
return;
|
|
384
|
+
case 'expr':
|
|
385
|
+
this.exprDiscard(s.e);
|
|
386
|
+
return;
|
|
387
|
+
case 'var':
|
|
388
|
+
for (const d of s.decls) {
|
|
389
|
+
if (!d.init)
|
|
390
|
+
continue;
|
|
391
|
+
this.expr(d.init);
|
|
392
|
+
this.storeLocal(this.lookupLocal(d.name));
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
case 'block':
|
|
396
|
+
this.stmts(s.body);
|
|
397
|
+
return;
|
|
398
|
+
case 'if': {
|
|
399
|
+
const elseL = this.newLabel();
|
|
400
|
+
this.condJump(s.c, false, elseL);
|
|
401
|
+
this.stmt(s.then);
|
|
402
|
+
if (s.else) {
|
|
403
|
+
const endL = this.newLabel();
|
|
404
|
+
this.jump(opcodes_js_1.Op.jump, endL);
|
|
405
|
+
this.place(elseL);
|
|
406
|
+
this.stmt(s.else);
|
|
407
|
+
this.place(endL);
|
|
408
|
+
}
|
|
409
|
+
else
|
|
410
|
+
this.place(elseL);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
case 'while': {
|
|
414
|
+
const condL = this.newLabel();
|
|
415
|
+
const bodyL = this.newLabel();
|
|
416
|
+
const endL = this.newLabel();
|
|
417
|
+
this.jump(opcodes_js_1.Op.jump, condL);
|
|
418
|
+
this.place(bodyL);
|
|
419
|
+
this.emit(opcodes_js_1.Op.label);
|
|
420
|
+
this.loop(endL, condL, () => this.stmt(s.body));
|
|
421
|
+
this.place(condL);
|
|
422
|
+
this.condJump(s.c, true, bodyL);
|
|
423
|
+
this.place(endL);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
case 'dowhile': {
|
|
427
|
+
const bodyL = this.newLabel();
|
|
428
|
+
const condL = this.newLabel();
|
|
429
|
+
const endL = this.newLabel();
|
|
430
|
+
this.place(bodyL);
|
|
431
|
+
this.emit(opcodes_js_1.Op.label);
|
|
432
|
+
this.loop(endL, condL, () => this.stmt(s.body));
|
|
433
|
+
this.place(condL);
|
|
434
|
+
this.condJump(s.c, true, bodyL);
|
|
435
|
+
this.place(endL);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
case 'for': {
|
|
439
|
+
if (s.init)
|
|
440
|
+
this.stmt(s.init);
|
|
441
|
+
const condL = this.newLabel();
|
|
442
|
+
const bodyL = this.newLabel();
|
|
443
|
+
const contL = this.newLabel();
|
|
444
|
+
const endL = this.newLabel();
|
|
445
|
+
this.jump(opcodes_js_1.Op.jump, condL);
|
|
446
|
+
this.place(bodyL);
|
|
447
|
+
this.emit(opcodes_js_1.Op.label);
|
|
448
|
+
this.loop(endL, contL, () => this.stmt(s.body));
|
|
449
|
+
this.place(contL);
|
|
450
|
+
if (s.update)
|
|
451
|
+
this.exprDiscard(s.update);
|
|
452
|
+
this.place(condL);
|
|
453
|
+
if (s.c)
|
|
454
|
+
this.condJump(s.c, true, bodyL);
|
|
455
|
+
else
|
|
456
|
+
this.jump(opcodes_js_1.Op.jump, bodyL);
|
|
457
|
+
this.place(endL);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
case 'forin':
|
|
461
|
+
this.forIn(s);
|
|
462
|
+
return;
|
|
463
|
+
case 'switch':
|
|
464
|
+
this.switchStmt(s);
|
|
465
|
+
return;
|
|
466
|
+
case 'try':
|
|
467
|
+
this.tryStmt(s);
|
|
468
|
+
return;
|
|
469
|
+
case 'return':
|
|
470
|
+
if (s.e) {
|
|
471
|
+
this.expr(s.e);
|
|
472
|
+
if (this.method.returnType)
|
|
473
|
+
this.coerceTo(this.method.returnType);
|
|
474
|
+
if (this.finallyStack.length) {
|
|
475
|
+
const t = this.temp();
|
|
476
|
+
this.setReg(t);
|
|
477
|
+
this.runFinallies(0);
|
|
478
|
+
this.getReg(t);
|
|
479
|
+
this.release(t);
|
|
480
|
+
}
|
|
481
|
+
this.emit(opcodes_js_1.Op.returnvalue);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
this.runFinallies(0);
|
|
485
|
+
this.emit(opcodes_js_1.Op.returnvoid);
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
case 'throw':
|
|
489
|
+
this.expr(s.e);
|
|
490
|
+
this.emit(opcodes_js_1.Op.throw);
|
|
491
|
+
return;
|
|
492
|
+
case 'break':
|
|
493
|
+
case 'continue': {
|
|
494
|
+
for (let i = this.loops.length - 1; i >= 0; i--) {
|
|
495
|
+
const l = this.loops[i];
|
|
496
|
+
if (s.label !== undefined) {
|
|
497
|
+
if (l.label !== s.label)
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
else if (l.kind === 'block' || (s.k === 'continue' && l.kind !== 'loop'))
|
|
501
|
+
continue;
|
|
502
|
+
if (s.k === 'continue' && l.kind !== 'loop')
|
|
503
|
+
throw this.err(`Cannot continue label '${s.label}'`, s.pos);
|
|
504
|
+
this.runFinallies(i + 1);
|
|
505
|
+
// Leave with / catch scopes opened inside the target loop.
|
|
506
|
+
for (const sc of this.extraScopes)
|
|
507
|
+
if (sc.depth > i)
|
|
508
|
+
this.items.push({ type: 'op', op: opcodes_js_1.Op.popscope, args: [], targets: [], offset: -1 });
|
|
509
|
+
this.jump(opcodes_js_1.Op.jump, s.k === 'break' ? l.breakLabel : l.continueLabel);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
throw this.err(`'${s.k}' outside of a loop${s.label ? ` (label ${s.label})` : ''}`, s.pos);
|
|
513
|
+
}
|
|
514
|
+
case 'labeled': {
|
|
515
|
+
const inner = s.body;
|
|
516
|
+
if (inner.k === 'while' || inner.k === 'dowhile' || inner.k === 'for' || inner.k === 'forin' || inner.k === 'switch') {
|
|
517
|
+
this.pendingLabel = s.label;
|
|
518
|
+
this.stmt(inner);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
const endL = this.newLabel();
|
|
522
|
+
this.loops.push({ kind: 'block', label: s.label, breakLabel: endL, continueLabel: -1 });
|
|
523
|
+
this.stmt(inner);
|
|
524
|
+
this.loops.pop();
|
|
525
|
+
this.place(endL);
|
|
526
|
+
}
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
case 'with': {
|
|
530
|
+
this.expr(s.obj);
|
|
531
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
532
|
+
const t = this.temp();
|
|
533
|
+
this.setReg(t);
|
|
534
|
+
this.emit(opcodes_js_1.Op.pushwith);
|
|
535
|
+
this.extraScopes.push({ depth: this.loops.length, reg: t, isWith: true });
|
|
536
|
+
this.stmt(s.body);
|
|
537
|
+
this.extraScopes.pop();
|
|
538
|
+
this.emit(opcodes_js_1.Op.popscope);
|
|
539
|
+
this.release(t);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
case 'dxns':
|
|
543
|
+
this.expr(s.e);
|
|
544
|
+
this.emit(opcodes_js_1.Op.dxnslate);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
loop(breakLabel, continueLabel, body) {
|
|
549
|
+
const label = this.pendingLabel;
|
|
550
|
+
this.pendingLabel = undefined;
|
|
551
|
+
this.loops.push({ kind: 'loop', label, breakLabel, continueLabel });
|
|
552
|
+
body();
|
|
553
|
+
this.loops.pop();
|
|
554
|
+
}
|
|
555
|
+
forIn(s) {
|
|
556
|
+
const objReg = this.temp();
|
|
557
|
+
const idxReg = this.temp();
|
|
558
|
+
this.expr(s.obj);
|
|
559
|
+
this.emit(opcodes_js_1.Op.coerce_a);
|
|
560
|
+
this.setReg(objReg);
|
|
561
|
+
this.emit(opcodes_js_1.Op.pushbyte, 0);
|
|
562
|
+
this.setReg(idxReg);
|
|
563
|
+
const condL = this.newLabel();
|
|
564
|
+
const bodyL = this.newLabel();
|
|
565
|
+
const endL = this.newLabel();
|
|
566
|
+
this.jump(opcodes_js_1.Op.jump, condL);
|
|
567
|
+
this.place(bodyL);
|
|
568
|
+
this.emit(opcodes_js_1.Op.label);
|
|
569
|
+
this.getReg(objReg);
|
|
570
|
+
this.getReg(idxReg);
|
|
571
|
+
this.emit(s.each ? opcodes_js_1.Op.nextvalue : opcodes_js_1.Op.nextname);
|
|
572
|
+
if (s.decl)
|
|
573
|
+
this.storeLocal(this.lookupLocal(s.decl.name));
|
|
574
|
+
else
|
|
575
|
+
this.storeTo(s.target);
|
|
576
|
+
this.loop(endL, condL, () => this.stmt(s.body));
|
|
577
|
+
this.place(condL);
|
|
578
|
+
this.emit(opcodes_js_1.Op.hasnext2, objReg, idxReg);
|
|
579
|
+
this.jump(opcodes_js_1.Op.iftrue, bodyL);
|
|
580
|
+
this.place(endL);
|
|
581
|
+
this.release(idxReg);
|
|
582
|
+
this.release(objReg);
|
|
583
|
+
}
|
|
584
|
+
/** Compiles `switch` the way ASC does: case bodies first, then a dispatcher feeding lookupswitch. */
|
|
585
|
+
switchStmt(s) {
|
|
586
|
+
// Switching on a register local needs no temporary.
|
|
587
|
+
const localDisc = s.disc.k === 'id' && !s.disc.ns ? this.lookupLocal(s.disc.name) : undefined;
|
|
588
|
+
const ownTemp = !(localDisc && localDisc.reg >= 0);
|
|
589
|
+
const disc = ownTemp ? this.temp() : localDisc.reg;
|
|
590
|
+
if (ownTemp) {
|
|
591
|
+
this.expr(s.disc);
|
|
592
|
+
this.setReg(disc);
|
|
593
|
+
}
|
|
594
|
+
const endL = this.newLabel();
|
|
595
|
+
const dispatchL = this.newLabel();
|
|
596
|
+
const caseLabels = s.cases.map(() => this.newLabel());
|
|
597
|
+
this.jump(opcodes_js_1.Op.jump, dispatchL);
|
|
598
|
+
const label = this.pendingLabel;
|
|
599
|
+
this.pendingLabel = undefined;
|
|
600
|
+
this.loops.push({ kind: 'switch', label, breakLabel: endL, continueLabel: -1 });
|
|
601
|
+
s.cases.forEach((c, i) => {
|
|
602
|
+
this.place(caseLabels[i]);
|
|
603
|
+
this.emit(opcodes_js_1.Op.label);
|
|
604
|
+
this.stmts(c.body);
|
|
605
|
+
});
|
|
606
|
+
this.loops.pop();
|
|
607
|
+
this.jump(opcodes_js_1.Op.jump, endL);
|
|
608
|
+
// Dispatcher: compute the case index, then lookupswitch.
|
|
609
|
+
this.place(dispatchL);
|
|
610
|
+
const tested = s.cases.map((c, i) => ({ c, i })).filter((x) => x.c.test !== null);
|
|
611
|
+
const defaultIdx = s.cases.findIndex((c) => c.test === null);
|
|
612
|
+
const switchL = this.newLabel();
|
|
613
|
+
tested.forEach(({ c }, k) => {
|
|
614
|
+
const next = this.newLabel();
|
|
615
|
+
this.expr(c.test);
|
|
616
|
+
this.getReg(disc);
|
|
617
|
+
this.jump(opcodes_js_1.Op.ifstrictne, next);
|
|
618
|
+
this.pushNumber(k);
|
|
619
|
+
this.jump(opcodes_js_1.Op.jump, switchL);
|
|
620
|
+
this.place(next);
|
|
621
|
+
});
|
|
622
|
+
this.pushNumber(tested.length);
|
|
623
|
+
this.place(switchL);
|
|
624
|
+
if (ownTemp)
|
|
625
|
+
this.release(disc);
|
|
626
|
+
const defaultTarget = defaultIdx >= 0 ? caseLabels[defaultIdx] : endL;
|
|
627
|
+
const targets = [defaultTarget, ...tested.map(({ i }) => caseLabels[i]), defaultTarget];
|
|
628
|
+
this.items.push({ type: 'op', op: opcodes_js_1.Op.lookupswitch, args: [], targets, offset: -1 });
|
|
629
|
+
this.place(endL);
|
|
630
|
+
}
|
|
631
|
+
finallyStack = [];
|
|
632
|
+
/** Extra scopes (with / catch) currently open: loop depth when pushed and the register holding the object. */
|
|
633
|
+
extraScopes = [];
|
|
634
|
+
/** Re-establishes the scope stack at an exception handler (the VM clears it). */
|
|
635
|
+
restoreScopes() {
|
|
636
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
637
|
+
this.emit(opcodes_js_1.Op.pushscope);
|
|
638
|
+
if (this.activationReg >= 0) {
|
|
639
|
+
this.getReg(this.activationReg);
|
|
640
|
+
this.emit(opcodes_js_1.Op.pushscope);
|
|
641
|
+
}
|
|
642
|
+
for (const sc of this.extraScopes) {
|
|
643
|
+
this.getReg(sc.reg);
|
|
644
|
+
this.emit(sc.isWith ? opcodes_js_1.Op.pushwith : opcodes_js_1.Op.pushscope);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
tryStmt(s) {
|
|
648
|
+
const fromL = this.newLabel();
|
|
649
|
+
const toL = this.newLabel();
|
|
650
|
+
const afterL = this.newLabel();
|
|
651
|
+
const depthBefore = this.scopeDepth;
|
|
652
|
+
if (s.finally)
|
|
653
|
+
this.finallyStack.push({ body: s.finally, loopDepth: this.loops.length });
|
|
654
|
+
this.place(fromL);
|
|
655
|
+
this.stmts(s.body);
|
|
656
|
+
this.place(toL);
|
|
657
|
+
if (s.finally)
|
|
658
|
+
this.stmts(s.finally);
|
|
659
|
+
this.jump(opcodes_js_1.Op.jump, afterL);
|
|
660
|
+
const catchEndL = this.newLabel();
|
|
661
|
+
for (const c of s.catches) {
|
|
662
|
+
const handlerL = this.newLabel();
|
|
663
|
+
const excType = c.type ? this.scope.resolveType(c.type) : 0;
|
|
664
|
+
this.exceptions.push({ from: fromL, to: toL, target: handlerL, excType, varName: this.publicQName(c.name) });
|
|
665
|
+
const excIndex = this.exceptions.length - 1;
|
|
666
|
+
this.place(handlerL);
|
|
667
|
+
this.scopeDepth = 0;
|
|
668
|
+
this.restoreScopes();
|
|
669
|
+
// ASC-style catch scope: the variable lives in a scope object so closures can see it.
|
|
670
|
+
const saved = this.locals.get(c.name);
|
|
671
|
+
this.locals.delete(c.name);
|
|
672
|
+
const scopeReg = this.temp();
|
|
673
|
+
this.emit(opcodes_js_1.Op.newcatch, excIndex);
|
|
674
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
675
|
+
this.setReg(scopeReg);
|
|
676
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
677
|
+
this.emit(opcodes_js_1.Op.pushscope);
|
|
678
|
+
this.emit(opcodes_js_1.Op.swap);
|
|
679
|
+
this.emit(opcodes_js_1.Op.setslot, 1);
|
|
680
|
+
this.extraScopes.push({ depth: this.loops.length, reg: scopeReg, isWith: false });
|
|
681
|
+
this.stmts(c.body);
|
|
682
|
+
this.extraScopes.pop();
|
|
683
|
+
this.emit(opcodes_js_1.Op.popscope);
|
|
684
|
+
if (saved)
|
|
685
|
+
this.locals.set(c.name, saved);
|
|
686
|
+
this.release(scopeReg);
|
|
687
|
+
if (s.finally)
|
|
688
|
+
this.stmts(s.finally);
|
|
689
|
+
this.jump(opcodes_js_1.Op.jump, afterL);
|
|
690
|
+
}
|
|
691
|
+
this.place(catchEndL);
|
|
692
|
+
if (s.finally) {
|
|
693
|
+
this.finallyStack.pop();
|
|
694
|
+
// Catch-all: run the finally block and rethrow.
|
|
695
|
+
const handlerL = this.newLabel();
|
|
696
|
+
this.exceptions.push({ from: fromL, to: catchEndL, target: handlerL, excType: 0, varName: 0 });
|
|
697
|
+
this.place(handlerL);
|
|
698
|
+
this.scopeDepth = 0;
|
|
699
|
+
this.restoreScopes();
|
|
700
|
+
const t = this.temp();
|
|
701
|
+
this.setReg(t);
|
|
702
|
+
this.stmts(s.finally);
|
|
703
|
+
this.getReg(t);
|
|
704
|
+
this.release(t);
|
|
705
|
+
this.emit(opcodes_js_1.Op.throw);
|
|
706
|
+
}
|
|
707
|
+
this.scopeDepth = depthBefore;
|
|
708
|
+
this.place(afterL);
|
|
709
|
+
}
|
|
710
|
+
/** Inlines pending finally blocks when control leaves them via return/break/continue. */
|
|
711
|
+
runFinallies(downToLoopDepth) {
|
|
712
|
+
for (let i = this.finallyStack.length - 1; i >= 0; i--) {
|
|
713
|
+
const f = this.finallyStack[i];
|
|
714
|
+
if (f.loopDepth < downToLoopDepth)
|
|
715
|
+
break;
|
|
716
|
+
// Run the block without it being active (a return inside finally must not recurse).
|
|
717
|
+
this.finallyStack.splice(i, 1);
|
|
718
|
+
this.stmts(f.body);
|
|
719
|
+
this.finallyStack.splice(i, 0, f);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
// -------------------------------------------------------------------------
|
|
723
|
+
// Expressions
|
|
724
|
+
// -------------------------------------------------------------------------
|
|
725
|
+
pushNumber(v) {
|
|
726
|
+
if (Number.isNaN(v))
|
|
727
|
+
this.emit(opcodes_js_1.Op.pushnan);
|
|
728
|
+
else if (isIntLiteral(v) && v >= -128 && v <= 127)
|
|
729
|
+
this.emit(opcodes_js_1.Op.pushbyte, v);
|
|
730
|
+
else if (isIntLiteral(v) && v >= -32768 && v <= 32767)
|
|
731
|
+
this.emit(opcodes_js_1.Op.pushshort, v);
|
|
732
|
+
else if (isIntLiteral(v))
|
|
733
|
+
this.emit(opcodes_js_1.Op.pushint, this.scope.abc.int(v));
|
|
734
|
+
else if (Number.isInteger(v) && v >= 0 && v <= 0xffffffff)
|
|
735
|
+
this.emit(opcodes_js_1.Op.pushuint, this.scope.abc.uint(v));
|
|
736
|
+
else
|
|
737
|
+
this.emit(opcodes_js_1.Op.pushdouble, this.scope.abc.double(v));
|
|
738
|
+
}
|
|
739
|
+
/** Multiname for a property access expression (member / index / descendants). */
|
|
740
|
+
propertyName(e) {
|
|
741
|
+
if (e.k === 'member') {
|
|
742
|
+
if (e.ns)
|
|
743
|
+
return { mn: this.mnQualified(e.ns, e.name, e.pos), late: false };
|
|
744
|
+
return { mn: e.attr ? this.mnAttr(e.name) : this.mn(e.name), late: false };
|
|
745
|
+
}
|
|
746
|
+
if (e.k === 'index')
|
|
747
|
+
return { mn: this.mnLate(e.attr), late: true };
|
|
748
|
+
throw this.err('Not a property reference', e.pos);
|
|
749
|
+
}
|
|
750
|
+
/** Name used to look up a bare identifier. */
|
|
751
|
+
identName(e) {
|
|
752
|
+
if (e.ns)
|
|
753
|
+
return this.mnQualified(e.ns, e.name, e.pos);
|
|
754
|
+
return this.scope.resolveName(e.name) ?? this.mn(e.name);
|
|
755
|
+
}
|
|
756
|
+
expr(e) {
|
|
757
|
+
switch (e.k) {
|
|
758
|
+
case 'num':
|
|
759
|
+
this.pushNumber(e.v);
|
|
760
|
+
return;
|
|
761
|
+
case 'str':
|
|
762
|
+
this.emit(opcodes_js_1.Op.pushstring, this.pool.string(e.v));
|
|
763
|
+
return;
|
|
764
|
+
case 'bool':
|
|
765
|
+
this.emit(e.v ? opcodes_js_1.Op.pushtrue : opcodes_js_1.Op.pushfalse);
|
|
766
|
+
return;
|
|
767
|
+
case 'null':
|
|
768
|
+
this.emit(opcodes_js_1.Op.pushnull);
|
|
769
|
+
return;
|
|
770
|
+
case 'undefined':
|
|
771
|
+
this.emit(opcodes_js_1.Op.pushundefined);
|
|
772
|
+
return;
|
|
773
|
+
case 'regex':
|
|
774
|
+
this.emit(opcodes_js_1.Op.getlex, this.publicQName('RegExp'));
|
|
775
|
+
this.emit(opcodes_js_1.Op.pushstring, this.pool.string(e.pattern));
|
|
776
|
+
this.emit(opcodes_js_1.Op.pushstring, this.pool.string(e.flags));
|
|
777
|
+
this.emit(opcodes_js_1.Op.construct, 2);
|
|
778
|
+
return;
|
|
779
|
+
case 'xml':
|
|
780
|
+
this.emit(opcodes_js_1.Op.getlex, this.publicQName(e.text.startsWith('<>') ? 'XMLList' : 'XML'));
|
|
781
|
+
this.emit(opcodes_js_1.Op.pushstring, this.pool.string(e.text.startsWith('<>') ? e.text.slice(2, -3) : e.text));
|
|
782
|
+
this.emit(opcodes_js_1.Op.construct, 1);
|
|
783
|
+
return;
|
|
784
|
+
case 'this':
|
|
785
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
786
|
+
return;
|
|
787
|
+
case 'super':
|
|
788
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
789
|
+
return;
|
|
790
|
+
case 'id': {
|
|
791
|
+
const local = e.ns ? undefined : this.lookupLocal(e.name);
|
|
792
|
+
if (local)
|
|
793
|
+
return this.loadLocal(local);
|
|
794
|
+
if (!e.ns && e.name === 'NaN')
|
|
795
|
+
return this.emit(opcodes_js_1.Op.pushnan);
|
|
796
|
+
if (!e.ns && e.name === 'Infinity')
|
|
797
|
+
return this.emit(opcodes_js_1.Op.pushdouble, this.scope.abc.double(Infinity));
|
|
798
|
+
const mn = this.identName(e);
|
|
799
|
+
if (e.ns) {
|
|
800
|
+
this.emit(opcodes_js_1.Op.findpropstrict, mn);
|
|
801
|
+
this.emit(opcodes_js_1.Op.getproperty, mn);
|
|
802
|
+
}
|
|
803
|
+
else
|
|
804
|
+
this.emit(opcodes_js_1.Op.getlex, mn);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
case 'member':
|
|
808
|
+
case 'index': {
|
|
809
|
+
if (e.obj.k === 'super') {
|
|
810
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
811
|
+
if (e.k === 'index')
|
|
812
|
+
this.expr(e.index);
|
|
813
|
+
this.emit(opcodes_js_1.Op.getsuper, this.propertyName(e).mn);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
this.expr(e.obj);
|
|
817
|
+
if (e.k === 'index')
|
|
818
|
+
this.expr(e.index);
|
|
819
|
+
this.emit(opcodes_js_1.Op.getproperty, this.propertyName(e).mn);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
case 'descendants':
|
|
823
|
+
this.expr(e.obj);
|
|
824
|
+
this.emit(opcodes_js_1.Op.getdescendants, e.attr ? this.mnAttr(e.name) : this.mn(e.name));
|
|
825
|
+
return;
|
|
826
|
+
case 'filter':
|
|
827
|
+
this.filter(e);
|
|
828
|
+
return;
|
|
829
|
+
case 'typeapply':
|
|
830
|
+
this.expr(e.base);
|
|
831
|
+
for (const p of e.params)
|
|
832
|
+
this.typeValue(p);
|
|
833
|
+
this.emit(opcodes_js_1.Op.applytype, e.params.length);
|
|
834
|
+
return;
|
|
835
|
+
case 'call':
|
|
836
|
+
this.call(e, true);
|
|
837
|
+
return;
|
|
838
|
+
case 'new':
|
|
839
|
+
this.newExpr(e);
|
|
840
|
+
return;
|
|
841
|
+
case 'vector': {
|
|
842
|
+
this.emit(opcodes_js_1.Op.getlex, this.pool.qname(this.pool.namespace(constants_js_1.NamespaceKind.PackageNamespace, '__AS3__.vec'), 'Vector'));
|
|
843
|
+
this.typeValue(e.type);
|
|
844
|
+
this.emit(opcodes_js_1.Op.applytype, 1);
|
|
845
|
+
this.pushNumber(e.items.length);
|
|
846
|
+
this.emit(opcodes_js_1.Op.construct, 1);
|
|
847
|
+
e.items.forEach((item, i) => {
|
|
848
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
849
|
+
this.pushNumber(i);
|
|
850
|
+
this.expr(item);
|
|
851
|
+
this.emit(opcodes_js_1.Op.setproperty, this.mnLate());
|
|
852
|
+
});
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
case 'unary':
|
|
856
|
+
this.unary(e);
|
|
857
|
+
return;
|
|
858
|
+
case 'update':
|
|
859
|
+
this.update(e, true);
|
|
860
|
+
return;
|
|
861
|
+
case 'binary':
|
|
862
|
+
this.binary(e);
|
|
863
|
+
return;
|
|
864
|
+
case 'cond': {
|
|
865
|
+
const elseL = this.newLabel();
|
|
866
|
+
const endL = this.newLabel();
|
|
867
|
+
this.condJump(e.c, false, elseL);
|
|
868
|
+
this.expr(e.t);
|
|
869
|
+
this.emit(opcodes_js_1.Op.coerce_a);
|
|
870
|
+
this.jump(opcodes_js_1.Op.jump, endL);
|
|
871
|
+
this.place(elseL);
|
|
872
|
+
this.expr(e.f);
|
|
873
|
+
this.emit(opcodes_js_1.Op.coerce_a);
|
|
874
|
+
this.place(endL);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
case 'assign':
|
|
878
|
+
this.assign(e, true);
|
|
879
|
+
return;
|
|
880
|
+
case 'array':
|
|
881
|
+
for (const it of e.items) {
|
|
882
|
+
if (it)
|
|
883
|
+
this.expr(it);
|
|
884
|
+
else
|
|
885
|
+
this.emit(opcodes_js_1.Op.pushundefined);
|
|
886
|
+
}
|
|
887
|
+
this.emit(opcodes_js_1.Op.newarray, e.items.length);
|
|
888
|
+
return;
|
|
889
|
+
case 'object':
|
|
890
|
+
for (const p of e.props) {
|
|
891
|
+
this.emit(opcodes_js_1.Op.pushstring, this.pool.string(String(p.key)));
|
|
892
|
+
this.expr(p.value);
|
|
893
|
+
}
|
|
894
|
+
this.emit(opcodes_js_1.Op.newobject, e.props.length);
|
|
895
|
+
return;
|
|
896
|
+
case 'function':
|
|
897
|
+
this.compileClosure(e.fn);
|
|
898
|
+
return;
|
|
899
|
+
case 'comma':
|
|
900
|
+
e.list.forEach((x, i) => (i < e.list.length - 1 ? this.exprDiscard(x) : this.expr(x)));
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/** Pushes a type as a value (for `as`, `is`, `Vector.<T>`). */
|
|
905
|
+
typeValue(t) {
|
|
906
|
+
if (t.name === '*') {
|
|
907
|
+
this.emit(opcodes_js_1.Op.pushnull);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (t.params) {
|
|
911
|
+
this.typeValue({ name: t.name, pos: t.pos });
|
|
912
|
+
for (const p of t.params)
|
|
913
|
+
this.typeValue(p);
|
|
914
|
+
this.emit(opcodes_js_1.Op.applytype, t.params.length);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
this.emit(opcodes_js_1.Op.getlex, this.scope.resolveType(t));
|
|
918
|
+
}
|
|
919
|
+
/** Evaluates `e` for its side effects only. */
|
|
920
|
+
exprDiscard(e) {
|
|
921
|
+
switch (e.k) {
|
|
922
|
+
case 'assign':
|
|
923
|
+
this.assign(e, false);
|
|
924
|
+
return;
|
|
925
|
+
case 'update':
|
|
926
|
+
this.update(e, false);
|
|
927
|
+
return;
|
|
928
|
+
case 'call':
|
|
929
|
+
this.call(e, false);
|
|
930
|
+
return;
|
|
931
|
+
case 'comma':
|
|
932
|
+
for (const x of e.list)
|
|
933
|
+
this.exprDiscard(x);
|
|
934
|
+
return;
|
|
935
|
+
default:
|
|
936
|
+
this.expr(e);
|
|
937
|
+
this.emit(opcodes_js_1.Op.pop);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
args(list) {
|
|
941
|
+
for (const a of list)
|
|
942
|
+
this.expr(a);
|
|
943
|
+
}
|
|
944
|
+
call(e, wantValue) {
|
|
945
|
+
const fn = e.fn;
|
|
946
|
+
const n = e.args.length;
|
|
947
|
+
if (fn.k === 'super') {
|
|
948
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
949
|
+
this.args(e.args);
|
|
950
|
+
this.emit(opcodes_js_1.Op.constructsuper, n);
|
|
951
|
+
if (wantValue)
|
|
952
|
+
this.emit(opcodes_js_1.Op.pushundefined);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if ((fn.k === 'member' || fn.k === 'index') && fn.obj.k === 'super') {
|
|
956
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
957
|
+
if (fn.k === 'index')
|
|
958
|
+
this.expr(fn.index);
|
|
959
|
+
this.args(e.args);
|
|
960
|
+
this.emit(wantValue ? opcodes_js_1.Op.callsuper : opcodes_js_1.Op.callsupervoid, this.propertyName(fn).mn, n);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (fn.k === 'member' || fn.k === 'index') {
|
|
964
|
+
this.expr(fn.obj);
|
|
965
|
+
if (fn.k === 'index')
|
|
966
|
+
this.expr(fn.index);
|
|
967
|
+
this.args(e.args);
|
|
968
|
+
this.emit(wantValue ? opcodes_js_1.Op.callproperty : opcodes_js_1.Op.callpropvoid, this.propertyName(fn).mn, n);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (fn.k === 'id' && !(this.lookupLocal(fn.name) && !fn.ns)) {
|
|
972
|
+
const mn = this.identName(fn);
|
|
973
|
+
this.emit(opcodes_js_1.Op.findpropstrict, mn);
|
|
974
|
+
this.args(e.args);
|
|
975
|
+
this.emit(wantValue ? opcodes_js_1.Op.callproperty : opcodes_js_1.Op.callpropvoid, mn, n);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
this.expr(fn);
|
|
979
|
+
this.emit(opcodes_js_1.Op.pushnull);
|
|
980
|
+
this.args(e.args);
|
|
981
|
+
this.emit(opcodes_js_1.Op.call, n);
|
|
982
|
+
if (!wantValue)
|
|
983
|
+
this.emit(opcodes_js_1.Op.pop);
|
|
984
|
+
}
|
|
985
|
+
newExpr(e) {
|
|
986
|
+
const c = e.ctor;
|
|
987
|
+
const n = e.args.length;
|
|
988
|
+
if (c.k === 'id' && (c.ns || !this.lookupLocal(c.name))) {
|
|
989
|
+
const mn = this.identName(c);
|
|
990
|
+
this.emit(opcodes_js_1.Op.findpropstrict, mn);
|
|
991
|
+
this.args(e.args);
|
|
992
|
+
this.emit(opcodes_js_1.Op.constructprop, mn, n);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
if (c.k === 'member' && c.obj.k !== 'super') {
|
|
996
|
+
this.expr(c.obj);
|
|
997
|
+
this.args(e.args);
|
|
998
|
+
this.emit(opcodes_js_1.Op.constructprop, this.propertyName(c).mn, n);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
this.expr(c);
|
|
1002
|
+
this.args(e.args);
|
|
1003
|
+
this.emit(opcodes_js_1.Op.construct, n);
|
|
1004
|
+
}
|
|
1005
|
+
unary(e) {
|
|
1006
|
+
switch (e.op) {
|
|
1007
|
+
case '!':
|
|
1008
|
+
this.expr(e.e);
|
|
1009
|
+
this.emit(opcodes_js_1.Op.not);
|
|
1010
|
+
return;
|
|
1011
|
+
case '-':
|
|
1012
|
+
this.expr(e.e);
|
|
1013
|
+
this.emit(opcodes_js_1.Op.negate);
|
|
1014
|
+
return;
|
|
1015
|
+
case '+':
|
|
1016
|
+
this.expr(e.e);
|
|
1017
|
+
this.emit(opcodes_js_1.Op.convert_d);
|
|
1018
|
+
return;
|
|
1019
|
+
case '~':
|
|
1020
|
+
this.expr(e.e);
|
|
1021
|
+
this.emit(opcodes_js_1.Op.bitnot);
|
|
1022
|
+
return;
|
|
1023
|
+
case 'typeof':
|
|
1024
|
+
if (e.e.k === 'id' && !e.e.ns && !this.lookupLocal(e.e.name)) {
|
|
1025
|
+
// typeof on an undeclared name must not throw.
|
|
1026
|
+
const mn = this.identName(e.e);
|
|
1027
|
+
this.emit(opcodes_js_1.Op.findproperty, mn);
|
|
1028
|
+
this.emit(opcodes_js_1.Op.getproperty, mn);
|
|
1029
|
+
}
|
|
1030
|
+
else
|
|
1031
|
+
this.expr(e.e);
|
|
1032
|
+
this.emit(opcodes_js_1.Op.typeof);
|
|
1033
|
+
return;
|
|
1034
|
+
case 'void':
|
|
1035
|
+
this.exprDiscard(e.e);
|
|
1036
|
+
this.emit(opcodes_js_1.Op.pushundefined);
|
|
1037
|
+
return;
|
|
1038
|
+
case 'delete': {
|
|
1039
|
+
const t = e.e;
|
|
1040
|
+
if (t.k === 'member' || t.k === 'index') {
|
|
1041
|
+
this.expr(t.obj);
|
|
1042
|
+
if (t.k === 'index')
|
|
1043
|
+
this.expr(t.index);
|
|
1044
|
+
this.emit(opcodes_js_1.Op.deleteproperty, this.propertyName(t).mn);
|
|
1045
|
+
}
|
|
1046
|
+
else if (t.k === 'id' && !this.lookupLocal(t.name)) {
|
|
1047
|
+
const mn = this.identName(t);
|
|
1048
|
+
this.emit(opcodes_js_1.Op.findproperty, mn);
|
|
1049
|
+
this.emit(opcodes_js_1.Op.deleteproperty, mn);
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
this.exprDiscard(t);
|
|
1053
|
+
this.emit(opcodes_js_1.Op.pushtrue);
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
throw this.err(`Unsupported unary operator '${e.op}'`, e.pos);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Generic read-modify-write on an lvalue. `compute` receives the old value on
|
|
1062
|
+
* the stack and must leave the new value. When `wantValue`, `resultOld`
|
|
1063
|
+
* selects whether the old (postfix) or new value is left on the stack.
|
|
1064
|
+
*/
|
|
1065
|
+
modify(target, compute, wantValue, resultOld) {
|
|
1066
|
+
// Locals.
|
|
1067
|
+
if (target.k === 'id' && !target.ns && this.lookupLocal(target.name)) {
|
|
1068
|
+
const v = this.lookupLocal(target.name);
|
|
1069
|
+
this.loadLocal(v);
|
|
1070
|
+
if (resultOld) {
|
|
1071
|
+
this.emit(opcodes_js_1.Op.convert_d);
|
|
1072
|
+
if (wantValue)
|
|
1073
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1074
|
+
compute();
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
compute();
|
|
1078
|
+
if (wantValue)
|
|
1079
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1080
|
+
}
|
|
1081
|
+
this.storeLocal(v);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
// Property targets: obj [, index] evaluated once.
|
|
1085
|
+
let mn;
|
|
1086
|
+
let isSuper = false;
|
|
1087
|
+
let idxReg = -1;
|
|
1088
|
+
if (target.k === 'id') {
|
|
1089
|
+
mn = this.identName(target);
|
|
1090
|
+
this.emit(opcodes_js_1.Op.findpropstrict, mn);
|
|
1091
|
+
}
|
|
1092
|
+
else if (target.k === 'member' || target.k === 'index') {
|
|
1093
|
+
isSuper = target.obj.k === 'super';
|
|
1094
|
+
// Re-evaluating trivial operands is cheaper (and decompiles better) than temporaries.
|
|
1095
|
+
const trivial = (x) => x.k === 'num' || x.k === 'str' || x.k === 'bool' || x.k === 'null' || x.k === 'this' || (x.k === 'id' && !x.ns);
|
|
1096
|
+
if (!isSuper && target.k === 'index' && trivial(target.obj) && trivial(target.index)) {
|
|
1097
|
+
const mnL = this.propertyName(target).mn;
|
|
1098
|
+
this.expr(target.obj);
|
|
1099
|
+
this.expr(target.index);
|
|
1100
|
+
this.expr(target.obj);
|
|
1101
|
+
this.expr(target.index);
|
|
1102
|
+
this.emit(opcodes_js_1.Op.getproperty, mnL);
|
|
1103
|
+
let res = -1;
|
|
1104
|
+
const save = () => {
|
|
1105
|
+
if (!wantValue)
|
|
1106
|
+
return;
|
|
1107
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1108
|
+
res = this.temp();
|
|
1109
|
+
this.setReg(res);
|
|
1110
|
+
};
|
|
1111
|
+
if (resultOld) {
|
|
1112
|
+
this.emit(opcodes_js_1.Op.convert_d);
|
|
1113
|
+
save();
|
|
1114
|
+
compute();
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
compute();
|
|
1118
|
+
save();
|
|
1119
|
+
}
|
|
1120
|
+
this.emit(opcodes_js_1.Op.setproperty, mnL);
|
|
1121
|
+
if (res >= 0) {
|
|
1122
|
+
this.getReg(res);
|
|
1123
|
+
this.release(res);
|
|
1124
|
+
}
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (isSuper)
|
|
1128
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
1129
|
+
else
|
|
1130
|
+
this.expr(target.obj);
|
|
1131
|
+
mn = this.propertyName(target).mn;
|
|
1132
|
+
if (target.k === 'index') {
|
|
1133
|
+
this.expr(target.index);
|
|
1134
|
+
idxReg = this.temp();
|
|
1135
|
+
this.setReg(idxReg);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
else
|
|
1139
|
+
throw this.err('Invalid assignment target', target.pos);
|
|
1140
|
+
// Stack: obj → obj, obj [, idx] → obj, old
|
|
1141
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1142
|
+
if (idxReg >= 0)
|
|
1143
|
+
this.getReg(idxReg);
|
|
1144
|
+
this.emit(isSuper ? opcodes_js_1.Op.getsuper : opcodes_js_1.Op.getproperty, mn);
|
|
1145
|
+
let resultReg = -1;
|
|
1146
|
+
const saveResult = () => {
|
|
1147
|
+
if (!wantValue)
|
|
1148
|
+
return;
|
|
1149
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1150
|
+
resultReg = this.temp();
|
|
1151
|
+
this.setReg(resultReg);
|
|
1152
|
+
};
|
|
1153
|
+
if (resultOld) {
|
|
1154
|
+
this.emit(opcodes_js_1.Op.convert_d);
|
|
1155
|
+
saveResult();
|
|
1156
|
+
compute();
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
compute();
|
|
1160
|
+
saveResult();
|
|
1161
|
+
}
|
|
1162
|
+
// Stack: obj, new → obj [, idx], new
|
|
1163
|
+
if (idxReg >= 0) {
|
|
1164
|
+
const valReg = this.temp();
|
|
1165
|
+
this.setReg(valReg);
|
|
1166
|
+
this.getReg(idxReg);
|
|
1167
|
+
this.getReg(valReg);
|
|
1168
|
+
this.release(valReg);
|
|
1169
|
+
}
|
|
1170
|
+
this.emit(isSuper ? opcodes_js_1.Op.setsuper : opcodes_js_1.Op.setproperty, mn);
|
|
1171
|
+
if (idxReg >= 0)
|
|
1172
|
+
this.release(idxReg);
|
|
1173
|
+
if (resultReg >= 0) {
|
|
1174
|
+
this.getReg(resultReg);
|
|
1175
|
+
this.release(resultReg);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
update(e, wantValue) {
|
|
1179
|
+
// Fast path: int local, statement position.
|
|
1180
|
+
if (!wantValue && e.e.k === 'id' && !e.e.ns) {
|
|
1181
|
+
const v = this.lookupLocal(e.e.name);
|
|
1182
|
+
if (v && v.reg >= 0) {
|
|
1183
|
+
const tn = this.scope.abc.qualifiedName(v.type);
|
|
1184
|
+
if (tn === 'int')
|
|
1185
|
+
this.emit(e.op === '++' ? opcodes_js_1.Op.inclocal_i : opcodes_js_1.Op.declocal_i, v.reg);
|
|
1186
|
+
else
|
|
1187
|
+
this.emit(e.op === '++' ? opcodes_js_1.Op.inclocal : opcodes_js_1.Op.declocal, v.reg);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
this.modify(e.e, () => this.emit(e.op === '++' ? opcodes_js_1.Op.increment : opcodes_js_1.Op.decrement), wantValue, !e.prefix);
|
|
1192
|
+
}
|
|
1193
|
+
/** Stores the value on top of the stack into an lvalue (consumes it). */
|
|
1194
|
+
storeTo(target) {
|
|
1195
|
+
if (target.k === 'id' && !target.ns) {
|
|
1196
|
+
const v = this.lookupLocal(target.name);
|
|
1197
|
+
if (v)
|
|
1198
|
+
return this.storeLocal(v);
|
|
1199
|
+
}
|
|
1200
|
+
const t = this.temp();
|
|
1201
|
+
this.setReg(t);
|
|
1202
|
+
if (target.k === 'id') {
|
|
1203
|
+
const mn = this.identName(target);
|
|
1204
|
+
this.emit(opcodes_js_1.Op.findproperty, mn);
|
|
1205
|
+
this.getReg(t);
|
|
1206
|
+
this.emit(opcodes_js_1.Op.setproperty, mn);
|
|
1207
|
+
}
|
|
1208
|
+
else if (target.k === 'member' || target.k === 'index') {
|
|
1209
|
+
const isSuper = target.obj.k === 'super';
|
|
1210
|
+
if (isSuper)
|
|
1211
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
1212
|
+
else
|
|
1213
|
+
this.expr(target.obj);
|
|
1214
|
+
if (target.k === 'index')
|
|
1215
|
+
this.expr(target.index);
|
|
1216
|
+
this.getReg(t);
|
|
1217
|
+
this.emit(isSuper ? opcodes_js_1.Op.setsuper : opcodes_js_1.Op.setproperty, this.propertyName(target).mn);
|
|
1218
|
+
}
|
|
1219
|
+
else
|
|
1220
|
+
throw this.err('Invalid assignment target', target.pos);
|
|
1221
|
+
this.release(t);
|
|
1222
|
+
}
|
|
1223
|
+
assign(e, wantValue) {
|
|
1224
|
+
if (e.op) {
|
|
1225
|
+
if (e.op === '&&' || e.op === '||') {
|
|
1226
|
+
const op = e.op;
|
|
1227
|
+
this.modify(e.target, () => {
|
|
1228
|
+
const endL = this.newLabel();
|
|
1229
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1230
|
+
this.jump(op === '&&' ? opcodes_js_1.Op.iffalse : opcodes_js_1.Op.iftrue, endL);
|
|
1231
|
+
this.emit(opcodes_js_1.Op.pop);
|
|
1232
|
+
this.expr(e.v);
|
|
1233
|
+
this.place(endL);
|
|
1234
|
+
}, wantValue, false);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
this.modify(e.target, () => {
|
|
1238
|
+
this.expr(e.v);
|
|
1239
|
+
this.emit(binaryOp(e.op));
|
|
1240
|
+
}, wantValue, false);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const target = e.target;
|
|
1244
|
+
// Simple, common forms first.
|
|
1245
|
+
if (target.k === 'id' && !target.ns) {
|
|
1246
|
+
const v = this.lookupLocal(target.name);
|
|
1247
|
+
if (v) {
|
|
1248
|
+
this.expr(e.v);
|
|
1249
|
+
if (wantValue)
|
|
1250
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1251
|
+
this.storeLocal(v);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const mn = this.identName(target);
|
|
1255
|
+
this.emit(opcodes_js_1.Op.findproperty, mn);
|
|
1256
|
+
this.expr(e.v);
|
|
1257
|
+
const t = wantValue ? this.temp() : -1;
|
|
1258
|
+
if (wantValue) {
|
|
1259
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1260
|
+
this.setReg(t);
|
|
1261
|
+
}
|
|
1262
|
+
this.emit(this.options.constTargets?.has(target.name) ? opcodes_js_1.Op.initproperty : opcodes_js_1.Op.setproperty, mn);
|
|
1263
|
+
if (wantValue) {
|
|
1264
|
+
this.getReg(t);
|
|
1265
|
+
this.release(t);
|
|
1266
|
+
}
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (target.k === 'member' || target.k === 'index') {
|
|
1270
|
+
const isSuper = target.obj.k === 'super';
|
|
1271
|
+
if (isSuper)
|
|
1272
|
+
this.emit(opcodes_js_1.Op.getlocal0);
|
|
1273
|
+
else
|
|
1274
|
+
this.expr(target.obj);
|
|
1275
|
+
if (target.k === 'index')
|
|
1276
|
+
this.expr(target.index);
|
|
1277
|
+
this.expr(e.v);
|
|
1278
|
+
const t = wantValue ? this.temp() : -1;
|
|
1279
|
+
if (wantValue) {
|
|
1280
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1281
|
+
this.setReg(t);
|
|
1282
|
+
}
|
|
1283
|
+
const isInit = !isSuper && target.k === 'member' && !target.ns && target.obj.k === 'this' && this.options.constTargets?.has(target.name);
|
|
1284
|
+
this.emit(isSuper ? opcodes_js_1.Op.setsuper : isInit ? opcodes_js_1.Op.initproperty : opcodes_js_1.Op.setproperty, this.propertyName(target).mn);
|
|
1285
|
+
if (wantValue) {
|
|
1286
|
+
this.getReg(t);
|
|
1287
|
+
this.release(t);
|
|
1288
|
+
}
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
throw this.err('Invalid assignment target', target.pos);
|
|
1292
|
+
}
|
|
1293
|
+
binary(e) {
|
|
1294
|
+
if (e.op === '&&' || e.op === '||') {
|
|
1295
|
+
const endL = this.newLabel();
|
|
1296
|
+
this.expr(e.a);
|
|
1297
|
+
this.emit(opcodes_js_1.Op.coerce_a);
|
|
1298
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1299
|
+
this.jump(e.op === '&&' ? opcodes_js_1.Op.iffalse : opcodes_js_1.Op.iftrue, endL);
|
|
1300
|
+
this.emit(opcodes_js_1.Op.pop);
|
|
1301
|
+
this.expr(e.b);
|
|
1302
|
+
this.emit(opcodes_js_1.Op.coerce_a);
|
|
1303
|
+
this.place(endL);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (e.op === 'as' || e.op === 'is') {
|
|
1307
|
+
this.expr(e.a);
|
|
1308
|
+
if (e.b.k === 'id' && e.b.name === '*') {
|
|
1309
|
+
if (e.op === 'is') {
|
|
1310
|
+
this.emit(opcodes_js_1.Op.pop);
|
|
1311
|
+
this.emit(opcodes_js_1.Op.pushtrue);
|
|
1312
|
+
}
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
this.expr(e.b);
|
|
1316
|
+
this.emit(e.op === 'as' ? opcodes_js_1.Op.astypelate : opcodes_js_1.Op.istypelate);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (e.op === '!=' || e.op === '!==') {
|
|
1320
|
+
this.expr(e.a);
|
|
1321
|
+
this.expr(e.b);
|
|
1322
|
+
this.emit(e.op === '!=' ? opcodes_js_1.Op.equals : opcodes_js_1.Op.strictequals);
|
|
1323
|
+
this.emit(opcodes_js_1.Op.not);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
this.expr(e.a);
|
|
1327
|
+
this.expr(e.b);
|
|
1328
|
+
this.emit(binaryOp(e.op));
|
|
1329
|
+
}
|
|
1330
|
+
/** Jumps to `label` when `e` is truthy (`onTrue`) or falsy. */
|
|
1331
|
+
condJump(e, onTrue, label) {
|
|
1332
|
+
if (e.k === 'unary' && e.op === '!')
|
|
1333
|
+
return this.condJump(e.e, !onTrue, label);
|
|
1334
|
+
if (e.k === 'binary' && (e.op === '&&' || e.op === '||')) {
|
|
1335
|
+
const isAnd = e.op === '&&';
|
|
1336
|
+
if (isAnd !== onTrue) {
|
|
1337
|
+
// (a && b) false → a false or b false; (a || b) true → a true or b true
|
|
1338
|
+
this.condJump(e.a, onTrue, label);
|
|
1339
|
+
this.condJump(e.b, onTrue, label);
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
const skip = this.newLabel();
|
|
1343
|
+
this.condJump(e.a, !onTrue, skip);
|
|
1344
|
+
this.condJump(e.b, onTrue, label);
|
|
1345
|
+
this.place(skip);
|
|
1346
|
+
}
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
if (e.k === 'binary') {
|
|
1350
|
+
const cmp = {
|
|
1351
|
+
'==': [opcodes_js_1.Op.ifeq, opcodes_js_1.Op.ifne],
|
|
1352
|
+
'!=': [opcodes_js_1.Op.ifne, opcodes_js_1.Op.ifeq],
|
|
1353
|
+
'===': [opcodes_js_1.Op.ifstricteq, opcodes_js_1.Op.ifstrictne],
|
|
1354
|
+
'!==': [opcodes_js_1.Op.ifstrictne, opcodes_js_1.Op.ifstricteq],
|
|
1355
|
+
'<': [opcodes_js_1.Op.iflt, opcodes_js_1.Op.ifnlt],
|
|
1356
|
+
'<=': [opcodes_js_1.Op.ifle, opcodes_js_1.Op.ifnle],
|
|
1357
|
+
'>': [opcodes_js_1.Op.ifgt, opcodes_js_1.Op.ifngt],
|
|
1358
|
+
'>=': [opcodes_js_1.Op.ifge, opcodes_js_1.Op.ifnge],
|
|
1359
|
+
};
|
|
1360
|
+
const pair = cmp[e.op];
|
|
1361
|
+
if (pair) {
|
|
1362
|
+
this.expr(e.a);
|
|
1363
|
+
this.expr(e.b);
|
|
1364
|
+
this.jump(onTrue ? pair[0] : pair[1], label);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (e.k === 'bool') {
|
|
1369
|
+
if (e.v === onTrue)
|
|
1370
|
+
this.jump(opcodes_js_1.Op.jump, label);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
this.expr(e);
|
|
1374
|
+
this.jump(onTrue ? opcodes_js_1.Op.iftrue : opcodes_js_1.Op.iffalse, label);
|
|
1375
|
+
}
|
|
1376
|
+
/** E4X filter: `xml.(cond)`. */
|
|
1377
|
+
filter(e) {
|
|
1378
|
+
const listReg = this.temp();
|
|
1379
|
+
const idxReg = this.temp();
|
|
1380
|
+
const resReg = this.temp();
|
|
1381
|
+
this.expr(e.obj);
|
|
1382
|
+
this.emit(opcodes_js_1.Op.checkfilter);
|
|
1383
|
+
this.setReg(listReg);
|
|
1384
|
+
this.emit(opcodes_js_1.Op.pushbyte, 0);
|
|
1385
|
+
this.setReg(idxReg);
|
|
1386
|
+
this.emit(opcodes_js_1.Op.getlex, this.publicQName('XMLList'));
|
|
1387
|
+
this.emit(opcodes_js_1.Op.pushstring, this.pool.string(''));
|
|
1388
|
+
this.emit(opcodes_js_1.Op.construct, 1);
|
|
1389
|
+
this.setReg(resReg);
|
|
1390
|
+
const condL = this.newLabel();
|
|
1391
|
+
const bodyL = this.newLabel();
|
|
1392
|
+
const skipL = this.newLabel();
|
|
1393
|
+
this.jump(opcodes_js_1.Op.jump, condL);
|
|
1394
|
+
this.place(bodyL);
|
|
1395
|
+
this.emit(opcodes_js_1.Op.label);
|
|
1396
|
+
this.getReg(listReg);
|
|
1397
|
+
this.getReg(idxReg);
|
|
1398
|
+
this.emit(opcodes_js_1.Op.nextvalue);
|
|
1399
|
+
this.emit(opcodes_js_1.Op.dup);
|
|
1400
|
+
const itemReg = this.temp();
|
|
1401
|
+
this.setReg(itemReg);
|
|
1402
|
+
this.emit(opcodes_js_1.Op.pushwith);
|
|
1403
|
+
this.condJump(e.cond, false, skipL);
|
|
1404
|
+
this.getReg(resReg);
|
|
1405
|
+
this.getReg(idxReg);
|
|
1406
|
+
this.getReg(itemReg);
|
|
1407
|
+
this.emit(opcodes_js_1.Op.setproperty, this.mnLate());
|
|
1408
|
+
this.place(skipL);
|
|
1409
|
+
this.emit(opcodes_js_1.Op.popscope);
|
|
1410
|
+
this.place(condL);
|
|
1411
|
+
this.emit(opcodes_js_1.Op.hasnext2, listReg, idxReg);
|
|
1412
|
+
this.jump(opcodes_js_1.Op.iftrue, bodyL);
|
|
1413
|
+
this.getReg(resReg);
|
|
1414
|
+
for (const r of [itemReg, resReg, idxReg, listReg])
|
|
1415
|
+
this.release(r);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
exports.FunctionCompiler = FunctionCompiler;
|
|
1419
|
+
function binaryOp(op) {
|
|
1420
|
+
const map = {
|
|
1421
|
+
'+': opcodes_js_1.Op.add,
|
|
1422
|
+
'-': opcodes_js_1.Op.subtract,
|
|
1423
|
+
'*': opcodes_js_1.Op.multiply,
|
|
1424
|
+
'/': opcodes_js_1.Op.divide,
|
|
1425
|
+
'%': opcodes_js_1.Op.modulo,
|
|
1426
|
+
'<<': opcodes_js_1.Op.lshift,
|
|
1427
|
+
'>>': opcodes_js_1.Op.rshift,
|
|
1428
|
+
'>>>': opcodes_js_1.Op.urshift,
|
|
1429
|
+
'&': opcodes_js_1.Op.bitand,
|
|
1430
|
+
'|': opcodes_js_1.Op.bitor,
|
|
1431
|
+
'^': opcodes_js_1.Op.bitxor,
|
|
1432
|
+
'==': opcodes_js_1.Op.equals,
|
|
1433
|
+
'===': opcodes_js_1.Op.strictequals,
|
|
1434
|
+
'<': opcodes_js_1.Op.lessthan,
|
|
1435
|
+
'<=': opcodes_js_1.Op.lessequals,
|
|
1436
|
+
'>': opcodes_js_1.Op.greaterthan,
|
|
1437
|
+
'>=': opcodes_js_1.Op.greaterequals,
|
|
1438
|
+
instanceof: opcodes_js_1.Op.instanceof,
|
|
1439
|
+
in: opcodes_js_1.Op.in,
|
|
1440
|
+
};
|
|
1441
|
+
const code = map[op];
|
|
1442
|
+
if (code === undefined)
|
|
1443
|
+
throw new Error(`Unsupported binary operator '${op}'`);
|
|
1444
|
+
return code;
|
|
1445
|
+
}
|
|
1446
|
+
// ---------------------------------------------------------------------------
|
|
1447
|
+
// AST queries
|
|
1448
|
+
// ---------------------------------------------------------------------------
|
|
1449
|
+
function forEachChildStmt(s, f) {
|
|
1450
|
+
switch (s.k) {
|
|
1451
|
+
case 'if':
|
|
1452
|
+
f(s.then);
|
|
1453
|
+
if (s.else)
|
|
1454
|
+
f(s.else);
|
|
1455
|
+
break;
|
|
1456
|
+
case 'while':
|
|
1457
|
+
case 'dowhile':
|
|
1458
|
+
case 'labeled':
|
|
1459
|
+
case 'with':
|
|
1460
|
+
f(s.body);
|
|
1461
|
+
break;
|
|
1462
|
+
case 'for':
|
|
1463
|
+
if (s.init)
|
|
1464
|
+
f(s.init);
|
|
1465
|
+
f(s.body);
|
|
1466
|
+
break;
|
|
1467
|
+
case 'forin':
|
|
1468
|
+
f(s.body);
|
|
1469
|
+
break;
|
|
1470
|
+
case 'block':
|
|
1471
|
+
s.body.forEach(f);
|
|
1472
|
+
break;
|
|
1473
|
+
case 'switch':
|
|
1474
|
+
for (const c of s.cases)
|
|
1475
|
+
c.body.forEach(f);
|
|
1476
|
+
break;
|
|
1477
|
+
case 'try':
|
|
1478
|
+
s.body.forEach(f);
|
|
1479
|
+
for (const c of s.catches)
|
|
1480
|
+
c.body.forEach(f);
|
|
1481
|
+
s.finally?.forEach(f);
|
|
1482
|
+
break;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
/** Collects hoisted `var` declarations (and function declarations) of a function body. */
|
|
1486
|
+
function collectVars(list, out) {
|
|
1487
|
+
const visit = (s) => {
|
|
1488
|
+
if (s.k === 'var')
|
|
1489
|
+
out.push(...s.decls);
|
|
1490
|
+
else if (s.k === 'forin' && s.decl)
|
|
1491
|
+
out.push(s.decl);
|
|
1492
|
+
else if (s.k === 'function')
|
|
1493
|
+
out.push({ name: s.fn.name, pos: s.pos, type: { name: 'Function', pos: s.pos } });
|
|
1494
|
+
forEachChildStmt(s, visit);
|
|
1495
|
+
};
|
|
1496
|
+
list.forEach(visit);
|
|
1497
|
+
}
|
|
1498
|
+
function walkAllExprs(list, f) {
|
|
1499
|
+
const ex = (e) => {
|
|
1500
|
+
if (!e)
|
|
1501
|
+
return;
|
|
1502
|
+
if (f(e) === false)
|
|
1503
|
+
return;
|
|
1504
|
+
for (const v of Object.values(e)) {
|
|
1505
|
+
if (Array.isArray(v)) {
|
|
1506
|
+
for (const x of v) {
|
|
1507
|
+
if (x && typeof x === 'object' && 'k' in x && 'pos' in x)
|
|
1508
|
+
ex(x);
|
|
1509
|
+
else if (x && typeof x === 'object' && 'value' in x)
|
|
1510
|
+
ex(x.value);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
else if (v && typeof v === 'object' && 'k' in v && 'pos' in v)
|
|
1514
|
+
ex(v);
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
const st = (s) => {
|
|
1518
|
+
switch (s.k) {
|
|
1519
|
+
case 'expr':
|
|
1520
|
+
case 'throw':
|
|
1521
|
+
ex(s.e);
|
|
1522
|
+
break;
|
|
1523
|
+
case 'return':
|
|
1524
|
+
ex(s.e);
|
|
1525
|
+
break;
|
|
1526
|
+
case 'var':
|
|
1527
|
+
s.decls.forEach((d) => ex(d.init));
|
|
1528
|
+
break;
|
|
1529
|
+
case 'if':
|
|
1530
|
+
case 'while':
|
|
1531
|
+
case 'dowhile':
|
|
1532
|
+
ex(s.c);
|
|
1533
|
+
break;
|
|
1534
|
+
case 'for':
|
|
1535
|
+
ex(s.c);
|
|
1536
|
+
ex(s.update);
|
|
1537
|
+
break;
|
|
1538
|
+
case 'forin':
|
|
1539
|
+
ex(s.obj);
|
|
1540
|
+
ex(s.target);
|
|
1541
|
+
break;
|
|
1542
|
+
case 'switch':
|
|
1543
|
+
ex(s.disc);
|
|
1544
|
+
s.cases.forEach((c) => ex(c.test));
|
|
1545
|
+
break;
|
|
1546
|
+
case 'with':
|
|
1547
|
+
ex(s.obj);
|
|
1548
|
+
break;
|
|
1549
|
+
case 'dxns':
|
|
1550
|
+
ex(s.e);
|
|
1551
|
+
break;
|
|
1552
|
+
case 'function':
|
|
1553
|
+
f({ k: 'function', fn: s.fn, pos: s.pos });
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
forEachChildStmt(s, st);
|
|
1557
|
+
};
|
|
1558
|
+
list.forEach(st);
|
|
1559
|
+
}
|
|
1560
|
+
function containsFunction(list) {
|
|
1561
|
+
let found = false;
|
|
1562
|
+
walkAllExprs(list, (e) => {
|
|
1563
|
+
if (e.k === 'function') {
|
|
1564
|
+
found = true;
|
|
1565
|
+
return false;
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
return found;
|
|
1569
|
+
}
|
|
1570
|
+
function usesArguments(list) {
|
|
1571
|
+
let found = false;
|
|
1572
|
+
walkAllExprs(list, (e) => {
|
|
1573
|
+
if (e.k === 'function')
|
|
1574
|
+
return false;
|
|
1575
|
+
if (e.k === 'id' && e.name === 'arguments')
|
|
1576
|
+
found = true;
|
|
1577
|
+
});
|
|
1578
|
+
return found;
|
|
1579
|
+
}
|
|
1580
|
+
function hasSuperCall(list) {
|
|
1581
|
+
let found = false;
|
|
1582
|
+
walkAllExprs(list, (e) => {
|
|
1583
|
+
if (e.k === 'function')
|
|
1584
|
+
return false;
|
|
1585
|
+
if (e.k === 'call' && e.fn.k === 'super')
|
|
1586
|
+
found = true;
|
|
1587
|
+
});
|
|
1588
|
+
return found;
|
|
1589
|
+
}
|