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,440 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileClassSource = compileClassSource;
4
+ const abc_file_js_1 = require("../abc/abc-file.js");
5
+ const constants_js_1 = require("../abc/constants.js");
6
+ const code_js_1 = require("../abc/code.js");
7
+ const analysis_js_1 = require("../abc/analysis.js");
8
+ const interner_js_1 = require("../abc/interner.js");
9
+ const opcodes_js_1 = require("../abc/opcodes.js");
10
+ const class_printer_js_1 = require("../decompiler/class-printer.js");
11
+ const codegen_js_1 = require("./codegen.js");
12
+ const lexer_js_1 = require("./lexer.js");
13
+ const parser_js_1 = require("./parser.js");
14
+ const scope_js_1 = require("./scope.js");
15
+ function normalizedSpan(src, span) {
16
+ try {
17
+ return (0, lexer_js_1.tokenize)(src.slice(span[0], span[1]))
18
+ .map((t) => `${t.kind}:${t.value}${t.flags ?? ''}`)
19
+ .join(' ');
20
+ }
21
+ catch {
22
+ return src.slice(span[0], span[1]);
23
+ }
24
+ }
25
+ function memberKey(m) {
26
+ if (m.k === 'field')
27
+ return `${m.isStatic ? 's' : 'i'}:field:${m.name}`;
28
+ if (m.k === 'static')
29
+ return 'static-block';
30
+ return `${m.isStatic ? 's' : 'i'}:${m.kind}:${m.name}`;
31
+ }
32
+ const TRAIT_KIND = { method: constants_js_1.TraitKind.Method, get: constants_js_1.TraitKind.Getter, set: constants_js_1.TraitKind.Setter };
33
+ function isConstant(e) {
34
+ return e.k === 'num' || e.k === 'str' || e.k === 'bool' || e.k === 'null' || e.k === 'undefined' || (e.k === 'id' && (e.name === 'NaN' || e.name === 'Infinity'));
35
+ }
36
+ /** Compiles every class in `source` into `abc`. */
37
+ function compileClassSource(abc, source, options = {}) {
38
+ const unit = (0, parser_js_1.parseAs3)(source, options.source);
39
+ const pool = options.interner ?? new interner_js_1.Interner(abc);
40
+ const result = { changes: [] };
41
+ for (const cls of unit.classes) {
42
+ const qualified = unit.package ? `${unit.package}.${cls.name}` : cls.name;
43
+ let ci = options.classIndex !== undefined && unit.classes.length === 1 ? options.classIndex : abc.findClass(qualified);
44
+ if (options.classIndex !== undefined && abc.className(ci) !== qualified) {
45
+ throw new lexer_js_1.CompileError(`Class index ${ci} is ${abc.className(ci)}, not ${qualified}`, cls.pos.line, cls.pos.col, options.source);
46
+ }
47
+ let isNew = false;
48
+ if (ci < 0) {
49
+ ci = createClass(abc, pool, unit, cls, options);
50
+ isNew = true;
51
+ result.changes.push({ className: qualified, member: '<class>', action: 'added' });
52
+ }
53
+ let baselineMembers = new Map();
54
+ if (isNew) {
55
+ // Everything is new; only the default constructor / cinit exist.
56
+ baselineMembers.set('static-block', '');
57
+ }
58
+ else if (options.baseline !== null) {
59
+ const baseText = options.baseline ?? (0, class_printer_js_1.decompileClass)(abc, ci);
60
+ try {
61
+ const baseUnit = (0, parser_js_1.parseAs3)(baseText);
62
+ const baseCls = baseUnit.classes.find((c) => c.name === cls.name);
63
+ if (baseCls)
64
+ baselineMembers = memberTexts(baseText, baseCls);
65
+ }
66
+ catch {
67
+ baselineMembers = new Map();
68
+ }
69
+ }
70
+ compileIntoClass(abc, pool, unit, cls, ci, source, baselineMembers, options, result);
71
+ }
72
+ return result;
73
+ }
74
+ function memberTexts(src, cls) {
75
+ const map = new Map();
76
+ const statics = [];
77
+ for (const m of cls.members) {
78
+ const text = normalizedSpan(src, m.span);
79
+ if (m.k === 'static')
80
+ statics.push(text);
81
+ else if (m.k === 'field' && m.isStatic) {
82
+ map.set(memberKey(m), text);
83
+ // Only non-constant initialisers run in the static initialiser.
84
+ if (m.init && !isConstant(m.init))
85
+ statics.push(text);
86
+ }
87
+ else
88
+ map.set(memberKey(m), text);
89
+ }
90
+ map.set('static-block', statics.join('\n'));
91
+ return map;
92
+ }
93
+ function compileIntoClass(abc, pool, unit, cls, ci, src, baseline, options, result) {
94
+ const inst = abc.instances[ci];
95
+ const klass = abc.classes[ci];
96
+ const className = abc.className(ci);
97
+ const memberNames = new Set();
98
+ for (const t of [...inst.traits, ...klass.traits])
99
+ memberNames.add(abc.multinameName(t.name));
100
+ for (const m of cls.members)
101
+ if (m.k !== 'static')
102
+ memberNames.add(m.name);
103
+ const scope = (0, scope_js_1.buildClassScope)({
104
+ abc,
105
+ pool,
106
+ pkg: unit.package,
107
+ imports: unit.imports,
108
+ useNamespaces: unit.useNamespaces,
109
+ classIndex: ci,
110
+ memberNames,
111
+ source: options.source,
112
+ });
113
+ const edited = memberTexts(src, cls);
114
+ const changed = (m) => baseline.get(memberKey(m)) !== edited.get(memberKey(m));
115
+ // Scope depths taken from existing bodies.
116
+ const iinitBody = abc.methodBody(inst.iinit);
117
+ const cinitBody = abc.methodBody(klass.cinit);
118
+ const instanceDepth = iinitBody?.initScopeDepth ?? (cinitBody ? cinitBody.initScopeDepth + 1 : 1);
119
+ const findTrait = (traits, name, kinds) => traits.find((t) => kinds.includes(t.kind) && abc.multinameName(t.name) === name);
120
+ // ---- Fields --------------------------------------------------------------
121
+ const newInstanceInits = [];
122
+ for (const m of cls.members) {
123
+ if (m.k !== 'field')
124
+ continue;
125
+ const traits = m.isStatic ? klass.traits : inst.traits;
126
+ const existing = findTrait(traits, m.name, [constants_js_1.TraitKind.Slot, constants_js_1.TraitKind.Const]);
127
+ if (existing && !changed(m))
128
+ continue;
129
+ const typeName = m.type ? scope.resolveType(m.type) : 0;
130
+ const fc = new codegen_js_1.FunctionCompiler(scope, { params: [], pos: m.pos }, { initScopeDepth: 0 });
131
+ const value = m.init && isConstant(m.init) ? fc.constantValue(m.init) : undefined;
132
+ if (existing) {
133
+ existing.typeName = typeName;
134
+ existing.kind = m.isConst ? constants_js_1.TraitKind.Const : constants_js_1.TraitKind.Slot;
135
+ existing.valueKind = value?.kind ?? 0;
136
+ existing.valueIndex = value ? value.value || 0 : 0;
137
+ if (value && value.value === 0 && value.kind !== 0)
138
+ existing.valueIndex = value.kind;
139
+ result.changes.push({ className, member: m.name, action: 'replaced' });
140
+ }
141
+ else {
142
+ const ns = nsFor(scope, m.isStatic && m.access === 'protected' ? 'static protected' : m.access, inst.protectedNs, m.pos);
143
+ const t = (0, abc_file_js_1.newTrait)({ kind: m.isConst ? constants_js_1.TraitKind.Const : constants_js_1.TraitKind.Slot, name: pool.qname(ns, m.name), typeName });
144
+ if (value) {
145
+ t.valueKind = value.kind;
146
+ t.valueIndex = value.value || value.kind;
147
+ }
148
+ traits.push(t);
149
+ result.changes.push({ className, member: m.name, action: 'added' });
150
+ }
151
+ if (m.init && !isConstant(m.init) && !m.isStatic)
152
+ newInstanceInits.push(m);
153
+ }
154
+ // ---- Static initialiser --------------------------------------------------
155
+ const staticChanged = baseline.get('static-block') !== edited.get('static-block');
156
+ if (staticChanged) {
157
+ const body = [];
158
+ for (const m of cls.members) {
159
+ if (m.k === 'static')
160
+ body.push(...m.body);
161
+ else if (m.k === 'field' && m.isStatic && m.init && !isConstant(m.init)) {
162
+ body.push({ k: 'expr', pos: m.pos, e: { k: 'assign', op: '', target: { k: 'id', name: m.name, pos: m.pos }, v: m.init, pos: m.pos } });
163
+ }
164
+ }
165
+ const staticConsts = new Set();
166
+ for (const t of klass.traits)
167
+ if (t.kind === constants_js_1.TraitKind.Const)
168
+ staticConsts.add(abc.multinameName(t.name));
169
+ const fc = new codegen_js_1.FunctionCompiler(scope, { params: [], body, pos: cls.pos }, {
170
+ constTargets: staticConsts,
171
+ methodIndex: klass.cinit,
172
+ initScopeDepth: cinitBody?.initScopeDepth ?? Math.max(0, instanceDepth - 1),
173
+ name: '',
174
+ });
175
+ fc.compile();
176
+ result.changes.push({ className, member: '<static initializer>', action: 'replaced', methodIndex: klass.cinit });
177
+ }
178
+ // ---- Methods -------------------------------------------------------------
179
+ const instanceConsts = new Set();
180
+ for (const t of inst.traits)
181
+ if (t.kind === constants_js_1.TraitKind.Const)
182
+ instanceConsts.add(abc.multinameName(t.name));
183
+ /** Instance field initialisers run at the start of the constructor, before `super()` (as ASC does). */
184
+ const fieldInitPrologue = (f) => {
185
+ for (const fld of newInstanceInits) {
186
+ f.emit(opcodes_js_1.Op.getlocal0);
187
+ f.expr(fld.init);
188
+ if (fld.type)
189
+ f.coerceTo(scope.resolveType(fld.type));
190
+ f.emit(opcodes_js_1.Op.initproperty, f.mn(fld.name));
191
+ }
192
+ };
193
+ const compileConstructor = (fn) => {
194
+ const fc = new codegen_js_1.FunctionCompiler(scope, fn, {
195
+ constTargets: instanceConsts,
196
+ methodIndex: inst.iinit,
197
+ initScopeDepth: instanceDepth,
198
+ name: abc.strings[abc.methods[inst.iinit]?.name ?? 0] ?? '',
199
+ isConstructor: true,
200
+ prologue: fieldInitPrologue,
201
+ });
202
+ fc.compile();
203
+ result.changes.push({ className, member: '<constructor>', action: 'replaced', methodIndex: inst.iinit });
204
+ };
205
+ const hasConstructor = cls.members.some((m) => m.k === 'method' && m.kind === 'constructor');
206
+ if (!hasConstructor && newInstanceInits.length > 0 && !cls.isInterface) {
207
+ // No constructor in the source: synthesise the implicit one so the
208
+ // initialisers are not lost. Only safe when the existing constructor is
209
+ // the compiler-generated default (always true for new classes).
210
+ if (!isDefaultConstructor(abc, inst.iinit)) {
211
+ const f = newInstanceInits[0];
212
+ throw new lexer_js_1.CompileError(`Field '${f.name}' has an initialiser that must run in the constructor, but the source of ${className} has no constructor ` +
213
+ 'and the existing one contains code. Add the constructor to the source.', f.pos.line, f.pos.col, options.source);
214
+ }
215
+ compileConstructor({ params: [], body: [], pos: cls.pos });
216
+ }
217
+ for (const m of cls.members) {
218
+ if (m.k !== 'method')
219
+ continue;
220
+ if (m.kind === 'constructor') {
221
+ if (!changed(m) && newInstanceInits.length === 0)
222
+ continue;
223
+ compileConstructor(m.fn);
224
+ continue;
225
+ }
226
+ if (!changed(m))
227
+ continue;
228
+ // Never replace bytecode with the empty body of a method the decompiler could not handle.
229
+ if (src.slice(m.span[0], m.span[1]).includes('// Decompilation failed') && !(m.fn.body ?? []).length)
230
+ continue;
231
+ if (cls.isInterface) {
232
+ const kind = TRAIT_KIND[m.kind];
233
+ if (findTrait(inst.traits, m.name, [kind]))
234
+ continue;
235
+ const sig = new codegen_js_1.FunctionCompiler(scope, { ...m.fn, body: undefined }, { initScopeDepth: 0, name: m.name }).method;
236
+ const idx = abc.methods.length;
237
+ abc.methods.push(sig);
238
+ const ifaceNs = pool.namespace(constants_js_1.NamespaceKind.Namespace, unit.package ? `${unit.package}:${cls.name}` : cls.name);
239
+ inst.traits.push((0, abc_file_js_1.newTrait)({ kind, name: pool.qname(ifaceNs, m.name), method: idx }));
240
+ result.changes.push({ className, member: describe(m), action: 'added', methodIndex: idx });
241
+ continue;
242
+ }
243
+ if (m.isNative || !m.fn.body) {
244
+ throw new lexer_js_1.CompileError(`Method ${m.name} has no body`, m.pos.line, m.pos.col, options.source);
245
+ }
246
+ const traits = m.isStatic ? klass.traits : inst.traits;
247
+ const kind = TRAIT_KIND[m.kind];
248
+ const existing = findTrait(traits, m.name, [kind]);
249
+ const depthFromExisting = existing ? abc.methodBody(existing.method)?.initScopeDepth : undefined;
250
+ const fc = new codegen_js_1.FunctionCompiler(scope, m.fn, {
251
+ methodIndex: existing?.method,
252
+ initScopeDepth: depthFromExisting ?? instanceDepth,
253
+ name: m.name,
254
+ });
255
+ const idx = fc.compile();
256
+ if (existing) {
257
+ result.changes.push({ className, member: describe(m), action: 'replaced', methodIndex: idx });
258
+ }
259
+ else {
260
+ const ns = nsFor(scope, m.isStatic && m.access === 'protected' ? 'static protected' : m.access, inst.protectedNs, m.pos);
261
+ const t = (0, abc_file_js_1.newTrait)({ kind, name: pool.qname(ns, m.name), method: idx });
262
+ if (m.isOverride)
263
+ t.attrs |= constants_js_1.TraitAttr.Override;
264
+ if (m.isFinal)
265
+ t.attrs |= constants_js_1.TraitAttr.Final;
266
+ traits.push(t);
267
+ result.changes.push({ className, member: describe(m), action: 'added', methodIndex: idx });
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * True when the constructor only does `super()` (what compilers generate for
273
+ * classes without an explicit constructor).
274
+ */
275
+ function isDefaultConstructor(abc, methodIndex) {
276
+ const body = abc.methodBody(methodIndex);
277
+ if (!body)
278
+ return true;
279
+ let items;
280
+ try {
281
+ items = (0, code_js_1.decodeCode)(body.code, body.exceptions).items;
282
+ }
283
+ catch {
284
+ return false;
285
+ }
286
+ if (items.some((i) => i.type === 'bytes'))
287
+ return false;
288
+ const ignored = new Set([opcodes_js_1.Op.label, opcodes_js_1.Op.nop, opcodes_js_1.Op.debug, opcodes_js_1.Op.debugline, opcodes_js_1.Op.debugfile, opcodes_js_1.Op.kill]);
289
+ const seq = items.filter((i) => i.type === 'op' && !ignored.has(i.op));
290
+ const expected = [opcodes_js_1.Op.getlocal0, opcodes_js_1.Op.pushscope, opcodes_js_1.Op.getlocal0, opcodes_js_1.Op.constructsuper, opcodes_js_1.Op.returnvoid];
291
+ return seq.length === expected.length && seq.every((i, k) => i.op === expected[k] && (i.op !== opcodes_js_1.Op.constructsuper || i.args[0] === 0));
292
+ }
293
+ function describe(m) {
294
+ return `${m.isStatic ? 'static ' : ''}${m.kind === 'get' ? 'get ' : m.kind === 'set' ? 'set ' : ''}${m.name}`;
295
+ }
296
+ function nsFor(scope, access, protectedNs, pos) {
297
+ if (access === 'protected' && !protectedNs)
298
+ throw new lexer_js_1.CompileError('Class has no protected namespace', pos.line, pos.col, scope.source);
299
+ try {
300
+ return (0, scope_js_1.memberNamespace)(scope, access);
301
+ }
302
+ catch (e) {
303
+ throw new lexer_js_1.CompileError(e.message, pos.line, pos.col, scope.source);
304
+ }
305
+ }
306
+ // ---------------------------------------------------------------------------
307
+ // New classes
308
+ // ---------------------------------------------------------------------------
309
+ /**
310
+ * Superclass chains (Object first) of classes extended somewhere in the ABC,
311
+ * harvested from existing script initialisers (`getlex A; pushscope; ... newclass`).
312
+ */
313
+ function knownChains(abc) {
314
+ const chains = new Map();
315
+ for (const s of abc.scripts) {
316
+ const body = abc.methodBody(s.init);
317
+ if (!body)
318
+ continue;
319
+ let items;
320
+ try {
321
+ items = (0, code_js_1.decodeCode)(body.code, body.exceptions).items;
322
+ }
323
+ catch {
324
+ continue;
325
+ }
326
+ let chain = [];
327
+ for (const it of items) {
328
+ if (it.type !== 'op')
329
+ continue;
330
+ if (it.op === opcodes_js_1.Op.getlex)
331
+ chain.push(it.args[0]);
332
+ else if (it.op === opcodes_js_1.Op.newclass) {
333
+ // Last getlex is the base class value itself (pushed again for newclass).
334
+ const unique = chain.filter((x, i) => i === 0 || x !== chain[i - 1]);
335
+ if (unique.length)
336
+ chains.set(abc.qualifiedName(unique[unique.length - 1]), unique);
337
+ const inst = abc.instances[it.args[0]];
338
+ if (inst)
339
+ chains.set(abc.qualifiedName(inst.name), [...unique, inst.name]);
340
+ chain = [];
341
+ }
342
+ else if (it.op === opcodes_js_1.Op.popscope || it.op === opcodes_js_1.Op.initproperty)
343
+ chain = [];
344
+ }
345
+ }
346
+ return chains;
347
+ }
348
+ function createClass(abc, pool, unit, cls, options) {
349
+ const pkg = unit.package;
350
+ const nsKind = cls.access === 'internal' ? constants_js_1.NamespaceKind.PackageInternalNs : constants_js_1.NamespaceKind.PackageNamespace;
351
+ const nameMn = pool.qname(pool.namespace(nsKind, pkg), cls.name);
352
+ const ci = abc.instances.length;
353
+ const protectedNs = pool.namespace(constants_js_1.NamespaceKind.ProtectedNamespace, pkg ? `${pkg}:${cls.name}` : cls.name);
354
+ // Provisional scope (for type resolution of extends / implements).
355
+ abc.instances.push({ name: nameMn, superName: 0, flags: 0, protectedNs,
356
+ interfaces: [], iinit: 0, traits: [] });
357
+ abc.classes.push({ cinit: 0, traits: [] });
358
+ const scope = (0, scope_js_1.buildClassScope)({
359
+ abc,
360
+ pool,
361
+ pkg,
362
+ imports: unit.imports,
363
+ useNamespaces: unit.useNamespaces,
364
+ classIndex: ci,
365
+ memberNames: new Set(cls.members.filter((m) => m.k !== 'static').map((m) => m.name)),
366
+ source: options.source,
367
+ });
368
+ const inst = abc.instances[ci];
369
+ const klass = abc.classes[ci];
370
+ if (cls.isInterface) {
371
+ inst.flags = constants_js_1.InstanceFlags.Interface | constants_js_1.InstanceFlags.Sealed;
372
+ inst.superName = 0;
373
+ inst.protectedNs = 0;
374
+ }
375
+ else {
376
+ inst.flags = (cls.isDynamic ? 0 : constants_js_1.InstanceFlags.Sealed) | (cls.isFinal ? constants_js_1.InstanceFlags.Final : 0) | constants_js_1.InstanceFlags.ProtectedNs;
377
+ inst.superName = cls.extends ? scope.resolveType(cls.extends) : pool.qname(pool.namespace(constants_js_1.NamespaceKind.PackageNamespace, ''), 'Object');
378
+ }
379
+ inst.interfaces = cls.implements.map((t) => scope.resolveType(t));
380
+ // Superclass chain for the script initialiser.
381
+ const objectMn = pool.qname(pool.namespace(constants_js_1.NamespaceKind.PackageNamespace, ''), 'Object');
382
+ let chain;
383
+ if (cls.isInterface)
384
+ chain = [];
385
+ else {
386
+ const known = knownChains(abc).get(abc.qualifiedName(inst.superName));
387
+ chain = known ?? (abc.qualifiedName(inst.superName) === 'Object' ? [objectMn] : [objectMn, inst.superName]);
388
+ }
389
+ const n = chain.length;
390
+ // Default constructor and static initialiser (members may replace them).
391
+ const empty = (isCtor) => new codegen_js_1.FunctionCompiler(scope, { params: [], body: [], pos: cls.pos }, { initScopeDepth: isCtor ? 3 + n : 2 + n, isConstructor: isCtor && !cls.isInterface, name: isCtor ? cls.name : '' });
392
+ inst.iinit = cls.isInterface ? addSignatureOnly(abc, scope, cls) : empty(true).compile();
393
+ klass.cinit = empty(false).compile();
394
+ // Script initialiser.
395
+ const items = [];
396
+ const op = (code, ...args) => {
397
+ items.push((0, code_js_1.insn)(code, args));
398
+ };
399
+ op(opcodes_js_1.Op.getlocal0);
400
+ op(opcodes_js_1.Op.pushscope);
401
+ op(opcodes_js_1.Op.getscopeobject, 0);
402
+ if (cls.isInterface)
403
+ op(opcodes_js_1.Op.pushnull);
404
+ else {
405
+ for (const c of chain) {
406
+ op(opcodes_js_1.Op.getlex, c);
407
+ op(opcodes_js_1.Op.pushscope);
408
+ }
409
+ op(opcodes_js_1.Op.getlex, inst.superName);
410
+ }
411
+ op(opcodes_js_1.Op.newclass, ci);
412
+ for (let i = 0; i < n; i++)
413
+ op(opcodes_js_1.Op.popscope);
414
+ op(opcodes_js_1.Op.initproperty, nameMn);
415
+ op(opcodes_js_1.Op.returnvoid);
416
+ const encoded = (0, code_js_1.encodeCode)(items);
417
+ const limits = (0, analysis_js_1.computeLimits)(abc, items);
418
+ const initMethod = abc.methods.length;
419
+ abc.methods.push({ paramTypes: [], returnType: 0, name: 0, flags: 0, optional: [], paramNames: [] });
420
+ abc.bodies.push({
421
+ method: initMethod,
422
+ maxStack: limits.maxStack,
423
+ localCount: 1,
424
+ initScopeDepth: 1,
425
+ maxScopeDepth: 1 + limits.maxScope,
426
+ code: encoded.code,
427
+ exceptions: [],
428
+ traits: [],
429
+ });
430
+ // Keep the last script last: it is the entry point executed when the ABC loads.
431
+ const script = { init: initMethod, traits: [(0, abc_file_js_1.newTrait)({ kind: constants_js_1.TraitKind.Class, name: nameMn, id: 1, classIndex: ci })] };
432
+ abc.scripts.splice(Math.max(0, abc.scripts.length - 1), 0, script);
433
+ return ci;
434
+ }
435
+ /** Interfaces have an iinit without a body. */
436
+ function addSignatureOnly(abc, scope, cls) {
437
+ const idx = abc.methods.length;
438
+ abc.methods.push({ paramTypes: [], returnType: 0, name: scope.pool.string(cls.name), flags: 0, optional: [], paramNames: [] });
439
+ return idx;
440
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * AS3 → AVM2 bytecode generation for method bodies.
3
+ *
4
+ * Name resolution strategy: locals and parameters are resolved statically to
5
+ * registers (or activation slots when closures capture them). Every other
6
+ * name is emitted as a `Multiname(name, openNamespaces)` and resolved by the
7
+ * VM at runtime through the scope chain, exactly as the Flex compiler does
8
+ * for names it cannot bind early. Type annotations are resolved to QNames.
9
+ */
10
+ import type { AbcFile, MethodInfo } from '../abc/abc-file.js';
11
+ import { type CodeItem } from '../abc/code.js';
12
+ import type { Interner } from '../abc/interner.js';
13
+ import type { Expr, FunctionDef, Pos, Stmt, TypeRef } from './ast.js';
14
+ import { CompileError } from './lexer.js';
15
+ /** Names of top-level (public, unnamed package) classes and functions of the Flash runtime. */
16
+ export declare const TOPLEVEL_NAMES: Set<string>;
17
+ export interface ClassScope {
18
+ abc: AbcFile;
19
+ pool: Interner;
20
+ /** Package of the compilation unit. */
21
+ pkg: string;
22
+ /** Namespace set used for unqualified / runtime names. */
23
+ openNsSet: number;
24
+ /** Namespace indices referenced by `ns` names (private, protected, custom...). */
25
+ namespaceByName(name: string): number | undefined;
26
+ /** QName / TypeName for a type annotation. */
27
+ resolveType(t: TypeRef): number;
28
+ /** Resolve a short or dotted name used as a value (class reference etc.); undefined = runtime lookup. */
29
+ resolveName(name: string): number | undefined;
30
+ source?: string;
31
+ }
32
+ export interface FunctionOptions {
33
+ /** Index to replace; omitted = append a new method. */
34
+ methodIndex?: number;
35
+ initScopeDepth: number;
36
+ /** Debug name stored in MethodInfo.name. */
37
+ name?: string;
38
+ /** Constructor: emits an implicit `super()` call when the body has none. */
39
+ isConstructor?: boolean;
40
+ /** Static initialiser / constructor field initialisers to run first. */
41
+ prologue?: (fc: FunctionCompiler) => void;
42
+ /** Names of const members that this function initialises (assignments use initproperty). */
43
+ constTargets?: ReadonlySet<string>;
44
+ }
45
+ /** Compiles one function (method, accessor, constructor or closure). */
46
+ export declare class FunctionCompiler {
47
+ readonly scope: ClassScope;
48
+ readonly fn: FunctionDef;
49
+ readonly options: FunctionOptions;
50
+ readonly items: CodeItem[];
51
+ private nextLabelId;
52
+ private exceptions;
53
+ private locals;
54
+ private nextReg;
55
+ private freeTemps;
56
+ private loops;
57
+ private activationReg;
58
+ private activationTraits;
59
+ private needsActivation;
60
+ /** Pending label for the next loop/switch statement. */
61
+ private pendingLabel;
62
+ private scopeDepth;
63
+ readonly method: MethodInfo;
64
+ constructor(scope: ClassScope, fn: FunctionDef, options: FunctionOptions);
65
+ err(msg: string, pos?: Pos): CompileError;
66
+ newLabel(): number;
67
+ place(label: number): void;
68
+ emit(op: number, ...args: number[]): void;
69
+ jump(op: number, label: number): void;
70
+ private temp;
71
+ private release;
72
+ private getReg;
73
+ private setReg;
74
+ private declare;
75
+ private get pool();
76
+ /** Runtime-resolved name: Multiname(name, open namespaces). */
77
+ mn(name: string): number;
78
+ /** Attribute multiname (`@name`). */
79
+ private mnAttr;
80
+ /** Late-bound multiname for `obj[expr]`. */
81
+ private mnLate;
82
+ /** Name with an explicit namespace (`ns::name`). */
83
+ private mnQualified;
84
+ private publicQName;
85
+ /** Default value for optional parameters / slots. */
86
+ constantValue(e: Expr): {
87
+ kind: number;
88
+ value: number;
89
+ };
90
+ /** Compiles the function and installs it in the ABC. Returns the method index. */
91
+ compile(): number;
92
+ /** Pushes the default value of a typed local, or returns undefined if none is needed. */
93
+ private defaultFor;
94
+ private compileClosure;
95
+ private lookupLocal;
96
+ private loadLocal;
97
+ /** Stores the value on top of the stack into a local (consumes it). */
98
+ private storeLocal;
99
+ /** Emits the coercion ASC would insert when storing into a typed location. */
100
+ coerceTo(type: number): void;
101
+ private stmts;
102
+ stmt(s: Stmt): void;
103
+ private loop;
104
+ private forIn;
105
+ /** Compiles `switch` the way ASC does: case bodies first, then a dispatcher feeding lookupswitch. */
106
+ private switchStmt;
107
+ private finallyStack;
108
+ /** Extra scopes (with / catch) currently open: loop depth when pushed and the register holding the object. */
109
+ private extraScopes;
110
+ /** Re-establishes the scope stack at an exception handler (the VM clears it). */
111
+ private restoreScopes;
112
+ private tryStmt;
113
+ /** Inlines pending finally blocks when control leaves them via return/break/continue. */
114
+ private runFinallies;
115
+ private pushNumber;
116
+ /** Multiname for a property access expression (member / index / descendants). */
117
+ private propertyName;
118
+ /** Name used to look up a bare identifier. */
119
+ private identName;
120
+ expr(e: Expr): void;
121
+ /** Pushes a type as a value (for `as`, `is`, `Vector.<T>`). */
122
+ private typeValue;
123
+ /** Evaluates `e` for its side effects only. */
124
+ exprDiscard(e: Expr): void;
125
+ private args;
126
+ private call;
127
+ private newExpr;
128
+ private unary;
129
+ /**
130
+ * Generic read-modify-write on an lvalue. `compute` receives the old value on
131
+ * the stack and must leave the new value. When `wantValue`, `resultOld`
132
+ * selects whether the old (postfix) or new value is left on the stack.
133
+ */
134
+ private modify;
135
+ private update;
136
+ /** Stores the value on top of the stack into an lvalue (consumes it). */
137
+ storeTo(target: Expr): void;
138
+ private assign;
139
+ private binary;
140
+ /** Jumps to `label` when `e` is truthy (`onTrue`) or falsy. */
141
+ condJump(e: Expr, onTrue: boolean, label: number): void;
142
+ /** E4X filter: `xml.(cond)`. */
143
+ private filter;
144
+ }