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