| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, 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 |
| 6 // OPEN DESIGN QUESTIONS: |
| 7 |
| 8 // Should the AST enforce that variable definitions are hoisted at the top? |
| 9 // This would simplify the AST for [For] and [ForIn] and also simplify block |
| 10 // flattening. |
| 11 // On the other hand, the code gets harder to test because the unparser only |
| 12 // works together with the middle-end. |
| 13 |
| 14 // Should the ${E.toString()} ==> ${E} rewrite be in the unparser? |
| 15 // It seems more like a semantic rewrite than a syntactic one. |
| 16 // On the other hand, it is really easy to do and costs almost nothing. |
| 17 |
| 18 // TODO(asgerf): Include metadata. |
| 19 // TODO(asgerf): Include cascade operator. |
| 20 library dart_printer; |
| 21 |
| 22 import '../dart2jslib.dart' as dart2js; |
| 23 import '../tree/tree.dart' as tree; |
| 24 import '../util/characters.dart' as characters; |
| 25 |
| 26 /// The following nodes correspond to [tree.Send] expressions: |
| 27 /// [FieldExpression], [IndexExpression], [Assignment], [Increment], |
| 28 /// [CallFunction], [CallMethod], [CallNew], [CallStatic], [UnaryOperator], |
| 29 /// [BinaryOperator], and [TypeOperator]. |
| 30 abstract class Node {} |
| 31 |
| 32 /// Receiver is an [Expression] or the [SuperReceiver]. |
| 33 abstract class Receiver extends Node {} |
| 34 |
| 35 /// Argument is an [Expression] or a [NamedArgument]. |
| 36 abstract class Argument extends Node {} |
| 37 |
| 38 abstract class Expression extends Node implements Receiver, Argument { |
| 39 bool get assignable => false; |
| 40 } |
| 41 |
| 42 abstract class Statement extends Node {} |
| 43 |
| 44 /// Used as receiver in expressions that dispatch to the super class. |
| 45 /// For instance, an expression such as `super.f()` is represented |
| 46 /// by a [CallMethod] node with [SuperReceiver] as its receiver. |
| 47 class SuperReceiver extends Receiver { |
| 48 static final SuperReceiver _instance = new SuperReceiver._create(); |
| 49 |
| 50 factory SuperReceiver() => _instance; |
| 51 SuperReceiver._create(); |
| 52 } |
| 53 |
| 54 /// Named arguments may occur in the argument list of |
| 55 /// [CallFunction], [CallMethod], [CallNew], and [CallStatic]. |
| 56 class NamedArgument extends Argument { |
| 57 final String name; |
| 58 final Expression expression; |
| 59 |
| 60 NamedArgument(this.name, this.expression); |
| 61 } |
| 62 |
| 63 class TypeAnnotation extends Node { |
| 64 final String name; |
| 65 final List<TypeAnnotation> typeArguments; |
| 66 |
| 67 TypeAnnotation(this.name, [this.typeArguments]); |
| 68 |
| 69 static final TypeAnnotation NUM = new TypeAnnotation("num"); |
| 70 static final TypeAnnotation INT = new TypeAnnotation("int"); |
| 71 static final TypeAnnotation DOUBLE = new TypeAnnotation("double"); |
| 72 static final TypeAnnotation BOOL = new TypeAnnotation("bool"); |
| 73 static final TypeAnnotation STRING = new TypeAnnotation("String"); |
| 74 static final TypeAnnotation DYNAMIC = new TypeAnnotation("dynamic"); |
| 75 } |
| 76 |
| 77 // STATEMENTS |
| 78 |
| 79 |
| 80 class Block extends Statement { |
| 81 final List<Statement> statements; |
| 82 |
| 83 Block(this.statements); |
| 84 } |
| 85 |
| 86 class Break extends Statement { |
| 87 final String label; |
| 88 |
| 89 Break([this.label]); |
| 90 } |
| 91 |
| 92 class Continue extends Statement { |
| 93 final String label; |
| 94 |
| 95 Continue([this.label]); |
| 96 } |
| 97 |
| 98 class EmptyStatement extends Statement { |
| 99 static final EmptyStatement _instance = new EmptyStatement._create(); |
| 100 |
| 101 factory EmptyStatement() => _instance; |
| 102 EmptyStatement._create(); |
| 103 } |
| 104 |
| 105 class ExpressionStatement extends Statement { |
| 106 final Expression expression; |
| 107 |
| 108 ExpressionStatement(this.expression); |
| 109 } |
| 110 |
| 111 class For extends Statement { |
| 112 final Node initializer; |
| 113 final Expression condition; |
| 114 final List<Expression> updates; |
| 115 final Statement body; |
| 116 |
| 117 /// Initializer must be [VariableDeclarations] or [Expression] or null. |
| 118 For(this.initializer, this.condition, this.updates, this.body) { |
| 119 assert(initializer == null |
| 120 || initializer is VariableDeclarations |
| 121 || initializer is Expression); |
| 122 } |
| 123 } |
| 124 |
| 125 class ForIn extends Statement { |
| 126 final Node leftHandValue; |
| 127 final Expression expression; |
| 128 final Statement body; |
| 129 |
| 130 /// [leftHandValue] must be [Identifier] or [VariableDeclarations] with |
| 131 /// exactly one definition, and that variable definition must have no |
| 132 /// initializer. |
| 133 ForIn(Node leftHandValue, this.expression, this.body) |
| 134 : this.leftHandValue = leftHandValue { |
| 135 assert(leftHandValue is Identifier |
| 136 || (leftHandValue is VariableDeclarations |
| 137 && leftHandValue.definitions.length == 1 |
| 138 && leftHandValue.definitions[0].initializer == null)); |
| 139 } |
| 140 } |
| 141 |
| 142 class While extends Statement { |
| 143 final Expression condition; |
| 144 final Statement body; |
| 145 |
| 146 While(this.condition, this.body); |
| 147 } |
| 148 |
| 149 class DoWhile extends Statement { |
| 150 final Statement body; |
| 151 final Expression condition; |
| 152 |
| 153 DoWhile(this.body, this.condition); |
| 154 } |
| 155 |
| 156 class If extends Statement { |
| 157 final Expression condition; |
| 158 final Statement thenStatement; |
| 159 final Statement elseStatement; |
| 160 |
| 161 If(this.condition, this.thenStatement, [this.elseStatement]); |
| 162 } |
| 163 |
| 164 class LabeledStatement extends Statement { |
| 165 final String label; |
| 166 final Statement statement; |
| 167 |
| 168 LabeledStatement(this.label, this.statement); |
| 169 } |
| 170 |
| 171 class Rethrow extends Statement { |
| 172 } |
| 173 |
| 174 class Return extends Statement { |
| 175 final Expression expression; |
| 176 |
| 177 Return([this.expression]); |
| 178 } |
| 179 |
| 180 class Switch extends Statement { |
| 181 final Expression expression; |
| 182 final List<SwitchCase> cases; |
| 183 |
| 184 Switch(this.expression, this.cases); |
| 185 } |
| 186 |
| 187 /// A sequence of case clauses followed by a sequence of statements. |
| 188 /// Represents the default case if [expressions] is null. |
| 189 /// |
| 190 /// NOTE: |
| 191 /// Control will never fall through to the following SwitchCase, even if |
| 192 /// the list of statements is empty. An empty list of statements will be |
| 193 /// unparsed to a semicolon to guarantee this behaviour. |
| 194 class SwitchCase extends Node { |
| 195 final List<Expression> expressions; |
| 196 final List<Statement> statements; |
| 197 |
| 198 SwitchCase(this.expressions, this.statements); |
| 199 SwitchCase.defaultCase(this.statements) : expressions = null; |
| 200 |
| 201 bool get isDefaultCase => expressions == null; |
| 202 } |
| 203 |
| 204 /// A try statement. The try, catch and finally blocks will automatically |
| 205 /// be printed inside a block statement if necessary. |
| 206 class Try extends Statement { |
| 207 final Statement tryBlock; |
| 208 final List<CatchBlock> catchBlocks; |
| 209 final Statement finallyBlock; |
| 210 |
| 211 Try(this.tryBlock, this.catchBlocks, [this.finallyBlock]) { |
| 212 assert(catchBlocks.length > 0 || finallyBlock != null); |
| 213 } |
| 214 } |
| 215 |
| 216 class CatchBlock extends Node { |
| 217 final TypeAnnotation onType; |
| 218 final String exceptionVar; |
| 219 final String stackVar; |
| 220 final Statement body; |
| 221 |
| 222 /// At least onType or exceptionVar must be given. |
| 223 /// stackVar may only be given if exceptionVar is also given. |
| 224 CatchBlock(this.body, {this.onType, this.exceptionVar, this.stackVar}) { |
| 225 // Must specify at least a type or an exception binding. |
| 226 assert(onType != null || exceptionVar != null); |
| 227 |
| 228 // We cannot bind the stack trace without binding the exception too. |
| 229 assert(stackVar == null || exceptionVar != null); |
| 230 } |
| 231 } |
| 232 |
| 233 class VariableDeclarations extends Statement { |
| 234 final TypeAnnotation type; |
| 235 final bool isFinal; |
| 236 final bool isConst; |
| 237 final List<VariableDeclaration> definitions; |
| 238 |
| 239 VariableDeclarations(this.definitions, |
| 240 { this.type, |
| 241 this.isFinal: false, |
| 242 this.isConst: false }) { |
| 243 // Cannot be both final and const. |
| 244 assert(!isFinal || !isConst); |
| 245 } |
| 246 } |
| 247 |
| 248 class VariableDeclaration extends Node { |
| 249 final String name; |
| 250 final Expression initializer; |
| 251 |
| 252 VariableDeclaration(this.name, [this.initializer]); |
| 253 } |
| 254 |
| 255 |
| 256 class FunctionDeclaration extends Statement { |
| 257 final TypeAnnotation returnType; |
| 258 final Parameters parameters; |
| 259 final String name; |
| 260 final Statement body; |
| 261 |
| 262 FunctionDeclaration(this.name, |
| 263 this.parameters, |
| 264 this.body, |
| 265 [ this.returnType ]); |
| 266 } |
| 267 |
| 268 class Parameters extends Node { |
| 269 final List<Parameter> requiredParameters; |
| 270 final List<Parameter> optionalParameters; |
| 271 final bool hasNamedParameters; |
| 272 |
| 273 Parameters(this.requiredParameters, |
| 274 [ this.optionalParameters, |
| 275 this.hasNamedParameters = false ]); |
| 276 |
| 277 Parameters.named(this.requiredParameters, this.optionalParameters) |
| 278 : hasNamedParameters = true; |
| 279 |
| 280 Parameters.positional(this.requiredParameters, this.optionalParameters) |
| 281 : hasNamedParameters = false; |
| 282 |
| 283 bool get hasOptionalParameters => |
| 284 optionalParameters != null && optionalParameters.length > 0; |
| 285 } |
| 286 |
| 287 class Parameter extends Node { |
| 288 final String name; |
| 289 |
| 290 /// Type of parameter, or return type of function parameter. |
| 291 final TypeAnnotation type; |
| 292 |
| 293 final Expression defaultValue; |
| 294 |
| 295 /// Parameters to function parameter. Null for non-function parameters. |
| 296 final Parameters parameters; |
| 297 |
| 298 Parameter(this.name, {this.type, this.defaultValue}) |
| 299 : parameters = null; |
| 300 |
| 301 Parameter.function(this.name, |
| 302 TypeAnnotation returnType, |
| 303 this.parameters, |
| 304 [this.defaultValue]) : type = returnType { |
| 305 assert(parameters != null); |
| 306 } |
| 307 |
| 308 /// True if this is a function parameter. |
| 309 bool get isFunction => parameters != null; |
| 310 |
| 311 // TODO(asgerf): Support modifiers on parameters (final, ...). |
| 312 } |
| 313 |
| 314 // EXPRESSIONS |
| 315 |
| 316 class FunctionExpression extends Expression { |
| 317 final Parameters parameters; |
| 318 final Statement body; |
| 319 |
| 320 FunctionExpression(this.parameters, this.body); |
| 321 } |
| 322 |
| 323 class Conditional extends Expression { |
| 324 final Expression condition; |
| 325 final Expression thenExpression; |
| 326 final Expression elseExpression; |
| 327 |
| 328 Conditional(this.condition, this.thenExpression, this.elseExpression); |
| 329 } |
| 330 |
| 331 /// An identifier expression. |
| 332 /// The unparser does not concern itself with scoping rules, and it is the |
| 333 /// responsibility of the AST creator to ensure that the identifier resolves |
| 334 /// to the proper definition. |
| 335 class Identifier extends Expression { |
| 336 final String name; |
| 337 |
| 338 Identifier(this.name); |
| 339 |
| 340 bool get assignable => true; |
| 341 } |
| 342 |
| 343 class Literal extends Expression { |
| 344 final dart2js.PrimitiveConstant value; |
| 345 |
| 346 Literal(this.value); |
| 347 } |
| 348 |
| 349 class LiteralList extends Expression { |
| 350 final bool isConst; |
| 351 final TypeAnnotation typeArgument; |
| 352 final List<Expression> values; |
| 353 |
| 354 LiteralList(this.values, {this.typeArgument, this.isConst: false}); |
| 355 } |
| 356 |
| 357 class LiteralMap extends Expression { |
| 358 final bool isConst; |
| 359 final List<TypeAnnotation> typeArguments; |
| 360 final List<LiteralMapEntry> entries; |
| 361 |
| 362 LiteralMap(this.entries, {this.typeArguments, this.isConst: false}) { |
| 363 assert(this.typeArguments == null |
| 364 || this.typeArguments.length == 0 |
| 365 || this.typeArguments.length == 2); |
| 366 } |
| 367 } |
| 368 |
| 369 class LiteralMapEntry extends Node { |
| 370 final Expression key; |
| 371 final Expression value; |
| 372 |
| 373 LiteralMapEntry(this.key, this.value); |
| 374 } |
| 375 |
| 376 class LiteralSymbol extends Expression { |
| 377 final String id; |
| 378 |
| 379 /// [id] should not include the # symbol |
| 380 LiteralSymbol(this.id); |
| 381 } |
| 382 |
| 383 /// StringConcat is used in place of string interpolation and juxtaposition. |
| 384 /// Semantically, each subexpression is evaluated and converted to a string |
| 385 /// by `toString()`. These string are then concatenated and returned. |
| 386 /// StringConcat unparses to a string literal, possibly with interpolations. |
| 387 /// The unparser will flatten nested StringConcats. |
| 388 /// A StringConcat node may have any number of children, including zero and one. |
| 389 class StringConcat extends Expression { |
| 390 final List<Expression> expressions; |
| 391 |
| 392 StringConcat(this.expressions); |
| 393 } |
| 394 |
| 395 /// Expression of form `e.f`. |
| 396 class FieldExpression extends Expression { |
| 397 final Receiver object; |
| 398 final String fieldName; |
| 399 |
| 400 FieldExpression(this.object, this.fieldName); |
| 401 |
| 402 bool get assignable => true; |
| 403 } |
| 404 |
| 405 /// Expression of form `e1[e2]`. |
| 406 class IndexExpression extends Expression { |
| 407 final Receiver object; |
| 408 final Expression index; |
| 409 |
| 410 IndexExpression(this.object, this.index); |
| 411 |
| 412 bool get assignable => true; |
| 413 } |
| 414 |
| 415 /// Expression of form `e(..)` |
| 416 /// Note that if [callee] is a [FieldExpression] this will translate into |
| 417 /// `(e.f)(..)` and not `e.f(..)`. Use a [CallMethod] to generate |
| 418 /// the latter type of expression. |
| 419 class CallFunction extends Expression { |
| 420 final Expression callee; |
| 421 final List<Argument> arguments; |
| 422 |
| 423 CallFunction(this.callee, this.arguments); |
| 424 } |
| 425 |
| 426 /// Expression of form `e.f(..)`. |
| 427 class CallMethod extends Expression { |
| 428 final Receiver object; |
| 429 final String methodName; |
| 430 final List<Argument> arguments; |
| 431 |
| 432 CallMethod(this.object, this.methodName, this.arguments); |
| 433 } |
| 434 |
| 435 /// Expression of form `new T(..)`, `new T.f(..)`, `const T(..)`, |
| 436 /// or `const T.f(..)`. |
| 437 class CallNew extends Expression { |
| 438 final bool isConst; |
| 439 final TypeAnnotation type; |
| 440 final String constructorName; |
| 441 final List<Argument> arguments; |
| 442 |
| 443 CallNew(this.type, |
| 444 this.arguments, |
| 445 { this.constructorName, |
| 446 this.isConst: false }); |
| 447 } |
| 448 |
| 449 /// Expression of form `T.f(..)`. |
| 450 class CallStatic extends Expression { |
| 451 final String className; |
| 452 final String methodName; |
| 453 final List<Argument> arguments; |
| 454 |
| 455 CallStatic(this.className, this.methodName, this.arguments); |
| 456 } |
| 457 |
| 458 /// Expression of form `!e` or `-e` or `~e`. |
| 459 class UnaryOperator extends Expression { |
| 460 final String operatorName; |
| 461 final Receiver operand; |
| 462 |
| 463 UnaryOperator(this.operatorName, this.operand) { |
| 464 assert(isUnaryOperator(operatorName)); |
| 465 } |
| 466 } |
| 467 |
| 468 /// Expression of form `e1 + e2`, `e1 - e2`, etc. |
| 469 /// This node also represents application of the logical operators && and ||. |
| 470 class BinaryOperator extends Expression { |
| 471 final Receiver left; |
| 472 final String operatorName; |
| 473 final Expression right; |
| 474 |
| 475 BinaryOperator(this.left, this.operatorName, this.right) { |
| 476 assert(isBinaryOperator(operatorName)); |
| 477 } |
| 478 } |
| 479 |
| 480 /// Expression of form `e is T` or `e is! T` or `e as T`. |
| 481 class TypeOperator extends Expression { |
| 482 final Expression expression; |
| 483 final String operatorName; |
| 484 final TypeAnnotation type; |
| 485 |
| 486 TypeOperator(this.expression, this.operatorName, this.type) { |
| 487 assert(operatorName == 'is' |
| 488 || operatorName == 'as' |
| 489 || operatorName == 'is!'); |
| 490 } |
| 491 } |
| 492 |
| 493 class Increment extends Expression { |
| 494 final Expression expression; |
| 495 final String operatorName; |
| 496 final bool isPrefix; |
| 497 |
| 498 Increment(this.expression, this.operatorName, this.isPrefix) { |
| 499 assert(operatorName == '++' || operatorName == '--'); |
| 500 assert(expression.assignable); |
| 501 } |
| 502 |
| 503 Increment.prefix(Expression expression, String operator) |
| 504 : this(expression, operator, true); |
| 505 |
| 506 Increment.postfix(Expression expression, String operator) |
| 507 : this(expression, operator, false); |
| 508 } |
| 509 |
| 510 class Assignment extends Expression { |
| 511 static final _operators = |
| 512 new Set.from(['=', '|=', '^=', '&=', '<<=', '>>=', |
| 513 '+=', '-=', '*=', '/=', '%=', '~/=']); |
| 514 |
| 515 final Expression left; |
| 516 final String operatorName; |
| 517 final Expression right; |
| 518 |
| 519 Assignment(this.left, this.operatorName, this.right) { |
| 520 assert(_operators.contains(operatorName)); |
| 521 assert(left.assignable); |
| 522 } |
| 523 } |
| 524 |
| 525 class Throw extends Expression { |
| 526 final Expression expression; |
| 527 |
| 528 Throw(this.expression); |
| 529 } |
| 530 |
| 531 class This extends Expression { |
| 532 static final This _instance = new This._create(); |
| 533 |
| 534 factory This() => _instance; |
| 535 This._create(); |
| 536 } |
| 537 |
| 538 // UNPARSER |
| 539 |
| 540 bool isUnaryOperator(String op) { |
| 541 return op == '!' || op == '-' || op == '~'; |
| 542 } |
| 543 bool isBinaryOperator(String op) { |
| 544 return Unparser._binaryPrecedence.containsKey(op); |
| 545 } |
| 546 |
| 547 |
| 548 const int NEWLINE = 10; |
| 549 const int CARRIAGE_RETURN = 13; |
| 550 |
| 551 /// The unparser will apply the following syntactic rewritings: |
| 552 /// Use short-hand function returns: |
| 553 /// foo(){return E} ==> foo() => E; |
| 554 /// Remove empty else branch: |
| 555 /// if (E) S else ; ==> if (E) S |
| 556 /// Flatten nested blocks: |
| 557 /// {S; {S; S}; S} ==> {S; S; S; S} |
| 558 /// Remove empty statements from block: |
| 559 /// {S; ; S} ==> {S; S} |
| 560 /// Unfold singleton blocks: |
| 561 /// {S} ==> S |
| 562 /// Empty block to empty statement: |
| 563 /// {} ==> ; |
| 564 /// Introduce not-equals operator: |
| 565 /// !(E == E) ==> E != E |
| 566 /// Introduce is-not operator: |
| 567 /// !(E is T) ==> E is!T |
| 568 /// Remove .toString() from string interpolation (see [StringConcat]) |
| 569 /// "X ${E.toString()} Y" ==> "X ${E} Y" |
| 570 /// |
| 571 /// The following transformations will NOT be applied here: |
| 572 /// Use implicit this: |
| 573 /// this.foo ==> foo (preconditions too complex for unparser) |
| 574 /// Merge adjacent variable definitions: |
| 575 /// var x; var y ==> var x,y; (hoisting will be done elsewhere) |
| 576 /// Merge adjacent labels: |
| 577 /// foo: bar: S ==> foobar: S (scoping is categorically ignored) |
| 578 /// |
| 579 /// The following transformations might be applied here in the future: |
| 580 /// Use implicit dynamic types: |
| 581 /// dynamic x = E ==> var x = E |
| 582 /// <dynamic>[] ==> [] |
| 583 class Unparser { |
| 584 StringSink output; |
| 585 |
| 586 Unparser(this.output); |
| 587 |
| 588 // Precedence levels |
| 589 static const EXPRESSION = 1; |
| 590 static const CONDITIONAL = 2; |
| 591 static const LOGICAL_OR = 3; |
| 592 static const LOGICAL_AND = 4; |
| 593 static const EQUALITY = 6; |
| 594 static const RELATIONAL = 7; |
| 595 static const BITWISE_OR = 8; |
| 596 static const BITWISE_XOR = 9; |
| 597 static const BITWISE_AND = 10; |
| 598 static const SHIFT = 11; |
| 599 static const ADDITIVE = 12; |
| 600 static const MULTIPLICATIVE = 13; |
| 601 static const UNARY = 14; |
| 602 static const POSTFIX_INCREMENT = 15; |
| 603 static const PRIMARY = 20; |
| 604 |
| 605 /// Precedence level required for the callee in a [FunctionCall]. |
| 606 static const CALLEE = 21; |
| 607 |
| 608 static const _binaryPrecedence = const { |
| 609 '&&': LOGICAL_AND, |
| 610 '||': LOGICAL_OR, |
| 611 |
| 612 '==': EQUALITY, |
| 613 '!=': EQUALITY, |
| 614 |
| 615 '>': RELATIONAL, |
| 616 '>=': RELATIONAL, |
| 617 '<': RELATIONAL, |
| 618 '<=': RELATIONAL, |
| 619 |
| 620 '|': BITWISE_OR, |
| 621 '^': BITWISE_XOR, |
| 622 '&': BITWISE_AND, |
| 623 |
| 624 '>>': SHIFT, |
| 625 '<<': SHIFT, |
| 626 |
| 627 '+': ADDITIVE, |
| 628 '-': ADDITIVE, |
| 629 |
| 630 '*': MULTIPLICATIVE, |
| 631 '%': MULTIPLICATIVE, |
| 632 '/': MULTIPLICATIVE, |
| 633 '~/': MULTIPLICATIVE, |
| 634 }; |
| 635 |
| 636 /// The type of quote used around string literals. |
| 637 static const QUOTE = "'"; |
| 638 static const QUOTE_CODE = 39; |
| 639 |
| 640 /// Return true if binary operators with the given precedence level are |
| 641 /// (left) associative. False if they are non-associative. |
| 642 static bool isAssociativeBinaryOperator(int precedence) { |
| 643 return precedence != EQUALITY && precedence != RELATIONAL; |
| 644 } |
| 645 |
| 646 |
| 647 void write(String s) { |
| 648 output.write(s); |
| 649 } |
| 650 |
| 651 /// Outputs each element from [items] separated by [separator]. |
| 652 /// The actual printing must be performed by the [callback]. |
| 653 void writeEach(String separator, Iterable items, void callback(any)) { |
| 654 bool first = true; |
| 655 for (var x in items) { |
| 656 if (first) { |
| 657 first = false; |
| 658 } else { |
| 659 write(separator); |
| 660 } |
| 661 callback(x); |
| 662 } |
| 663 } |
| 664 |
| 665 void writeOperator(String operator) { |
| 666 write(" "); // TODO(asgerf): Minimize use of whitespace. |
| 667 write(operator); |
| 668 write(" "); |
| 669 } |
| 670 |
| 671 /// Unfolds singleton blocks and returns the inner statement. |
| 672 /// If an empty block is found, the [EmptyStatement] is returned instead. |
| 673 Statement unfoldBlocks(Statement stmt) { |
| 674 while (stmt is Block && stmt.statements.length == 1) { |
| 675 Statement inner = (stmt as Block).statements[0]; |
| 676 if (definesVariable(inner)) { |
| 677 return stmt; // Do not unfold block with lexical scope. |
| 678 } |
| 679 stmt = inner; |
| 680 } |
| 681 if (stmt is Block && stmt.statements.length == 0) |
| 682 return new EmptyStatement(); |
| 683 return stmt; |
| 684 } |
| 685 |
| 686 void writeArgument(Argument arg) { |
| 687 if (arg is NamedArgument) { |
| 688 write(arg.name); |
| 689 write(':'); |
| 690 writeExpression(arg.expression); |
| 691 } else { |
| 692 writeExpression(arg); |
| 693 } |
| 694 } |
| 695 |
| 696 /// Prints the expression [e]. |
| 697 void writeExpression(Expression e) { |
| 698 writeExp(e, EXPRESSION); |
| 699 } |
| 700 |
| 701 /// Prints [e] as an expression with precedence of at least [minPrecedence], |
| 702 /// using parentheses if necessary to raise the precedence level. |
| 703 /// Abusing terminology slightly, the function accepts a [Receiver] which |
| 704 /// may also be the [SuperReceiver] object. |
| 705 void writeExp(Receiver e, int minPrecedence, {beginStmt:false}) { |
| 706 // TODO(asgerf): |
| 707 // Would there be a significant speedup using a Visitor or a method |
| 708 // on the AST instead of a chain of "if (e is T)" statements? |
| 709 void withPrecedence(int actual, void action()) { |
| 710 if (actual < minPrecedence) { |
| 711 write("("); |
| 712 beginStmt = false; |
| 713 action(); |
| 714 write(")"); |
| 715 } else { |
| 716 action(); |
| 717 } |
| 718 } |
| 719 if (e is SuperReceiver) { |
| 720 write('super'); |
| 721 } else if (e is FunctionExpression) { |
| 722 Statement stmt = unfoldBlocks(e.body); |
| 723 int precedence = stmt is Return ? EXPRESSION : PRIMARY; |
| 724 withPrecedence(precedence, () { |
| 725 writeParameters(e.parameters); |
| 726 if (stmt is Return) { |
| 727 write('=> '); // TODO(asgerf): Minimize use of whitespace. |
| 728 writeExp(stmt.expression, EXPRESSION); |
| 729 } else { |
| 730 writeBlock(stmt); |
| 731 } |
| 732 }); |
| 733 } else if (e is Conditional) { |
| 734 withPrecedence(CONDITIONAL, () { |
| 735 writeExp(e.condition, LOGICAL_OR, beginStmt: beginStmt); |
| 736 write(' ? '); // TODO(asgerf): Minimize use of whitespace. |
| 737 writeExp(e.thenExpression, EXPRESSION); |
| 738 write(' : '); |
| 739 writeExp(e.elseExpression, EXPRESSION); |
| 740 }); |
| 741 } else if (e is Identifier) { |
| 742 write(e.name); |
| 743 } else if (e is Literal) { |
| 744 if (e.value is dart2js.StringConstant) { |
| 745 writeStringLiteral(e); |
| 746 } |
| 747 else { |
| 748 write(e.value.toString()); |
| 749 } |
| 750 } else if (e is LiteralList) { |
| 751 if (e.isConst) { |
| 752 write(' const '); // TODO(asgerf): Minimize use of whitespace. |
| 753 } |
| 754 if (e.typeArgument != null) { |
| 755 write('<'); |
| 756 writeType(e.typeArgument); |
| 757 write('>'); |
| 758 } |
| 759 write('['); |
| 760 writeEach(',', e.values, writeExpression); |
| 761 write(']'); |
| 762 } |
| 763 else if (e is LiteralMap) { |
| 764 // The curly brace can be mistaken for a block statement if we |
| 765 // are at the beginning of a statement. |
| 766 bool needParen = beginStmt; |
| 767 if (e.isConst) { |
| 768 write(' const '); // TODO(asgerf): Minimize use of whitespace. |
| 769 needParen = false; |
| 770 } |
| 771 if (e.typeArguments != null && e.typeArguments.length > 0) { |
| 772 write('<'); |
| 773 writeEach(',', e.typeArguments, writeType); |
| 774 write('>'); |
| 775 needParen = false; |
| 776 } |
| 777 if (needParen) { |
| 778 write('('); |
| 779 } |
| 780 write('{'); |
| 781 writeEach(',', e.entries, (LiteralMapEntry en) { |
| 782 writeExp(en.key, EXPRESSION); |
| 783 write(' : '); // TODO(asgerf): Minimize use of whitespace. |
| 784 writeExp(en.value, EXPRESSION); |
| 785 }); |
| 786 write('}'); |
| 787 if (needParen) { |
| 788 write(')'); |
| 789 } |
| 790 } else if (e is LiteralSymbol) { |
| 791 write('#'); |
| 792 write(e.id); // TODO(asgerf): Do we need to escape something here? |
| 793 } else if (e is StringConcat) { |
| 794 writeStringLiteral(e); |
| 795 } else if (e is UnaryOperator) { |
| 796 Receiver operand = e.operand; |
| 797 // !(x == y) ==> x != y. |
| 798 if (e.operatorName == '!' && |
| 799 operand is BinaryOperator && operand.operatorName == '==') { |
| 800 withPrecedence(EQUALITY, () { |
| 801 writeExp(operand.left, RELATIONAL); |
| 802 writeOperator('!='); |
| 803 writeExp(operand.right, RELATIONAL); |
| 804 }); |
| 805 } |
| 806 // !(x is T) ==> x is!T |
| 807 else if (e.operatorName == '!' && |
| 808 operand is TypeOperator && operand.operatorName == 'is') { |
| 809 withPrecedence(RELATIONAL, () { |
| 810 writeExp(operand.expression, BITWISE_OR); |
| 811 write(' is!'); // TODO(asgerf): Minimize use of whitespace. |
| 812 writeType(operand.type); |
| 813 }); |
| 814 } |
| 815 else { |
| 816 withPrecedence(UNARY, () { |
| 817 writeOperator(e.operatorName); |
| 818 writeExp(e.operand, UNARY); |
| 819 }); |
| 820 } |
| 821 } else if (e is BinaryOperator) { |
| 822 int precedence = _binaryPrecedence[e.operatorName]; |
| 823 withPrecedence(precedence, () { |
| 824 // All binary operators are left-associative or non-associative. |
| 825 // For each operand, we use either the same precedence level as |
| 826 // the current operator, or one higher. |
| 827 int deltaLeft = isAssociativeBinaryOperator(precedence) ? 0 : 1; |
| 828 writeExp(e.left, precedence + deltaLeft, beginStmt: beginStmt); |
| 829 writeOperator(e.operatorName); |
| 830 writeExp(e.right, precedence + 1); |
| 831 }); |
| 832 } else if (e is TypeOperator) { |
| 833 withPrecedence(RELATIONAL, () { |
| 834 writeExp(e.expression, BITWISE_OR, beginStmt: beginStmt); |
| 835 write(' '); |
| 836 write(e.operatorName); |
| 837 write(' '); |
| 838 writeType(e.type); |
| 839 }); |
| 840 } else if (e is Assignment) { |
| 841 withPrecedence(EXPRESSION, () { |
| 842 writeExp(e.left, PRIMARY, beginStmt: beginStmt); |
| 843 writeOperator(e.operatorName); |
| 844 writeExp(e.right, EXPRESSION); |
| 845 }); |
| 846 } else if (e is FieldExpression) { |
| 847 withPrecedence(PRIMARY, () { |
| 848 writeExp(e.object, PRIMARY, beginStmt: beginStmt); |
| 849 write('.'); |
| 850 write(e.fieldName); |
| 851 }); |
| 852 } else if (e is IndexExpression) { |
| 853 withPrecedence(CALLEE, () { |
| 854 writeExp(e.object, PRIMARY, beginStmt: beginStmt); |
| 855 write('['); |
| 856 writeExp(e.index, EXPRESSION); |
| 857 write(']'); |
| 858 }); |
| 859 } else if (e is CallFunction) { |
| 860 withPrecedence(CALLEE, () { |
| 861 writeExp(e.callee, CALLEE, beginStmt: beginStmt); |
| 862 write('('); |
| 863 writeEach(',', e.arguments, writeArgument); |
| 864 write(')'); |
| 865 }); |
| 866 } else if (e is CallMethod) { |
| 867 withPrecedence(CALLEE, () { |
| 868 writeExp(e.object, PRIMARY, beginStmt: beginStmt); |
| 869 write('.'); |
| 870 write(e.methodName); |
| 871 write('('); |
| 872 writeEach(',', e.arguments, writeArgument); |
| 873 write(')'); |
| 874 }); |
| 875 } else if (e is CallNew) { |
| 876 withPrecedence(CALLEE, () { |
| 877 write(' '); // TODO(asgerf): Minimize use of whitespace. |
| 878 write(e.isConst ? 'const ' : 'new '); |
| 879 writeType(e.type); |
| 880 if (e.constructorName != null) { |
| 881 write('.'); |
| 882 write(e.constructorName); |
| 883 } |
| 884 write('('); |
| 885 writeEach(',', e.arguments, writeArgument); |
| 886 write(')'); |
| 887 }); |
| 888 } else if (e is CallStatic) { |
| 889 withPrecedence(CALLEE, () { |
| 890 write(e.className); |
| 891 write('.'); |
| 892 write(e.methodName); |
| 893 write('('); |
| 894 writeEach(',', e.arguments, writeArgument); |
| 895 write(')'); |
| 896 }); |
| 897 } else if (e is Increment) { |
| 898 int precedence = e.isPrefix ? UNARY : POSTFIX_INCREMENT; |
| 899 withPrecedence(precedence, () { |
| 900 if (e.isPrefix) { |
| 901 write(e.operatorName); |
| 902 writeExp(e.expression, PRIMARY); |
| 903 } else { |
| 904 writeExp(e.expression, PRIMARY, beginStmt: beginStmt); |
| 905 write(e.operatorName); |
| 906 } |
| 907 }); |
| 908 } else if (e is Throw) { |
| 909 withPrecedence(EXPRESSION, () { |
| 910 write('throw '); |
| 911 writeExp(e.expression, EXPRESSION); |
| 912 }); |
| 913 } else if (e is This) { |
| 914 write('this'); |
| 915 } else { |
| 916 throw "Unexpected expression: $e"; |
| 917 } |
| 918 } |
| 919 |
| 920 void writeParameters(Parameters params) { |
| 921 write('('); |
| 922 bool first = true; |
| 923 writeEach(',', params.requiredParameters, (Parameter p) { |
| 924 if (p.type != null) { |
| 925 writeType(p.type); |
| 926 write(' '); |
| 927 } |
| 928 write(p.name); |
| 929 if (p.parameters != null) { |
| 930 writeParameters(p.parameters); |
| 931 } |
| 932 }); |
| 933 if (params.hasOptionalParameters) { |
| 934 if (params.requiredParameters.length > 0) { |
| 935 write(','); |
| 936 } |
| 937 write(params.hasNamedParameters ? '{' : '['); |
| 938 writeEach(',', params.optionalParameters, (Parameter p) { |
| 939 if (p.type != null) { |
| 940 writeType(p.type); |
| 941 write(' '); |
| 942 } |
| 943 write(p.name); |
| 944 if (p.parameters != null) { |
| 945 writeParameters(p.parameters); |
| 946 } |
| 947 if (p.defaultValue != null) { |
| 948 write(params.hasNamedParameters ? ':' : '='); |
| 949 writeExp(p.defaultValue, EXPRESSION); |
| 950 } |
| 951 }); |
| 952 write(params.hasNamedParameters ? '}' : ']'); |
| 953 } |
| 954 write(')'); |
| 955 } |
| 956 |
| 957 void writeStatement(Statement stmt, {bool shortIf: true}) { |
| 958 stmt = unfoldBlocks(stmt); |
| 959 if (stmt is Block) { |
| 960 write('{'); |
| 961 stmt.statements.forEach(writeBlockMember); |
| 962 write('}'); |
| 963 } else if (stmt is Break) { |
| 964 write('break'); |
| 965 if (stmt.label != null) { |
| 966 write(' '); |
| 967 write(stmt.label); |
| 968 } |
| 969 write(';'); |
| 970 } else if (stmt is Continue) { |
| 971 write('continue'); |
| 972 if (stmt.label != null) { |
| 973 write(' '); |
| 974 write(stmt.label); |
| 975 } |
| 976 write(';'); |
| 977 } else if (stmt is EmptyStatement) { |
| 978 write(';'); |
| 979 } else if (stmt is ExpressionStatement) { |
| 980 writeExp(stmt.expression, EXPRESSION, beginStmt:true); |
| 981 write(';'); |
| 982 } else if (stmt is For) { |
| 983 write('for('); |
| 984 Node init = stmt.initializer; |
| 985 if (init is Expression) { |
| 986 writeExp(init, EXPRESSION); |
| 987 } else if (init is VariableDeclarations) { |
| 988 writeVariableDefinitions(init); |
| 989 } |
| 990 write(';'); |
| 991 if (stmt.condition != null) { |
| 992 writeExp(stmt.condition, EXPRESSION); |
| 993 } |
| 994 write(';'); |
| 995 writeEach(',', stmt.updates, writeExpression); |
| 996 write(')'); |
| 997 writeStatement(stmt.body, shortIf: shortIf); |
| 998 } else if (stmt is ForIn) { |
| 999 write('for('); |
| 1000 Node lhv = stmt.leftHandValue; |
| 1001 if (lhv is Identifier) { |
| 1002 write(lhv.name); |
| 1003 } else { |
| 1004 writeVariableDefinitions(lhv as VariableDeclarations); |
| 1005 } |
| 1006 write(' in '); |
| 1007 writeExp(stmt.expression, EXPRESSION); |
| 1008 write(')'); |
| 1009 writeStatement(stmt.body, shortIf: shortIf); |
| 1010 } else if (stmt is While) { |
| 1011 write('while('); |
| 1012 writeExp(stmt.condition, EXPRESSION); |
| 1013 write(')'); |
| 1014 writeStatement(stmt.body, shortIf: shortIf); |
| 1015 } else if (stmt is DoWhile) { |
| 1016 write('do '); // TODO(asgerf): Minimize use of whitespace. |
| 1017 writeStatement(stmt.body); |
| 1018 write('while('); |
| 1019 writeExp(stmt.condition, EXPRESSION); |
| 1020 write(');'); |
| 1021 } else if (stmt is If) { |
| 1022 // if (E) S else ; ==> if (E) S |
| 1023 Statement elsePart = unfoldBlocks(stmt.elseStatement); |
| 1024 if (elsePart is EmptyStatement) { |
| 1025 elsePart = null; |
| 1026 } |
| 1027 if (!shortIf && elsePart == null) { |
| 1028 write('{'); |
| 1029 } |
| 1030 write('if('); |
| 1031 writeExp(stmt.condition, EXPRESSION); |
| 1032 write(')'); |
| 1033 writeStatement(stmt.thenStatement, shortIf: elsePart == null); |
| 1034 if (elsePart != null) { |
| 1035 write('else '); |
| 1036 writeStatement(elsePart, shortIf: shortIf); |
| 1037 } |
| 1038 if (!shortIf && elsePart == null) { |
| 1039 write('}'); |
| 1040 } |
| 1041 } else if (stmt is LabeledStatement) { |
| 1042 write(stmt.label); |
| 1043 write(':'); |
| 1044 writeStatement(stmt.statement, shortIf: shortIf); |
| 1045 } else if (stmt is Rethrow) { |
| 1046 write('rethrow;'); |
| 1047 } else if (stmt is Return) { |
| 1048 write('return'); |
| 1049 if (stmt.expression != null) { |
| 1050 write(' '); |
| 1051 writeExp(stmt.expression, EXPRESSION); |
| 1052 } |
| 1053 write(';'); |
| 1054 } else if (stmt is Switch) { |
| 1055 write('switch('); |
| 1056 writeExp(stmt.expression, EXPRESSION); |
| 1057 write('){'); |
| 1058 for (SwitchCase caze in stmt.cases) { |
| 1059 if (caze.isDefaultCase) { |
| 1060 write('default:'); |
| 1061 } else { |
| 1062 for (Expression exp in caze.expressions) { |
| 1063 write('case '); |
| 1064 writeExp(exp, EXPRESSION); |
| 1065 write(':'); |
| 1066 } |
| 1067 } |
| 1068 if (caze.statements.isEmpty) { |
| 1069 write(';'); // Prevent fall-through. |
| 1070 } else { |
| 1071 caze.statements.forEach(writeBlockMember); |
| 1072 } |
| 1073 } |
| 1074 write('}'); |
| 1075 } else if (stmt is Try) { |
| 1076 write('try'); |
| 1077 writeBlock(stmt.tryBlock); |
| 1078 for (CatchBlock block in stmt.catchBlocks) { |
| 1079 if (block.onType != null) { |
| 1080 write('on '); |
| 1081 writeType(block.onType); |
| 1082 } |
| 1083 if (block.exceptionVar != null) { |
| 1084 write('catch('); |
| 1085 write(block.exceptionVar); |
| 1086 if (block.stackVar != null) { |
| 1087 write(','); |
| 1088 write(block.stackVar); |
| 1089 } |
| 1090 write(')'); |
| 1091 } |
| 1092 writeBlock(block.body); |
| 1093 } |
| 1094 if (stmt.finallyBlock != null) { |
| 1095 write('finally'); |
| 1096 writeBlock(stmt.finallyBlock); |
| 1097 } |
| 1098 } else if (stmt is VariableDeclarations) { |
| 1099 writeVariableDefinitions(stmt); |
| 1100 write(';'); |
| 1101 } else if (stmt is FunctionDeclaration) { |
| 1102 if (stmt.returnType != null) { |
| 1103 writeType(stmt.returnType); |
| 1104 write(' '); |
| 1105 } |
| 1106 write(stmt.name); |
| 1107 writeParameters(stmt.parameters); |
| 1108 Statement body = unfoldBlocks(stmt.body); |
| 1109 if (body is Return) { |
| 1110 write('=> '); // TODO(asgerf): Minimize use of whitespace. |
| 1111 writeExp(body.expression, EXPRESSION); |
| 1112 write(';'); |
| 1113 } else { |
| 1114 writeBlock(body); |
| 1115 } |
| 1116 } else { |
| 1117 throw "Unexpected statement: $stmt"; |
| 1118 } |
| 1119 } |
| 1120 |
| 1121 /// Writes a variable definition statement without the trailing semicolon |
| 1122 void writeVariableDefinitions(VariableDeclarations vds) { |
| 1123 if (vds.isConst) |
| 1124 write('const '); |
| 1125 else if (vds.isFinal) |
| 1126 write('final '); |
| 1127 if (vds.type != null) { |
| 1128 writeType(vds.type); |
| 1129 write(' '); |
| 1130 } |
| 1131 if (!vds.isConst && !vds.isFinal && vds.type == null) { |
| 1132 write('var '); |
| 1133 } |
| 1134 writeEach(',', vds.definitions, (VariableDeclaration vd) { |
| 1135 write(vd.name); |
| 1136 if (vd.initializer != null) { |
| 1137 write('='); |
| 1138 writeExp(vd.initializer, EXPRESSION); |
| 1139 } |
| 1140 }); |
| 1141 } |
| 1142 |
| 1143 /// True of statements that introduce variables in the scope of their |
| 1144 /// surrounding block. Blocks containing such statements cannot be unfolded. |
| 1145 bool definesVariable(Statement s) { |
| 1146 return s is VariableDeclarations || s is FunctionDeclaration; |
| 1147 } |
| 1148 |
| 1149 /// Writes the given statement in a context where only blocks are allowed. |
| 1150 void writeBlock(Statement stmt) { |
| 1151 if (stmt is Block) { |
| 1152 writeStatement(stmt); |
| 1153 } else { |
| 1154 write('{'); |
| 1155 writeBlockMember(stmt); |
| 1156 write('}'); |
| 1157 } |
| 1158 } |
| 1159 |
| 1160 /// Outputs a statement that is a member of a block statement (or a similar |
| 1161 /// sequence of statements, such as in switch statement). |
| 1162 /// This will flatten blocks and skip empty statement. |
| 1163 void writeBlockMember(Statement stmt) { |
| 1164 if (stmt is Block && !stmt.statements.any(definesVariable)) { |
| 1165 stmt.statements.forEach(writeBlockMember); |
| 1166 } else if (stmt is EmptyStatement) { |
| 1167 // do nothing |
| 1168 } else { |
| 1169 writeStatement(stmt); |
| 1170 } |
| 1171 } |
| 1172 |
| 1173 void writeType(TypeAnnotation type) { |
| 1174 write(type.name); |
| 1175 if (type.typeArguments != null && type.typeArguments.length > 0) { |
| 1176 write('<'); |
| 1177 writeEach(',', type.typeArguments, writeType); |
| 1178 write('>'); |
| 1179 } |
| 1180 } |
| 1181 |
| 1182 void writeStringLiteral(Expression node) { |
| 1183 // TODO(asgerf): This might be a bit too expensive. Benchmark. |
| 1184 // Flatten the StringConcat tree. |
| 1185 List parts = []; // Expression or int (char node) |
| 1186 void collectParts(Expression e) { |
| 1187 if (e is StringConcat) { |
| 1188 e.expressions.forEach(collectParts); |
| 1189 } else if (e is Literal && e.value is dart2js.StringConstant) { |
| 1190 for (int char in e.value.value) { |
| 1191 parts.add(char); |
| 1192 } |
| 1193 } else if (e is CallMethod && |
| 1194 e.object is Expression && // Do not match super.toString() |
| 1195 e.methodName == "toString" && |
| 1196 e.arguments.length == 0) { |
| 1197 // ${e.toString()} ==> ${e} |
| 1198 collectParts(e.object); |
| 1199 } else { |
| 1200 parts.add(e); |
| 1201 } |
| 1202 } |
| 1203 collectParts(node); |
| 1204 |
| 1205 // We use a dynamic algorithm to compute the optimal way of printing |
| 1206 // the string literal. |
| 1207 // |
| 1208 // Using string juxtapositions, it is possible to switch from one quoting |
| 1209 // to another, e.g. the constant "''''" '""""' uses this trick. |
| 1210 // |
| 1211 // As we move through the string from left to right, we maintain a strategy |
| 1212 // for each StringQuoting Q, denoting the best way to print the current |
| 1213 // prefix so that we end with a string literal quoted with Q. |
| 1214 // At every step, each strategy is either: |
| 1215 // 1) Updated to include the cost of printing the next character. |
| 1216 // 2) Abandoned because it is cheaper to use another strategy as prefix, |
| 1217 // and then switching quotation using a juxtaposition. |
| 1218 |
| 1219 int getQuoteCost(tree.StringQuoting quot) { |
| 1220 return quot.leftQuoteLength + quot.rightQuoteLength; |
| 1221 } |
| 1222 |
| 1223 // Create initial scores for each StringQuoting and index them |
| 1224 // into raw/non-raw and single-quote/double-quote. |
| 1225 List<OpenStringChunk> best = <OpenStringChunk>[]; |
| 1226 List<int> raws = <int>[]; |
| 1227 List<int> nonRaws = <int>[]; |
| 1228 List<int> sqs = <int>[]; |
| 1229 List<int> dqs = <int>[]; |
| 1230 for (tree.StringQuoting q in tree.StringQuoting.mapping) { |
| 1231 // Ignore multiline quotings for now. Encoding of line breaks is unclear. |
| 1232 // TODO(asgerf): Include multiline quotation schemes. |
| 1233 if (q.leftQuoteCharCount >= 3) |
| 1234 continue; |
| 1235 OpenStringChunk chunk = new OpenStringChunk(null, q, getQuoteCost(q)); |
| 1236 int index = best.length; |
| 1237 best.add(chunk); |
| 1238 |
| 1239 if (q.raw) { |
| 1240 raws.add(index); |
| 1241 } else { |
| 1242 nonRaws.add(index); |
| 1243 } |
| 1244 if (q.quote == characters.$SQ) { |
| 1245 sqs.add(index); |
| 1246 } else { |
| 1247 dqs.add(index); |
| 1248 } |
| 1249 } |
| 1250 |
| 1251 /// True if [x] is a letter, digit, or underscore. |
| 1252 /// Such characters may not follow a shorthand string interpolation. |
| 1253 bool isIdentifierPartNoDollar(dynamic x) { |
| 1254 if (x is! int) |
| 1255 return false; |
| 1256 return (characters.$0 <= x && x <= characters.$9) |
| 1257 || (characters.$A <= x && x <= characters.$Z) |
| 1258 || (characters.$a <= x && x <= characters.$z) |
| 1259 || (x == characters.$_); |
| 1260 } |
| 1261 |
| 1262 /// Applies additional cost to each track in [penalized], and considers |
| 1263 /// switching from each [penalized] to a [nonPenalized] track. |
| 1264 void penalize(List<int> penalized, |
| 1265 List<int> nonPenalized, |
| 1266 int endIndex, |
| 1267 num cost(tree.StringQuoting q)) { |
| 1268 for (int j in penalized) { |
| 1269 // Check if another track can benefit from switching from this track. |
| 1270 for (int k in nonPenalized) { |
| 1271 num newCost = best[j].cost |
| 1272 + 1 // Whitespace in string juxtaposition |
| 1273 + getQuoteCost(best[k].quoting); |
| 1274 if (newCost < best[k].cost) { |
| 1275 best[k] = new OpenStringChunk( |
| 1276 best[j].end(endIndex), |
| 1277 best[k].quoting, |
| 1278 newCost); |
| 1279 } |
| 1280 } |
| 1281 best[j].cost += cost(best[j].quoting); |
| 1282 } |
| 1283 } |
| 1284 |
| 1285 // Iterate through the string and update the score for each StringQuoting. |
| 1286 for (int i = 0; i < parts.length; i++) { |
| 1287 var part = parts[i]; |
| 1288 if (part is int) { |
| 1289 int char = part; |
| 1290 switch (char) { |
| 1291 case characters.$$: |
| 1292 case characters.$BACKSLASH: |
| 1293 penalize(nonRaws, raws, i, (q) => 1); |
| 1294 break; |
| 1295 case characters.$DQ: |
| 1296 penalize(dqs, sqs, i, (q) => q.raw ? double.INFINITY : 1); |
| 1297 break; |
| 1298 case characters.$SQ: |
| 1299 penalize(sqs, dqs, i, (q) => q.raw ? double.INFINITY : 1); |
| 1300 break; |
| 1301 case NEWLINE: |
| 1302 case CARRIAGE_RETURN: |
| 1303 penalize(raws, nonRaws, i, (q) => double.INFINITY); |
| 1304 break; |
| 1305 } |
| 1306 } else { |
| 1307 // Penalize raw literals for string interpolation. |
| 1308 penalize(raws, nonRaws, i, (q) => double.INFINITY); |
| 1309 |
| 1310 // Splitting a string can sometimes allow us to use a shorthand |
| 1311 // string interpolation that would otherwise be illegal. |
| 1312 // E.g. "...${foo}x..." -> "...$foo" 'x...' |
| 1313 // If are other factors that make splitting advantageous, |
| 1314 // we can gain even more by doing the split here. |
| 1315 if (part is Identifier && |
| 1316 !part.name.contains(r'$') && |
| 1317 i + 1 < parts.length && |
| 1318 isIdentifierPartNoDollar(parts[i+1])) { |
| 1319 for (int j in nonRaws) { |
| 1320 for (int k = 0; k < best.length; k++) { |
| 1321 num newCost = best[j].cost |
| 1322 + 1 // Whitespace in string juxtaposition |
| 1323 - 2 // Save two curly braces |
| 1324 + getQuoteCost(best[k].quoting); |
| 1325 if (newCost < best[k].cost) { |
| 1326 best[k] = new OpenStringChunk( |
| 1327 best[j].end(i+1), |
| 1328 best[k].quoting, |
| 1329 newCost); |
| 1330 } |
| 1331 } |
| 1332 } |
| 1333 } |
| 1334 } |
| 1335 } |
| 1336 |
| 1337 // Select the cheapest strategy |
| 1338 OpenStringChunk bestChunk = best[0]; |
| 1339 for (OpenStringChunk chunk in best) { |
| 1340 if (chunk.cost < bestChunk.cost) { |
| 1341 bestChunk = chunk; |
| 1342 } |
| 1343 } |
| 1344 |
| 1345 void printChunk(StringChunk chunk) { |
| 1346 int startIndex; |
| 1347 if (chunk.previous != null) { |
| 1348 printChunk(chunk.previous); |
| 1349 write(' '); // String juxtaposition requires a space between literals. |
| 1350 startIndex = chunk.previous.endIndex; |
| 1351 } else { |
| 1352 startIndex = 0; |
| 1353 } |
| 1354 if (chunk.quoting.raw) { |
| 1355 write('r'); |
| 1356 } |
| 1357 write(chunk.quoting.quoteChar); |
| 1358 bool raw = chunk.quoting.raw; |
| 1359 int quoteCode = chunk.quoting.quote; |
| 1360 for (int i=startIndex; i<chunk.endIndex; i++) { |
| 1361 var part = parts[i]; |
| 1362 if (part is int) { |
| 1363 int char = part; |
| 1364 switch (char) { |
| 1365 case characters.$$: |
| 1366 if (raw) |
| 1367 write(r'$'); |
| 1368 else |
| 1369 write(r'\$'); |
| 1370 break; |
| 1371 case characters.$BACKSLASH: |
| 1372 if (raw) |
| 1373 write(r'\'); |
| 1374 else |
| 1375 write(r'\\'); |
| 1376 break; |
| 1377 case characters.$DQ: |
| 1378 if (quoteCode == char) { |
| 1379 write(r'\"'); |
| 1380 } else { |
| 1381 write(r'"'); |
| 1382 } |
| 1383 break; |
| 1384 case characters.$SQ: |
| 1385 if (quoteCode == char) { |
| 1386 write(r"\'"); |
| 1387 } else { |
| 1388 write(r"'"); |
| 1389 } |
| 1390 break; |
| 1391 case NEWLINE: |
| 1392 write(r'\n'); |
| 1393 break; |
| 1394 case CARRIAGE_RETURN: |
| 1395 write(r'\r'); |
| 1396 break; |
| 1397 default: |
| 1398 write(new String.fromCharCode(char)); |
| 1399 } |
| 1400 } else if (part is Identifier && |
| 1401 !part.name.contains(r'$') && |
| 1402 (i == chunk.endIndex - 1 || |
| 1403 !isIdentifierPartNoDollar(parts[i+1]))) { |
| 1404 write(r'$'); |
| 1405 write(part.name); |
| 1406 } else { |
| 1407 write(r'${'); |
| 1408 writeExpression(part); |
| 1409 write('}'); |
| 1410 } |
| 1411 } |
| 1412 write(chunk.quoting.quoteChar); |
| 1413 } |
| 1414 printChunk(bestChunk.end(parts.length)); |
| 1415 } |
| 1416 |
| 1417 } |
| 1418 |
| 1419 |
| 1420 /// Strategy for printing a prefix of a string literal. |
| 1421 /// A chunk represents the substring going from [:previous.endIndex:] to |
| 1422 /// [endIndex] (or from 0 to [endIndex] if [previous] is null). |
| 1423 class StringChunk { |
| 1424 final StringChunk previous; |
| 1425 final tree.StringQuoting quoting; |
| 1426 final int endIndex; |
| 1427 |
| 1428 StringChunk(this.previous, this.quoting, this.endIndex); |
| 1429 } |
| 1430 |
| 1431 /// [StringChunk] that has not yet been assigned an [endIndex]. |
| 1432 /// It additionally has a [cost] denoting the number of auxilliary characters |
| 1433 /// (quotes, spaces, etc) needed to print the literal using this strategy |
| 1434 class OpenStringChunk { |
| 1435 final StringChunk previous; |
| 1436 final tree.StringQuoting quoting; |
| 1437 num cost; |
| 1438 |
| 1439 OpenStringChunk(this.previous, this.quoting, this.cost); |
| 1440 |
| 1441 StringChunk end(int endIndex) { |
| 1442 return new StringChunk(previous, quoting, endIndex); |
| 1443 } |
| 1444 } |
| OLD | NEW |