OLD | NEW |
| (Empty) |
1 // Decompile a JS file. This will be painful. | |
2 include("../utils/dumpast.js"); | |
3 include("../utils/astml.js"); | |
4 | |
5 let visitor = { | |
6 _visitArray: function (arr, pre, post, comma) { | |
7 if (pre === undefined) pre = '('; | |
8 if (post === undefined) post = ')'; | |
9 if (comma === undefined) comma = ', '; | |
10 output(pre); | |
11 for each (let arg in arr) { | |
12 arg.visit(this); | |
13 output(comma); | |
14 } | |
15 if (arr.length > 0) | |
16 unwrite(comma.length); | |
17 output(post); | |
18 }, | |
19 _visitMaybeBlock: function (body) { | |
20 if (body.type == "BlockStatement") { | |
21 output(" "); | |
22 body.visit(this); | |
23 } else { | |
24 flush().indent(); | |
25 body.visit(this); | |
26 unindent(); | |
27 } | |
28 }, | |
29 _visitNeedBlock: function (stmt, noFlush) { | |
30 if (stmt.type == "EmptyStatement") { | |
31 output("{}") | |
32 if (!noFlush) | |
33 flush(); | |
34 } | |
35 else if (stmt.type == "ReturnStatement") { | |
36 output("{").flush().indent(); | |
37 stmt.visit(this); | |
38 unindent().output("}"); | |
39 if (!noFlush) | |
40 flush(); | |
41 } | |
42 else | |
43 stmt.visit(this); | |
44 }, | |
45 visitProgram: function (program) {}, | |
46 visitFunctionDeclaration: function (func) { | |
47 output("function "); | |
48 if (func.name) | |
49 output(func.name); | |
50 this._visitArray(func.arguments, '(', ') '); | |
51 this._visitNeedBlock(func.body, true); | |
52 return true; | |
53 }, | |
54 visitParameter: function (p) { | |
55 output(p.name); | |
56 }, | |
57 visitBlockStatement: function (stmt) { | |
58 output("{").flush().indent(); | |
59 }, | |
60 postvisitBlockStatement: function (stmt) { | |
61 output("}").unindent().flush(); | |
62 }, | |
63 visitVarStatement: function (stmt) { | |
64 output(stmt.vartype).output(" "); | |
65 this._visitArray(stmt.variables, '', ''); | |
66 if (!this._noFlush) | |
67 output(';').flush(); | |
68 this._noFlush = false; | |
69 return true; | |
70 }, | |
71 visitVarDeclaration: function (decl) { | |
72 output(decl.name); | |
73 if ("initializer" in decl) | |
74 output(" = "); | |
75 }, | |
76 visitLetStatement: function (stmt) { | |
77 output("let "); | |
78 this._visitArray(stmt.variables, '(', ')'); | |
79 if (!this._noFlush) | |
80 output(';').flush(); | |
81 this._noFlush = false; | |
82 return true; | |
83 }, | |
84 visitExpressionStatement: function (stmt) {}, | |
85 postvisitExpressionStatement: function (stmt) { | |
86 output(";").flush(); | |
87 }, | |
88 visitEmptyStatement: function (stmt) { output(";").flush(); }, | |
89 visitIfStatement: function (stmt) { | |
90 output("if ("); | |
91 stmt.cond.visit(this); | |
92 output(")"); | |
93 this._visitMaybeBlock(stmt.body); | |
94 if (stmt.elsebody) { | |
95 output(" else"); | |
96 this._visitMaybeBlock(stmt.elsebody); | |
97 } | |
98 return true; | |
99 }, | |
100 visitDoWhileStatement: function (stmt) { | |
101 output("do"); | |
102 this._visitMaybeBlock(stmt.body); | |
103 output("while ("); | |
104 stmt.cond.visit(this); | |
105 output(");").flush(); | |
106 return true; | |
107 }, | |
108 visitWhileStatement: function (stmt) { | |
109 output("while ("); | |
110 stmt.cond.visit(this); | |
111 output(")"); | |
112 this._visitMaybeBlock(stmt.body); | |
113 return true; | |
114 }, | |
115 visitForStatement: function (stmt) { | |
116 output("for ("); | |
117 stmt.init.visit(this); | |
118 stmt.cond.visit(this); | |
119 if (stmt.cond.type != "EmptyStatement") | |
120 output("; "); | |
121 stmt.inc.visit(this); | |
122 output(")"); | |
123 this._visitMaybeBlock(stmt.body); | |
124 return true; | |
125 }, | |
126 visitForInStatement: function (stmt) { | |
127 output(stmt.itertype).output(" ("); | |
128 this._noFlush = true; | |
129 stmt.itervar.visit(this); | |
130 output(" in "); | |
131 stmt.iterrange.visit(this); | |
132 output(")"); | |
133 this._visitMaybeBlock(stmt.body); | |
134 return true; | |
135 }, | |
136 visitContinueStatement: function (stmt) { | |
137 output("continue"); | |
138 if ("label" in stmt) | |
139 output(" ").output(stmt.label); | |
140 output(";").flush(); | |
141 }, | |
142 visitBreakStatement: function (stmt) { | |
143 output("break"); | |
144 if ("label" in stmt) | |
145 output(" ").output(stmt.label); | |
146 output(";").flush(); | |
147 }, | |
148 visitReturnStatement: function (stmt) { output("return "); }, | |
149 postvisitReturnStatement: function (stmt) { output(";").flush(); }, | |
150 visitWithStatement: function (stmt) { | |
151 output("with ("); | |
152 stmt.variable.visit(this); | |
153 output(")"); | |
154 this._visitMaybeBlock(stmt.body); | |
155 return true; | |
156 }, | |
157 visitLabeledStatement: function (stmt) { output(stmt.label).output(": "); }, | |
158 visitSwitchStatement: function (stmt) { | |
159 output("switch ("); | |
160 stmt.expr.visit(this); | |
161 output(") {").flush().indent(); | |
162 this._visitArray(stmt.cases, '', '', ''); | |
163 output("}").unindent().flush(); | |
164 return true; | |
165 }, | |
166 visitSwitchCase: function (stmt) { | |
167 if ("expr" in stmt) { | |
168 output("case "); | |
169 stmt.expr.visit(this); | |
170 output(": "); | |
171 } else | |
172 output("default: "); | |
173 stmt.body.visit(this); | |
174 return true; | |
175 }, | |
176 visitThrowStatement: function (stmt) { output("throw "); }, | |
177 postvisitThrowStatement: function (stmt) { output(";").flush(); }, | |
178 visitTryStatement: function (stmt) { | |
179 output("try "); | |
180 this._visitNeedBlock(stmt.body); | |
181 this._visitArray(stmt.catchers, '', '', '\n' + indentStr); | |
182 if (stmt.fin) { | |
183 output("finally "); | |
184 this._visitNeedBlock(stmt.fin); | |
185 } | |
186 return true; | |
187 }, | |
188 visitCatchStatement: function (stmt) { | |
189 output("catch ("); | |
190 stmt.variable.visit(this); | |
191 if ("cond" in stmt) { | |
192 output(" if "); | |
193 stmt.cond.visit(this); | |
194 } | |
195 output(")"); | |
196 this._visitNeedBlock(stmt.body); | |
197 return true; | |
198 }, | |
199 visitDebuggerStatement: function (stmt) { output("debugger;").flush(); }, | |
200 | |
201 visitThisExpression: function (expr) { output("this"); }, | |
202 visitMemberExpression: function (expr) { | |
203 let needparen = expr.precedence + 1 < expr.container.precedence; | |
204 if (needparen) | |
205 output("("); | |
206 expr.container.visit(this); | |
207 if (needparen) | |
208 output(")"); | |
209 | |
210 if ("constmember" in expr && /^[_a-zA-Z]\w*$/.test(expr.constmember)) | |
211 output(".").output(expr.constmember); | |
212 else { | |
213 output("["); | |
214 expr.member.visit(this); | |
215 output("]"); | |
216 } | |
217 return true; | |
218 }, | |
219 visitNewExpression: function (expr) { | |
220 let needparen = expr.precedence < expr.constructor.precedence; | |
221 output("new "); | |
222 if (needparen) | |
223 output("("); | |
224 expr.constructor.visit(this); | |
225 if (needparen) | |
226 output(")"); | |
227 this._visitArray(expr.arguments); | |
228 return true; | |
229 }, | |
230 visitCallExpression: function (expr) { | |
231 let needparen = expr.precedence < expr.func.precedence; | |
232 if (needparen) | |
233 output("("); | |
234 expr.func.visit(this); | |
235 if (needparen) | |
236 output(")"); | |
237 this._visitArray(expr.arguments); | |
238 return true; | |
239 }, | |
240 visitLiteralExpression: function (expr) { | |
241 switch (expr.objtype) { | |
242 case "string": | |
243 output('"').output(sanitize(expr.value, '"')).output('"'); | |
244 break; | |
245 case "number": | |
246 case "boolean": | |
247 case "regex": | |
248 output(expr.value.toString()); | |
249 break; | |
250 case "null": | |
251 output("null"); | |
252 break; | |
253 default: | |
254 throw "Unknown literal " + expr.objtype; | |
255 }; | |
256 }, | |
257 visitObjectLiteral: function (obj) { | |
258 output('{').flush().indent(); | |
259 this._visitArray(obj.setters, '', '', ',\n' + indentStr); | |
260 flush().output('}').unindent(); | |
261 return true; | |
262 }, | |
263 visitPropertyLiteral: function (prop) { | |
264 if ("proptype" in prop) { | |
265 if (prop.value.type == "LiteralExpression") { | |
266 prop.property.visit(this); | |
267 output(" ").output(prop.proptype).output(": "); | |
268 prop.value.visit(this); | |
269 return true; | |
270 } | |
271 if (prop.proptype == "getter") | |
272 output("get "); | |
273 else if (prop.proptype == "setter") | |
274 output("set "); | |
275 else | |
276 throw "Unknown type: " + prop.proptype; | |
277 prop.property.visit(this); | |
278 if (prop.value.type != "FunctionDeclaration") | |
279 throw "Expection function, found: " + prop.value.type; | |
280 if (prop.value.name) { | |
281 output(" ").output(prop.value.name); | |
282 } | |
283 this._visitArray(prop.value.arguments, '(', ') '); | |
284 this._visitNeedBlock(prop.value.body, true); | |
285 return true; | |
286 } | |
287 prop.property.visit(this); | |
288 output(": "); | |
289 prop.value.visit(this); | |
290 return true; | |
291 }, | |
292 visitArrayLiteral: function (arr) { | |
293 this._visitArray(arr.members, '[', ']', ', '); | |
294 return true; | |
295 }, | |
296 visitArrayComprehensionExpression: function (arrcomp) { | |
297 output('['); | |
298 let enp = arrcomp.element.precedence > 16; | |
299 if (enp) | |
300 output("("); | |
301 arrcomp.element.visit(this); | |
302 if (enp) | |
303 output(")"); | |
304 output(" ").output(arrcomp.itertype).output("("); | |
305 arrcomp.itervar.visit(this); | |
306 output(" in "); | |
307 arrcomp.iterrange.visit(this); | |
308 output(")"); | |
309 if ("iterif" in arrcomp) { | |
310 output(" if ("); | |
311 arrcomp.iterif.visit(this); | |
312 output(")"); | |
313 } | |
314 output("]"); | |
315 return true; | |
316 }, | |
317 visitIdentifierExpression: function (expr) { | |
318 output(expr.name); | |
319 if ("initializer" in expr) { | |
320 output(" = "); | |
321 expr.initializer.visit(this); | |
322 return true; | |
323 } | |
324 }, | |
325 visitPostfixExpression: function (expr) {}, | |
326 postvisitPostfixExpression: function (expr) { | |
327 output(expr.operator); | |
328 }, | |
329 visitUnaryExpression: function (expr) { | |
330 if (expr.operator != '()') { | |
331 output(expr.operator); | |
332 if (expr.operator.length > 1) | |
333 output(" "); | |
334 } | |
335 let np = expr.precedence < expr.operand.precedence; | |
336 if (expr.operator == '()' || np) { | |
337 output("("); | |
338 expr.operand.visit(this); | |
339 output(")"); | |
340 return true; | |
341 } | |
342 }, | |
343 visitBinaryExpression: function (expr) { | |
344 let lhp = expr.precedence < expr.lhs.precedence; | |
345 let rhp = expr.precedence < expr.rhs.precedence; | |
346 if (lhp) | |
347 output("("); | |
348 expr.lhs.visit(this); | |
349 if (lhp) | |
350 output(")"); | |
351 output(" ").output(expr.operator).output(" "); | |
352 if (rhp) | |
353 output("("); | |
354 expr.rhs.visit(this); | |
355 if (rhp) | |
356 output(")"); | |
357 return true; | |
358 }, | |
359 visitConditionalExpression: function (expr) { | |
360 let lhp = expr.precedence < expr.cond.precedence; | |
361 let mhp = expr.precedence < expr.iftrue.precedence; | |
362 let rhp = expr.precedence < expr.iffalse.precedence; | |
363 if (lhp) | |
364 output("("); | |
365 expr.cond.visit(this); | |
366 if (lhp) | |
367 output(")"); | |
368 output(" ? "); | |
369 if (mhp) | |
370 output("("); | |
371 expr.iftrue.visit(this); | |
372 if (mhp) | |
373 output(")"); | |
374 output(" : "); | |
375 if (rhp) | |
376 output("("); | |
377 expr.iffalse.visit(this); | |
378 if (rhp) | |
379 output(")"); | |
380 return true; | |
381 }, | |
382 visitAssignmentExpression: function (expr) { | |
383 let lhp = expr.precedence < expr.lhs.precedence; | |
384 let rhp = expr.precedence < expr.rhs.precedence; | |
385 if (lhp) | |
386 output("("); | |
387 expr.lhs.visit(this); | |
388 if (lhp) | |
389 output(")"); | |
390 output(" ").output(expr.operator).output("= "); | |
391 if (rhp) | |
392 output("("); | |
393 expr.rhs.visit(this); | |
394 if (rhp) | |
395 output(")"); | |
396 return true; | |
397 }, | |
398 }; | |
399 | |
400 /* Reminder */ | |
401 for (let f in structure) { | |
402 if (!("visit" + f in visitor)) | |
403 throw "Please visit " + f; | |
404 } | |
405 | |
406 function process_js(ast) { | |
407 if (!ast) | |
408 return; | |
409 ast = makeAST(ast); | |
410 walkAST(ast, visitor); | |
411 } | |
412 | |
413 /* Output functions */ | |
414 let buffer = "", indentStr = ""; | |
415 function output(str) { | |
416 buffer += str; | |
417 return global; | |
418 } | |
419 function unwrite(numChars) { | |
420 buffer = buffer.substring(0, buffer.length - numChars); | |
421 return global; | |
422 } | |
423 function flush() { | |
424 _print(buffer); | |
425 buffer = indentStr; | |
426 return global; | |
427 } | |
428 function indent() { | |
429 indentStr += " "; | |
430 buffer = " " + buffer; | |
431 return global; | |
432 } | |
433 function unindent() { | |
434 indentStr = indentStr.substring(2); | |
435 buffer = buffer.substring(2); | |
436 return global; | |
437 } | |
438 | |
439 function sanitize(str, q) { | |
440 function replace(x) { | |
441 if (x == q) return '\\' + q; | |
442 if (x == '\\') return '\\\\'; | |
443 if (x == '\b') return '\\b'; | |
444 if (x == '\f') return '\\f'; | |
445 if (x == '\n') return '\\n'; | |
446 if (x == '\r') return '\\r'; | |
447 if (x == '\t') return '\\t'; | |
448 if (x == '\v') return '\\v'; | |
449 let val = x.charCodeAt(0) | |
450 if (x < ' ') return '\\x' + (val - val % 16) / 16 + (val % 16); | |
451 return x; | |
452 } | |
453 return [replace(x) for each (x in str)].join(''); | |
454 } | |
OLD | NEW |