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