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