Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(237)

Side by Side Diff: pkg/compiler/lib/src/dart_backend/backend_ast_to_frontend_ast.dart

Issue 693183006: Revert "Move dart2js from sdk/lib/_internal/compiler to pkg/compiler" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 dart_tree_printer;
6
7 import '../constants/values.dart' as values;
8 import '../dart_types.dart' as types;
9 import '../dart2jslib.dart' as dart2js;
10 import '../elements/elements.dart' as elements;
11 import '../tree/tree.dart' as tree;
12 import '../scanner/scannerlib.dart';
13 import '../util/util.dart';
14 import 'backend_ast_nodes.dart';
15 import 'backend_ast_emitter.dart' show createTypeAnnotation;
16
17 /// Translates the backend AST to Dart frontend AST.
18 tree.FunctionExpression emit(dart2js.TreeElementMapping treeElements,
19 Node definition) {
20 return new TreePrinter(treeElements).makeExpression(definition);
21 }
22
23 /// If true, the unparser will insert a coment in front of every function
24 /// it emits. This helps indicate which functions were translated by the new
25 /// backend.
26 bool INSERT_NEW_BACKEND_COMMENT =
27 const bool.fromEnvironment('USE_NEW_BACKEND', defaultValue: false);
28
29 /// Converts backend ASTs to frontend ASTs.
30 class TreePrinter {
31 dart2js.TreeElementMapping treeElements;
32
33 TreePrinter([this.treeElements]);
34
35 void setElement(tree.Node node, elements.Element element, source) {
36 if (treeElements != null) {
37 if (element == null) {
38 throw "Missing element from ${source}";
39 }
40 treeElements[node] = element;
41 }
42 }
43
44 void setType(tree.Node node, types.DartType type, source) {
45 if (treeElements != null) {
46 if (type == null) {
47 throw "Missing type from ${source}";
48 }
49 treeElements.setType(node, type);
50 }
51 }
52
53 // Group tokens: () [] {} <>
54 static BeginGroupToken makeGroup(PrecedenceInfo open, PrecedenceInfo close) {
55 BeginGroupToken openTok = new BeginGroupToken(open, -1);
56 openTok.endGroup = new SymbolToken(close, -1);
57 return openTok;
58 }
59
60 final BeginGroupToken openParen = makeGroup(OPEN_PAREN_INFO,
61 CLOSE_PAREN_INFO);
62 final BeginGroupToken openBrace = makeGroup(OPEN_CURLY_BRACKET_INFO,
63 CLOSE_CURLY_BRACKET_INFO);
64 final BeginGroupToken openBracket = makeGroup(OPEN_SQUARE_BRACKET_INFO,
65 CLOSE_SQUARE_BRACKET_INFO);
66 final BeginGroupToken lt = makeGroup(LT_INFO, GT_INFO);
67
68 Token get closeParen => openParen.endGroup;
69 Token get closeBrace => openBrace.endGroup;
70 Token get closeBracket => openBracket.endGroup;
71 Token get gt => lt.endGroup;
72
73 // Symbol tokens
74 final Token semicolon = new SymbolToken(SEMICOLON_INFO, -1);
75 final Token indexToken = new SymbolToken(INDEX_INFO, -1); // "[]"
76 final Token question = new SymbolToken(QUESTION_INFO, -1);
77 final Token colon = new SymbolToken(COLON_INFO, -1);
78 final Token hash = new SymbolToken(HASH_INFO, -1);
79 final Token bang = new SymbolToken(BANG_INFO, -1);
80 final Token eq = new SymbolToken(EQ_INFO, -1);
81
82 // Keyword tokens
83 static Token makeIdToken(String text) {
84 return new StringToken.fromString(IDENTIFIER_INFO, text, -1);
85 }
86 final Token newToken = makeIdToken('new');
87 final Token constToken = makeIdToken('const');
88 final Token throwToken = makeIdToken('throw');
89 final Token rethrowToken = makeIdToken('rethrow');
90 final Token breakToken = makeIdToken('break');
91 final Token continueToken = makeIdToken('continue');
92 final Token doToken = makeIdToken('do');
93 final Token whileToken = makeIdToken('while');
94 final Token ifToken = makeIdToken('if');
95 final Token elseToken = makeIdToken('else');
96 final Token awaitToken = makeIdToken('await');
97 final Token forToken = makeIdToken('for');
98 final Token inToken = makeIdToken('in');
99 final Token returnToken = makeIdToken('return');
100 final Token switchToken = makeIdToken('switch');
101 final Token caseToken = makeIdToken('case');
102 final Token defaultToken = makeIdToken('default');
103 final Token tryToken = makeIdToken('try');
104 final Token catchToken = makeIdToken('catch');
105 final Token onToken = makeIdToken('on');
106 final Token finallyToken = makeIdToken('finally');
107 final Token getToken = makeIdToken('get');
108 final Token setToken = makeIdToken('set');
109 final Token classToken = makeIdToken('class');
110 final Token extendsToken = makeIdToken('extends');
111 final Token withToken = makeIdToken('with');
112 final Token implementsToken = makeIdToken('implements');
113
114 static tree.Identifier makeIdentifier(String name) {
115 return new tree.Identifier(
116 new StringToken.fromString(IDENTIFIER_INFO, name, -1));
117 }
118
119 static tree.Operator makeOperator(String name) {
120 return new tree.Operator(
121 new StringToken.fromString(IDENTIFIER_INFO, name, -1));
122 }
123
124 // Utilities for creating NodeLists
125 Link<tree.Node> makeLink(Iterable<tree.Node> nodes) {
126 LinkBuilder builder = new LinkBuilder();
127 for (tree.Node node in nodes) {
128 builder.addLast(node);
129 }
130 return builder.toLink();
131 }
132
133 tree.NodeList blankList() {
134 return new tree.NodeList(null, makeLink([]), null, '');
135 }
136 tree.NodeList singleton(tree.Node node) {
137 return new tree.NodeList(null, makeLink([node]), null, '');
138 }
139 tree.NodeList makeList(String delimiter,
140 Iterable<tree.Node> nodes,
141 { Token open,
142 Token close }) {
143 return new tree.NodeList(open, makeLink(nodes), close, delimiter);
144 }
145 tree.NodeList parenList(String delimiter, Iterable<tree.Node> nodes) {
146 return makeList(delimiter, nodes, open: openParen, close: closeParen);
147 }
148 tree.NodeList bracketList(String delimiter, Iterable<tree.Node> nodes) {
149 return makeList(delimiter, nodes, open: openBracket, close: closeBracket);
150 }
151 tree.NodeList braceList(String delimiter, Iterable<tree.Node> nodes) {
152 return makeList(delimiter, nodes, open: openBrace, close: closeBrace);
153 }
154 tree.NodeList argList(Iterable<tree.Node> nodes) {
155 return parenList(',', nodes);
156 }
157 tree.NodeList typeArgList(Iterable<tree.Node> nodes) {
158 return makeList(',', nodes, open: lt, close: gt);
159 }
160
161 /// Converts a qualified name into nested Sends.
162 tree.Node makeName(String name) {
163 if (name == null) {
164 return null;
165 }
166 List<String> names = name.split('.').toList(growable:false);
167 tree.Node node = makeIdentifier(names[0]);
168 for (int i = 1; i < names.length; i++) {
169 node = new tree.Send(node, makeIdentifier(names[i]));
170 }
171 return node;
172 }
173
174 static Token assignmentToken(String operatorName) {
175 switch (operatorName) {
176 case '=': return new SymbolToken(EQ_INFO, -1);
177 case '+=': return new SymbolToken(PLUS_EQ_INFO, -1);
178 case '-=': return new SymbolToken(MINUS_EQ_INFO, -1);
179 case '*=': return new SymbolToken(STAR_EQ_INFO, -1);
180 case '/=': return new SymbolToken(SLASH_EQ_INFO, -1);
181 case '~/=': return new SymbolToken(TILDE_SLASH_EQ_INFO, -1);
182 case '%=': return new SymbolToken(PERCENT_EQ_INFO, -1);
183 case '&=': return new SymbolToken(AMPERSAND_EQ_INFO, -1);
184 case '^=': return new SymbolToken(CARET_EQ_INFO, -1);
185 case '|=': return new SymbolToken(BAR_EQ_INFO, -1);
186 case '>>=': return new SymbolToken(GT_GT_EQ_INFO, -1);
187 case '<<=': return new SymbolToken(LT_LT_EQ_INFO, -1);
188 default:
189 throw "Unrecognized assignment operator: $operatorName";
190 }
191 }
192
193 static Token binopToken(String operatorName) {
194 switch (operatorName) {
195 case '+': return new SymbolToken(PLUS_INFO, -1);
196 case '-': return new SymbolToken(MINUS_INFO, -1);
197 case '*': return new SymbolToken(STAR_INFO, -1);
198 case '/': return new SymbolToken(SLASH_INFO, -1);
199 case '~/': return new SymbolToken(TILDE_SLASH_INFO, -1);
200 case '%': return new SymbolToken(PERCENT_INFO, -1);
201 case '&': return new SymbolToken(AMPERSAND_INFO, -1);
202 case '^': return new SymbolToken(CARET_INFO, -1);
203 case '|': return new SymbolToken(BAR_INFO, -1);
204 case '>>': return new SymbolToken(GT_GT_INFO, -1);
205 case '<<': return new SymbolToken(LT_LT_INFO, -1);
206 case '==': return new SymbolToken(EQ_EQ_INFO, -1);
207 case '!=': return new SymbolToken(BANG_EQ_INFO, -1);
208 case '>': return new SymbolToken(GT_INFO, -1);
209 case '>=': return new SymbolToken(GT_EQ_INFO, -1);
210 case '<': return new SymbolToken(LT_INFO, -1);
211 case '<=': return new SymbolToken(LT_EQ_INFO, -1);
212 case '&&': return new SymbolToken(AMPERSAND_AMPERSAND_INFO, -1);
213 case '||': return new SymbolToken(BAR_BAR_INFO, -1);
214 default:
215 throw "Unrecognized binary operator: $operatorName";
216 }
217 }
218
219 static Token incrementToken(String operatorName) {
220 switch (operatorName) {
221 case '++': return new SymbolToken(PLUS_PLUS_INFO, -1);
222 case '--': return new SymbolToken(MINUS_MINUS_INFO, -1);
223 default:
224 throw "Unrecognized increment operator: $operatorName";
225 }
226 }
227
228 static Token typeOpToken(String operatorName) {
229 switch (operatorName) { // "is!" is not an operator in the frontend AST.
230 case 'is': return new SymbolToken(IS_INFO, -1);
231 case 'as': return new SymbolToken(AS_INFO, -1);
232 default:
233 throw 'Unrecognized type operator: $operatorName';
234 }
235 }
236
237 Token unopToken(String operatorName) {
238 switch (operatorName) {
239 case '-': return new SymbolToken(MINUS_INFO, -1);
240 case '~': return new SymbolToken(TILDE_INFO, -1);
241 case '!': return bang;
242 default:
243 throw "Unrecognized unary operator: $operatorName";
244 }
245 }
246
247 tree.Node makeStaticReceiver(elements.Element element) {
248 if (treeElements == null) return null;
249 if (element.isStatic) {
250 elements.ClassElement enclosingClass = element.enclosingClass;
251 tree.Send send = new tree.Send(
252 null,
253 makeIdentifier(enclosingClass.name));
254 treeElements[send] = enclosingClass;
255 return send;
256 } else {
257 return null;
258 }
259 }
260
261 tree.Node makeArgument(Argument arg) {
262 if (arg is Expression) {
263 return makeExpression(arg);
264 } else if (arg is NamedArgument) {
265 return new tree.NamedArgument(
266 makeIdentifier(arg.name),
267 colon,
268 makeExpression(arg.expression));
269 } else {
270 throw "Unrecognized argument type: ${arg}";
271 }
272 }
273
274 tree.Node makeExpression(Expression exp) {
275 return makeExp(exp, EXPRESSION);
276 }
277
278 /// Converts [exp] to a [tree.Node] that unparses to an expression with
279 /// a precedence level of at least [minPrecedence]. The expression will be
280 /// wrapped in a parenthesis if necessary.
281 tree.Node makeExp(Receiver exp, int minPrecedence, {bool beginStmt: false}) {
282 tree.Node result;
283 int precedence;
284 bool needParen = false;
285 if (exp is SuperReceiver) {
286 precedence = CALLEE;
287 result = makeIdentifier('super');
288 } else if (exp is Assignment) {
289 Expression left = exp.left;
290 tree.Node receiver;
291 tree.Node selector;
292 tree.NodeList arguments;
293 elements.Element element;
294 if (left is Identifier) {
295 receiver = makeStaticReceiver(left.element);
296 selector = makeIdentifier(left.name);
297 arguments = singleton(makeExpression(exp.right));
298 element = left.element;
299 } else if (left is FieldExpression) {
300 receiver = makeExp(left.object, PRIMARY, beginStmt: beginStmt);
301 selector = makeIdentifier(left.fieldName);
302 arguments = singleton(makeExpression(exp.right));
303 } else if (left is IndexExpression) {
304 receiver = makeExp(left.object, PRIMARY, beginStmt: beginStmt);
305 selector = new tree.Operator(indexToken);
306 arguments = bracketList(',',
307 [makeExpression(left.index), makeExpression(exp.right)]);
308 } else {
309 throw "Unexpected left-hand side of assignment: ${left}";
310 }
311 tree.Operator op = new tree.Operator(assignmentToken(exp.operator));
312 result = new tree.SendSet(receiver, selector, op, arguments);
313 if (left is Identifier) {
314 setElement(result, element, exp);
315 }
316 precedence = EXPRESSION;
317 } else if (exp is BinaryOperator) {
318 precedence = BINARY_PRECEDENCE[exp.operator];
319 int deltaLeft = isAssociativeBinaryOperator(precedence) ? 0 : 1;
320 result = new tree.Send(
321 makeExp(exp.left, precedence + deltaLeft, beginStmt: beginStmt),
322 new tree.Operator(binopToken(exp.operator)),
323 singleton(makeExp(exp.right, precedence + 1)));
324 } else if (exp is CallFunction) {
325 precedence = CALLEE;
326 tree.Node selector;
327 Expression callee = exp.callee;
328 elements.Element element;
329 tree.Node receiver;
330 if (callee is Identifier) {
331 receiver = makeStaticReceiver(callee.element);
332 selector = makeIdentifier(callee.name);
333 element = callee.element;
334 } else {
335 selector = makeExp(callee, CALLEE, beginStmt: beginStmt);
336 }
337 result = new tree.Send(
338 receiver,
339 selector,
340 argList(exp.arguments.map(makeArgument)));
341 if (callee is Identifier) {
342 setElement(result, element, exp);
343 }
344 } else if (exp is CallMethod) {
345 precedence = CALLEE;
346 tree.Node receiver = exp.object is This
347 ? null
348 : makeExp(exp.object, PRIMARY, beginStmt: beginStmt);
349 result = new tree.Send(
350 receiver,
351 makeIdentifier(exp.methodName),
352 argList(exp.arguments.map(makeArgument)));
353 } else if (exp is CallNew) {
354 precedence = CALLEE;
355 tree.Node selector = makeName(exp.type.name);
356 if (exp.type.typeArguments.length > 0) {
357 selector = new tree.TypeAnnotation(
358 selector,
359 typeArgList(exp.type.typeArguments.map(makeType)));
360 setType(selector, exp.dartType, exp);
361 }
362 if (exp.constructorName != null) {
363 selector = new tree.Send(
364 selector,
365 makeIdentifier(exp.constructorName));
366 }
367 tree.Send send = new tree.Send(
368 null,
369 selector,
370 argList(exp.arguments.map(makeArgument)));
371 result = new tree.NewExpression(
372 exp.isConst ? constToken : newToken,
373 send);
374 setType(result, exp.dartType, exp);
375 setElement(send, exp.constructor, exp);
376 } else if (exp is CallStatic) {
377 precedence = CALLEE;
378 result = new tree.Send(
379 makeStaticReceiver(exp.element),
380 makeIdentifier(exp.methodName),
381 argList(exp.arguments.map(makeArgument)));
382 setElement(result, exp.element, exp);
383 } else if (exp is Conditional) {
384 precedence = CONDITIONAL;
385 result = new tree.Conditional(
386 makeExp(exp.condition, LOGICAL_OR, beginStmt: beginStmt),
387 makeExp(exp.thenExpression, EXPRESSION),
388 makeExp(exp.elseExpression, EXPRESSION),
389 question,
390 colon);
391 } else if (exp is FieldExpression) {
392 precedence = PRIMARY;
393 tree.Node receiver = exp.object is This
394 ? null
395 : makeExp(exp.object, PRIMARY, beginStmt: beginStmt);
396 result = new tree.Send(receiver, makeIdentifier(exp.fieldName));
397 } else if (exp is FunctionExpression) {
398 precedence = PRIMARY;
399 if (beginStmt && exp.name != null) {
400 needParen = true; // Do not mistake for function declaration.
401 }
402 Token getOrSet = exp.isGetter
403 ? getToken
404 : exp.isSetter
405 ? setToken
406 : null;
407 tree.NodeList parameters = exp.isGetter
408 ? makeList("", [])
409 : makeParameters(exp.parameters);
410 tree.Node body = makeFunctionBody(exp.body);
411 result = new tree.FunctionExpression(
412 functionName(exp),
413 parameters,
414 body,
415 exp.returnType == null || exp.element.isConstructor
416 ? null
417 : makeType(exp.returnType),
418 makeFunctionModifiers(exp),
419 null, // initializers
420 getOrSet, // get/set
421 null); // async modifier
422 setElement(result, exp.element, exp);
423 } else if (exp is Identifier) {
424 precedence = CALLEE;
425 result = new tree.Send(
426 makeStaticReceiver(exp.element),
427 makeIdentifier(exp.name));
428 setElement(result, exp.element, exp);
429 } else if (exp is Increment) {
430 Expression lvalue = exp.expression;
431 tree.Node receiver;
432 tree.Node selector;
433 tree.Node argument;
434 bool innerBeginStmt = beginStmt && !exp.isPrefix;
435 if (lvalue is Identifier) {
436 receiver = makeStaticReceiver(lvalue.element);
437 selector = makeIdentifier(lvalue.name);
438 } else if (lvalue is FieldExpression) {
439 receiver = makeExp(lvalue.object, PRIMARY, beginStmt: innerBeginStmt);
440 selector = makeIdentifier(lvalue.fieldName);
441 } else if (lvalue is IndexExpression) {
442 receiver = makeExp(lvalue.object, PRIMARY, beginStmt: innerBeginStmt);
443 selector = new tree.Operator(indexToken);
444 argument = makeExpression(lvalue.index);
445 } else {
446 throw "Unrecognized left-hand side: ${lvalue}";
447 }
448 tree.Operator op = new tree.Operator(incrementToken(exp.operator));
449 if (exp.isPrefix) {
450 precedence = UNARY;
451 result = new tree.SendSet.prefix(receiver, selector, op, argument);
452 } else {
453 precedence = POSTFIX_INCREMENT;
454 result = new tree.SendSet.postfix(receiver, selector, op, argument);
455 }
456 if (lvalue is Identifier) {
457 setElement(result, lvalue.element, exp);
458 }
459 } else if (exp is IndexExpression) {
460 precedence = CALLEE;
461 result = new tree.Send(
462 makeExp(exp.object, PRIMARY, beginStmt: beginStmt),
463 new tree.Operator(indexToken),
464 bracketList(',', [makeExpression(exp.index)]));
465 } else if (exp is Literal) {
466 precedence = CALLEE;
467 values.PrimitiveConstantValue value = exp.value;
468 Token tok = new StringToken.fromString(
469 STRING_INFO, '${value.primitiveValue}', -1);
470 if (value.isString) {
471 result = unparseStringLiteral(exp);
472 } else if (value.isInt) {
473 result = new tree.LiteralInt(tok, null);
474 } else if (value.isDouble) {
475 if (value.primitiveValue == double.INFINITY) {
476 precedence = MULTIPLICATIVE;
477 tok = new StringToken.fromString(STRING_INFO, '1/0.0', -1);
478 } else if (value.primitiveValue == double.NEGATIVE_INFINITY) {
479 precedence = MULTIPLICATIVE;
480 tok = new StringToken.fromString(STRING_INFO, '-1/0.0', -1);
481 } else if (value.primitiveValue.isNaN) {
482 precedence = MULTIPLICATIVE;
483 tok = new StringToken.fromString(STRING_INFO, '0/0.0', -1);
484 }
485 result = new tree.LiteralDouble(tok, null);
486 } else if (value.isBool) {
487 result = new tree.LiteralBool(tok, null);
488 } else if (value.isNull) {
489 result = new tree.LiteralNull(tok);
490 } else {
491 throw "Unrecognized constant: ${value}";
492 }
493 } else if (exp is LiteralList) {
494 precedence = PRIMARY;
495 tree.NodeList typeArgs = null;
496 if (exp.typeArgument != null) {
497 typeArgs = typeArgList([makeType(exp.typeArgument)]);
498 }
499 result = new tree.LiteralList(
500 typeArgs,
501 bracketList(',', exp.values.map(makeExpression)),
502 exp.isConst ? constToken : null);
503 } else if (exp is LiteralMap) {
504 precedence = PRIMARY;
505 if (beginStmt) {
506 // The opening brace can be confused with a block statement.
507 needParen = true;
508 }
509 tree.NodeList typeArgs = null;
510 if (exp.typeArguments != null && exp.typeArguments.length > 0) {
511 typeArgs = typeArgList(exp.typeArguments.map(makeType));
512 }
513 result = new tree.LiteralMap(
514 typeArgs,
515 braceList(',', exp.entries.map(makeLiteralMapEntry)),
516 exp.isConst ? constToken : null);
517 } else if (exp is LiteralSymbol) {
518 precedence = PRIMARY;
519 result = new tree.LiteralSymbol(
520 hash,
521 makeList('.', exp.id.split('.').map(makeIdentifier)));
522 } else if (exp is LiteralType) {
523 precedence = TYPE_LITERAL;
524 elements.Element optionalElement = exp.type.element;
525 result = new tree.Send(
526 optionalElement == null ? null : makeStaticReceiver(optionalElement),
527 makeIdentifier(exp.name));
528 treeElements.setType(result, exp.type);
529 if (optionalElement != null) { // dynamic does not have an element
530 setElement(result, optionalElement, exp);
531 }
532 } else if (exp is ReifyTypeVar) {
533 precedence = PRIMARY;
534 result = new tree.Send(
535 null,
536 makeIdentifier(exp.name));
537 setElement(result, exp.element, exp);
538 setType(result, exp.element.type, exp);
539 } else if (exp is StringConcat) {
540 precedence = PRIMARY;
541 result = unparseStringLiteral(exp);
542 } else if (exp is This) {
543 precedence = CALLEE;
544 result = makeIdentifier('this');
545 } else if (exp is Throw) {
546 precedence = EXPRESSION; // ???
547 result = new tree.Throw(
548 makeExpression(exp.expression),
549 throwToken,
550 throwToken); // endToken not used by unparser
551 } else if (exp is TypeOperator) {
552 precedence = RELATIONAL;
553 tree.Operator operator;
554 tree.Node rightOperand = makeType(exp.type);
555 if (exp.operator == 'is!') {
556 operator = new tree.Operator(typeOpToken('is'));
557 rightOperand = new tree.Send(
558 rightOperand,
559 new tree.Operator(bang),
560 blankList());
561 } else {
562 operator = new tree.Operator(typeOpToken(exp.operator));
563 }
564 result = new tree.Send(
565 makeExp(exp.expression, BITWISE_OR, beginStmt: beginStmt),
566 operator,
567 singleton(rightOperand));
568 } else if (exp is UnaryOperator) {
569 precedence = UNARY;
570 result = new tree.Send.prefix(
571 makeExp(exp.operand, UNARY),
572 new tree.Operator(unopToken(exp.operatorName)));
573 } else {
574 throw "Unknown expression type: ${exp}";
575 }
576
577 needParen = needParen || precedence < minPrecedence;
578 if (needParen) {
579 result = parenthesize(result);
580 }
581 return result;
582 }
583
584 /// Creates a LiteralString with [verbatim] as the value.
585 /// No (un)quoting or (un)escaping will be performed by this method.
586 /// The [DartString] inside the literal will be set to null because the
587 /// code emitter does not use it.
588 tree.LiteralString makeVerbatimStringLiteral(String verbatim) {
589 Token tok = new StringToken.fromString(STRING_INFO, verbatim, -1);
590 return new tree.LiteralString(tok, null);
591 }
592
593 tree.LiteralMapEntry makeLiteralMapEntry(LiteralMapEntry en) {
594 return new tree.LiteralMapEntry(
595 makeExpression(en.key),
596 colon,
597 makeExpression(en.value));
598 }
599
600 /// A comment token to be inserted when [INSERT_NEW_BACKEND_COMMENT] is true.
601 final SymbolToken newBackendComment = new SymbolToken(
602 const PrecedenceInfo('/* new backend */ ', 0, OPEN_CURLY_BRACKET_TOKEN),
603 -1);
604
605 tree.Node makeFunctionBody(Statement stmt) {
606 if (INSERT_NEW_BACKEND_COMMENT) {
607 return new tree.Block(makeList('', [makeBlock(stmt)],
608 open: newBackendComment));
609 } else {
610 return makeBlock(stmt);
611 }
612 }
613
614 /// Produces a statement in a context where only blocks are allowed.
615 tree.Node makeBlock(Statement stmt) {
616 if (stmt is Block || stmt is EmptyStatement) {
617 return makeStatement(stmt);
618 } else {
619 return new tree.Block(braceList('', [makeStatement(stmt)]));
620 }
621 }
622
623 /// Adds the given statement to a block. If the statement itself is a block
624 /// it will be flattened (if this does not change lexical scoping).
625 void addBlockMember(Statement stmt, List<tree.Node> accumulator) {
626 if (stmt is Block && !stmt.statements.any(Unparser.definesVariable)) {
627 for (Statement innerStmt in stmt.statements) {
628 addBlockMember(innerStmt, accumulator);
629 }
630 } else if (stmt is EmptyStatement) {
631 // No need to include empty statements inside blocks
632 } else {
633 accumulator.add(makeStatement(stmt));
634 }
635 }
636
637 /// True if [stmt] is equivalent to an empty statement.
638 bool isEmptyStatement(Statement stmt) {
639 return stmt is EmptyStatement ||
640 (stmt is Block && stmt.statements.every(isEmptyStatement));
641 }
642
643 tree.Node makeStatement(Statement stmt, {bool shortIf: true}) {
644 if (stmt is Block) {
645 List<tree.Node> body = <tree.Node>[];
646 for (Statement innerStmt in stmt.statements) {
647 addBlockMember(innerStmt, body);
648 }
649 return new tree.Block(braceList('', body));
650 } else if (stmt is Break) {
651 return new tree.BreakStatement(
652 stmt.label == null ? null : makeIdentifier(stmt.label),
653 breakToken,
654 semicolon);
655 } else if (stmt is Continue) {
656 return new tree.ContinueStatement(
657 stmt.label == null ? null : makeIdentifier(stmt.label),
658 continueToken,
659 semicolon);
660 } else if (stmt is DoWhile) {
661 return new tree.DoWhile(
662 makeStatement(stmt.body, shortIf: shortIf),
663 parenthesize(makeExpression(stmt.condition)),
664 doToken,
665 whileToken,
666 semicolon);
667 } else if (stmt is EmptyStatement) {
668 return new tree.EmptyStatement(semicolon);
669 } else if (stmt is ExpressionStatement) {
670 return new tree.ExpressionStatement(
671 makeExp(stmt.expression, EXPRESSION, beginStmt: true),
672 semicolon);
673 } else if (stmt is For) {
674 tree.Node initializer;
675 if (stmt.initializer is VariableDeclarations) {
676 initializer = makeVariableDeclarations(stmt.initializer);
677 } else if (stmt.initializer is Expression) {
678 initializer = makeExpression(stmt.initializer);
679 } else {
680 initializer = null;
681 }
682 tree.Node condition;
683 if (stmt.condition != null) {
684 condition = new tree.ExpressionStatement(
685 makeExpression(stmt.condition),
686 semicolon);
687 } else {
688 condition = new tree.EmptyStatement(semicolon);
689 }
690 return new tree.For(
691 initializer,
692 condition,
693 makeList(',', stmt.updates.map(makeExpression)),
694 makeStatement(stmt.body, shortIf: shortIf),
695 forToken);
696 } else if (stmt is ForIn) {
697 tree.Node left;
698 if (stmt.leftHandValue is Identifier) {
699 left = makeExpression(stmt.leftHandValue);
700 } else {
701 left = makeVariableDeclarations(stmt.leftHandValue);
702 }
703 return new tree.ForIn(
704 left,
705 makeExpression(stmt.expression),
706 makeStatement(stmt.body, shortIf: shortIf),
707 awaitToken,
708 forToken,
709 inToken);
710 } else if (stmt is FunctionDeclaration) {
711 tree.FunctionExpression function = new tree.FunctionExpression(
712 stmt.name != null ? makeIdentifier(stmt.name) : null,
713 makeParameters(stmt.parameters),
714 makeFunctionBody(stmt.body),
715 stmt.returnType != null ? makeType(stmt.returnType) : null,
716 makeEmptyModifiers(),
717 null, // initializers
718 null, // get/set
719 null); // async modifier
720 setElement(function, stmt.function.element, stmt);
721 return new tree.FunctionDeclaration(function);
722 } else if (stmt is If) {
723 if (stmt.elseStatement == null || isEmptyStatement(stmt.elseStatement)) {
724 tree.Node node = new tree.If(
725 parenthesize(makeExpression(stmt.condition)),
726 makeStatement(stmt.thenStatement),
727 null, // else statement
728 ifToken,
729 null); // else token
730 if (shortIf)
731 return node;
732 else
733 return new tree.Block(braceList('', [node]));
734 } else {
735 return new tree.If(
736 parenthesize(makeExpression(stmt.condition)),
737 makeStatement(stmt.thenStatement, shortIf: false),
738 makeStatement(stmt.elseStatement, shortIf: shortIf),
739 ifToken,
740 elseToken); // else token
741 }
742 } else if (stmt is LabeledStatement) {
743 List<tree.Label> labels = [];
744 Statement inner = stmt;
745 while (inner is LabeledStatement) {
746 LabeledStatement lbl = inner as LabeledStatement;
747 labels.add(new tree.Label(makeIdentifier(lbl.label), colon));
748 inner = lbl.statement;
749 }
750 return new tree.LabeledStatement(
751 makeList('', labels),
752 makeStatement(inner, shortIf: shortIf));
753 } else if (stmt is Rethrow) {
754 return new tree.Rethrow(rethrowToken, semicolon);
755 } else if (stmt is Return) {
756 return new tree.Return(
757 returnToken,
758 semicolon,
759 stmt.expression == null ? null : makeExpression(stmt.expression));
760 } else if (stmt is Switch) {
761 return new tree.SwitchStatement(
762 parenthesize(makeExpression(stmt.expression)),
763 braceList('', stmt.cases.map(makeSwitchCase)),
764 switchToken);
765 } else if (stmt is Try) {
766 return new tree.TryStatement(
767 makeBlock(stmt.tryBlock),
768 braceList('', stmt.catchBlocks.map(makeCatchBlock)),
769 stmt.finallyBlock == null ? null : makeBlock(stmt.finallyBlock),
770 tryToken,
771 finallyToken);
772 } else if (stmt is VariableDeclarations) {
773 return makeVariableDeclarations(stmt, useVar: true, endToken: semicolon);
774 } else if (stmt is While) {
775 return new tree.While(
776 parenthesize(makeExpression(stmt.condition)),
777 makeStatement(stmt.body, shortIf: shortIf),
778 whileToken);
779 } else {
780 throw "Unrecognized statement: ${stmt}";
781 }
782 }
783
784 tree.Node makeVariableDeclaration(VariableDeclaration vd) {
785 tree.Node id = makeIdentifier(vd.name);
786 setElement(id, vd.element, vd);
787 if (vd.initializer == null) {
788 return id;
789 }
790 tree.Node send = new tree.SendSet(
791 null,
792 id,
793 new tree.Operator(eq),
794 singleton(makeExpression(vd.initializer)));
795 setElement(send, vd.element, vd);
796 return send;
797 }
798
799 /// If [useVar] is true, the variable definition will use `var` as modifier
800 /// if no other modifiers are present.
801 /// [endToken] will be used to terminate the declaration list.
802 tree.Node makeVariableDeclarations(VariableDeclarations decl,
803 { bool useVar: false,
804 Token endToken: null }) {
805 return new tree.VariableDefinitions(
806 decl.type == null ? null : makeType(decl.type),
807 makeVarModifiers(isConst: decl.isConst,
808 isFinal: decl.isFinal,
809 useVar: useVar && decl.type == null),
810 makeList(',',
811 decl.declarations.map(makeVariableDeclaration),
812 close: endToken));
813 }
814
815 tree.CatchBlock makeCatchBlock(CatchBlock block) {
816 List<tree.VariableDefinitions> formals = [];
817 if (block.exceptionVar != null) {
818 formals.add(new tree.VariableDefinitions(
819 null,
820 makeEmptyModifiers(),
821 singleton(makeIdentifier(block.exceptionVar))));
822 }
823 if (block.stackVar != null) {
824 formals.add(new tree.VariableDefinitions(
825 null,
826 makeEmptyModifiers(),
827 singleton(makeIdentifier(block.stackVar))));
828 }
829 return new tree.CatchBlock(
830 block.onType == null ? null : makeType(block.onType),
831 block.exceptionVar == null ? null : argList(formals),
832 makeBlock(block.body),
833 block.onType == null ? null : onToken,
834 block.exceptionVar == null ? null : catchToken);
835 }
836
837 tree.SwitchCase makeSwitchCase(SwitchCase caze) {
838 if (caze.isDefaultCase) {
839 return new tree.SwitchCase(
840 blankList(),
841 defaultToken,
842 makeList('', caze.statements.map(makeStatement)),
843 null); // startToken unused by unparser
844 } else {
845 return new tree.SwitchCase(
846 makeList('', caze.expressions.map(makeCaseMatch)),
847 null, // defaultKeyword,
848 makeList('', caze.statements.map(makeStatement)),
849 null); // startToken unused by unparser
850 }
851 }
852
853 tree.CaseMatch makeCaseMatch(Expression exp) {
854 return new tree.CaseMatch(caseToken, makeExpression(exp), colon);
855 }
856
857 tree.TypeAnnotation makeType(TypeAnnotation type) {
858 tree.NodeList typeArgs;
859 if (type.typeArguments.length > 0) {
860 typeArgs = typeArgList(type.typeArguments.map(makeType));
861 } else {
862 typeArgs = null;
863 }
864 tree.TypeAnnotation result =
865 new tree.TypeAnnotation(makeIdentifier(type.name), typeArgs);
866 setType(result, type.dartType, type);
867 return result;
868 }
869
870 tree.NodeList makeParameters(Parameters params) {
871 List<tree.Node> nodes =
872 params.requiredParameters.map(makeParameter).toList();
873 if (params.hasOptionalParameters) {
874 Token assign = params.hasNamedParameters ? colon : eq;
875 Token open = params.hasNamedParameters ? openBrace : openBracket;
876 Token close = params.hasNamedParameters ? closeBrace : closeBracket;
877 Iterable<tree.Node> opt =
878 params.optionalParameters.map((p) => makeParameter(p,assign));
879 nodes.add(new tree.NodeList(open, makeLink(opt), close, ','));
880 }
881 return argList(nodes);
882 }
883
884 /// [assignOperator] is used for writing the default value.
885 tree.Node makeParameter(Parameter param, [Token assignOperator]) {
886 if (param.isFunction) {
887 tree.Node definition = new tree.FunctionExpression(
888 makeIdentifier(param.name),
889 makeParameters(param.parameters),
890 null, // body
891 param.type == null ? null : makeType(param.type),
892 makeEmptyModifiers(), // TODO: Function parameter modifiers?
893 null, // initializers
894 null, // get/set
895 null); // async modifier
896 if (param.element != null) {
897 setElement(definition, param.element, param);
898 }
899 if (param.defaultValue != null) {
900 return new tree.SendSet(
901 null,
902 definition,
903 new tree.Operator(assignOperator),
904 singleton(makeExpression(param.defaultValue)));
905 } else {
906 return new tree.VariableDefinitions(
907 null,
908 makeEmptyModifiers(),
909 singleton(definition));
910 }
911 } else {
912 tree.Node definition;
913 if (param.defaultValue != null) {
914 definition = new tree.SendSet(
915 null,
916 makeIdentifier(param.name),
917 new tree.Operator(assignOperator),
918 singleton(makeExpression(param.defaultValue)));
919 } else {
920 definition = makeIdentifier(param.name);
921 }
922 if (param.element != null) {
923 setElement(definition, param.element, param);
924 }
925 return new tree.VariableDefinitions(
926 param.type == null ? null : makeType(param.type),
927 makeEmptyModifiers(), // TODO: Parameter modifiers?
928 singleton(definition));
929 }
930 }
931
932 tree.Modifiers makeEmptyModifiers() {
933 return new tree.Modifiers(blankList());
934 }
935
936 tree.Modifiers makeModifiers({bool isStatic: false,
937 bool isAbstract: false,
938 bool isFactory: false,
939 bool isConst: false,
940 bool isFinal: false,
941 bool isVar: false}) {
942 List<tree.Node> nodes = [];
943 if (isStatic) {
944 nodes.add(makeIdentifier('static'));
945 }
946 if (isAbstract) {
947 nodes.add(makeIdentifier('abstract'));
948 }
949 if (isFactory) {
950 nodes.add(makeIdentifier('factory'));
951 }
952 if (isConst) {
953 nodes.add(makeIdentifier('const'));
954 }
955 if (isFinal) {
956 nodes.add(makeIdentifier('final'));
957 }
958 if (isVar) {
959 nodes.add(makeIdentifier('var'));
960 }
961 return new tree.Modifiers(makeList('', nodes));
962 }
963
964 tree.Modifiers makeVarModifiers({bool isConst: false,
965 bool isFinal: false,
966 bool useVar: false}) {
967 return makeModifiers(isConst: isConst,
968 isFinal: isFinal,
969 isVar: useVar && !(isConst || isFinal));
970 }
971
972 tree.Modifiers makeFunctionModifiers(FunctionExpression exp) {
973 if (exp.element == null) return makeEmptyModifiers();
974 return makeModifiers(isStatic: exp.element.isStatic,
975 isFactory: exp.element.isFactoryConstructor);
976 }
977
978 tree.Node makeNodeForClassElement(elements.ClassElement cls) {
979 if (cls.isMixinApplication) {
980 return makeNamedMixinApplication(cls);
981 } else {
982 return makeClassNode(cls);
983 }
984 }
985
986 /// Create a [tree.NodeList] containing the type variable declarations in
987 /// [typeVaraiables.
988 tree.NodeList makeTypeParameters(List<types.DartType> typeVariables) {
989 if (typeVariables.isEmpty) {
990 return new tree.NodeList.empty();
991 } else {
992 List<tree.Node> typeVariableList = <tree.Node>[];
993 for (types.TypeVariableType typeVariable in typeVariables) {
994 tree.Node id = makeIdentifier(typeVariable.name);
995 treeElements[id] = typeVariable.element;
996 tree.Node bound;
997 if (!typeVariable.element.bound.isObject) {
998 bound = makeType(createTypeAnnotation(typeVariable.element.bound));
999 }
1000 tree.TypeVariable node = new tree.TypeVariable(id, bound);
1001 treeElements.setType(node, typeVariable);
1002 typeVariableList.add(node);
1003 }
1004 return makeList(',', typeVariableList, open: lt, close: gt);
1005 }
1006 }
1007
1008 /// Create a [tree.NodeList] containing the declared interfaces.
1009 ///
1010 /// [interfaces] is from [elements.ClassElement] in reverse declaration order
1011 /// and it contains mixins. To produce a list of the declared interfaces only,
1012 /// interfaces in [mixinTypes] are omitted.
1013 ///
1014 /// [forNamedMixinApplication] is because the structure of the [tree.NodeList]
1015 /// differs between [tree.NamedMixinApplication] and [tree.ClassNode].
1016 // TODO(johnniwinther): Normalize interfaces on[tree.NamedMixinApplication]
1017 // and [tree.ClassNode].
1018 tree.NodeList makeInterfaces(Link<types.DartType> interfaces,
1019 Set<types.DartType> mixinTypes,
1020 {bool forNamedMixinApplication: false}) {
1021 Link<tree.Node> typeAnnotations = const Link<tree.Node>();
1022 for (Link<types.DartType> link = interfaces;
1023 !link.isEmpty;
1024 link = link.tail) {
1025 types.DartType interface = link.head;
1026 if (!mixinTypes.contains(interface)) {
1027 typeAnnotations =
1028 typeAnnotations.prepend(makeType(createTypeAnnotation(interface)));
1029 }
1030 }
1031 if (typeAnnotations.isEmpty) {
1032 return forNamedMixinApplication ? null : new tree.NodeList.empty();
1033 } else {
1034 return new tree.NodeList(
1035 forNamedMixinApplication ? null : implementsToken,
1036 typeAnnotations, null, ',');
1037 }
1038 }
1039
1040 /// Creates a [tree.NamedMixinApplication] node for [cls].
1041 // TODO(johnniwinther): Unify creation of mixin lists between
1042 // [NamedMixinApplicationElement] and [ClassElement].
1043 tree.NamedMixinApplication makeNamedMixinApplication(
1044 elements.MixinApplicationElement cls) {
1045
1046 assert(dart2js.invariant(cls, !cls.isUnnamedMixinApplication,
1047 message: "Cannot create ClassNode for unnamed mixin application "
1048 "$cls."));
1049 tree.Modifiers modifiers = makeModifiers(isAbstract: cls.isAbstract);
1050 tree.Identifier name = makeIdentifier(cls.name);
1051 tree.NodeList typeParameters = makeTypeParameters(cls.typeVariables);
1052
1053 Set<types.DartType> mixinTypes = new Set<types.DartType>();
1054 Link<tree.Node> mixins = const Link<tree.Node>();
1055
1056 void addMixin(types.DartType mixinType) {
1057 mixinTypes.add(mixinType);
1058 mixins = mixins.prepend(makeType(createTypeAnnotation(mixinType)));
1059 }
1060
1061 addMixin(cls.mixinType);
1062
1063 tree.Node superclass;
1064 types.InterfaceType supertype = cls.supertype;
1065 while (supertype.element.isUnnamedMixinApplication) {
1066 elements.MixinApplicationElement mixinApplication = supertype.element;
1067 addMixin(cls.asInstanceOf(mixinApplication.mixin));
1068 supertype = mixinApplication.supertype;
1069 }
1070 superclass =
1071 makeType(createTypeAnnotation(cls.asInstanceOf(supertype.element)));
1072 tree.Node supernode = new tree.MixinApplication(
1073 superclass, new tree.NodeList(null, mixins, null, ','));
1074
1075 tree.NodeList interfaces = makeInterfaces(
1076 cls.interfaces, mixinTypes, forNamedMixinApplication: true);
1077
1078 return new tree.NamedMixinApplication(
1079 name, typeParameters, modifiers, supernode,
1080 interfaces, classToken, semicolon);
1081 }
1082
1083 /// Creates a [tree.ClassNode] node for [cls].
1084 tree.ClassNode makeClassNode(elements.ClassElement cls) {
1085 assert(dart2js.invariant(cls, !cls.isUnnamedMixinApplication,
1086 message: "Cannot create ClassNode for unnamed mixin application "
1087 "$cls."));
1088 tree.Modifiers modifiers = makeModifiers(isAbstract: cls.isAbstract);
1089 tree.Identifier name = makeIdentifier(cls.name);
1090 tree.NodeList typeParameters = makeTypeParameters(cls.typeVariables);
1091 tree.Node supernode;
1092 types.InterfaceType supertype = cls.supertype;
1093 Set<types.DartType> mixinTypes = new Set<types.DartType>();
1094 Link<tree.Node> mixins = const Link<tree.Node>();
1095
1096 void addMixin(types.DartType mixinType) {
1097 mixinTypes.add(mixinType);
1098 mixins = mixins.prepend(makeType(createTypeAnnotation(mixinType)));
1099 }
1100
1101 if (supertype != null) {
1102 tree.Node superclass;
1103 if (supertype.element.isUnnamedMixinApplication) {
1104 while (supertype.element.isUnnamedMixinApplication) {
1105 elements.MixinApplicationElement mixinApplication = supertype.element;
1106 addMixin(cls.asInstanceOf(mixinApplication.mixin));
1107 supertype = mixinApplication.supertype;
1108 }
1109 tree.Node superclass =
1110 makeType(createTypeAnnotation(cls.asInstanceOf(supertype.element)));
1111 supernode = new tree.MixinApplication(
1112 superclass, new tree.NodeList(null, mixins, null, ','));
1113 } else if (!supertype.isObject) {
1114 supernode = makeType(createTypeAnnotation(supertype));
1115 }
1116 }
1117 tree.NodeList interfaces = makeInterfaces(
1118 cls.interfaces, mixinTypes);
1119
1120 Token extendsKeyword = supernode != null ? extendsToken : null;
1121 return new tree.ClassNode(
1122 modifiers, name, typeParameters, supernode,
1123 interfaces, openBrace, extendsKeyword,
1124 null, // No body.
1125 closeBrace);
1126 }
1127
1128 tree.Node functionName(FunctionExpression exp) {
1129 String name = exp.name;
1130 if (name == null) return null;
1131 if (isUserDefinableOperator(name)) {
1132 return makeOperator("operator$name");
1133 } else if (name == "unary-") {
1134 return makeOperator("operator-");
1135 }
1136 return makeIdentifier(name);
1137 }
1138
1139 tree.Node parenthesize(tree.Node node) {
1140 return new tree.ParenthesizedExpression(node, openParen);
1141 }
1142
1143 tree.Node unparseStringLiteral(Expression exp) {
1144 StringLiteralOutput output = Unparser.analyzeStringLiteral(exp);
1145 List parts = output.parts;
1146 tree.Node printStringChunk(StringChunk chunk) {
1147 bool raw = chunk.quoting.raw;
1148 int quoteCode = chunk.quoting.quote;
1149
1150 List<tree.StringInterpolationPart> literalParts = [];
1151 tree.LiteralString firstLiteral;
1152 tree.Node currentInterpolation;
1153
1154 // sb contains the current unfinished LiteralString
1155 StringBuffer sb = new StringBuffer();
1156 if (raw) {
1157 sb.write('r');
1158 }
1159 for (int i = 0; i < chunk.quoting.leftQuoteCharCount; i++) {
1160 sb.write(chunk.quoting.quoteChar);
1161 }
1162
1163 // Print every character and string interpolation
1164 int startIndex = chunk.previous != null ? chunk.previous.endIndex : 0;
1165 for (int i = startIndex; i < chunk.endIndex; i++) {
1166 var part = parts[i];
1167 if (part is Expression) {
1168 // Finish the previous string interpolation, if there is one.
1169 tree.LiteralString lit = makeVerbatimStringLiteral(sb.toString());
1170 if (currentInterpolation != null) {
1171 literalParts.add(new tree.StringInterpolationPart(
1172 currentInterpolation,
1173 lit));
1174 } else {
1175 firstLiteral = lit;
1176 }
1177 sb.clear();
1178 currentInterpolation = makeExpression(part);
1179 } else {
1180 int char = part;
1181 sb.write(Unparser.getEscapedCharacter(char, quoteCode, raw));
1182 }
1183 }
1184
1185 // Print ending quotes
1186 for (int i = 0; i < chunk.quoting.rightQuoteLength; i++) {
1187 sb.write(chunk.quoting.quoteChar);
1188 }
1189
1190 // Finish the previous string interpolation, if there is one.
1191 // Then wrap everything in a StringInterpolation, if relevant.
1192 tree.LiteralString lit = makeVerbatimStringLiteral(sb.toString());
1193 tree.Node node;
1194 if (firstLiteral == null) {
1195 node = lit;
1196 } else {
1197 literalParts.add(new tree.StringInterpolationPart(
1198 currentInterpolation,
1199 lit));
1200 node = new tree.StringInterpolation(
1201 firstLiteral,
1202 makeList('', literalParts));
1203 }
1204
1205 // Juxtapose with the previous string chunks, if any.
1206 if (chunk.previous != null) {
1207 return new tree.StringJuxtaposition(
1208 printStringChunk(chunk.previous),
1209 node);
1210 } else {
1211 return node;
1212 }
1213 }
1214 return printStringChunk(output.chunk);
1215 }
1216
1217 }
OLDNEW
« no previous file with comments | « pkg/compiler/lib/src/dart_backend/backend_ast_nodes.dart ('k') | pkg/compiler/lib/src/dart_backend/copy_propagator.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698