| 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_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 } | |
| OLD | NEW |