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,635 @@
1
+ import { AssemblerError } from '../../errors.js';
2
+ import { newTrait } from '../abc-file.js';
3
+ import { computeLimits, minLocalCount } from '../analysis.js';
4
+ import { encodeCode } from '../code.js';
5
+ import { ConstantKind, MethodFlags, MultinameKind, NAMESPACE_KIND_BY_NAME, TraitAttr, TraitKind } from '../constants.js';
6
+ import { Interner } from '../interner.js';
7
+ import { opByName } from '../opcodes.js';
8
+ import { TRAIT_KIND_BY_TEXT } from './format.js';
9
+ import { LineCursor, tokenizeLine } from './lexer.js';
10
+ // ---------------------------------------------------------------------------
11
+ // Constant parsing
12
+ // ---------------------------------------------------------------------------
13
+ class ConstParser {
14
+ abc;
15
+ pool;
16
+ constructor(abc, pool) {
17
+ this.abc = abc;
18
+ this.pool = pool;
19
+ }
20
+ namespace(c) {
21
+ if (c.tryPunct('*'))
22
+ return 0;
23
+ const tok = c.peek();
24
+ const kindName = c.id();
25
+ const kind = NAMESPACE_KIND_BY_NAME.get(kindName) ?? (/^Namespace_0x[0-9a-f]+$/i.test(kindName) ? parseInt(kindName.slice(12), 16) : undefined);
26
+ if (kind === undefined)
27
+ throw c.error(`Unknown namespace kind '${kindName}'`, tok);
28
+ c.punct('(');
29
+ const name = c.stringOrNull();
30
+ let hint;
31
+ if (c.tryPunct(','))
32
+ hint = c.integer(0);
33
+ c.punct(')');
34
+ try {
35
+ return this.pool.namespace(kind, name, hint);
36
+ }
37
+ catch (e) {
38
+ throw c.error(e.message, tok);
39
+ }
40
+ }
41
+ nsSet(c) {
42
+ c.punct('[');
43
+ const list = [];
44
+ if (!c.isPunct(']')) {
45
+ do
46
+ list.push(this.namespace(c));
47
+ while (c.tryPunct(','));
48
+ }
49
+ c.punct(']');
50
+ return this.pool.nsSet(list);
51
+ }
52
+ multiname(c) {
53
+ if (c.tryPunct('*'))
54
+ return 0;
55
+ if (c.isId('null')) {
56
+ c.next();
57
+ return 0;
58
+ }
59
+ const tok = c.peek();
60
+ const kindName = c.id();
61
+ const K = MultinameKind;
62
+ const kind = K[kindName];
63
+ if (kind === undefined)
64
+ throw c.error(`Unknown multiname kind '${kindName}'`, tok);
65
+ c.punct('(');
66
+ let result;
67
+ switch (kind) {
68
+ case K.QName:
69
+ case K.QNameA: {
70
+ const ns = this.namespace(c);
71
+ c.punct(',');
72
+ const name = this.pool.string(c.stringOrNull());
73
+ result = this.pool.multiname({ kind, ns, name });
74
+ break;
75
+ }
76
+ case K.RTQName:
77
+ case K.RTQNameA:
78
+ result = this.pool.multiname({ kind, name: this.pool.string(c.stringOrNull()) });
79
+ break;
80
+ case K.RTQNameL:
81
+ case K.RTQNameLA:
82
+ result = this.pool.multiname({ kind });
83
+ break;
84
+ case K.Multiname:
85
+ case K.MultinameA: {
86
+ const name = this.pool.string(c.stringOrNull());
87
+ c.punct(',');
88
+ result = this.pool.multiname({ kind, name, nsSet: this.nsSet(c) });
89
+ break;
90
+ }
91
+ case K.MultinameL:
92
+ case K.MultinameLA:
93
+ result = this.pool.multiname({ kind, nsSet: this.nsSet(c) });
94
+ break;
95
+ case K.TypeName: {
96
+ const base = this.multiname(c);
97
+ c.punct(',');
98
+ c.punct('[');
99
+ const params = [];
100
+ if (!c.isPunct(']')) {
101
+ do
102
+ params.push(this.multiname(c));
103
+ while (c.tryPunct(','));
104
+ }
105
+ c.punct(']');
106
+ result = this.pool.multiname({ kind, base, params });
107
+ break;
108
+ }
109
+ default:
110
+ throw c.error(`Unsupported multiname kind '${kindName}'`, tok);
111
+ }
112
+ c.punct(')');
113
+ return result;
114
+ }
115
+ /** Constant value → { kind, index }. */
116
+ value(c) {
117
+ const tok = c.peek();
118
+ const name = c.id();
119
+ const C = ConstantKind;
120
+ const arg = (f) => {
121
+ c.punct('(');
122
+ const v = f();
123
+ c.punct(')');
124
+ return v;
125
+ };
126
+ switch (name) {
127
+ case 'Integer':
128
+ return { kind: C.Int, value: this.pool.int(arg(() => c.integer(-0x80000000, 0x7fffffff))) };
129
+ case 'UInteger':
130
+ return { kind: C.UInt, value: this.pool.uint(arg(() => c.integer(0, 0xffffffff))) };
131
+ case 'Double':
132
+ return { kind: C.Double, value: this.pool.double(arg(() => c.number())) };
133
+ case 'Utf8':
134
+ return { kind: C.Utf8, value: this.pool.string(arg(() => c.stringOrNull())) };
135
+ case 'True':
136
+ case 'False':
137
+ case 'Null':
138
+ case 'Undefined': {
139
+ const kind = name === 'True' ? C.True : name === 'False' ? C.False : name === 'Null' ? C.Null : C.Undefined;
140
+ const index = c.isPunct('(') ? arg(() => c.integer(0)) : kind;
141
+ return { kind, value: index };
142
+ }
143
+ case 'RawValue': {
144
+ c.punct('(');
145
+ const kind = c.integer(0, 255);
146
+ c.punct(',');
147
+ const value = c.integer(0);
148
+ c.punct(')');
149
+ return { kind, value };
150
+ }
151
+ default: {
152
+ const nsKind = NAMESPACE_KIND_BY_NAME.get(name);
153
+ if (nsKind === undefined)
154
+ throw c.error(`Unknown value '${name}'`, tok);
155
+ c.pos--;
156
+ return { kind: nsKind, value: this.namespace(c) };
157
+ }
158
+ }
159
+ }
160
+ trait(c, methodRef) {
161
+ c.id('trait');
162
+ const kindTok = c.peek();
163
+ const kind = TRAIT_KIND_BY_TEXT.get(c.id());
164
+ if (kind === undefined)
165
+ throw c.error('Unknown trait kind (slot, const, method, getter, setter, class, function)', kindTok);
166
+ const t = newTrait({ name: this.multiname(c), kind });
167
+ while (!c.done) {
168
+ const keyTok = c.peek();
169
+ const key = c.id();
170
+ switch (key) {
171
+ case 'slotid':
172
+ case 'dispid':
173
+ t.id = c.integer(0);
174
+ break;
175
+ case 'type':
176
+ t.typeName = this.multiname(c);
177
+ break;
178
+ case 'value': {
179
+ const v = this.value(c);
180
+ t.valueKind = v.kind;
181
+ t.valueIndex = v.value;
182
+ break;
183
+ }
184
+ case 'class':
185
+ t.classIndex = c.integer(0);
186
+ break;
187
+ case 'method':
188
+ t.method = methodRef(c);
189
+ break;
190
+ case 'final':
191
+ t.attrs |= TraitAttr.Final;
192
+ break;
193
+ case 'override':
194
+ t.attrs |= TraitAttr.Override;
195
+ break;
196
+ case 'metadata':
197
+ t.attrs |= TraitAttr.Metadata;
198
+ c.punct('[');
199
+ if (!c.isPunct(']')) {
200
+ do
201
+ t.metadata.push(c.integer(0));
202
+ while (c.tryPunct(','));
203
+ }
204
+ c.punct(']');
205
+ break;
206
+ default:
207
+ throw c.error(`Unknown trait attribute '${key}'`, keyTok);
208
+ }
209
+ }
210
+ if (kind !== TraitKind.Slot && kind !== TraitKind.Const && t.valueIndex !== 0)
211
+ throw c.error('Only slot/const traits can have a value');
212
+ return t;
213
+ }
214
+ }
215
+ // ---------------------------------------------------------------------------
216
+ // Method assembly
217
+ // ---------------------------------------------------------------------------
218
+ function splitLines(text, source) {
219
+ const out = [];
220
+ text.split('\n').forEach((raw, i) => {
221
+ const tokens = tokenizeLine(raw, i + 1, source);
222
+ if (tokens.length)
223
+ out.push({ no: i + 1, cur: new LineCursor(tokens, i + 1, source) });
224
+ });
225
+ return out;
226
+ }
227
+ function parseFlags(c) {
228
+ let flags = 0;
229
+ do {
230
+ const tok = c.peek();
231
+ if (tok?.type === 'num') {
232
+ flags |= c.integer(0, 255);
233
+ continue;
234
+ }
235
+ const name = c.id();
236
+ const bit = MethodFlags[name];
237
+ if (bit === undefined)
238
+ throw c.error(`Unknown method flag '${name}'`, tok);
239
+ flags |= bit;
240
+ } while (c.tryPunct('|'));
241
+ return flags;
242
+ }
243
+ function assembleOne(abc, lines, start, options, consts) {
244
+ const header = lines[start].cur;
245
+ header.id('method');
246
+ let methodIndex;
247
+ let created = false;
248
+ if (header.isId('new')) {
249
+ header.next();
250
+ methodIndex = abc.methods.length;
251
+ created = true;
252
+ }
253
+ else {
254
+ methodIndex = header.integer(0);
255
+ if (methodIndex >= abc.methods.length)
256
+ throw header.error(`Method index ${methodIndex} out of range (0..${abc.methods.length - 1}); use 'method new' to add one`);
257
+ }
258
+ header.end();
259
+ const existingBody = created ? undefined : abc.methodBody(methodIndex);
260
+ const method = { paramTypes: [], returnType: 0, name: 0, flags: 0, optional: [], paramNames: [] };
261
+ const methodRef = (c) => {
262
+ const tok = c.peek();
263
+ const v = c.integer(0);
264
+ if (v >= abc.methods.length + (created ? 1 : 0))
265
+ throw c.error(`Method index ${v} out of range`, tok);
266
+ return v;
267
+ };
268
+ let section = 'method';
269
+ let hasBody = false;
270
+ let maxStack = 'auto';
271
+ let localCount = 'auto';
272
+ let maxScopeDepth = 'auto';
273
+ let initScopeDepth = existingBody?.initScopeDepth ?? 0;
274
+ const traits = [];
275
+ const tries = [];
276
+ const items = [];
277
+ const labels = new Map();
278
+ const labelDefined = new Set();
279
+ const labelRefs = [];
280
+ const labelId = (name) => {
281
+ let id = labels.get(name);
282
+ if (id === undefined) {
283
+ id = labels.size;
284
+ labels.set(name, id);
285
+ }
286
+ return id;
287
+ };
288
+ const labelRef = (c) => {
289
+ const tok = c.next();
290
+ if (tok.type !== 'id')
291
+ throw c.error('Expected label', tok);
292
+ labelRefs.push({ name: tok.text, line: c, col: tok.col });
293
+ return labelId(tok.text);
294
+ };
295
+ const tryEndpoint = (c) => {
296
+ const tok = c.next();
297
+ if (tok.type === 'offset')
298
+ return tok.value;
299
+ if (tok.type !== 'id')
300
+ throw c.error('Expected label or @offset', tok);
301
+ labelRefs.push({ name: tok.text, line: c, col: tok.col });
302
+ labelId(tok.text);
303
+ return tok.text;
304
+ };
305
+ const limitValue = (c) => {
306
+ if (c.isId('auto')) {
307
+ c.next();
308
+ return 'auto';
309
+ }
310
+ return c.integer(0);
311
+ };
312
+ let i = start + 1;
313
+ let sawEnd = false;
314
+ for (; i < lines.length; i++) {
315
+ const { cur: c } = lines[i];
316
+ const first = c.peek();
317
+ if (first.type === 'id' && c.tokens.length === 1) {
318
+ if (first.text === 'end') {
319
+ sawEnd = true;
320
+ i++;
321
+ break;
322
+ }
323
+ if (first.text === 'body' && section === 'method') {
324
+ section = 'body';
325
+ hasBody = true;
326
+ continue;
327
+ }
328
+ if (first.text === 'code' && (section === 'body' || section === 'method')) {
329
+ section = 'code';
330
+ hasBody = true;
331
+ continue;
332
+ }
333
+ }
334
+ if (first.type === 'id' && first.text === 'method' && section !== 'code') {
335
+ throw c.error("Missing 'end' before next method");
336
+ }
337
+ if (section === 'method') {
338
+ const key = c.id();
339
+ switch (key) {
340
+ case 'name':
341
+ method.name = consts.pool.string(c.stringOrNull());
342
+ break;
343
+ case 'returns':
344
+ method.returnType = consts.multiname(c);
345
+ break;
346
+ case 'param':
347
+ method.paramTypes.push(consts.multiname(c));
348
+ break;
349
+ case 'flags':
350
+ method.flags |= parseFlags(c);
351
+ break;
352
+ case 'optional':
353
+ method.optional.push(consts.value(c));
354
+ break;
355
+ case 'paramname':
356
+ method.paramNames.push(consts.pool.string(c.stringOrNull()));
357
+ break;
358
+ default:
359
+ throw c.error(`Unknown method field '${key}' (expected name, returns, param, flags, optional, paramname, body, code or end)`, first);
360
+ }
361
+ c.end();
362
+ continue;
363
+ }
364
+ if (section === 'body') {
365
+ if (c.isId('trait')) {
366
+ traits.push(consts.trait(c, methodRef));
367
+ continue;
368
+ }
369
+ const key = c.id();
370
+ switch (key) {
371
+ case 'maxstack':
372
+ maxStack = limitValue(c);
373
+ break;
374
+ case 'localcount':
375
+ localCount = limitValue(c);
376
+ break;
377
+ case 'initscopedepth':
378
+ initScopeDepth = c.integer(0);
379
+ break;
380
+ case 'maxscopedepth':
381
+ maxScopeDepth = limitValue(c);
382
+ break;
383
+ case 'try': {
384
+ c.id('from');
385
+ const from = tryEndpoint(c);
386
+ c.id('to');
387
+ const to = tryEndpoint(c);
388
+ c.id('target');
389
+ const target = tryEndpoint(c);
390
+ let excType = 0;
391
+ let varName = 0;
392
+ while (!c.done) {
393
+ const k = c.id();
394
+ if (k === 'type')
395
+ excType = consts.multiname(c);
396
+ else if (k === 'name')
397
+ varName = consts.multiname(c);
398
+ else
399
+ throw c.error(`Unknown try attribute '${k}'`);
400
+ }
401
+ tries.push({ line: c, from, to, target, excType, varName });
402
+ break;
403
+ }
404
+ default:
405
+ throw c.error(`Unknown body field '${key}' (expected maxstack, localcount, initscopedepth, maxscopedepth, trait, try or code)`, first);
406
+ }
407
+ c.end();
408
+ continue;
409
+ }
410
+ // code section
411
+ if (first.type === 'id' && c.peek(1)?.type === 'punct' && c.peek(1).text === ':') {
412
+ c.pos += 2;
413
+ if (labelDefined.has(first.text))
414
+ throw c.error(`Label '${first.text}' defined twice`, first);
415
+ labelDefined.add(first.text);
416
+ items.push({ type: 'label', id: labelId(first.text) });
417
+ if (c.done)
418
+ continue;
419
+ }
420
+ items.push(parseInstruction(c, consts, labelRef, methodRef));
421
+ }
422
+ if (!sawEnd) {
423
+ const last = lines[lines.length - 1];
424
+ throw new AssemblerError(`Missing 'end' for method block starting at line ${lines[start].no}`, last.no, 1, options.source);
425
+ }
426
+ for (const ref of labelRefs) {
427
+ if (!labelDefined.has(ref.name))
428
+ throw new AssemblerError(`Undefined label '${ref.name}'`, ref.line.line, ref.col, options.source);
429
+ }
430
+ // Apply ----------------------------------------------------------------
431
+ if (method.optional.length > 0)
432
+ method.flags |= MethodFlags.HAS_OPTIONAL;
433
+ if (method.paramNames.length > 0)
434
+ method.flags |= MethodFlags.HAS_PARAM_NAMES;
435
+ if (created)
436
+ abc.methods.push(method);
437
+ else
438
+ abc.methods[methodIndex] = method;
439
+ const bodyIdx = abc.bodies.findIndex((b) => b.method === methodIndex);
440
+ if (!hasBody) {
441
+ if (bodyIdx >= 0)
442
+ abc.bodies.splice(bodyIdx, 1);
443
+ return { result: { methodIndex, created }, next: i };
444
+ }
445
+ const codeExceptions = [];
446
+ const numericTries = [];
447
+ tries.forEach((t, k) => {
448
+ if (typeof t.from === 'number' || typeof t.to === 'number' || typeof t.target === 'number')
449
+ numericTries.push(k);
450
+ codeExceptions.push({
451
+ from: typeof t.from === 'string' ? labels.get(t.from) : -1,
452
+ to: typeof t.to === 'string' ? labels.get(t.to) : -1,
453
+ target: typeof t.target === 'string' ? labels.get(t.target) : -1,
454
+ excType: t.excType,
455
+ varName: t.varName,
456
+ });
457
+ });
458
+ let encoded;
459
+ try {
460
+ encoded = encodeCode(items, codeExceptions.filter((_, k) => !numericTries.includes(k)));
461
+ }
462
+ catch (e) {
463
+ throw new AssemblerError(e.message, lines[start].no, 1, options.source);
464
+ }
465
+ const resolve = (v) => (typeof v === 'number' ? v : encoded.labelOffsets.get(labels.get(v)));
466
+ const exceptions = tries.map((t) => ({ from: resolve(t.from), to: resolve(t.to), target: resolve(t.target), excType: t.excType, varName: t.varName }));
467
+ const limits = computeLimits(abc, items, codeExceptions.filter((_, k) => !numericTries.includes(k)));
468
+ const mode = options.limits ?? 'grow';
469
+ const pick = (declared, computed) => declared === 'auto' || mode === 'auto' ? computed : mode === 'grow' ? Math.max(declared, computed) : declared;
470
+ const body = {
471
+ method: methodIndex,
472
+ maxStack: pick(maxStack, limits.maxStack),
473
+ localCount: pick(localCount, Math.max(limits.registers, minLocalCount(method))),
474
+ initScopeDepth,
475
+ maxScopeDepth: pick(maxScopeDepth, initScopeDepth + limits.maxScope),
476
+ code: encoded.code,
477
+ exceptions,
478
+ traits,
479
+ };
480
+ if (bodyIdx >= 0)
481
+ abc.bodies[bodyIdx] = body;
482
+ else
483
+ abc.bodies.push(body);
484
+ return { result: { methodIndex, created }, next: i };
485
+ }
486
+ function parseInstruction(c, consts, labelRef, methodRef) {
487
+ const tok = c.peek();
488
+ const name = c.id();
489
+ if (name === '.bytes') {
490
+ const bytes = [];
491
+ while (!c.done) {
492
+ const t = c.next();
493
+ const text = t.text;
494
+ if (!/^[0-9a-fA-F]{1,2}$/.test(text))
495
+ throw c.error(`Expected hex byte, got '${text}'`, t);
496
+ bytes.push(parseInt(text, 16));
497
+ }
498
+ return { type: 'bytes', data: new Uint8Array(bytes) };
499
+ }
500
+ const info = opByName(name);
501
+ if (!info)
502
+ throw c.error(`Unknown instruction '${name}'`, tok);
503
+ const ins = { type: 'op', op: info.code, args: [], targets: [], offset: -1 };
504
+ const abc = consts.abc;
505
+ info.operands.forEach((t, k) => {
506
+ if (k > 0)
507
+ c.punct(',');
508
+ switch (t) {
509
+ case 'u8':
510
+ ins.args.push(c.integer(0, 255));
511
+ break;
512
+ case 's8':
513
+ ins.args.push(c.integer(-128, 127));
514
+ break;
515
+ case 'short':
516
+ ins.args.push(c.integer(-0x80000000, 0xffffffff) | 0);
517
+ break;
518
+ case 'u30':
519
+ case 'argc':
520
+ case 'reg':
521
+ case 'class':
522
+ case 'exception':
523
+ ins.args.push(c.integer(0, 0x3fffffff));
524
+ break;
525
+ case 'method':
526
+ ins.args.push(methodRef(c));
527
+ break;
528
+ case 'int':
529
+ ins.args.push(abc.int(c.integer(-0x80000000, 0x7fffffff)));
530
+ break;
531
+ case 'uint':
532
+ ins.args.push(abc.uint(c.integer(0, 0xffffffff)));
533
+ break;
534
+ case 'double':
535
+ ins.args.push(abc.double(c.number()));
536
+ break;
537
+ case 'string':
538
+ ins.args.push(consts.pool.string(c.stringOrNull()));
539
+ break;
540
+ case 'namespace':
541
+ ins.args.push(consts.namespace(c));
542
+ break;
543
+ case 'multiname':
544
+ ins.args.push(consts.multiname(c));
545
+ break;
546
+ case 'branch':
547
+ ins.targets.push(labelRef(c));
548
+ break;
549
+ case 'switch': {
550
+ ins.targets.push(labelRef(c));
551
+ c.punct(',');
552
+ c.punct('[');
553
+ if (!c.isPunct(']')) {
554
+ do
555
+ ins.targets.push(labelRef(c));
556
+ while (c.tryPunct(','));
557
+ }
558
+ c.punct(']');
559
+ if (ins.targets.length < 2)
560
+ throw c.error('lookupswitch needs at least one case');
561
+ break;
562
+ }
563
+ }
564
+ });
565
+ c.end();
566
+ return ins;
567
+ }
568
+ /**
569
+ * Assembles one or more `method ... end` blocks into `abc`, replacing the
570
+ * referenced methods (or appending them for `method new`).
571
+ */
572
+ export function assembleMethods(abc, text, options = {}) {
573
+ const lines = splitLines(text, options.source);
574
+ const consts = new ConstParser(abc, options.interner ?? new Interner(abc));
575
+ const results = [];
576
+ let i = 0;
577
+ while (i < lines.length) {
578
+ const c = lines[i].cur;
579
+ if (!c.isId('method'))
580
+ throw c.error("Expected 'method'");
581
+ const { result, next } = assembleOne(abc, lines, i, options, consts);
582
+ results.push(result);
583
+ i = next;
584
+ }
585
+ return results;
586
+ }
587
+ /** Assembles a single method block. */
588
+ export function assembleMethod(abc, text, options = {}) {
589
+ const r = assembleMethods(abc, text, options);
590
+ if (r.length !== 1)
591
+ throw new Error(`Expected exactly one method block, got ${r.length}`);
592
+ return r[0];
593
+ }
594
+ /**
595
+ * Parses P-code instructions (the contents of a `code` section) into code
596
+ * items, interning constants into `abc`. Labels are returned by name.
597
+ */
598
+ export function assembleCode(abc, text, options = {}) {
599
+ const consts = new ConstParser(abc, options.interner ?? new Interner(abc));
600
+ const labels = new Map();
601
+ const defined = new Set();
602
+ const labelId = (name) => {
603
+ let id = labels.get(name);
604
+ if (id === undefined) {
605
+ id = labels.size;
606
+ labels.set(name, id);
607
+ }
608
+ return id;
609
+ };
610
+ const items = [];
611
+ const refs = [];
612
+ for (const { cur: c } of splitLines(text, options.source)) {
613
+ const first = c.peek();
614
+ if (first.type === 'id' && c.peek(1)?.type === 'punct' && c.peek(1).text === ':') {
615
+ c.pos += 2;
616
+ if (defined.has(first.text))
617
+ throw c.error(`Label '${first.text}' defined twice`, first);
618
+ defined.add(first.text);
619
+ items.push({ type: 'label', id: labelId(first.text) });
620
+ if (c.done)
621
+ continue;
622
+ }
623
+ items.push(parseInstruction(c, consts, (lc) => {
624
+ const t = lc.next();
625
+ if (t.type !== 'id')
626
+ throw lc.error('Expected label', t);
627
+ refs.push({ name: t.text, c: lc, col: t.col });
628
+ return labelId(t.text);
629
+ }, (lc) => lc.integer(0)));
630
+ }
631
+ for (const r of refs)
632
+ if (!defined.has(r.name))
633
+ throw new AssemblerError(`Undefined label '${r.name}'`, r.c.line, r.col, options.source);
634
+ return { items, labels };
635
+ }
@@ -0,0 +1,17 @@
1
+ import type { AbcFile } from '../abc-file.js';
2
+ import { type CodeItem, type DecodedCode, type Instruction } from '../code.js';
3
+ export interface DisassembleOptions {
4
+ /** Text appended as a comment after the `method N` header. */
5
+ title?: string;
6
+ /** Indentation for section contents (default two spaces). */
7
+ indent?: string;
8
+ }
9
+ /** Formats a single instruction (without indentation). */
10
+ export declare function formatInstruction(abc: AbcFile, ins: Instruction): string;
11
+ /** Formats decoded code items, one per line. */
12
+ export declare function formatCode(abc: AbcFile, code: DecodedCode | readonly CodeItem[], indent?: string): string[];
13
+ /**
14
+ * Disassembles a method (signature + body) to P-code text. The output can be
15
+ * edited and fed back to {@link assembleMethods}.
16
+ */
17
+ export declare function disassembleMethod(abc: AbcFile, methodIndex: number, options?: DisassembleOptions): string;