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