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