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

Side by Side Diff: pkg/compiler/lib/src/js/printer.dart

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

Powered by Google App Engine
This is Rietveld 408576698