Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: sdk/lib/_internal/compiler/implementation/js/printer.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698