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,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
+ }