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,228 @@
1
+ /**
2
+ * Small ActionScript 3 AST used by the decompiler, plus a precedence-aware printer.
3
+ */
4
+ export type Expr = {
5
+ k: 'lit';
6
+ v: string | number | boolean | null | undefined;
7
+ } | {
8
+ k: 'raw';
9
+ text: string;
10
+ } | {
11
+ k: 'this';
12
+ } | {
13
+ k: 'super';
14
+ } | {
15
+ k: 'global';
16
+ } | {
17
+ k: 'local';
18
+ reg: number;
19
+ } | {
20
+ k: 'name';
21
+ name: string;
22
+ }
23
+ /** Scope object returned by findprop; only meaningful as the target of a member access. */
24
+ | {
25
+ k: 'findprop';
26
+ name: string;
27
+ } | {
28
+ k: 'member';
29
+ obj: Expr;
30
+ name: string;
31
+ attr?: boolean;
32
+ ns?: string;
33
+ } | {
34
+ k: 'index';
35
+ obj: Expr;
36
+ index: Expr;
37
+ attr?: boolean;
38
+ } | {
39
+ k: 'descendants';
40
+ obj: Expr;
41
+ name: string;
42
+ } | {
43
+ k: 'call';
44
+ fn: Expr;
45
+ args: Expr[];
46
+ } | {
47
+ k: 'new';
48
+ ctor: Expr;
49
+ args: Expr[];
50
+ } | {
51
+ k: 'unary';
52
+ op: string;
53
+ e: Expr;
54
+ } | {
55
+ k: 'postfix';
56
+ op: '++' | '--';
57
+ e: Expr;
58
+ } | {
59
+ k: 'binary';
60
+ op: string;
61
+ a: Expr;
62
+ b: Expr;
63
+ } | {
64
+ k: 'cond';
65
+ c: Expr;
66
+ t: Expr;
67
+ f: Expr;
68
+ } | {
69
+ k: 'assign';
70
+ target: Expr;
71
+ v: Expr;
72
+ op?: string;
73
+ } | {
74
+ k: 'array';
75
+ items: Expr[];
76
+ } | {
77
+ k: 'object';
78
+ props: Array<[Expr, Expr]>;
79
+ } | {
80
+ k: 'function';
81
+ methodIndex: number;
82
+ } | {
83
+ k: 'applytype';
84
+ base: Expr;
85
+ params: Expr[];
86
+ } | {
87
+ k: 'coerce';
88
+ type: string;
89
+ e: Expr;
90
+ } | {
91
+ k: 'activation';
92
+ } | {
93
+ k: 'catchscope';
94
+ index: number;
95
+ } | {
96
+ k: 'hasnext2';
97
+ objReg: number;
98
+ idxReg: number;
99
+ } | {
100
+ k: 'next';
101
+ value: boolean;
102
+ obj: Expr;
103
+ idx: Expr;
104
+ } | {
105
+ k: 'hasnext';
106
+ obj: Expr;
107
+ idx: Expr;
108
+ } | {
109
+ k: 'slot';
110
+ obj: Expr;
111
+ slot: number;
112
+ name?: string;
113
+ };
114
+ export interface CatchClause {
115
+ name: string;
116
+ type: string;
117
+ body: Stmt[];
118
+ }
119
+ export interface SwitchCase {
120
+ /** null = default. */
121
+ test: Expr | null;
122
+ body: Stmt[];
123
+ }
124
+ export type Stmt = {
125
+ k: 'expr';
126
+ e: Expr;
127
+ } | {
128
+ k: 'var';
129
+ name: string;
130
+ type?: string;
131
+ init?: Expr;
132
+ } | {
133
+ k: 'return';
134
+ e?: Expr;
135
+ } | {
136
+ k: 'throw';
137
+ e: Expr;
138
+ } | {
139
+ k: 'if';
140
+ c: Expr;
141
+ then: Stmt[];
142
+ else?: Stmt[];
143
+ } | {
144
+ k: 'while';
145
+ c: Expr;
146
+ body: Stmt[];
147
+ label?: string;
148
+ } | {
149
+ k: 'dowhile';
150
+ c: Expr;
151
+ body: Stmt[];
152
+ label?: string;
153
+ } | {
154
+ k: 'for';
155
+ init?: Stmt;
156
+ c?: Expr;
157
+ update?: Expr;
158
+ body: Stmt[];
159
+ label?: string;
160
+ } | {
161
+ k: 'forin';
162
+ each: boolean;
163
+ target: Expr;
164
+ declare?: {
165
+ name: string;
166
+ type?: string;
167
+ };
168
+ obj: Expr;
169
+ body: Stmt[];
170
+ label?: string;
171
+ } | {
172
+ k: 'switch';
173
+ disc: Expr;
174
+ cases: SwitchCase[];
175
+ label?: string;
176
+ } | {
177
+ k: 'try';
178
+ body: Stmt[];
179
+ catches: CatchClause[];
180
+ finally?: Stmt[];
181
+ } | {
182
+ k: 'break';
183
+ label?: string;
184
+ } | {
185
+ k: 'continue';
186
+ label?: string;
187
+ } | {
188
+ k: 'with';
189
+ obj: Expr;
190
+ body: Stmt[];
191
+ } | {
192
+ k: 'comment';
193
+ text: string;
194
+ } | {
195
+ k: 'block';
196
+ body: Stmt[];
197
+ };
198
+ export declare const lit: (v: string | number | boolean | null | undefined) => Expr;
199
+ export declare const raw: (text: string) => Expr;
200
+ /** Logical negation with simplification of comparisons and double negation. */
201
+ export declare function not(e: Expr): Expr;
202
+ export declare function stripCoerce(e: Expr): Expr;
203
+ /** True when evaluating `e` can have side effects (so it must not be dropped or duplicated). */
204
+ export declare function hasSideEffects(e: Expr): boolean;
205
+ /** Structural equality ignoring implicit coercions (used to detect `x = x + 1` → `x++` etc.). */
206
+ export declare function exprEquals(a: Expr, b: Expr): boolean;
207
+ export declare function readsLocal(e: Expr, reg: number): boolean;
208
+ export declare function walkExpr(e: Expr, f: (e: Expr) => void): void;
209
+ /** Visits every statement list recursively (pre-order). */
210
+ export declare function walkStmts(list: Stmt[], f: (s: Stmt, list: Stmt[], index: number) => void): void;
211
+ export declare function childLists(s: Stmt): Stmt[][];
212
+ export interface PrintContext {
213
+ localName(reg: number): string;
214
+ /** Renders a closure (newfunction) body. */
215
+ functionExpr(methodIndex: number, indent: string): string;
216
+ indentUnit: string;
217
+ }
218
+ export declare class Printer {
219
+ readonly ctx: PrintContext;
220
+ constructor(ctx: PrintContext);
221
+ expr(e: Expr, minPrec?: number, indent?: string): string;
222
+ private member;
223
+ private exprInner;
224
+ stmts(list: Stmt[], indent: string): string[];
225
+ private block;
226
+ private labelPrefix;
227
+ stmt(s: Stmt, indent: string): string[];
228
+ }
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Small ActionScript 3 AST used by the decompiler, plus a precedence-aware printer.
3
+ */
4
+ // ---------------------------------------------------------------------------
5
+ // Constructors / helpers
6
+ // ---------------------------------------------------------------------------
7
+ export const lit = (v) => ({ k: 'lit', v });
8
+ export const raw = (text) => ({ k: 'raw', text });
9
+ /** Logical negation with simplification of comparisons and double negation. */
10
+ export function not(e) {
11
+ if (e.k === 'unary' && e.op === '!')
12
+ return e.e;
13
+ if (e.k === 'binary') {
14
+ const inv = { '==': '!=', '!=': '==', '===': '!==', '!==': '===' };
15
+ const o = inv[e.op];
16
+ if (o)
17
+ return { ...e, op: o };
18
+ }
19
+ if (e.k === 'lit' && typeof e.v === 'boolean')
20
+ return lit(!e.v);
21
+ return { k: 'unary', op: '!', e };
22
+ }
23
+ export function stripCoerce(e) {
24
+ while (e.k === 'coerce')
25
+ e = e.e;
26
+ return e;
27
+ }
28
+ /** True when evaluating `e` can have side effects (so it must not be dropped or duplicated). */
29
+ export function hasSideEffects(e) {
30
+ switch (e.k) {
31
+ case 'call':
32
+ case 'new':
33
+ case 'assign':
34
+ case 'postfix':
35
+ case 'function':
36
+ return e.k !== 'function';
37
+ case 'unary':
38
+ return e.op === 'delete' || e.op === '++' || e.op === '--' || hasSideEffects(e.e);
39
+ case 'member':
40
+ return hasSideEffects(e.obj);
41
+ case 'index':
42
+ return hasSideEffects(e.obj) || hasSideEffects(e.index);
43
+ case 'binary':
44
+ return hasSideEffects(e.a) || hasSideEffects(e.b);
45
+ case 'cond':
46
+ return hasSideEffects(e.c) || hasSideEffects(e.t) || hasSideEffects(e.f);
47
+ case 'coerce':
48
+ return hasSideEffects(e.e);
49
+ case 'array':
50
+ return e.items.some(hasSideEffects);
51
+ case 'object':
52
+ return e.props.some(([a, b]) => hasSideEffects(a) || hasSideEffects(b));
53
+ default:
54
+ return false;
55
+ }
56
+ }
57
+ /** Structural equality ignoring implicit coercions (used to detect `x = x + 1` → `x++` etc.). */
58
+ export function exprEquals(a, b) {
59
+ const canon = (e) => JSON.stringify(e, (_k, v) => {
60
+ let x = v;
61
+ while (x && typeof x === 'object' && x.k === 'coerce')
62
+ x = x.e;
63
+ return x;
64
+ });
65
+ return canon(a) === canon(b);
66
+ }
67
+ export function readsLocal(e, reg) {
68
+ let found = false;
69
+ walkExpr(e, (x) => {
70
+ if (x.k === 'local' && x.reg === reg)
71
+ found = true;
72
+ });
73
+ return found;
74
+ }
75
+ export function walkExpr(e, f) {
76
+ f(e);
77
+ switch (e.k) {
78
+ case 'member':
79
+ case 'descendants':
80
+ walkExpr(e.obj, f);
81
+ break;
82
+ case 'index':
83
+ walkExpr(e.obj, f);
84
+ walkExpr(e.index, f);
85
+ break;
86
+ case 'call':
87
+ walkExpr(e.fn, f);
88
+ e.args.forEach((x) => walkExpr(x, f));
89
+ break;
90
+ case 'new':
91
+ walkExpr(e.ctor, f);
92
+ e.args.forEach((x) => walkExpr(x, f));
93
+ break;
94
+ case 'unary':
95
+ case 'postfix':
96
+ case 'coerce':
97
+ walkExpr(e.e, f);
98
+ break;
99
+ case 'binary':
100
+ walkExpr(e.a, f);
101
+ walkExpr(e.b, f);
102
+ break;
103
+ case 'cond':
104
+ walkExpr(e.c, f);
105
+ walkExpr(e.t, f);
106
+ walkExpr(e.f, f);
107
+ break;
108
+ case 'assign':
109
+ walkExpr(e.target, f);
110
+ walkExpr(e.v, f);
111
+ break;
112
+ case 'array':
113
+ e.items.forEach((x) => walkExpr(x, f));
114
+ break;
115
+ case 'object':
116
+ e.props.forEach(([a, b]) => {
117
+ walkExpr(a, f);
118
+ walkExpr(b, f);
119
+ });
120
+ break;
121
+ case 'applytype':
122
+ walkExpr(e.base, f);
123
+ e.params.forEach((x) => walkExpr(x, f));
124
+ break;
125
+ case 'next':
126
+ case 'hasnext':
127
+ walkExpr(e.obj, f);
128
+ walkExpr(e.idx, f);
129
+ break;
130
+ case 'slot':
131
+ walkExpr(e.obj, f);
132
+ break;
133
+ }
134
+ }
135
+ /** Visits every statement list recursively (pre-order). */
136
+ export function walkStmts(list, f) {
137
+ for (let i = 0; i < list.length; i++) {
138
+ const s = list[i];
139
+ f(s, list, i);
140
+ for (const child of childLists(s))
141
+ walkStmts(child, f);
142
+ }
143
+ }
144
+ export function childLists(s) {
145
+ switch (s.k) {
146
+ case 'if':
147
+ return s.else ? [s.then, s.else] : [s.then];
148
+ case 'while':
149
+ case 'dowhile':
150
+ case 'for':
151
+ case 'forin':
152
+ case 'with':
153
+ case 'block':
154
+ return [s.body];
155
+ case 'switch':
156
+ return s.cases.map((c) => c.body);
157
+ case 'try':
158
+ return [s.body, ...s.catches.map((c) => c.body), ...(s.finally ? [s.finally] : [])];
159
+ default:
160
+ return [];
161
+ }
162
+ }
163
+ // ---------------------------------------------------------------------------
164
+ // Printer
165
+ // ---------------------------------------------------------------------------
166
+ const BINARY_PREC = {
167
+ ',': 0,
168
+ '||': 3,
169
+ '&&': 4,
170
+ '|': 5,
171
+ '^': 6,
172
+ '&': 7,
173
+ '==': 8,
174
+ '!=': 8,
175
+ '===': 8,
176
+ '!==': 8,
177
+ '<': 9,
178
+ '>': 9,
179
+ '<=': 9,
180
+ '>=': 9,
181
+ instanceof: 9,
182
+ is: 9,
183
+ as: 9,
184
+ in: 9,
185
+ '<<': 10,
186
+ '>>': 10,
187
+ '>>>': 10,
188
+ '+': 11,
189
+ '-': 11,
190
+ '*': 12,
191
+ '/': 12,
192
+ '%': 12,
193
+ };
194
+ function precOf(e) {
195
+ switch (e.k) {
196
+ case 'assign':
197
+ return 1;
198
+ case 'cond':
199
+ return 2;
200
+ case 'binary':
201
+ return BINARY_PREC[e.op] ?? 9;
202
+ case 'unary':
203
+ return 13;
204
+ case 'postfix':
205
+ return 14;
206
+ case 'new':
207
+ return 15;
208
+ case 'call':
209
+ case 'member':
210
+ case 'index':
211
+ case 'descendants':
212
+ case 'applytype':
213
+ return 16;
214
+ case 'coerce':
215
+ return precOf(e.e);
216
+ case 'function':
217
+ return 0;
218
+ default:
219
+ return 17;
220
+ }
221
+ }
222
+ const IDENT_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
223
+ function formatNumber(v) {
224
+ if (Number.isNaN(v))
225
+ return 'NaN';
226
+ if (v === Infinity)
227
+ return 'Infinity';
228
+ if (v === -Infinity)
229
+ return '-Infinity';
230
+ if (Object.is(v, -0))
231
+ return '-0';
232
+ return String(v);
233
+ }
234
+ export class Printer {
235
+ ctx;
236
+ constructor(ctx) {
237
+ this.ctx = ctx;
238
+ }
239
+ expr(e, minPrec = 0, indent = '') {
240
+ const s = this.exprInner(e, indent);
241
+ return precOf(e) < minPrec ? `(${s})` : s;
242
+ }
243
+ member(obj, indent) {
244
+ if (obj.k === 'new')
245
+ return `(${this.expr(obj, 0, indent)})`;
246
+ return this.expr(obj, 16, indent);
247
+ }
248
+ exprInner(e, indent) {
249
+ switch (e.k) {
250
+ case 'lit':
251
+ if (typeof e.v === 'string')
252
+ return JSON.stringify(e.v);
253
+ if (typeof e.v === 'number')
254
+ return formatNumber(e.v);
255
+ if (e.v === undefined)
256
+ return 'undefined';
257
+ return String(e.v);
258
+ case 'raw':
259
+ return e.text;
260
+ case 'this':
261
+ return 'this';
262
+ case 'super':
263
+ return 'super';
264
+ case 'global':
265
+ return 'global';
266
+ case 'local':
267
+ return this.ctx.localName(e.reg);
268
+ case 'name':
269
+ return e.name;
270
+ case 'findprop':
271
+ return e.name;
272
+ case 'member': {
273
+ if (e.obj.k === 'findprop' || e.obj.k === 'global' || e.obj.k === 'activation' || e.obj.k === 'catchscope')
274
+ return (e.attr ? '@' : '') + e.name;
275
+ const nsPart = e.ns ? `${e.ns}::` : '';
276
+ if (!e.attr && !nsPart && !IDENT_RE.test(e.name))
277
+ return `${this.member(e.obj, indent)}[${JSON.stringify(e.name)}]`;
278
+ return `${this.member(e.obj, indent)}.${e.attr ? '@' : ''}${nsPart}${e.name}`;
279
+ }
280
+ case 'index':
281
+ return `${this.member(e.obj, indent)}${e.attr ? '.@' : ''}[${this.expr(e.index, 0, indent)}]`;
282
+ case 'descendants':
283
+ return `${this.member(e.obj, indent)}..${e.name}`;
284
+ case 'call':
285
+ return `${this.expr(e.fn, 16, indent)}(${e.args.map((a) => this.expr(a, 2, indent)).join(', ')})`;
286
+ case 'new': {
287
+ const ctor = e.ctor.k === 'call' ? `(${this.expr(e.ctor, 0, indent)})` : this.expr(e.ctor, 16, indent);
288
+ return `new ${ctor}(${e.args.map((a) => this.expr(a, 2, indent)).join(', ')})`;
289
+ }
290
+ case 'unary': {
291
+ const word = /^[a-z]/.test(e.op);
292
+ const inner = this.expr(e.e, 13, indent);
293
+ // Avoid "- -x" / "!-x" ambiguities.
294
+ return word ? `${e.op} ${inner}` : e.op === '-' && inner.startsWith('-') ? `-(${inner})` : `${e.op}${inner}`;
295
+ }
296
+ case 'postfix':
297
+ return `${this.expr(e.e, 14, indent)}${e.op}`;
298
+ case 'binary': {
299
+ const p = BINARY_PREC[e.op] ?? 9;
300
+ if (e.op === ',')
301
+ return `${this.expr(e.a, 1, indent)}, ${this.expr(e.b, 1, indent)}`;
302
+ return `${this.expr(e.a, p, indent)} ${e.op} ${this.expr(e.b, p + 1, indent)}`;
303
+ }
304
+ case 'cond':
305
+ return `${this.expr(e.c, 3, indent)} ? ${this.expr(e.t, 2, indent)} : ${this.expr(e.f, 2, indent)}`;
306
+ case 'assign':
307
+ return `${this.expr(e.target, 16, indent)} ${e.op ?? ''}= ${this.expr(e.v, 1, indent)}`;
308
+ case 'array':
309
+ return `[${e.items.map((a) => this.expr(a, 2, indent)).join(', ')}]`;
310
+ case 'object':
311
+ if (e.props.length === 0)
312
+ return '{}';
313
+ return `{${e.props
314
+ .map(([k, v]) => {
315
+ const key = k.k === 'lit' && typeof k.v === 'string' && IDENT_RE.test(k.v) ? k.v : this.expr(k, 17, indent);
316
+ return `${key}: ${this.expr(v, 2, indent)}`;
317
+ })
318
+ .join(', ')}}`;
319
+ case 'function':
320
+ return this.ctx.functionExpr(e.methodIndex, indent);
321
+ case 'applytype':
322
+ return `${this.expr(e.base, 16, indent)}.<${e.params.map((p) => this.expr(p, 0, indent)).join(', ')}>`;
323
+ case 'coerce':
324
+ return this.exprInner(e.e, indent);
325
+ case 'activation':
326
+ return '/*activation*/';
327
+ case 'catchscope':
328
+ return '/*catch*/';
329
+ case 'hasnext2':
330
+ return `hasnext2(${this.ctx.localName(e.objReg)}, ${this.ctx.localName(e.idxReg)})`;
331
+ case 'next':
332
+ return `${e.value ? 'nextvalue' : 'nextname'}(${this.expr(e.obj, 0, indent)}, ${this.expr(e.idx, 0, indent)})`;
333
+ case 'hasnext':
334
+ return `hasnext(${this.expr(e.obj, 0, indent)}, ${this.expr(e.idx, 0, indent)})`;
335
+ case 'slot':
336
+ return e.name ?? `${this.member(e.obj, indent)}./*slot ${e.slot}*/`;
337
+ }
338
+ }
339
+ stmts(list, indent) {
340
+ const out = [];
341
+ for (const s of list)
342
+ out.push(...this.stmt(s, indent));
343
+ return out;
344
+ }
345
+ block(body, indent) {
346
+ return [`${indent}{`, ...this.stmts(body, indent + this.ctx.indentUnit), `${indent}}`];
347
+ }
348
+ labelPrefix(label, indent, out) {
349
+ if (label)
350
+ out.push(`${indent}${label}:`);
351
+ }
352
+ stmt(s, indent) {
353
+ const I = indent;
354
+ const out = [];
355
+ switch (s.k) {
356
+ case 'expr':
357
+ out.push(`${I}${this.expr(s.e, 0, I)};`);
358
+ break;
359
+ case 'var':
360
+ out.push(`${I}var ${s.name}${s.type ? ':' + s.type : ''}${s.init ? ' = ' + this.expr(s.init, 1, I) : ''};`);
361
+ break;
362
+ case 'return':
363
+ out.push(s.e ? `${I}return ${this.expr(s.e, 0, I)};` : `${I}return;`);
364
+ break;
365
+ case 'throw':
366
+ out.push(`${I}throw ${this.expr(s.e, 0, I)};`);
367
+ break;
368
+ case 'if': {
369
+ out.push(`${I}if(${this.expr(s.c, 0, I)})`, ...this.block(s.then, I));
370
+ let els = s.else;
371
+ while (els && els.length) {
372
+ const only = els.length === 1 ? els[0] : undefined;
373
+ if (only && only.k === 'if') {
374
+ out.push(`${I}else if(${this.expr(only.c, 0, I)})`, ...this.block(only.then, I));
375
+ els = only.else;
376
+ }
377
+ else {
378
+ out.push(`${I}else`, ...this.block(els, I));
379
+ break;
380
+ }
381
+ }
382
+ break;
383
+ }
384
+ case 'while':
385
+ this.labelPrefix(s.label, I, out);
386
+ out.push(`${I}while(${this.expr(s.c, 0, I)})`, ...this.block(s.body, I));
387
+ break;
388
+ case 'dowhile':
389
+ this.labelPrefix(s.label, I, out);
390
+ out.push(`${I}do`, ...this.block(s.body, I), `${I}while(${this.expr(s.c, 0, I)});`);
391
+ break;
392
+ case 'for': {
393
+ this.labelPrefix(s.label, I, out);
394
+ const init = s.init ? this.stmt(s.init, '')[0].replace(/;$/, '') : '';
395
+ out.push(`${I}for(${init}; ${s.c ? this.expr(s.c, 0, I) : ''}; ${s.update ? this.expr(s.update, 0, I) : ''})`, ...this.block(s.body, I));
396
+ break;
397
+ }
398
+ case 'forin': {
399
+ this.labelPrefix(s.label, I, out);
400
+ const target = s.declare ? `var ${s.declare.name}${s.declare.type ? ':' + s.declare.type : ''}` : this.expr(s.target, 0, I);
401
+ out.push(`${I}for${s.each ? ' each' : ''}(${target} in ${this.expr(s.obj, 0, I)})`, ...this.block(s.body, I));
402
+ break;
403
+ }
404
+ case 'switch': {
405
+ this.labelPrefix(s.label, I, out);
406
+ out.push(`${I}switch(${this.expr(s.disc, 0, I)})`, `${I}{`);
407
+ const I2 = I + this.ctx.indentUnit;
408
+ for (const c of s.cases) {
409
+ out.push(c.test ? `${I2}case ${this.expr(c.test, 0, I2)}:` : `${I2}default:`);
410
+ out.push(...this.stmts(c.body, I2 + this.ctx.indentUnit));
411
+ }
412
+ out.push(`${I}}`);
413
+ break;
414
+ }
415
+ case 'try':
416
+ out.push(`${I}try`, ...this.block(s.body, I));
417
+ for (const c of s.catches)
418
+ out.push(`${I}catch(${c.name}:${c.type})`, ...this.block(c.body, I));
419
+ if (s.finally)
420
+ out.push(`${I}finally`, ...this.block(s.finally, I));
421
+ break;
422
+ case 'break':
423
+ out.push(`${I}break${s.label ? ' ' + s.label : ''};`);
424
+ break;
425
+ case 'continue':
426
+ out.push(`${I}continue${s.label ? ' ' + s.label : ''};`);
427
+ break;
428
+ case 'with':
429
+ out.push(`${I}with(${this.expr(s.obj, 0, I)})`, ...this.block(s.body, I));
430
+ break;
431
+ case 'comment':
432
+ for (const line of s.text.split('\n'))
433
+ out.push(`${I}// ${line}`);
434
+ break;
435
+ case 'block':
436
+ out.push(...this.block(s.body, I));
437
+ break;
438
+ }
439
+ return out;
440
+ }
441
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Control-flow graph over decoded method code, with dominator and
3
+ * post-dominator trees.
4
+ */
5
+ import type { AbcFile } from '../abc/abc-file.js';
6
+ import type { CodeException, CodeItem, Instruction } from '../abc/code.js';
7
+ export type BlockKind = 'fall' | 'jump' | 'cond' | 'switch' | 'return' | 'throw' | 'raw' | 'end';
8
+ export interface Block {
9
+ id: number;
10
+ /** Instructions (labels removed). */
11
+ insns: Instruction[];
12
+ labels: number[];
13
+ kind: BlockKind;
14
+ /** cond: [taken, fallthrough]; switch: [default, ...cases]; fall/jump: [next]. */
15
+ succs: number[];
16
+ preds: number[];
17
+ /** Stack height on entry (-1 if unreachable). */
18
+ entryHeight: number;
19
+ }
20
+ export interface Cfg {
21
+ blocks: Block[];
22
+ labelBlock: Map<number, number>;
23
+ exceptions: CodeException[];
24
+ /** Immediate dominator per block (-1 for entry / unreachable). */
25
+ idom: number[];
26
+ /** Immediate post-dominator per block (-1 = virtual exit). */
27
+ ipdom: number[];
28
+ /** Reverse post-order index (lower = earlier). */
29
+ order: number[];
30
+ }
31
+ export declare function buildCfg(abc: AbcFile, items: readonly CodeItem[], exceptions: readonly CodeException[]): Cfg;
32
+ /** True if `a` dominates `b`. */
33
+ export declare function dominates(cfg: Cfg, a: number, b: number): boolean;
34
+ export interface Loop {
35
+ header: number;
36
+ body: Set<number>;
37
+ latches: number[];
38
+ }
39
+ /** Natural loops keyed by header block. */
40
+ export declare function findLoops(cfg: Cfg): Map<number, Loop>;