OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of js; | |
6 | |
7 class Printer extends Indentation implements NodeVisitor { | |
8 final bool shouldCompressOutput; | |
9 leg.Compiler compiler; | |
10 leg.CodeBuffer outBuffer; | |
11 bool inForInit = false; | |
12 bool atStatementBegin = false; | |
13 final DanglingElseVisitor danglingElseVisitor; | |
14 final LocalNamer localNamer; | |
15 bool pendingSemicolon = false; | |
16 bool pendingSpace = false; | |
17 DumpInfoTask monitor = null; | |
18 | |
19 static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]'); | |
20 static final expressionContinuationRegExp = new RegExp(r'^[-+([]'); | |
21 | |
22 Printer(leg.Compiler compiler, DumpInfoTask monitor, | |
23 { allowVariableMinification: true }) | |
24 : shouldCompressOutput = compiler.enableMinification, | |
25 monitor = monitor, | |
26 this.compiler = compiler, | |
27 outBuffer = new leg.CodeBuffer(), | |
28 danglingElseVisitor = new DanglingElseVisitor(compiler), | |
29 localNamer = determineRenamer(compiler.enableMinification, | |
30 allowVariableMinification); | |
31 | |
32 static LocalNamer determineRenamer(bool shouldCompressOutput, | |
33 bool allowVariableMinification) { | |
34 return (shouldCompressOutput && allowVariableMinification) | |
35 ? new MinifyRenamer() : new IdentityNamer(); | |
36 } | |
37 | |
38 /// Always emit a newline, even under `enableMinification`. | |
39 void forceLine() { | |
40 out("\n"); | |
41 } | |
42 /// Emits a newline for readability. | |
43 void lineOut() { | |
44 if (!shouldCompressOutput) forceLine(); | |
45 } | |
46 void spaceOut() { | |
47 if (!shouldCompressOutput) out(" "); | |
48 } | |
49 | |
50 String lastAddedString = null; | |
51 int get lastCharCode { | |
52 if (lastAddedString == null) return 0; | |
53 assert(lastAddedString.length != ""); | |
54 return lastAddedString.codeUnitAt(lastAddedString.length - 1); | |
55 } | |
56 | |
57 void out(String str) { | |
58 if (str != "") { | |
59 if (pendingSemicolon) { | |
60 if (!shouldCompressOutput) { | |
61 outBuffer.add(";"); | |
62 } else if (str != "}") { | |
63 // We want to output newline instead of semicolon because it makes | |
64 // the raw stack traces much easier to read and it also makes line- | |
65 // based tools like diff work much better. JavaScript will | |
66 // automatically insert the semicolon at the newline if it means a | |
67 // parsing error is avoided, so we can only do this trick if the | |
68 // next line is not something that can be glued onto a valid | |
69 // expression to make a new valid expression. | |
70 | |
71 // If we're using the new emitter where most pretty printed code | |
72 // is escaped in strings, it is a lot easier to deal with semicolons | |
73 // than newlines because the former doesn't need escaping. | |
74 if (USE_NEW_EMITTER || expressionContinuationRegExp.hasMatch(str)) { | |
75 outBuffer.add(";"); | |
76 } else { | |
77 outBuffer.add("\n"); | |
78 } | |
79 } | |
80 } | |
81 if (pendingSpace && | |
82 (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) { | |
83 outBuffer.add(" "); | |
84 } | |
85 pendingSpace = false; | |
86 pendingSemicolon = false; | |
87 outBuffer.add(str); | |
88 lastAddedString = str; | |
89 } | |
90 } | |
91 | |
92 void outLn(String str) { | |
93 out(str); | |
94 lineOut(); | |
95 } | |
96 | |
97 void outSemicolonLn() { | |
98 if (shouldCompressOutput) { | |
99 pendingSemicolon = true; | |
100 } else { | |
101 out(";"); | |
102 forceLine(); | |
103 } | |
104 } | |
105 | |
106 void outIndent(String str) { indent(); out(str); } | |
107 void outIndentLn(String str) { indent(); outLn(str); } | |
108 void indent() { | |
109 if (!shouldCompressOutput) { | |
110 out(indentation); | |
111 } | |
112 } | |
113 | |
114 void beginSourceRange(Node node) { | |
115 if (node.sourcePosition != null) { | |
116 outBuffer.beginMappedRange(); | |
117 outBuffer.setSourceLocation(node.sourcePosition); | |
118 } | |
119 } | |
120 | |
121 void endSourceRange(Node node) { | |
122 if (node.endSourcePosition != null) { | |
123 outBuffer.setSourceLocation(node.endSourcePosition); | |
124 } | |
125 if (node.sourcePosition != null) { | |
126 outBuffer.endMappedRange(); | |
127 } | |
128 } | |
129 | |
130 visit(Node node) { | |
131 beginSourceRange(node); | |
132 if (monitor != null) monitor.enteringAst(node, outBuffer.length); | |
133 | |
134 node.accept(this); | |
135 | |
136 if (monitor != null) monitor.exitingAst(node, outBuffer.length); | |
137 endSourceRange(node); | |
138 } | |
139 | |
140 visitCommaSeparated(List<Node> nodes, int hasRequiredType, | |
141 {bool newInForInit, bool newAtStatementBegin}) { | |
142 for (int i = 0; i < nodes.length; i++) { | |
143 if (i != 0) { | |
144 atStatementBegin = false; | |
145 out(","); | |
146 spaceOut(); | |
147 } | |
148 visitNestedExpression(nodes[i], hasRequiredType, | |
149 newInForInit: newInForInit, | |
150 newAtStatementBegin: newAtStatementBegin); | |
151 } | |
152 } | |
153 | |
154 visitAll(List<Node> nodes) { | |
155 nodes.forEach(visit); | |
156 } | |
157 | |
158 visitProgram(Program program) { | |
159 visitAll(program.body); | |
160 } | |
161 | |
162 visitBlob(Blob node) { | |
163 outBuffer.addBuffer(node.buffer); | |
164 } | |
165 | |
166 bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) { | |
167 if (body is Block) { | |
168 spaceOut(); | |
169 blockOut(body, false, needsNewline); | |
170 return true; | |
171 } | |
172 if (shouldCompressOutput && needsSeparation) { | |
173 // If [shouldCompressOutput] is false, then the 'lineOut' will insert | |
174 // the separation. | |
175 out(" "); | |
176 } else { | |
177 lineOut(); | |
178 } | |
179 indentBlock(() => visit(body)); | |
180 return false; | |
181 } | |
182 | |
183 void blockOutWithoutBraces(Node node) { | |
184 if (node is Block) { | |
185 beginSourceRange(node); | |
186 Block block = node; | |
187 block.statements.forEach(blockOutWithoutBraces); | |
188 endSourceRange(node); | |
189 } else { | |
190 visit(node); | |
191 } | |
192 } | |
193 | |
194 void blockOut(Block node, bool shouldIndent, bool needsNewline) { | |
195 if (shouldIndent) indent(); | |
196 beginSourceRange(node); | |
197 out("{"); | |
198 lineOut(); | |
199 indentBlock(() => node.statements.forEach(blockOutWithoutBraces)); | |
200 indent(); | |
201 out("}"); | |
202 endSourceRange(node); | |
203 if (needsNewline) lineOut(); | |
204 } | |
205 | |
206 visitBlock(Block block) { | |
207 blockOut(block, true, true); | |
208 } | |
209 | |
210 visitExpressionStatement(ExpressionStatement expressionStatement) { | |
211 indent(); | |
212 visitNestedExpression(expressionStatement.expression, EXPRESSION, | |
213 newInForInit: false, newAtStatementBegin: true); | |
214 outSemicolonLn(); | |
215 } | |
216 | |
217 visitEmptyStatement(EmptyStatement nop) { | |
218 outIndentLn(";"); | |
219 } | |
220 | |
221 void ifOut(If node, bool shouldIndent) { | |
222 Node then = node.then; | |
223 Node elsePart = node.otherwise; | |
224 bool hasElse = node.hasElse; | |
225 | |
226 // Handle dangling elses and a work-around for Android 4.0 stock browser. | |
227 // Android 4.0 requires braces for a single do-while in the `then` branch. | |
228 // See issue 10923. | |
229 if (hasElse) { | |
230 bool needsBraces = node.then.accept(danglingElseVisitor) || then is Do; | |
231 if (needsBraces) { | |
232 then = new Block(<Statement>[then]); | |
233 } | |
234 } | |
235 if (shouldIndent) indent(); | |
236 out("if"); | |
237 spaceOut(); | |
238 out("("); | |
239 visitNestedExpression(node.condition, EXPRESSION, | |
240 newInForInit: false, newAtStatementBegin: false); | |
241 out(")"); | |
242 bool thenWasBlock = | |
243 blockBody(then, needsSeparation: false, needsNewline: !hasElse); | |
244 if (hasElse) { | |
245 if (thenWasBlock) { | |
246 spaceOut(); | |
247 } else { | |
248 indent(); | |
249 } | |
250 out("else"); | |
251 if (elsePart is If) { | |
252 pendingSpace = true; | |
253 ifOut(elsePart, false); | |
254 } else { | |
255 blockBody(elsePart, needsSeparation: true, needsNewline: true); | |
256 } | |
257 } | |
258 } | |
259 | |
260 visitIf(If node) { | |
261 ifOut(node, true); | |
262 } | |
263 | |
264 visitFor(For loop) { | |
265 outIndent("for"); | |
266 spaceOut(); | |
267 out("("); | |
268 if (loop.init != null) { | |
269 visitNestedExpression(loop.init, EXPRESSION, | |
270 newInForInit: true, newAtStatementBegin: false); | |
271 } | |
272 out(";"); | |
273 if (loop.condition != null) { | |
274 spaceOut(); | |
275 visitNestedExpression(loop.condition, EXPRESSION, | |
276 newInForInit: false, newAtStatementBegin: false); | |
277 } | |
278 out(";"); | |
279 if (loop.update != null) { | |
280 spaceOut(); | |
281 visitNestedExpression(loop.update, EXPRESSION, | |
282 newInForInit: false, newAtStatementBegin: false); | |
283 } | |
284 out(")"); | |
285 blockBody(loop.body, needsSeparation: false, needsNewline: true); | |
286 } | |
287 | |
288 visitForIn(ForIn loop) { | |
289 outIndent("for"); | |
290 spaceOut(); | |
291 out("("); | |
292 visitNestedExpression(loop.leftHandSide, EXPRESSION, | |
293 newInForInit: true, newAtStatementBegin: false); | |
294 out(" in"); | |
295 pendingSpace = true; | |
296 visitNestedExpression(loop.object, EXPRESSION, | |
297 newInForInit: false, newAtStatementBegin: false); | |
298 out(")"); | |
299 blockBody(loop.body, needsSeparation: false, needsNewline: true); | |
300 } | |
301 | |
302 visitWhile(While loop) { | |
303 outIndent("while"); | |
304 spaceOut(); | |
305 out("("); | |
306 visitNestedExpression(loop.condition, EXPRESSION, | |
307 newInForInit: false, newAtStatementBegin: false); | |
308 out(")"); | |
309 blockBody(loop.body, needsSeparation: false, needsNewline: true); | |
310 } | |
311 | |
312 visitDo(Do loop) { | |
313 outIndent("do"); | |
314 if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) { | |
315 spaceOut(); | |
316 } else { | |
317 indent(); | |
318 } | |
319 out("while"); | |
320 spaceOut(); | |
321 out("("); | |
322 visitNestedExpression(loop.condition, EXPRESSION, | |
323 newInForInit: false, newAtStatementBegin: false); | |
324 out(")"); | |
325 outSemicolonLn(); | |
326 } | |
327 | |
328 visitContinue(Continue node) { | |
329 if (node.targetLabel == null) { | |
330 outIndent("continue"); | |
331 } else { | |
332 outIndent("continue ${node.targetLabel}"); | |
333 } | |
334 outSemicolonLn(); | |
335 } | |
336 | |
337 visitBreak(Break node) { | |
338 if (node.targetLabel == null) { | |
339 outIndent("break"); | |
340 } else { | |
341 outIndent("break ${node.targetLabel}"); | |
342 } | |
343 outSemicolonLn(); | |
344 } | |
345 | |
346 visitReturn(Return node) { | |
347 if (node.value == null) { | |
348 outIndent("return"); | |
349 } else { | |
350 outIndent("return"); | |
351 pendingSpace = true; | |
352 visitNestedExpression(node.value, EXPRESSION, | |
353 newInForInit: false, newAtStatementBegin: false); | |
354 } | |
355 outSemicolonLn(); | |
356 } | |
357 | |
358 visitThrow(Throw node) { | |
359 outIndent("throw"); | |
360 pendingSpace = true; | |
361 visitNestedExpression(node.expression, EXPRESSION, | |
362 newInForInit: false, newAtStatementBegin: false); | |
363 outSemicolonLn(); | |
364 } | |
365 | |
366 visitTry(Try node) { | |
367 outIndent("try"); | |
368 blockBody(node.body, needsSeparation: true, needsNewline: false); | |
369 if (node.catchPart != null) { | |
370 visit(node.catchPart); | |
371 } | |
372 if (node.finallyPart != null) { | |
373 spaceOut(); | |
374 out("finally"); | |
375 blockBody(node.finallyPart, needsSeparation: true, needsNewline: true); | |
376 } else { | |
377 lineOut(); | |
378 } | |
379 } | |
380 | |
381 visitCatch(Catch node) { | |
382 spaceOut(); | |
383 out("catch"); | |
384 spaceOut(); | |
385 out("("); | |
386 visitNestedExpression(node.declaration, EXPRESSION, | |
387 newInForInit: false, newAtStatementBegin: false); | |
388 out(")"); | |
389 blockBody(node.body, needsSeparation: false, needsNewline: true); | |
390 } | |
391 | |
392 visitSwitch(Switch node) { | |
393 outIndent("switch"); | |
394 spaceOut(); | |
395 out("("); | |
396 visitNestedExpression(node.key, EXPRESSION, | |
397 newInForInit: false, newAtStatementBegin: false); | |
398 out(")"); | |
399 spaceOut(); | |
400 outLn("{"); | |
401 indentBlock(() => visitAll(node.cases)); | |
402 outIndentLn("}"); | |
403 } | |
404 | |
405 visitCase(Case node) { | |
406 outIndent("case"); | |
407 pendingSpace = true; | |
408 visitNestedExpression(node.expression, EXPRESSION, | |
409 newInForInit: false, newAtStatementBegin: false); | |
410 outLn(":"); | |
411 if (!node.body.statements.isEmpty) { | |
412 indentBlock(() => blockOutWithoutBraces(node.body)); | |
413 } | |
414 } | |
415 | |
416 visitDefault(Default node) { | |
417 outIndentLn("default:"); | |
418 if (!node.body.statements.isEmpty) { | |
419 indentBlock(() => blockOutWithoutBraces(node.body)); | |
420 } | |
421 } | |
422 | |
423 visitLabeledStatement(LabeledStatement node) { | |
424 outIndent("${node.label}:"); | |
425 blockBody(node.body, needsSeparation: false, needsNewline: true); | |
426 } | |
427 | |
428 void functionOut(Fun fun, Node name, VarCollector vars) { | |
429 out("function"); | |
430 if (name != null) { | |
431 out(" "); | |
432 // Name must be a [Decl]. Therefore only test for primary expressions. | |
433 visitNestedExpression(name, PRIMARY, | |
434 newInForInit: false, newAtStatementBegin: false); | |
435 } | |
436 localNamer.enterScope(vars); | |
437 out("("); | |
438 if (fun.params != null) { | |
439 visitCommaSeparated(fun.params, PRIMARY, | |
440 newInForInit: false, newAtStatementBegin: false); | |
441 } | |
442 out(")"); | |
443 blockBody(fun.body, needsSeparation: false, needsNewline: false); | |
444 localNamer.leaveScope(); | |
445 } | |
446 | |
447 visitFunctionDeclaration(FunctionDeclaration declaration) { | |
448 VarCollector vars = new VarCollector(); | |
449 vars.visitFunctionDeclaration(declaration); | |
450 indent(); | |
451 functionOut(declaration.function, declaration.name, vars); | |
452 lineOut(); | |
453 } | |
454 | |
455 visitNestedExpression(Expression node, int requiredPrecedence, | |
456 {bool newInForInit, bool newAtStatementBegin}) { | |
457 bool needsParentheses = | |
458 // a - (b + c). | |
459 (requiredPrecedence != EXPRESSION && | |
460 node.precedenceLevel < requiredPrecedence) || | |
461 // for (a = (x in o); ... ; ... ) { ... } | |
462 (newInForInit && node is Binary && node.op == "in") || | |
463 // (function() { ... })(). | |
464 // ({a: 2, b: 3}.toString()). | |
465 (newAtStatementBegin && (node is NamedFunction || | |
466 node is Fun || | |
467 node is ObjectInitializer)); | |
468 if (needsParentheses) { | |
469 inForInit = false; | |
470 atStatementBegin = false; | |
471 out("("); | |
472 visit(node); | |
473 out(")"); | |
474 } else { | |
475 inForInit = newInForInit; | |
476 atStatementBegin = newAtStatementBegin; | |
477 visit(node); | |
478 } | |
479 } | |
480 | |
481 visitVariableDeclarationList(VariableDeclarationList list) { | |
482 out("var "); | |
483 visitCommaSeparated(list.declarations, ASSIGNMENT, | |
484 newInForInit: inForInit, newAtStatementBegin: false); | |
485 } | |
486 | |
487 visitAssignment(Assignment assignment) { | |
488 visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE, | |
489 newInForInit: inForInit, | |
490 newAtStatementBegin: atStatementBegin); | |
491 if (assignment.value != null) { | |
492 spaceOut(); | |
493 String op = assignment.op; | |
494 if (op != null) out(op); | |
495 out("="); | |
496 spaceOut(); | |
497 visitNestedExpression(assignment.value, ASSIGNMENT, | |
498 newInForInit: inForInit, | |
499 newAtStatementBegin: false); | |
500 } | |
501 } | |
502 | |
503 visitVariableInitialization(VariableInitialization initialization) { | |
504 visitAssignment(initialization); | |
505 } | |
506 | |
507 visitConditional(Conditional cond) { | |
508 visitNestedExpression(cond.condition, LOGICAL_OR, | |
509 newInForInit: inForInit, | |
510 newAtStatementBegin: atStatementBegin); | |
511 spaceOut(); | |
512 out("?"); | |
513 spaceOut(); | |
514 // The then part is allowed to have an 'in'. | |
515 visitNestedExpression(cond.then, ASSIGNMENT, | |
516 newInForInit: false, newAtStatementBegin: false); | |
517 spaceOut(); | |
518 out(":"); | |
519 spaceOut(); | |
520 visitNestedExpression(cond.otherwise, ASSIGNMENT, | |
521 newInForInit: inForInit, newAtStatementBegin: false); | |
522 } | |
523 | |
524 visitNew(New node) { | |
525 out("new "); | |
526 visitNestedExpression(node.target, CALL, | |
527 newInForInit: inForInit, newAtStatementBegin: false); | |
528 out("("); | |
529 visitCommaSeparated(node.arguments, ASSIGNMENT, | |
530 newInForInit: false, newAtStatementBegin: false); | |
531 out(")"); | |
532 } | |
533 | |
534 visitCall(Call call) { | |
535 visitNestedExpression(call.target, LEFT_HAND_SIDE, | |
536 newInForInit: inForInit, | |
537 newAtStatementBegin: atStatementBegin); | |
538 out("("); | |
539 visitCommaSeparated(call.arguments, ASSIGNMENT, | |
540 newInForInit: false, newAtStatementBegin: false); | |
541 out(")"); | |
542 } | |
543 | |
544 visitBinary(Binary binary) { | |
545 Expression left = binary.left; | |
546 Expression right = binary.right; | |
547 String op = binary.op; | |
548 int leftPrecedenceRequirement; | |
549 int rightPrecedenceRequirement; | |
550 bool leftSpace = true; // left<HERE>op right | |
551 switch (op) { | |
552 case ',': | |
553 // x, (y, z) <=> (x, y), z. | |
554 leftPrecedenceRequirement = EXPRESSION; | |
555 rightPrecedenceRequirement = EXPRESSION; | |
556 leftSpace = false; | |
557 break; | |
558 case "||": | |
559 leftPrecedenceRequirement = LOGICAL_OR; | |
560 // x || (y || z) <=> (x || y) || z. | |
561 rightPrecedenceRequirement = LOGICAL_OR; | |
562 break; | |
563 case "&&": | |
564 leftPrecedenceRequirement = LOGICAL_AND; | |
565 // x && (y && z) <=> (x && y) && z. | |
566 rightPrecedenceRequirement = LOGICAL_AND; | |
567 break; | |
568 case "|": | |
569 leftPrecedenceRequirement = BIT_OR; | |
570 // x | (y | z) <=> (x | y) | z. | |
571 rightPrecedenceRequirement = BIT_OR; | |
572 break; | |
573 case "^": | |
574 leftPrecedenceRequirement = BIT_XOR; | |
575 // x ^ (y ^ z) <=> (x ^ y) ^ z. | |
576 rightPrecedenceRequirement = BIT_XOR; | |
577 break; | |
578 case "&": | |
579 leftPrecedenceRequirement = BIT_AND; | |
580 // x & (y & z) <=> (x & y) & z. | |
581 rightPrecedenceRequirement = BIT_AND; | |
582 break; | |
583 case "==": | |
584 case "!=": | |
585 case "===": | |
586 case "!==": | |
587 leftPrecedenceRequirement = EQUALITY; | |
588 rightPrecedenceRequirement = RELATIONAL; | |
589 break; | |
590 case "<": | |
591 case ">": | |
592 case "<=": | |
593 case ">=": | |
594 case "instanceof": | |
595 case "in": | |
596 leftPrecedenceRequirement = RELATIONAL; | |
597 rightPrecedenceRequirement = SHIFT; | |
598 break; | |
599 case ">>": | |
600 case "<<": | |
601 case ">>>": | |
602 leftPrecedenceRequirement = SHIFT; | |
603 rightPrecedenceRequirement = ADDITIVE; | |
604 break; | |
605 case "+": | |
606 case "-": | |
607 leftPrecedenceRequirement = ADDITIVE; | |
608 // We cannot remove parenthesis for "+" because | |
609 // x + (y + z) <!=> (x + y) + z: | |
610 // Example: | |
611 // "a" + (1 + 2) => "a3"; | |
612 // ("a" + 1) + 2 => "a12"; | |
613 rightPrecedenceRequirement = MULTIPLICATIVE; | |
614 break; | |
615 case "*": | |
616 case "/": | |
617 case "%": | |
618 leftPrecedenceRequirement = MULTIPLICATIVE; | |
619 // We cannot remove parenthesis for "*" because of precision issues. | |
620 rightPrecedenceRequirement = UNARY; | |
621 break; | |
622 default: | |
623 compiler.internalError(NO_LOCATION_SPANNABLE, "Forgot operator: $op"); | |
624 } | |
625 | |
626 visitNestedExpression(left, leftPrecedenceRequirement, | |
627 newInForInit: inForInit, | |
628 newAtStatementBegin: atStatementBegin); | |
629 | |
630 if (op == "in" || op == "instanceof") { | |
631 // There are cases where the space is not required but without further | |
632 // analysis we cannot know. | |
633 out(" "); | |
634 out(op); | |
635 out(" "); | |
636 } else { | |
637 if (leftSpace) spaceOut(); | |
638 out(op); | |
639 spaceOut(); | |
640 } | |
641 visitNestedExpression(right, rightPrecedenceRequirement, | |
642 newInForInit: inForInit, | |
643 newAtStatementBegin: false); | |
644 } | |
645 | |
646 visitPrefix(Prefix unary) { | |
647 String op = unary.op; | |
648 switch (op) { | |
649 case "delete": | |
650 case "void": | |
651 case "typeof": | |
652 // There are cases where the space is not required but without further | |
653 // analysis we cannot know. | |
654 out(op); | |
655 out(" "); | |
656 break; | |
657 case "+": | |
658 case "++": | |
659 if (lastCharCode == charCodes.$PLUS) out(" "); | |
660 out(op); | |
661 break; | |
662 case "-": | |
663 case "--": | |
664 if (lastCharCode == charCodes.$MINUS) out(" "); | |
665 out(op); | |
666 break; | |
667 default: | |
668 out(op); | |
669 } | |
670 visitNestedExpression(unary.argument, UNARY, | |
671 newInForInit: inForInit, newAtStatementBegin: false); | |
672 } | |
673 | |
674 visitPostfix(Postfix postfix) { | |
675 visitNestedExpression(postfix.argument, LEFT_HAND_SIDE, | |
676 newInForInit: inForInit, | |
677 newAtStatementBegin: atStatementBegin); | |
678 out(postfix.op); | |
679 } | |
680 | |
681 visitVariableUse(VariableUse ref) { | |
682 out(localNamer.getName(ref.name)); | |
683 } | |
684 | |
685 visitThis(This node) { | |
686 out("this"); | |
687 } | |
688 | |
689 visitVariableDeclaration(VariableDeclaration decl) { | |
690 out(localNamer.getName(decl.name)); | |
691 } | |
692 | |
693 visitParameter(Parameter param) { | |
694 out(localNamer.getName(param.name)); | |
695 } | |
696 | |
697 bool isDigit(int charCode) { | |
698 return charCodes.$0 <= charCode && charCode <= charCodes.$9; | |
699 } | |
700 | |
701 bool isValidJavaScriptId(String field) { | |
702 if (field.length < 3) return false; | |
703 // Ignore the leading and trailing string-delimiter. | |
704 for (int i = 1; i < field.length - 1; i++) { | |
705 // TODO(floitsch): allow more characters. | |
706 int charCode = field.codeUnitAt(i); | |
707 if (!(charCodes.$a <= charCode && charCode <= charCodes.$z || | |
708 charCodes.$A <= charCode && charCode <= charCodes.$Z || | |
709 charCode == charCodes.$$ || | |
710 charCode == charCodes.$_ || | |
711 i != 1 && isDigit(charCode))) { | |
712 return false; | |
713 } | |
714 } | |
715 // TODO(floitsch): normally we should also check that the field is not a | |
716 // reserved word. We don't generate fields with reserved word names except | |
717 // for 'super'. | |
718 if (field == '"super"') return false; | |
719 return true; | |
720 } | |
721 | |
722 visitAccess(PropertyAccess access) { | |
723 visitNestedExpression(access.receiver, CALL, | |
724 newInForInit: inForInit, | |
725 newAtStatementBegin: atStatementBegin); | |
726 Node selector = access.selector; | |
727 if (selector is LiteralString) { | |
728 LiteralString selectorString = selector; | |
729 String fieldWithQuotes = selectorString.value; | |
730 if (isValidJavaScriptId(fieldWithQuotes)) { | |
731 if (access.receiver is LiteralNumber) out(" "); | |
732 out("."); | |
733 out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)); | |
734 return; | |
735 } | |
736 } | |
737 out("["); | |
738 visitNestedExpression(selector, EXPRESSION, | |
739 newInForInit: false, newAtStatementBegin: false); | |
740 out("]"); | |
741 } | |
742 | |
743 visitNamedFunction(NamedFunction namedFunction) { | |
744 VarCollector vars = new VarCollector(); | |
745 vars.visitNamedFunction(namedFunction); | |
746 functionOut(namedFunction.function, namedFunction.name, vars); | |
747 } | |
748 | |
749 visitFun(Fun fun) { | |
750 VarCollector vars = new VarCollector(); | |
751 vars.visitFun(fun); | |
752 functionOut(fun, null, vars); | |
753 } | |
754 | |
755 visitLiteralBool(LiteralBool node) { | |
756 out(node.value ? "true" : "false"); | |
757 } | |
758 | |
759 visitLiteralString(LiteralString node) { | |
760 out(node.value); | |
761 } | |
762 | |
763 visitLiteralNumber(LiteralNumber node) { | |
764 int charCode = node.value.codeUnitAt(0); | |
765 if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) { | |
766 out(" "); | |
767 } | |
768 out(node.value); | |
769 } | |
770 | |
771 visitLiteralNull(LiteralNull node) { | |
772 out("null"); | |
773 } | |
774 | |
775 visitArrayInitializer(ArrayInitializer node) { | |
776 out("["); | |
777 List<ArrayElement> elements = node.elements; | |
778 int elementIndex = 0; | |
779 for (int i = 0; i < node.length; i++) { | |
780 if (elementIndex < elements.length && | |
781 elements[elementIndex].index == i) { | |
782 visitNestedExpression(elements[elementIndex].value, ASSIGNMENT, | |
783 newInForInit: false, newAtStatementBegin: false); | |
784 elementIndex++; | |
785 // We can avoid a trailing "," if there was an element just before. So | |
786 // `[1]` and `[1,]` are the same, but `[,]` and `[]` are not. | |
787 if (i != node.length - 1) { | |
788 out(","); | |
789 spaceOut(); | |
790 } | |
791 } else { | |
792 out(","); | |
793 } | |
794 } | |
795 out("]"); | |
796 } | |
797 | |
798 visitArrayElement(ArrayElement node) { | |
799 throw "Unreachable"; | |
800 } | |
801 | |
802 visitObjectInitializer(ObjectInitializer node) { | |
803 // Print all the properties on one line until we see a function-valued | |
804 // property. Ideally, we would use a proper pretty-printer to make the | |
805 // decision based on layout. | |
806 List<Property> properties = node.properties; | |
807 out("{"); | |
808 indentMore(); | |
809 for (int i = 0; i < properties.length; i++) { | |
810 Expression value = properties[i].value; | |
811 if (i != 0) { | |
812 out(","); | |
813 if (node.isOneLiner) spaceOut(); | |
814 } | |
815 if (!node.isOneLiner) { | |
816 forceLine(); | |
817 indent(); | |
818 } | |
819 visit(properties[i]); | |
820 } | |
821 indentLess(); | |
822 if (!node.isOneLiner && !properties.isEmpty) { | |
823 lineOut(); | |
824 indent(); | |
825 } | |
826 out("}"); | |
827 } | |
828 | |
829 visitProperty(Property node) { | |
830 if (node.name is LiteralString) { | |
831 LiteralString nameString = node.name; | |
832 String name = nameString.value; | |
833 if (isValidJavaScriptId(name)) { | |
834 out(name.substring(1, name.length - 1)); | |
835 } else { | |
836 out(name); | |
837 } | |
838 } else { | |
839 assert(node.name is LiteralNumber); | |
840 LiteralNumber nameNumber = node.name; | |
841 out(nameNumber.value); | |
842 } | |
843 out(":"); | |
844 spaceOut(); | |
845 visitNestedExpression(node.value, ASSIGNMENT, | |
846 newInForInit: false, newAtStatementBegin: false); | |
847 } | |
848 | |
849 visitRegExpLiteral(RegExpLiteral node) { | |
850 out(node.pattern); | |
851 } | |
852 | |
853 visitLiteralExpression(LiteralExpression node) { | |
854 String template = node.template; | |
855 List<Expression> inputs = node.inputs; | |
856 | |
857 List<String> parts = template.split('#'); | |
858 int inputsLength = inputs == null ? 0 : inputs.length; | |
859 if (parts.length != inputsLength + 1) { | |
860 compiler.internalError(NO_LOCATION_SPANNABLE, | |
861 'Wrong number of arguments for JS: $template'); | |
862 } | |
863 // Code that uses JS must take care of operator precedences, and | |
864 // put parenthesis if needed. | |
865 out(parts[0]); | |
866 for (int i = 0; i < inputsLength; i++) { | |
867 visit(inputs[i]); | |
868 out(parts[i + 1]); | |
869 } | |
870 } | |
871 | |
872 visitLiteralStatement(LiteralStatement node) { | |
873 outLn(node.code); | |
874 } | |
875 | |
876 visitInterpolatedNode(InterpolatedNode node) { | |
877 out('#${node.name}'); | |
878 } | |
879 | |
880 visitInterpolatedExpression(InterpolatedExpression node) => | |
881 visitInterpolatedNode(node); | |
882 | |
883 visitInterpolatedLiteral(InterpolatedLiteral node) => | |
884 visitInterpolatedNode(node); | |
885 | |
886 visitInterpolatedParameter(InterpolatedParameter node) => | |
887 visitInterpolatedNode(node); | |
888 | |
889 visitInterpolatedSelector(InterpolatedSelector node) => | |
890 visitInterpolatedNode(node); | |
891 | |
892 visitInterpolatedStatement(InterpolatedStatement node) { | |
893 outLn('#${node.name}'); | |
894 } | |
895 | |
896 void visitComment(Comment node) { | |
897 if (shouldCompressOutput) return; | |
898 String comment = node.comment.trim(); | |
899 if (comment.isEmpty) return; | |
900 for (var line in comment.split('\n')) { | |
901 if (comment.startsWith('//')) { | |
902 outIndentLn(line.trim()); | |
903 } else { | |
904 outIndentLn('// ${line.trim()}'); | |
905 } | |
906 } | |
907 } | |
908 } | |
909 | |
910 | |
911 class OrderedSet<T> { | |
912 final Set<T> set; | |
913 final List<T> list; | |
914 | |
915 OrderedSet() : set = new Set<T>(), list = <T>[]; | |
916 | |
917 void add(T x) { | |
918 if (!set.contains(x)) { | |
919 set.add(x); | |
920 list.add(x); | |
921 } | |
922 } | |
923 | |
924 void forEach(void fun(T x)) { | |
925 list.forEach(fun); | |
926 } | |
927 } | |
928 | |
929 // Collects all the var declarations in the function. We need to do this in a | |
930 // separate pass because JS vars are lifted to the top of the function. | |
931 class VarCollector extends BaseVisitor { | |
932 bool nested; | |
933 final OrderedSet<String> vars; | |
934 final OrderedSet<String> params; | |
935 | |
936 VarCollector() : nested = false, | |
937 vars = new OrderedSet<String>(), | |
938 params = new OrderedSet<String>(); | |
939 | |
940 void forEachVar(void fn(String v)) => vars.forEach(fn); | |
941 void forEachParam(void fn(String p)) => params.forEach(fn); | |
942 | |
943 void collectVarsInFunction(Fun fun) { | |
944 if (!nested) { | |
945 nested = true; | |
946 if (fun.params != null) { | |
947 for (int i = 0; i < fun.params.length; i++) { | |
948 params.add(fun.params[i].name); | |
949 } | |
950 } | |
951 visitBlock(fun.body); | |
952 nested = false; | |
953 } | |
954 } | |
955 | |
956 void visitFunctionDeclaration(FunctionDeclaration declaration) { | |
957 // Note that we don't bother collecting the name of the function. | |
958 collectVarsInFunction(declaration.function); | |
959 } | |
960 | |
961 void visitNamedFunction(NamedFunction namedFunction) { | |
962 // Note that we don't bother collecting the name of the function. | |
963 collectVarsInFunction(namedFunction.function); | |
964 } | |
965 | |
966 void visitFun(Fun fun) { | |
967 collectVarsInFunction(fun); | |
968 } | |
969 | |
970 void visitThis(This node) {} | |
971 | |
972 void visitVariableDeclaration(VariableDeclaration decl) { | |
973 if (decl.allowRename) vars.add(decl.name); | |
974 } | |
975 } | |
976 | |
977 | |
978 /** | |
979 * Returns true, if the given node must be wrapped into braces when used | |
980 * as then-statement in an [If] that has an else branch. | |
981 */ | |
982 class DanglingElseVisitor extends BaseVisitor<bool> { | |
983 leg.Compiler compiler; | |
984 | |
985 DanglingElseVisitor(this.compiler); | |
986 | |
987 bool visitProgram(Program node) => false; | |
988 | |
989 bool visitNode(Statement node) { | |
990 compiler.internalError(NO_LOCATION_SPANNABLE, "Forgot node: $node"); | |
991 return null; | |
992 } | |
993 | |
994 bool visitBlock(Block node) => false; | |
995 bool visitExpressionStatement(ExpressionStatement node) => false; | |
996 bool visitEmptyStatement(EmptyStatement node) => false; | |
997 bool visitIf(If node) { | |
998 if (!node.hasElse) return true; | |
999 return node.otherwise.accept(this); | |
1000 } | |
1001 bool visitFor(For node) => node.body.accept(this); | |
1002 bool visitForIn(ForIn node) => node.body.accept(this); | |
1003 bool visitWhile(While node) => node.body.accept(this); | |
1004 bool visitDo(Do node) => false; | |
1005 bool visitContinue(Continue node) => false; | |
1006 bool visitBreak(Break node) => false; | |
1007 bool visitReturn(Return node) => false; | |
1008 bool visitThrow(Throw node) => false; | |
1009 bool visitTry(Try node) { | |
1010 if (node.finallyPart != null) { | |
1011 return node.finallyPart.accept(this); | |
1012 } else { | |
1013 return node.catchPart.accept(this); | |
1014 } | |
1015 } | |
1016 bool visitCatch(Catch node) => node.body.accept(this); | |
1017 bool visitSwitch(Switch node) => false; | |
1018 bool visitCase(Case node) => false; | |
1019 bool visitDefault(Default node) => false; | |
1020 bool visitFunctionDeclaration(FunctionDeclaration node) => false; | |
1021 bool visitLabeledStatement(LabeledStatement node) | |
1022 => node.body.accept(this); | |
1023 bool visitLiteralStatement(LiteralStatement node) => true; | |
1024 | |
1025 bool visitExpression(Expression node) => false; | |
1026 } | |
1027 | |
1028 | |
1029 leg.CodeBuffer prettyPrint(Node node, leg.Compiler compiler, | |
1030 {DumpInfoTask monitor, | |
1031 allowVariableMinification: true}) { | |
1032 Printer printer = | |
1033 new Printer(compiler, monitor, | |
1034 allowVariableMinification: allowVariableMinification); | |
1035 printer.visit(node); | |
1036 return printer.outBuffer; | |
1037 } | |
1038 | |
1039 | |
1040 abstract class LocalNamer { | |
1041 String getName(String oldName); | |
1042 String declareVariable(String oldName); | |
1043 String declareParameter(String oldName); | |
1044 void enterScope(VarCollector vars); | |
1045 void leaveScope(); | |
1046 } | |
1047 | |
1048 | |
1049 class IdentityNamer implements LocalNamer { | |
1050 String getName(String oldName) => oldName; | |
1051 String declareVariable(String oldName) => oldName; | |
1052 String declareParameter(String oldName) => oldName; | |
1053 void enterScope(VarCollector vars) {} | |
1054 void leaveScope() {} | |
1055 } | |
1056 | |
1057 | |
1058 class MinifyRenamer implements LocalNamer { | |
1059 final List<Map<String, String>> maps = []; | |
1060 final List<int> parameterNumberStack = []; | |
1061 final List<int> variableNumberStack = []; | |
1062 int parameterNumber = 0; | |
1063 int variableNumber = 0; | |
1064 | |
1065 MinifyRenamer(); | |
1066 | |
1067 void enterScope(VarCollector vars) { | |
1068 maps.add(new Map<String, String>()); | |
1069 variableNumberStack.add(variableNumber); | |
1070 parameterNumberStack.add(parameterNumber); | |
1071 vars.forEachVar(declareVariable); | |
1072 vars.forEachParam(declareParameter); | |
1073 } | |
1074 | |
1075 void leaveScope() { | |
1076 maps.removeLast(); | |
1077 variableNumber = variableNumberStack.removeLast(); | |
1078 parameterNumber = parameterNumberStack.removeLast(); | |
1079 } | |
1080 | |
1081 String getName(String oldName) { | |
1082 // Go from inner scope to outer looking for mapping of name. | |
1083 for (int i = maps.length - 1; i >= 0; i--) { | |
1084 var map = maps[i]; | |
1085 var replacement = map[oldName]; | |
1086 if (replacement != null) return replacement; | |
1087 } | |
1088 return oldName; | |
1089 } | |
1090 | |
1091 static const LOWER_CASE_LETTERS = 26; | |
1092 static const LETTERS = LOWER_CASE_LETTERS; | |
1093 static const DIGITS = 10; | |
1094 | |
1095 static int nthLetter(int n) { | |
1096 return (n < LOWER_CASE_LETTERS) ? | |
1097 charCodes.$a + n : | |
1098 charCodes.$A + n - LOWER_CASE_LETTERS; | |
1099 } | |
1100 | |
1101 // Parameters go from a to z and variables go from z to a. This makes each | |
1102 // argument list and each top-of-function var declaration look similar and | |
1103 // helps gzip compress the file. If we have more than 26 arguments and | |
1104 // variables then we meet somewhere in the middle of the alphabet. After | |
1105 // that we give up trying to be nice to the compression algorithm and just | |
1106 // use the same namespace for arguments and variables, starting with A, and | |
1107 // moving on to a0, a1, etc. | |
1108 String declareVariable(String oldName) { | |
1109 if (avoidRenaming(oldName)) return oldName; | |
1110 var newName; | |
1111 if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { | |
1112 // Variables start from z and go backwards, for better gzipability. | |
1113 newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber); | |
1114 } else { | |
1115 // After 26 variables and parameters we allocate them in the same order. | |
1116 newName = getNameNumber(oldName, variableNumber + parameterNumber); | |
1117 } | |
1118 variableNumber++; | |
1119 return newName; | |
1120 } | |
1121 | |
1122 String declareParameter(String oldName) { | |
1123 if (avoidRenaming(oldName)) return oldName; | |
1124 var newName; | |
1125 if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { | |
1126 newName = getNameNumber(oldName, parameterNumber); | |
1127 } else { | |
1128 newName = getNameNumber(oldName, variableNumber + parameterNumber); | |
1129 } | |
1130 parameterNumber++; | |
1131 return newName; | |
1132 } | |
1133 | |
1134 bool avoidRenaming(String oldName) { | |
1135 // Variables of this $form$ are used in pattern matching the message of JS | |
1136 // exceptions, so should not be renamed. | |
1137 // TODO(sra): Introduce a way for indicating in the JS text which variables | |
1138 // should not be renamed. | |
1139 return oldName.startsWith(r'$') && oldName.endsWith(r'$'); | |
1140 } | |
1141 | |
1142 String getNameNumber(String oldName, int n) { | |
1143 if (maps.isEmpty) return oldName; | |
1144 | |
1145 String newName; | |
1146 if (n < LETTERS) { | |
1147 // Start naming variables a, b, c, ..., z, A, B, C, ..., Z. | |
1148 newName = new String.fromCharCodes([nthLetter(n)]); | |
1149 } else { | |
1150 // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ... | |
1151 // For all functions with fewer than 500 locals this is just as compact | |
1152 // as using aa, ab, etc. but avoids clashes with keywords. | |
1153 n -= LETTERS; | |
1154 int digit = n % DIGITS; | |
1155 n ~/= DIGITS; | |
1156 int alphaChars = 1; | |
1157 int nameSpaceSize = LETTERS; | |
1158 // Find out whether we should use the 1-character namespace (size 52), the | |
1159 // 2-character namespace (size 52*52), etc. | |
1160 while (n >= nameSpaceSize) { | |
1161 n -= nameSpaceSize; | |
1162 alphaChars++; | |
1163 nameSpaceSize *= LETTERS; | |
1164 } | |
1165 var codes = <int>[]; | |
1166 for (var i = 0; i < alphaChars; i++) { | |
1167 nameSpaceSize ~/= LETTERS; | |
1168 codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS)); | |
1169 } | |
1170 codes.add(charCodes.$0 + digit); | |
1171 newName = new String.fromCharCodes(codes); | |
1172 } | |
1173 assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName)); | |
1174 maps.last[oldName] = newName; | |
1175 return newName; | |
1176 } | |
1177 } | |
OLD | NEW |