OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library ddc.src.codegen.js_codegen; | 5 library ddc.src.codegen.js_codegen; |
6 | 6 |
7 import 'dart:io' show Directory; | 7 import 'dart:io' show Directory, File; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; | 9 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; | 10 import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
11 import 'package:analyzer/src/generated/constant.dart'; | 11 import 'package:analyzer/src/generated/constant.dart'; |
12 import 'package:analyzer/src/generated/element.dart'; | 12 import 'package:analyzer/src/generated/element.dart'; |
13 import 'package:analyzer/src/generated/scanner.dart' | 13 import 'package:analyzer/src/generated/scanner.dart' |
14 show StringToken, Token, TokenType; | 14 show StringToken, Token, TokenType; |
15 import 'package:path/path.dart' as path; | 15 import 'package:path/path.dart' as path; |
16 | 16 |
17 // TODO(jmesserly): import from its own package | |
18 import 'package:dev_compiler/src/js/js_ast.dart' as JS; | |
19 import 'package:dev_compiler/src/js/js_ast.dart' show js; | |
20 | |
17 import 'package:dev_compiler/src/checker/rules.dart'; | 21 import 'package:dev_compiler/src/checker/rules.dart'; |
18 import 'package:dev_compiler/src/info.dart'; | 22 import 'package:dev_compiler/src/info.dart'; |
19 import 'package:dev_compiler/src/report.dart'; | 23 import 'package:dev_compiler/src/report.dart'; |
20 import 'package:dev_compiler/src/utils.dart'; | 24 import 'package:dev_compiler/src/utils.dart'; |
21 import 'code_generator.dart'; | 25 import 'code_generator.dart'; |
22 | 26 |
23 // This must match the optional parameter name used in runtime.js | 27 // This must match the optional parameter name used in runtime.js |
24 const String optionalParameters = r'opt$'; | 28 const String _jsNamedParameterName = r'opt$'; |
25 | 29 |
26 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { | 30 class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
27 final LibraryInfo libraryInfo; | 31 final LibraryInfo libraryInfo; |
28 final TypeRules rules; | 32 final TypeRules rules; |
29 final OutWriter out; | |
30 final String _libraryName; | 33 final String _libraryName; |
31 | 34 |
32 /// The variable for the target of the current `..` cascade expression. | 35 /// The variable for the target of the current `..` cascade expression. |
33 SimpleIdentifier _cascadeTarget; | 36 SimpleIdentifier _cascadeTarget; |
34 | 37 |
35 ClassDeclaration currentClass; | 38 ClassDeclaration currentClass; |
36 ConstantEvaluator _constEvaluator; | 39 ConstantEvaluator _constEvaluator; |
37 | 40 |
38 final _exports = <String>[]; | 41 final _exports = <String>[]; |
39 final _lazyFields = <VariableDeclaration>[]; | 42 final _lazyFields = <VariableDeclaration>[]; |
40 final _properties = <FunctionDeclaration>[]; | 43 final _properties = <FunctionDeclaration>[]; |
41 | 44 |
42 static final int _indexExpressionPrecedence = | 45 JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules) |
43 new IndexExpression.forTarget(null, null, null, null).precedence; | |
44 | |
45 static final int _prefixExpressionPrecedence = | |
46 new PrefixExpression(null, null).precedence; | |
47 | |
48 JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules, this.out) | |
49 : libraryInfo = libraryInfo, | 46 : libraryInfo = libraryInfo, |
50 rules = rules, | 47 rules = rules, |
51 _libraryName = jsLibraryName(libraryInfo.library); | 48 _libraryName = jsLibraryName(libraryInfo.library); |
52 | 49 |
53 Element get currentLibrary => libraryInfo.library; | 50 Element get currentLibrary => libraryInfo.library; |
54 | 51 |
55 void generateLibrary( | 52 JS.Block generateLibrary( |
56 Iterable<CompilationUnit> units, CheckerReporter reporter) { | 53 Iterable<CompilationUnit> units, CheckerReporter reporter) { |
57 out.write(""" | 54 var body = <JS.Statement>[]; |
58 var $_libraryName; | |
59 (function ($_libraryName) { | |
60 'use strict'; | |
61 """, 2); | |
62 | |
63 for (var unit in units) { | 55 for (var unit in units) { |
64 // TODO(jmesserly): this is needed because RestrictedTypeRules can send | 56 // TODO(jmesserly): this is needed because RestrictedTypeRules can send |
65 // messages to CheckerReporter, for things like missing types. | 57 // messages to CheckerReporter, for things like missing types. |
66 // We should probably refactor so this can't happen. | 58 // We should probably refactor so this can't happen. |
67 | |
68 var source = unit.element.source; | 59 var source = unit.element.source; |
69 _constEvaluator = new ConstantEvaluator(source, rules.provider); | 60 _constEvaluator = new ConstantEvaluator(source, rules.provider); |
70 reporter.enterSource(source); | 61 reporter.enterSource(source); |
71 unit.accept(this); | 62 body.add(unit.accept(this)); |
72 reporter.leaveSource(); | 63 reporter.leaveSource(); |
73 } | 64 } |
74 | 65 |
75 if (_exports.isNotEmpty) out.write('// Exports:\n'); | 66 if (_exports.isNotEmpty) body.add(js.comment('Exports:')); |
76 | 67 |
77 // TODO(jmesserly): make these immutable in JS? | 68 // TODO(jmesserly): make these immutable in JS? |
78 for (var name in _exports) { | 69 for (var name in _exports) { |
79 out.write('${_libraryName}.$name = $name;\n'); | 70 body.add(js.statement('#.# = #;', [_libraryName, name, name])); |
80 } | 71 } |
81 | 72 |
82 out.write(""" | 73 var name = _libraryName; |
83 })($_libraryName || ($_libraryName = {})); | 74 return new JS.Block([ |
84 """, -2); | 75 js.statement('var #;', name), |
76 js.statement(''' | |
77 (function (#) { | |
78 'use strict'; | |
79 #; | |
80 })(# || (# = {})); | |
81 ''', [name, body, name, name]) | |
82 ]); | |
85 } | 83 } |
86 | 84 |
87 @override | 85 @override |
88 void visitCompilationUnit(CompilationUnit node) { | 86 JS.Statement visitCompilationUnit(CompilationUnit node) { |
89 _visitNode(node.scriptTag); | 87 // TODO(jmesserly): scriptTag, directives. |
90 _visitNodeList(node.directives); | 88 var body = <JS.Statement>[]; |
91 for (var child in node.declarations) { | 89 for (var child in node.declarations) { |
92 // Attempt to group adjacent fields/properties. | 90 // Attempt to group adjacent fields/properties. |
93 if (child is! TopLevelVariableDeclaration) _flushLazyFields(); | 91 if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
94 if (child is! FunctionDeclaration) _flushLibraryProperties(); | 92 if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
95 | 93 |
96 child.accept(this); | 94 var code = child.accept(this); |
95 if (code != null) body.add(code); | |
97 } | 96 } |
98 // Flush any unwritten fields/properties. | 97 // Flush any unwritten fields/properties. |
99 _flushLazyFields(); | 98 _flushLazyFields(body); |
100 _flushLibraryProperties(); | 99 _flushLibraryProperties(body); |
100 return _statement(body); | |
101 } | 101 } |
102 | 102 |
103 bool isPublic(String name) => !name.startsWith('_'); | 103 bool isPublic(String name) => !name.startsWith('_'); |
104 | 104 |
105 /// Conversions that we don't handle end up here. | 105 /// Conversions that we don't handle end up here. |
106 @override | 106 @override |
107 void visitConversion(Conversion node) { | 107 visitConversion(Conversion node) { |
108 var from = node.baseType; | 108 var from = node.baseType; |
109 var to = node.convertedType; | 109 var to = node.convertedType; |
110 | 110 |
111 // All Dart number types map to a JS double. | 111 // All Dart number types map to a JS double. |
112 if (rules.isNumType(from) && | 112 if (rules.isNumType(from) && |
113 (rules.isIntType(to) || rules.isDoubleType(to))) { | 113 (rules.isIntType(to) || rules.isDoubleType(to))) { |
114 // TODO(jmesserly): a lot of these checks are meaningless, as people use | 114 // TODO(jmesserly): a lot of these checks are meaningless, as people use |
115 // `num` to mean "any kind of number" rather than "could be null". | 115 // `num` to mean "any kind of number" rather than "could be null". |
116 // The core libraries especially suffer from this problem, with many of | 116 // The core libraries especially suffer from this problem, with many of |
117 // the `num` methods returning `num`. | 117 // the `num` methods returning `num`. |
118 if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) { | 118 if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) { |
119 // Converting from a nullable number to a non-nullable number | 119 // Converting from a nullable number to a non-nullable number |
120 // only requires a null check. | 120 // only requires a null check. |
121 out.write('dart.notNull('); | 121 return js.call('dart.notNull(#)', node.expression.accept(this)); |
122 node.expression.accept(this); | |
123 out.write(')'); | |
124 } else { | 122 } else { |
125 // A no-op in JavaScript. | 123 // A no-op in JavaScript. |
126 node.expression.accept(this); | 124 return node.expression.accept(this); |
127 } | 125 } |
128 return; | |
129 } | 126 } |
130 | 127 |
131 _writeCast(node.expression, to); | 128 return _emitCast(node.expression, to); |
132 } | 129 } |
133 | 130 |
134 @override | 131 @override |
135 void visitAsExpression(AsExpression node) { | 132 visitAsExpression(AsExpression node) => |
136 _writeCast(node.expression, node.type.type); | 133 _emitCast(node.expression, node.type.type); |
137 } | |
138 | 134 |
139 void _writeCast(Expression node, DartType type) { | 135 _emitCast(Expression node, DartType type) => |
140 out.write('dart.as('); | 136 js.call('dart.as(#)', [[node.accept(this), _emitTypeName(type)]]); |
141 node.accept(this); | |
142 out.write(', '); | |
143 _writeTypeName(type); | |
144 out.write(')'); | |
145 } | |
146 | 137 |
147 @override | 138 @override |
148 void visitIsExpression(IsExpression node) { | 139 visitIsExpression(IsExpression node) { |
149 // Generate `is` as `instanceof` or `typeof` depending on the RHS type. | 140 // Generate `is` as `dart.is` or `typeof` depending on the RHS type. |
150 if (node.notOperator != null) out.write('!'); | 141 JS.Expression result; |
151 | |
152 var type = node.type.type; | 142 var type = node.type.type; |
153 var lhs = node.expression; | 143 var lhs = node.expression.accept(this); |
154 var typeofName = _jsTypeofName(type); | 144 var typeofName = _jsTypeofName(type); |
155 if (typeofName != null) { | 145 if (typeofName != null) { |
156 if (node.notOperator != null) out.write('('); | 146 result = js.call('typeof # == #', [lhs, typeofName]); |
157 out.write('typeof '); | |
158 // We're going to replace the `is` operator with higher-precedence prefix | |
159 // `typeof` operator, so add parens around the left side if necessary. | |
160 _visitExpression(lhs, _prefixExpressionPrecedence); | |
161 out.write(' == "$typeofName"'); | |
162 if (node.notOperator != null) out.write(')'); | |
163 } else { | 147 } else { |
164 // Always go through a runtime helper, because implicit interfaces. | 148 // Always go through a runtime helper, because implicit interfaces. |
165 out.write('dart.is('); | 149 result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]); |
166 lhs.accept(this); | |
167 out.write(', '); | |
168 _writeTypeName(type); | |
169 out.write(')'); | |
170 } | 150 } |
151 | |
152 if (node.notOperator != null) { | |
153 return js.call('!#', result); | |
154 } | |
155 return result; | |
171 } | 156 } |
172 | 157 |
173 String _jsTypeofName(DartType t) { | 158 String _jsTypeofName(DartType t) { |
174 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; | 159 if (rules.isIntType(t) || rules.isDoubleType(t)) return 'number'; |
175 if (rules.isStringType(t)) return 'string'; | 160 if (rules.isStringType(t)) return 'string'; |
176 if (rules.isBoolType(t)) return 'boolean'; | 161 if (rules.isBoolType(t)) return 'boolean'; |
177 return null; | 162 return null; |
178 } | 163 } |
179 | 164 |
180 @override | 165 @override |
181 void visitFunctionTypeAlias(FunctionTypeAlias node) { | 166 visitFunctionTypeAlias(FunctionTypeAlias node) { |
182 // TODO(vsm): Do we need to record type info the generated code for a | 167 // TODO(vsm): Do we need to record type info the generated code for a |
183 // typedef? | 168 // typedef? |
184 } | 169 } |
185 | 170 |
186 @override | 171 @override |
187 void visitTypeName(TypeName node) { | 172 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); |
Jennifer Messerly
2015/02/25 18:36:20
types used to be handled in two ways, here and _wr
| |
188 _visitNode(node.name); | 173 |
189 _visitNode(node.typeArguments); | 174 @override |
175 JS.Statement visitClassTypeAlias(ClassTypeAlias node) { | |
176 var name = node.name.name; | |
177 var heritage = | |
178 js.call('dart.mixin(#)', [_visitList(node.withClause.mixinTypes)]); | |
179 var classDecl = new JS.ClassDeclaration( | |
180 new JS.ClassExpression(new JS.VariableDeclaration(name), heritage, [])); | |
181 if (isPublic(name)) _exports.add(name); | |
182 return _addTypeParameters(node.typeParameters, name, classDecl); | |
190 } | 183 } |
191 | 184 |
192 @override | 185 @override |
193 void visitTypeArgumentList(TypeArgumentList node) { | 186 visitTypeParameter(TypeParameter node) => new JS.Parameter(node.name.name); |
194 out.write('\$('); | 187 |
195 _visitNodeList(node.arguments, separator: ', '); | 188 JS.Statement _addTypeParameters( |
196 out.write(')'); | 189 TypeParameterList node, String name, JS.Statement clazz) { |
190 if (node == null) return clazz; | |
191 | |
192 var genericName = '$name\$'; | |
193 var genericDef = js.statement( | |
194 'let # = dart.generic(function(#) { #; return #; });', [ | |
195 genericName, | |
196 _visitList(node.typeParameters), | |
197 clazz, | |
198 name | |
199 ]); | |
200 | |
201 // TODO(jmesserly): we may not want this to be `dynamic` if the generic | |
202 // has a lower bound, e.g. `<T extends SomeType>`. | |
203 // https://github.com/dart-lang/dart-dev-compiler/issues/38 | |
204 var typeArgs = new List.filled(node.typeParameters.length, 'dynamic'); | |
205 | |
206 var dynInst = js.statement('let # = #(#);', [name, genericName, typeArgs]); | |
207 | |
208 // TODO(jmesserly): is it worth exporting both names? Alternatively we could | |
209 // put the generic type constructor on the <dynamic> instance. | |
210 if (isPublic(name)) _exports.add('${name}\$'); | |
211 return new JS.Block([genericDef, dynInst]); | |
197 } | 212 } |
198 | 213 |
199 @override | 214 @override |
200 void visitClassTypeAlias(ClassTypeAlias node) { | 215 JS.Statement visitClassDeclaration(ClassDeclaration node) { |
201 var name = node.name.name; | |
202 _beginTypeParameters(node.typeParameters, name); | |
203 out.write('class $name extends dart.mixin('); | |
204 _visitNodeList(node.withClause.mixinTypes, separator: ', '); | |
205 out.write(') {}\n\n'); | |
206 _endTypeParameters(node.typeParameters, name); | |
207 if (isPublic(name)) _exports.add(name); | |
208 } | |
209 | |
210 @override | |
211 void visitTypeParameter(TypeParameter node) { | |
212 out.write(node.name.name); | |
213 } | |
214 | |
215 void _beginTypeParameters(TypeParameterList node, String name) { | |
216 if (node == null) return; | |
217 out.write('let ${name}\$ = dart.generic(function('); | |
218 _visitNodeList(node.typeParameters, separator: ', '); | |
219 out.write(') {\n', 2); | |
220 } | |
221 | |
222 void _endTypeParameters(TypeParameterList node, String name) { | |
223 if (node == null) return; | |
224 // Return the specialized class. | |
225 out.write('return $name;\n'); | |
226 out.write('});\n', -2); | |
227 // Construct the "default" version of the generic type for easy interop. | |
228 out.write('let $name = ${name}\$('); | |
229 for (int i = 0, len = node.typeParameters.length; i < len; i++) { | |
230 if (i > 0) out.write(', '); | |
231 // TODO(jmesserly): we may not want this to be `dynamic` if the generic | |
232 // has a lower bound, e.g. `<T extends SomeType>`. | |
233 // https://github.com/dart-lang/dart-dev-compiler/issues/38 | |
234 out.write('dynamic'); | |
235 } | |
236 out.write(');\n'); | |
237 // TODO(jmesserly): is it worth exporting both names? Alternatively we could | |
238 // put the generic type constructor on the <dynamic> instance. | |
239 if (isPublic(name)) _exports.add('${name}\$'); | |
240 } | |
241 | |
242 @override | |
243 void visitClassDeclaration(ClassDeclaration node) { | |
244 currentClass = node; | 216 currentClass = node; |
245 | 217 |
218 var body = <JS.Statement>[]; | |
219 | |
246 var name = node.name.name; | 220 var name = node.name.name; |
247 _beginTypeParameters(node.typeParameters, name); | 221 var ctors = <ConstructorDeclaration>[]; |
248 out.write('class $name extends '); | 222 var fields = <FieldDeclaration>[]; |
249 | 223 var staticFields = <FieldDeclaration>[]; |
250 if (node.withClause != null) { | |
251 out.write('dart.mixin('); | |
252 } | |
253 if (node.extendsClause != null) { | |
254 _visitNode(node.extendsClause.superclass); | |
255 } else { | |
256 out.write('dart.Object'); | |
257 } | |
258 if (node.withClause != null) { | |
259 _visitNodeList(node.withClause.mixinTypes, prefix: ', ', separator: ', '); | |
260 out.write(')'); | |
261 } | |
262 | |
263 out.write(' {\n', 2); | |
264 | |
265 var ctors = new List<ConstructorDeclaration>(); | |
266 var fields = new List<FieldDeclaration>(); | |
267 var staticFields = new List<FieldDeclaration>(); | |
268 for (var member in node.members) { | 224 for (var member in node.members) { |
269 if (member is ConstructorDeclaration) { | 225 if (member is ConstructorDeclaration) { |
270 ctors.add(member); | 226 ctors.add(member); |
271 } else if (member is FieldDeclaration) { | 227 } else if (member is FieldDeclaration) { |
272 (member.isStatic ? staticFields : fields).add(member); | 228 (member.isStatic ? staticFields : fields).add(member); |
273 } | 229 } |
274 } | 230 } |
275 | 231 |
232 var jsMethods = <JS.Method>[]; | |
276 // Iff no constructor is specified for a class C, it implicitly has a | 233 // Iff no constructor is specified for a class C, it implicitly has a |
277 // default constructor `C() : super() {}`, unless C is class Object. | 234 // default constructor `C() : super() {}`, unless C is class Object. |
278 if (ctors.isEmpty && !node.element.type.isObject) { | 235 if (ctors.isEmpty && !node.element.type.isObject) { |
279 _generateImplicitConstructor(node, name, fields); | 236 jsMethods.add(_emitImplicitConstructor(node, name, fields)); |
280 } | 237 } |
281 | 238 |
282 for (var member in node.members) { | 239 for (var member in node.members) { |
283 if (member is ConstructorDeclaration) { | 240 if (member is ConstructorDeclaration) { |
284 _generateConstructor(member, name, fields); | 241 jsMethods.add(_emitConstructor(member, name, fields)); |
285 } else if (member is MethodDeclaration) { | 242 } else if (member is MethodDeclaration) { |
286 member.accept(this); | 243 jsMethods.add(member.accept(this)); |
287 } | 244 } |
288 } | 245 } |
289 | 246 |
290 out.write('}\n', -2); | 247 // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
248 // This lets them use for-of loops transparently. | |
249 // https://github.com/lukehoban/es6features#iterators--forof | |
250 if (node.element.library.isDartCore && node.element.name == 'Iterable') { | |
251 JS.Fun body = js.call('''function() { | |
252 var iterator = this.iterator; | |
253 return { | |
254 next() { | |
255 var done = iterator.moveNext(); | |
256 return { done: done, current: done ? void 0 : iterator.current }; | |
257 } | |
258 }; | |
259 }'''); | |
260 jsMethods.add(new JS.Method(js.call('Symbol.iterator'), body)); | |
261 } | |
262 | |
263 JS.Expression heritage; | |
264 if (node.extendsClause != null) { | |
265 heritage = _visit(node.extendsClause.superclass); | |
266 } else { | |
267 heritage = js.call('dart.Object'); | |
268 } | |
269 if (node.withClause != null) { | |
270 var mixins = _visitList(node.withClause.mixinTypes); | |
271 mixins.insert(0, heritage); | |
272 heritage = js.call('dart.mixin(#)', [mixins]); | |
273 } | |
274 body.add(new JS.ClassDeclaration(new JS.ClassExpression( | |
275 new JS.VariableDeclaration(name), heritage, | |
276 jsMethods.where((m) => m != null).toList(growable: false)))); | |
291 | 277 |
292 if (isPublic(name)) _exports.add(name); | 278 if (isPublic(name)) _exports.add(name); |
293 | 279 |
294 // Named constructors | 280 // Named constructors |
295 for (ConstructorDeclaration member in ctors) { | 281 for (ConstructorDeclaration member in ctors) { |
296 if (member.name != null) { | 282 if (member.name != null) { |
297 var ctorName = member.name.name; | 283 body.add(js.statement('dart.defineNamedConstructor(#, #);', [ |
298 out.write('dart.defineNamedConstructor($name, "$ctorName");\n'); | 284 name, |
285 js.string(member.name.name, "'") | |
286 ])); | |
299 } | 287 } |
300 } | 288 } |
301 | 289 |
302 // Static fields | 290 // Static fields |
303 var lazyStatics = <VariableDeclaration>[]; | 291 var lazyStatics = <VariableDeclaration>[]; |
304 for (FieldDeclaration member in staticFields) { | 292 for (FieldDeclaration member in staticFields) { |
305 for (VariableDeclaration field in member.fields.variables) { | 293 for (VariableDeclaration field in member.fields.variables) { |
306 var prefix = '$name.${field.name.name}'; | 294 var fieldName = field.name.name; |
307 if (field.initializer == null) { | 295 if (field.isConst || _isFieldInitConstant(field)) { |
308 out.write('$prefix = null;\n'); | 296 var init = _visit(field.initializer); |
309 } else if (field.isConst || _isFieldInitConstant(field)) { | 297 if (init == null) init = new JS.LiteralNull(); |
310 out.write('$prefix = '); | 298 body.add(js.statement('#.# = #;', [name, fieldName, init])); |
311 field.initializer.accept(this); | |
312 out.write(';\n'); | |
313 } else { | 299 } else { |
314 lazyStatics.add(field); | 300 lazyStatics.add(field); |
315 } | 301 } |
316 } | 302 } |
317 } | 303 } |
318 _writeLazyFields(name, lazyStatics); | 304 var lazy = _emitLazyFields(name, lazyStatics); |
305 if (lazy != null) body.add(lazy); | |
319 | 306 |
320 // Support for adapting dart:core Iterator/Iterable to ES6 versions. | |
321 // This lets them use for-of loops transparently. | |
322 // https://github.com/lukehoban/es6features#iterators--forof | |
323 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-iterable-interf ace | |
324 // TODO(jmesserly): put this straight in the class as an instance method, | |
325 // once V8 supports symbols for method names: `[Symbol.iterator]() { ... }`. | |
326 if (node.element.library.isDartCore && node.element.name == 'Iterable') { | |
327 out.write(''' | |
328 $name.prototype[Symbol.iterator] = function() { | |
329 var iterator = this.iterator; | |
330 return { | |
331 next: function() { | |
332 var done = iterator.moveNext(); | |
333 return { done: done, current: done ? void 0 : iterator.current }; | |
334 } | |
335 }; | |
336 }; | |
337 '''); | |
338 } | |
339 | |
340 _endTypeParameters(node.typeParameters, name); | |
341 | |
342 out.write('\n'); | |
343 currentClass = null; | 307 currentClass = null; |
308 return _addTypeParameters(node.typeParameters, name, _statement(body)); | |
344 } | 309 } |
345 | 310 |
346 /// Generates the implicit default constructor for class C of the form | 311 /// Generates the implicit default constructor for class C of the form |
347 /// `C() : super() {}`. | 312 /// `C() : super() {}`. |
348 void _generateImplicitConstructor( | 313 JS.Method _emitImplicitConstructor( |
349 ClassDeclaration node, String name, List<FieldDeclaration> fields) { | 314 ClassDeclaration node, String name, List<FieldDeclaration> fields) { |
350 // If we don't have a method body, skip this. | 315 // If we don't have a method body, skip this. |
351 if (fields.isEmpty) return; | 316 if (fields.isEmpty) return null; |
352 | 317 |
353 out.write('$name() {\n', 2); | 318 var body = _initializeFields(fields); |
354 _initializeFields(fields); | 319 var superCall = _superConstructorCall(node); |
355 _superConstructorCall(node); | 320 if (superCall != null) body = [[body, superCall]]; |
356 out.write('}\n', -2); | 321 return new JS.Method( |
322 new JS.PropertyName(name), js.call('function() { #; }', body)); | |
357 } | 323 } |
358 | 324 |
359 void _generateConstructor(ConstructorDeclaration node, String className, | 325 JS.Method _emitConstructor(ConstructorDeclaration node, String className, |
360 List<FieldDeclaration> fields) { | 326 List<FieldDeclaration> fields) { |
361 if (node.externalKeyword != null) { | 327 if (node.externalKeyword != null) return null; |
362 out.write('/* Unimplemented $node */\n'); | 328 |
363 return; | 329 var name = _constructorName(className, node.name); |
364 } | |
365 | 330 |
366 // We generate constructors as initializer methods in the class; | 331 // We generate constructors as initializer methods in the class; |
367 // this allows use of `super` for instance methods/properties. | 332 // this allows use of `super` for instance methods/properties. |
368 // It also avoids V8 restrictions on `super` in default constructors. | 333 // It also avoids V8 restrictions on `super` in default constructors. |
369 out.write(className); | 334 return new JS.Method(new JS.PropertyName(name), new JS.Fun( |
370 if (node.name != null) { | 335 node.parameters.accept(this), _emitConstructorBody(node, fields))); |
371 out.write('\$${node.name.name}'); | |
372 } | |
373 out.write('('); | |
374 _visitNode(node.parameters); | |
375 out.write(') {\n', 2); | |
376 _generateConstructorBody(node, fields); | |
377 out.write('}\n', -2); | |
378 } | 336 } |
379 | 337 |
380 void _generateConstructorBody( | 338 String _constructorName(String className, SimpleIdentifier name) { |
339 if (name == null) return className; | |
340 return '$className\$${name.name}'; | |
341 } | |
342 | |
343 JS.Block _emitConstructorBody( | |
381 ConstructorDeclaration node, List<FieldDeclaration> fields) { | 344 ConstructorDeclaration node, List<FieldDeclaration> fields) { |
382 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; | 345 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; |
383 if (node.redirectedConstructor != null) { | 346 if (node.redirectedConstructor != null) { |
384 out.write('return new '); | 347 return js.statement('{ return new #(#); }', [ |
385 node.redirectedConstructor.accept(this); | 348 node.redirectedConstructor.accept(this), |
386 out.write('('); | 349 node.parameters.accept(this) |
387 _visitNode(node.parameters); | 350 ]); |
388 out.write(');\n'); | |
389 return; | |
390 } | 351 } |
391 | 352 |
353 var body = <JS.Statement>[]; | |
354 | |
392 // Generate optional/named argument value assignment. These can not have | 355 // Generate optional/named argument value assignment. These can not have |
393 // side effects, and may be used by the constructor's initializers, so it's | 356 // side effects, and may be used by the constructor's initializers, so it's |
394 // nice to do them first. | 357 // nice to do them first. |
395 _generateArgumentInitializers(node.parameters); | 358 var init = _emitArgumentInitializers(node.parameters); |
359 if (init != null) body.add(init); | |
396 | 360 |
397 // Redirecting constructors: these are not allowed to have initializers, | 361 // Redirecting constructors: these are not allowed to have initializers, |
398 // and the redirecting ctor invocation runs before field initializers. | 362 // and the redirecting ctor invocation runs before field initializers. |
399 var redirectCall = node.initializers.firstWhere( | 363 var redirectCall = node.initializers.firstWhere( |
400 (i) => i is RedirectingConstructorInvocation, orElse: () => null); | 364 (i) => i is RedirectingConstructorInvocation, orElse: () => null); |
401 | 365 |
402 if (redirectCall != null) { | 366 if (redirectCall != null) { |
403 redirectCall.accept(this); | 367 body.add(redirectCall.accept(this)); |
404 return; | 368 return new JS.Block(body); |
405 } | 369 } |
406 | 370 |
407 // Initializers only run for non-factory constructors. | 371 // Initializers only run for non-factory constructors. |
408 if (node.factoryKeyword == null) { | 372 if (node.factoryKeyword == null) { |
409 // Generate field initializers. | 373 // Generate field initializers. |
410 // These are expanded into each non-redirecting constructor. | 374 // These are expanded into each non-redirecting constructor. |
411 // In the future we may want to create an initializer function if we have | 375 // In the future we may want to create an initializer function if we have |
412 // multiple constructors, but it needs to be balanced against readability. | 376 // multiple constructors, but it needs to be balanced against readability. |
413 _initializeFields(fields, node.parameters, node.initializers); | 377 body.add(_initializeFields(fields, node.parameters, node.initializers)); |
414 | 378 |
415 var superCall = node.initializers.firstWhere( | 379 var superCall = node.initializers.firstWhere( |
416 (i) => i is SuperConstructorInvocation, orElse: () => null); | 380 (i) => i is SuperConstructorInvocation, orElse: () => null); |
417 | 381 |
418 // If no superinitializer is provided, an implicit superinitializer of the | 382 // If no superinitializer is provided, an implicit superinitializer of the |
419 // form `super()` is added at the end of the initializer list, unless the | 383 // form `super()` is added at the end of the initializer list, unless the |
420 // enclosing class is class Object. | 384 // enclosing class is class Object. |
421 if (superCall == null) { | 385 var jsSuper = _superConstructorCall(node.parent, superCall); |
422 _superConstructorCall(node.parent); | 386 if (jsSuper != null) body.add(jsSuper); |
423 } else { | |
424 _superConstructorCall(node.parent, node.name, superCall.constructorName, | |
425 superCall.argumentList); | |
426 } | |
427 } | 387 } |
428 | 388 |
429 var body = node.body; | 389 body.add(node.body.accept(this)); |
430 if (body is BlockFunctionBody) { | 390 return new JS.Block(body); |
431 body.block.statements.accept(this); | |
432 } else if (body is ExpressionFunctionBody) { | |
433 _visitNode(body.expression, prefix: 'return ', suffix: ';\n'); | |
434 } else { | |
435 assert(body is EmptyFunctionBody); | |
436 } | |
437 } | 391 } |
438 | 392 |
439 @override | 393 @override |
440 void visitRedirectingConstructorInvocation( | 394 JS.Statement visitRedirectingConstructorInvocation( |
441 RedirectingConstructorInvocation node) { | 395 RedirectingConstructorInvocation node) { |
442 var parent = node.parent as ConstructorDeclaration; | 396 ClassDeclaration classDecl = node.parent.parent; |
397 var className = classDecl.name.name; | |
443 | 398 |
444 if (parent.name != null) { | 399 var name = _constructorName(className, node.constructorName); |
445 out.write(parent.name.name); | 400 return js.statement('this.#(#);', [name, node.argumentList.accept(this)]); |
Siggi Cherem (dart-lang)
2015/02/25 18:56:02
would it work if you did '#.call(this, #)'?
I'm a
Jennifer Messerly
2015/02/25 19:07:51
ah, good catch! this was actually a bug fix, I sho
| |
446 } else { | |
447 _writeTypeName((parent.parent as ClassDeclaration).element.type); | |
448 } | |
449 out.write('.call(this'); | |
450 _visitArgumentsWithCommaPrefix(node.argumentList); | |
451 out.write(');\n'); | |
452 } | 401 } |
453 | 402 |
454 void _superConstructorCall(ClassDeclaration clazz, [SimpleIdentifier ctorName, | 403 JS.Statement _superConstructorCall(ClassDeclaration clazz, |
455 SimpleIdentifier superCtorName, ArgumentList args]) { | 404 [SuperConstructorInvocation node]) { |
405 var superCtorName = node != null ? node.constructorName : null; | |
406 | |
456 var element = clazz.element; | 407 var element = clazz.element; |
457 if (superCtorName == null && | 408 if (superCtorName == null && |
458 (element.type.isObject || element.supertype.isObject)) { | 409 (element.type.isObject || element.supertype.isObject)) { |
459 return; | 410 return null; |
460 } | 411 } |
461 | 412 |
462 var supertypeName = element.supertype.name; | 413 var supertypeName = element.supertype.name; |
463 out.write('super.$supertypeName'); | 414 var name = _constructorName(supertypeName, superCtorName); |
464 if (superCtorName != null) out.write('\$${superCtorName.name}'); | 415 |
465 out.write('('); | 416 var args = node != null ? node.argumentList.accept(this) : []; |
466 _visitNode(args); | 417 return js.statement('super.#(#);', [name, args]); |
467 out.write(');\n'); | |
468 } | 418 } |
469 | 419 |
470 /// Initialize fields. They follow the sequence: | 420 /// Initialize fields. They follow the sequence: |
471 /// | 421 /// |
472 /// 1. field declaration initializer if non-const, | 422 /// 1. field declaration initializer if non-const, |
473 /// 2. field initializing parameters, | 423 /// 2. field initializing parameters, |
474 /// 3. constructor field initializers, | 424 /// 3. constructor field initializers, |
475 /// 4. initialize fields not covered in 1-3 | 425 /// 4. initialize fields not covered in 1-3 |
476 void _initializeFields(List<FieldDeclaration> fields, | 426 JS.Statement _initializeFields(List<FieldDeclaration> fields, |
477 [FormalParameterList parameters, | 427 [FormalParameterList parameters, |
478 NodeList<ConstructorInitializer> initializers]) { | 428 NodeList<ConstructorInitializer> initializers]) { |
429 var body = <JS.Statement>[]; | |
479 | 430 |
480 // Run field initializers if they can have side-effects. | 431 // Run field initializers if they can have side-effects. |
481 var unsetFields = new Map<String, VariableDeclaration>(); | 432 var unsetFields = new Map<String, VariableDeclaration>(); |
482 for (var declaration in fields) { | 433 for (var declaration in fields) { |
483 for (var field in declaration.fields.variables) { | 434 for (var field in declaration.fields.variables) { |
484 if (_isFieldInitConstant(field)) { | 435 if (_isFieldInitConstant(field)) { |
485 unsetFields[field.name.name] = field; | 436 unsetFields[field.name.name] = field; |
486 } else { | 437 } else { |
487 _visitNode(field, suffix: ';\n'); | 438 body.add(js.statement( |
439 '# = #;', [field.name.accept(this), _visitInitializer(field)])); | |
488 } | 440 } |
489 } | 441 } |
490 } | 442 } |
491 | 443 |
492 // Initialize fields from `this.fieldName` parameters. | 444 // Initialize fields from `this.fieldName` parameters. |
493 if (parameters != null) { | 445 if (parameters != null) { |
494 for (var p in parameters.parameters) { | 446 for (var p in parameters.parameters) { |
495 if (p is DefaultFormalParameter) p = p.parameter; | 447 if (p is DefaultFormalParameter) p = p.parameter; |
496 if (p is FieldFormalParameter) { | 448 if (p is FieldFormalParameter) { |
497 var name = p.identifier.name; | 449 var name = p.identifier.name; |
498 out.write('this.$name = $name;\n'); | 450 body.add(js.statement('this.# = #;', [name, name])); |
499 unsetFields.remove(name); | 451 unsetFields.remove(name); |
500 } | 452 } |
501 } | 453 } |
502 } | 454 } |
503 | 455 |
504 // Run constructor field initializers such as `: foo = bar.baz` | 456 // Run constructor field initializers such as `: foo = bar.baz` |
505 if (initializers != null) { | 457 if (initializers != null) { |
506 for (var init in initializers) { | 458 for (var init in initializers) { |
507 if (init is ConstructorFieldInitializer) { | 459 if (init is ConstructorFieldInitializer) { |
508 init.fieldName.accept(this); | 460 body.add(js.statement('# = #;', [ |
509 out.write(' = '); | 461 init.fieldName.accept(this), |
510 init.expression.accept(this); | 462 init.expression.accept(this) |
511 out.write(';\n'); | 463 ])); |
512 unsetFields.remove(init.fieldName.name); | 464 unsetFields.remove(init.fieldName.name); |
513 } | 465 } |
514 } | 466 } |
515 } | 467 } |
516 | 468 |
517 // Initialize all remaining fields | 469 // Initialize all remaining fields |
518 unsetFields.forEach((name, field) { | 470 unsetFields.forEach((name, field) { |
519 out.write('this.$name = '); | 471 JS.Expression value; |
520 var expression = field.initializer; | 472 if (field.initializer != null) { |
521 if (expression != null) { | 473 value = field.initializer.accept(this); |
522 expression.accept(this); | |
523 } else { | 474 } else { |
524 var type = rules.elementType(field.element); | 475 var type = rules.elementType(field.element); |
525 if (rules.maybeNonNullableType(type)) { | 476 if (rules.maybeNonNullableType(type)) { |
526 out.write('dart.as(null, '); | 477 value = js.call('dart.as(null, #)', _emitTypeName(type)); |
527 _writeTypeName(type); | |
528 out.write(')'); | |
529 } else { | 478 } else { |
530 out.write('null'); | 479 value = new JS.LiteralNull(); |
531 } | 480 } |
532 } | 481 } |
533 out.write(';\n'); | 482 body.add(js.statement('this.# = #;', [name, value])); |
534 }); | 483 }); |
484 | |
485 return _statement(body); | |
535 } | 486 } |
536 | 487 |
537 FormalParameterList _parametersOf(node) { | 488 FormalParameterList _parametersOf(node) { |
489 // Note: ConstructorDeclaration is intentionally skipped here so we can | |
490 // emit the argument initializers in a different place. | |
491 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we | |
492 // could handle argument initializers more consistently in a separate | |
493 // lowering pass. | |
538 if (node is MethodDeclaration) return node.parameters; | 494 if (node is MethodDeclaration) return node.parameters; |
539 if (node is FunctionDeclaration) node = node.functionExpression; | 495 if (node is FunctionDeclaration) node = node.functionExpression; |
540 if (node is FunctionExpression) return node.parameters; | 496 if (node is FunctionExpression) return node.parameters; |
541 return null; | 497 return null; |
542 } | 498 } |
543 | 499 |
544 bool _hasArgumentInitializers(FormalParameterList parameters) { | 500 bool _hasArgumentInitializers(FormalParameterList parameters) { |
545 if (parameters == null) return false; | 501 if (parameters == null) return false; |
546 return parameters.parameters.any((p) => p.kind != ParameterKind.REQUIRED); | 502 return parameters.parameters.any((p) => p.kind != ParameterKind.REQUIRED); |
547 } | 503 } |
548 | 504 |
549 void _generateArgumentInitializers(FormalParameterList parameters) { | 505 JS.Statement _emitArgumentInitializers(FormalParameterList parameters) { |
550 if (parameters == null) return; | 506 if (parameters == null || !_hasArgumentInitializers(parameters)) { |
507 return null; | |
508 } | |
509 | |
510 var body = []; | |
551 for (var param in parameters.parameters) { | 511 for (var param in parameters.parameters) { |
552 // TODO(justinfagnani): rename identifier if necessary | 512 // TODO(justinfagnani): rename identifier if necessary |
553 var name = param.identifier.name; | 513 var name = param.identifier.name; |
554 | 514 |
555 if (param.kind == ParameterKind.NAMED) { | 515 if (param.kind == ParameterKind.NAMED) { |
556 out.write('let $name = opt\$.$name === undefined ? '); | 516 body.add(js.statement('let # = opt\$.# === void 0 ? # : opt\$.#;', [ |
557 if (param is DefaultFormalParameter && param.defaultValue != null) { | 517 name, |
558 param.defaultValue.accept(this); | 518 name, |
559 } else { | 519 _defaultParamValue(param), |
560 out.write('null'); | 520 name |
561 } | 521 ])); |
562 out.write(' : opt\$.$name;\n'); | |
563 } else if (param.kind == ParameterKind.POSITIONAL) { | 522 } else if (param.kind == ParameterKind.POSITIONAL) { |
564 out.write('if ($name === undefined) $name = '); | 523 body.add(js.statement('if (# === void 0) # = #;', [ |
565 if (param is DefaultFormalParameter && param.defaultValue != null) { | 524 name, |
566 param.defaultValue.accept(this); | 525 name, |
567 } else { | 526 _defaultParamValue(param) |
568 out.write('null'); | 527 ])); |
569 } | 528 } |
570 out.write(';\n'); | 529 } |
571 } | 530 return _statement(body); |
572 } | 531 } |
573 } | 532 |
574 | 533 JS.Expression _defaultParamValue(FormalParameter param) { |
575 @override | 534 if (param is DefaultFormalParameter && param.defaultValue != null) { |
576 void visitMethodDeclaration(MethodDeclaration node) { | 535 return param.defaultValue.accept(this); |
577 if (node.isAbstract) return; | 536 } else { |
578 if (node.externalKeyword != null) { | 537 return new JS.LiteralNull(); |
579 out.write('/* Unimplemented $node */\n'); | 538 } |
580 return; | 539 } |
581 } | 540 |
582 | 541 @override |
583 if (node.isStatic) { | 542 JS.Method visitMethodDeclaration(MethodDeclaration node) { |
584 out.write('static '); | 543 if (node.isAbstract || node.externalKeyword != null) return null; |
585 } | 544 |
586 if (node.isGetter) { | 545 var params = _visit(node.parameters); |
587 out.write('get '); | 546 if (params == null) params = []; |
588 } else if (node.isSetter) { | 547 |
589 out.write('set '); | 548 return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)), |
590 } | 549 new JS.Fun(params, node.body.accept(this)), |
591 | 550 isGetter: node.isGetter, |
592 var name = _canonicalMethodName(node.name.name); | 551 isSetter: node.isSetter, |
593 out.write('$name('); | 552 isStatic: node.isStatic); |
594 _visitNode(node.parameters); | 553 } |
595 out.write(') '); | 554 |
596 _visitNode(node.body); | 555 @override |
597 out.write('\n'); | 556 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { |
598 } | |
599 | |
600 @override | |
601 void visitFunctionDeclaration(FunctionDeclaration node) { | |
602 assert(node.parent is CompilationUnit); | 557 assert(node.parent is CompilationUnit); |
603 | 558 |
604 if (node.externalKeyword != null) { | 559 if (node.externalKeyword != null) return null; |
605 // TODO(jmesserly): the toString visitor in Analyzer doesn't include the | |
606 // external keyword for FunctionDeclaration. | |
607 out.write('/* Unimplemented external $node */\n'); | |
608 return; | |
609 } | |
610 | 560 |
611 if (node.isGetter || node.isSetter) { | 561 if (node.isGetter || node.isSetter) { |
612 // Add these later so we can use getter/setter syntax. | 562 // Add these later so we can use getter/setter syntax. |
613 _properties.add(node); | 563 _properties.add(node); |
614 } else { | 564 return null; |
615 _flushLibraryProperties(); | 565 } |
616 _writeFunctionDeclaration(node); | 566 |
617 } | 567 var body = <JS.Statement>[]; |
618 } | 568 _flushLibraryProperties(body); |
619 | 569 |
620 void _writeFunctionDeclaration(FunctionDeclaration node) { | |
621 var name = node.name.name; | 570 var name = node.name.name; |
622 | 571 body.add(js.comment('Function $name: ${node.element.type}')); |
623 if (node.isGetter) { | 572 |
624 out.write('get '); | 573 body.add(new JS.FunctionDeclaration(new JS.VariableDeclaration(name), |
625 } else if (node.isSetter) { | 574 node.functionExpression.accept(this))); |
626 out.write('set '); | 575 |
627 } else { | 576 if (isPublic(name)) _exports.add(name); |
628 out.write("// Function $name: ${node.element.type}\n"); | 577 return _statement(body); |
629 out.write('function '); | 578 } |
630 } | 579 |
631 | 580 JS.Method _emitTopLevelProperty(FunctionDeclaration node) { |
632 out.write('$name'); | 581 var name = node.name.name; |
633 node.functionExpression.accept(this); | 582 if (isPublic(name)) _exports.add(name); |
634 | 583 return new JS.Method( |
635 if (!node.isGetter && !node.isSetter) { | 584 new JS.PropertyName(name), node.functionExpression.accept(this), |
636 out.write('\n'); | 585 isGetter: node.isGetter, isSetter: node.isSetter); |
637 if (isPublic(name)) _exports.add(name); | 586 } |
638 out.write('\n'); | 587 |
639 } | 588 @override |
640 } | 589 JS.Expression visitFunctionExpression(FunctionExpression node) { |
641 | 590 var params = _visit(node.parameters); |
642 @override | 591 if (params == null) params = []; |
643 void visitFunctionExpression(FunctionExpression node) { | 592 |
644 if (node.parent is FunctionDeclaration) { | 593 if (node.parent is FunctionDeclaration) { |
645 out.write('('); | 594 return new JS.Fun(params, node.body.accept(this)); |
646 _visitNode(node.parameters); | 595 } else { |
647 out.write(') '); | 596 var bindThis = _maybeBindThis(node.body); |
648 node.body.accept(this); | 597 |
649 } else { | 598 String code; |
650 var bindThis = _needsBindThis(node.body); | 599 AstNode body; |
651 if (bindThis) out.write("("); | 600 var nodeBody = node.body; |
652 out.write("("); | 601 if (nodeBody is ExpressionFunctionBody) { |
653 _visitNode(node.parameters); | 602 code = '(#) => #'; |
654 out.write(") => "); | 603 body = nodeBody.expression; |
655 var body = node.body; | 604 } else { |
656 if (body is ExpressionFunctionBody) body = body.expression; | 605 code = '(#) => { #; }'; |
657 body.accept(this); | 606 body = nodeBody; |
658 if (bindThis) out.write(").bind(this)"); | 607 } |
659 } | 608 return js.call('($code)$bindThis', [params, body.accept(this)]); |
660 } | 609 } |
661 | 610 } |
662 @override | 611 |
663 void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { | 612 @override |
613 JS.Statement visitFunctionDeclarationStatement( | |
614 FunctionDeclarationStatement node) { | |
664 var func = node.functionDeclaration; | 615 var func = node.functionDeclaration; |
665 if (func.isGetter || func.isSetter) { | 616 if (func.isGetter || func.isSetter) { |
666 out.write('/* Unimplemented function get/set statement: $node */'); | 617 return js.comment('Unimplemented function get/set statement: $node'); |
667 return; | 618 } |
668 } | 619 |
669 | 620 var name = new JS.VariableDeclaration(func.name.name); |
670 var name = func.name.name; | 621 return new JS.Block([ |
671 out.write("// Function $name: ${func.element.type}\n"); | 622 js.comment("// Function ${func.name.name}: ${func.element.type}\n"), |
672 out.write('function $name'); | 623 new JS.FunctionDeclaration(name, func.functionExpression.accept(this)) |
673 func.functionExpression.accept(this); | 624 ]); |
674 out.write('\n'); | |
675 } | 625 } |
676 | 626 |
677 /// Writes a simple identifier. This can handle implicit `this` as well as | 627 /// Writes a simple identifier. This can handle implicit `this` as well as |
678 /// going through the qualified library name if necessary. | 628 /// going through the qualified library name if necessary. |
679 @override | 629 @override |
680 void visitSimpleIdentifier(SimpleIdentifier node) { | 630 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { |
681 var e = node.staticElement; | 631 var e = node.staticElement; |
682 if (e == null) { | 632 if (e == null) { |
683 out.write('/* Unimplemented unknown name */'); | 633 return js.commentExpression( |
684 } else { | 634 'Unimplemented unknown name', new JS.VariableUse(node.name)); |
685 if (e.enclosingElement is CompilationUnitElement && | 635 } |
686 (e.library != libraryInfo.library || _needsModuleGetter(e))) { | 636 var name = node.name; |
687 out.write('${jsLibraryName(e.library)}.'); | 637 if (e.enclosingElement is CompilationUnitElement && |
688 } else if (currentClass != null && _needsImplicitThis(e)) { | 638 (e.library != libraryInfo.library || _needsModuleGetter(e))) { |
689 out.write('this.'); | 639 return js.call('#.#', [jsLibraryName(e.library), name]); |
690 } | 640 } else if (currentClass != null && _needsImplicitThis(e)) { |
691 } | 641 return js.call('this.#', name); |
692 out.write(node.name); | 642 } |
693 } | 643 return new JS.VariableUse(name); |
694 | 644 } |
695 void _writeTypeName(DartType type) { | 645 |
646 JS.Expression _emitTypeName(DartType type) { | |
696 var name = type.name; | 647 var name = type.name; |
697 var lib = type.element.library; | 648 var lib = type.element.library; |
698 if (name == '') { | 649 if (name == '') { |
699 // TODO(jmesserly): remove when we're using coercion reifier. | 650 // TODO(jmesserly): remove when we're using coercion reifier. |
700 out.write('/* Unimplemented type $type */'); | 651 return _unimplementedCall('Unimplemented type $type'); |
701 return; | 652 } |
702 } | 653 |
703 | 654 var typeArgs = null; |
704 if (lib != currentLibrary && lib != null) { | |
705 out.write(jsLibraryName(lib)); | |
706 out.write('.'); | |
707 } | |
708 out.write(name); | |
709 | |
710 if (type is ParameterizedType) { | 655 if (type is ParameterizedType) { |
711 // TODO(jmesserly): this is a workaround for an analyzer bug, see: | 656 // TODO(jmesserly): this is a workaround for an analyzer bug, see: |
712 // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a6 26dd8d16881cdb8e8b9c3fa | 657 // https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a6 26dd8d16881cdb8e8b9c3fa |
713 if (type is! FunctionType || type.element is FunctionTypeAlias) { | 658 if (type is! FunctionType || type.element is FunctionTypeAlias) { |
714 var args = type.typeArguments; | 659 var args = type.typeArguments; |
715 if (args.any((a) => a != rules.provider.dynamicType)) { | 660 if (args.any((a) => a != rules.provider.dynamicType)) { |
716 out.write('\$('); | 661 name = '$name\$'; |
717 for (var arg in args) { | 662 typeArgs = args.map(_emitTypeName); |
718 if (arg != args.first) out.write(', '); | |
719 _writeTypeName(arg); | |
720 } | |
721 out.write(')'); | |
722 } | 663 } |
723 } | 664 } |
724 } | 665 } |
725 } | 666 |
726 | 667 JS.Expression result; |
727 @override | 668 if (lib != currentLibrary && lib != null) { |
728 void visitAssignmentExpression(AssignmentExpression node) { | 669 result = js.call('#.#', [jsLibraryName(lib), name]); |
670 } else { | |
671 result = new JS.VariableUse(name); | |
672 } | |
673 | |
674 if (typeArgs != null) { | |
675 result = js.call('#(#)', [result, typeArgs]); | |
676 } | |
677 return result; | |
678 } | |
679 | |
680 @override | |
681 JS.Node visitAssignmentExpression(AssignmentExpression node) { | |
729 var lhs = node.leftHandSide; | 682 var lhs = node.leftHandSide; |
730 var rhs = node.rightHandSide; | 683 var rhs = node.rightHandSide; |
731 if (lhs is IndexExpression) { | 684 if (lhs is IndexExpression) { |
685 String code; | |
732 var target = _getTarget(lhs); | 686 var target = _getTarget(lhs); |
733 if (rules.isDynamicTarget(target)) { | 687 if (rules.isDynamicTarget(target)) { |
734 out.write('dart.dsetindex('); | 688 code = 'dart.dsetindex(#, #, #)'; |
735 target.accept(this); | |
736 out.write(', '); | |
737 lhs.index.accept(this); | |
738 out.write(', '); | |
739 rhs.accept(this); | |
740 out.write(')'); | |
741 } else { | 689 } else { |
742 target.accept(this); | 690 code = '#.set(#, #)'; |
743 out.write('.set('); | 691 } |
744 lhs.index.accept(this); | 692 return js.call(code, [ |
745 out.write(', '); | 693 target.accept(this), |
746 rhs.accept(this); | 694 lhs.index.accept(this), |
747 out.write(')'); | 695 rhs.accept(this) |
748 } | 696 ]); |
749 return; | |
750 } | 697 } |
751 | 698 |
752 if (lhs is PropertyAccess) { | 699 if (lhs is PropertyAccess) { |
753 var target = _getTarget(lhs); | 700 var target = _getTarget(lhs); |
754 if (rules.isDynamicTarget(target)) { | 701 if (rules.isDynamicTarget(target)) { |
755 out.write('dart.dput('); | 702 return js.call('dart.dput(#, #, #)', [ |
756 target.accept(this); | 703 target.accept(this), |
757 out.write(', "${lhs.propertyName.name}", '); | 704 js.string(lhs.propertyName.name, "'"), |
758 rhs.accept(this); | 705 rhs.accept(this) |
759 out.write(')'); | 706 ]); |
760 return; | 707 } |
761 } | 708 } |
762 } | 709 |
763 | 710 if (node.parent is ExpressionStatement && |
764 lhs.accept(this); | 711 rhs is CascadeExpression && |
765 out.write(' = '); | 712 _isStateless(lhs, rhs)) { |
766 rhs.accept(this); | 713 // Special case: cascade assignment to a variable in a statement. |
767 } | 714 // We can reuse the variable to desugar it: |
768 | 715 // result = []..length = length; |
769 @override | 716 // becomes: |
770 void visitExpressionFunctionBody(ExpressionFunctionBody node) { | 717 // result = []; |
771 var parameters = _parametersOf(node.parent); | 718 // result.length = length; |
772 var initArgs = parameters != null && _hasArgumentInitializers(parameters); | 719 var savedCascadeTemp = _cascadeTarget; |
773 if (initArgs) { | 720 _cascadeTarget = lhs; |
774 out.write('{\n', 2); | 721 |
775 _generateArgumentInitializers(parameters); | 722 var body = []; |
776 } else { | 723 body.add( |
777 out.write('{ '); | 724 js.statement('# = #;', [lhs.accept(this), rhs.target.accept(this)])); |
778 } | 725 for (var section in rhs.cascadeSections) { |
779 out.write('return '); | 726 body.add(new JS.ExpressionStatement(section.accept(this))); |
780 node.expression.accept(this); | 727 } |
781 if (initArgs) { | 728 |
782 out.write('\n}', -2); | 729 _cascadeTarget = savedCascadeTemp; |
783 } else { | 730 return _statement(body); |
784 out.write('; }'); | 731 } |
785 } | 732 |
786 } | 733 return js.call('# = #', [lhs.accept(this), rhs.accept(this)]); |
787 | 734 } |
788 @override | 735 |
789 void visitEmptyFunctionBody(EmptyFunctionBody node) { | 736 @override |
790 out.write('{}'); | 737 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { |
791 } | 738 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
792 | 739 var ret = new JS.Return(node.expression.accept(this)); |
793 @override | 740 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); |
794 void visitBlockFunctionBody(BlockFunctionBody node) { | 741 } |
795 out.write('{\n', 2); | 742 |
796 _generateArgumentInitializers(_parametersOf(node.parent)); | 743 @override |
797 _visitNodeList(node.block.statements); | 744 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); |
798 out.write('}', -2); | 745 |
799 } | 746 @override |
800 | 747 JS.Block visitBlockFunctionBody(BlockFunctionBody node) { |
801 @override | 748 var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
802 void visitBlock(Block node) { | 749 var block = visitBlock(node.block); |
803 out.write("{\n", 2); | 750 if (initArgs != null) return new JS.Block([initArgs, block]); |
804 node.statements.accept(this); | 751 return block; |
805 out.write("}\n", -2); | 752 } |
806 } | 753 |
807 | 754 @override |
808 @override | 755 JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements)); |
809 void visitMethodInvocation(MethodInvocation node) { | 756 |
757 @override | |
758 JS.Expression visitMethodInvocation(MethodInvocation node) { | |
810 var target = node.isCascaded ? _cascadeTarget : node.target; | 759 var target = node.isCascaded ? _cascadeTarget : node.target; |
811 | 760 |
812 if (rules.isDynamicCall(node.methodName)) { | 761 if (rules.isDynamicCall(node.methodName)) { |
762 var args = node.argumentList.accept(this); | |
813 if (target != null) { | 763 if (target != null) { |
814 out.write('dart.dinvoke('); | 764 return js.call('dart.dinvoke(#, #, #)', [ |
815 target.accept(this); | 765 target.accept(this), |
816 out.write(', "${node.methodName.name}"'); | 766 js.string(node.methodName.name, "'"), |
767 args | |
768 ]); | |
817 } else { | 769 } else { |
818 out.write('dart.dinvokef('); | 770 return js.call( |
819 node.methodName.accept(this); | 771 'dart.dinvokef(#, #)', [node.methodName.accept(this), args]); |
820 } | 772 } |
821 | |
822 _visitArgumentsWithCommaPrefix(node.argumentList); | |
823 out.write(')'); | |
824 return; | |
825 } | 773 } |
826 | 774 |
827 // TODO(jmesserly): if this resolves to a getter returning a function with | 775 // TODO(jmesserly): if this resolves to a getter returning a function with |
828 // a call method, we don't generate the `.call` correctly. | 776 // a call method, we don't generate the `.call` correctly. |
777 | |
778 var targetJs; | |
829 if (target != null) { | 779 if (target != null) { |
830 target.accept(this); | 780 targetJs = js.call('#.#', [target.accept(this), node.methodName.name]); |
831 out.write('.${node.methodName.name}'); | 781 } else { |
832 } else { | 782 targetJs = node.methodName.accept(this); |
833 node.methodName.accept(this); | 783 } |
834 } | 784 |
835 | 785 return js.call('#(#)', [targetJs, node.argumentList.accept(this)]); |
836 out.write('('); | 786 } |
837 node.argumentList.accept(this); | 787 |
838 out.write(')'); | 788 @override |
839 } | 789 JS.Expression visitFunctionExpressionInvocation( |
840 | 790 FunctionExpressionInvocation node) { |
841 @override | 791 var code; |
842 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | |
843 if (rules.isDynamicCall(node.function)) { | 792 if (rules.isDynamicCall(node.function)) { |
844 out.write('dart.dinvokef('); | 793 code = 'dart.dinvokef(#, #)'; |
845 node.function.accept(this); | 794 } else { |
846 _visitArgumentsWithCommaPrefix(node.argumentList); | 795 code = '#(#)'; |
847 out.write(')'); | 796 } |
848 } else { | 797 return js.call( |
849 node.function.accept(this); | 798 code, [node.function.accept(this), node.argumentList.accept(this)]); |
850 out.write('('); | 799 } |
851 node.argumentList.accept(this); | 800 |
852 out.write(')'); | 801 @override |
853 } | 802 List<JS.Expression> visitArgumentList(ArgumentList node) { |
854 } | 803 var args = <JS.Expression>[]; |
855 | 804 var named = <JS.Property>[]; |
856 /// Writes an argument list. This does not write the parens, because sometimes | 805 for (var arg in node.arguments) { |
857 /// a parameter will need to be added before the start of the list, so | |
858 /// writing parens is the responsibility of the parent node. | |
859 @override | |
860 void visitArgumentList(ArgumentList node) { | |
861 var args = node.arguments; | |
862 | |
863 bool hasNamed = false; | |
864 for (int i = 0; i < args.length; i++) { | |
865 if (i > 0) out.write(', '); | |
866 | |
867 var arg = args[i]; | |
868 if (arg is NamedExpression) { | 806 if (arg is NamedExpression) { |
869 if (!hasNamed) out.write('{'); | 807 named.add(visitNamedExpression(arg)); |
870 hasNamed = true; | 808 } else { |
871 } | 809 args.add(arg.accept(this)); |
872 arg.accept(this); | 810 } |
873 } | 811 } |
874 if (hasNamed) out.write('}'); | 812 if (named.isNotEmpty) { |
875 } | 813 args.add(new JS.ObjectInitializer(named)); |
876 | 814 } |
877 void _visitArgumentsWithCommaPrefix(ArgumentList node) { | 815 return args; |
878 if (node == null) return; | 816 } |
879 if (node.arguments.isNotEmpty) out.write(', '); | 817 |
880 visitArgumentList(node); | 818 @override |
881 } | 819 JS.Property visitNamedExpression(NamedExpression node) { |
882 | |
883 @override | |
884 void visitNamedExpression(NamedExpression node) { | |
885 assert(node.parent is ArgumentList); | 820 assert(node.parent is ArgumentList); |
886 node.name.accept(this); | 821 return new JS.Property(new JS.PropertyName(node.name.label.name), |
887 out.write(' '); | 822 node.expression.accept(this)); |
888 node.expression.accept(this); | 823 } |
889 } | 824 |
890 | 825 @override |
891 @override | 826 List<JS.Parameter> visitFormalParameterList(FormalParameterList node) { |
892 void visitFormalParameterList(FormalParameterList node) { | 827 var result = <JS.Parameter>[]; |
893 int length = node.parameters.length; | 828 for (FormalParameter param in node.parameters) { |
894 bool hasOptionalParameters = false; | |
895 bool hasPositionalParameters = false; | |
896 | |
897 for (int i = 0; i < length; i++) { | |
898 var param = node.parameters[i]; | |
899 if (param.kind == ParameterKind.NAMED) { | 829 if (param.kind == ParameterKind.NAMED) { |
900 hasOptionalParameters = true; | 830 result.add(new JS.Parameter(_jsNamedParameterName)); |
901 } else { | 831 break; |
902 if (hasPositionalParameters) out.write(', '); | 832 } |
903 hasPositionalParameters = true; | 833 result.add(new JS.Parameter(param.identifier.name)); |
904 param.accept(this); | 834 } |
905 } | 835 return result; |
906 } | 836 } |
907 if (hasOptionalParameters) { | 837 |
908 if (hasPositionalParameters) out.write(', '); | 838 @override |
909 out.write(optionalParameters); | 839 JS.Statement visitExpressionStatement(ExpressionStatement node) => |
910 } | 840 _expressionStatement(node.expression.accept(this)); |
911 } | 841 |
912 | 842 // Some expressions may choose to generate themselves as JS statements |
913 @override | 843 // if their parent is in a statement context. |
914 void visitFieldFormalParameter(FieldFormalParameter node) { | 844 // TODO(jmesserly): refactor so we handle the special cases here, and |
915 // Named parameters are handled as a single object, so we skip individual | 845 // can use better return types on the expression visit methods. |
916 // parameters | 846 JS.Statement _expressionStatement(expr) => |
917 if (node.kind != ParameterKind.NAMED) { | 847 expr is JS.Statement ? expr : new JS.ExpressionStatement(expr); |
918 out.write(node.identifier.name); | 848 |
919 } | 849 @override |
920 } | 850 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => |
921 | 851 new JS.EmptyStatement(); |
922 @override | 852 |
923 void visitDefaultFormalParameter(DefaultFormalParameter node) { | 853 @override |
924 // Named parameters are handled as a single object, so we skip individual | 854 JS.Statement visitAssertStatement(AssertStatement node) => |
925 // parameters | 855 // TODO(jmesserly): only emit in checked mode. |
926 if (node.kind != ParameterKind.NAMED) { | 856 js.statement('dart.assert(#);', node.condition.accept(this)); |
927 out.write(node.identifier.name); | 857 |
928 } | 858 @override |
929 } | 859 JS.Return visitReturnStatement(ReturnStatement node) => |
930 | 860 new JS.Return(_visit(node.expression)); |
931 @override | 861 |
932 void visitExpressionStatement(ExpressionStatement node) { | 862 @override |
933 node.expression.accept(this); | 863 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
934 out.write(';\n'); | 864 var body = <JS.Statement>[]; |
935 } | 865 |
936 | |
937 @override | |
938 void visitEmptyStatement(EmptyStatement node) { | |
939 out.write(';\n'); | |
940 } | |
941 | |
942 @override | |
943 void visitAssertStatement(AssertStatement node) { | |
944 // TODO(jmesserly): only emit in checked mode. | |
945 _visitNode(node.condition, prefix: 'dart.assert(', suffix: ');\n'); | |
946 } | |
947 | |
948 @override | |
949 void visitReturnStatement(ReturnStatement node) { | |
950 out.write('return'); | |
951 _visitNode(node.expression, prefix: ' '); | |
952 out.write(';\n'); | |
953 } | |
954 | |
955 @override | |
956 void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | |
957 for (var field in node.variables.variables) { | 866 for (var field in node.variables.variables) { |
958 var name = field.name.name; | |
959 if (field.isConst) { | 867 if (field.isConst) { |
960 // constant fields don't change, so we can generate them as `let` | 868 // constant fields don't change, so we can generate them as `let` |
961 // but add them to the module's exports | 869 // but add them to the module's exports |
962 _visitNode(field, prefix: 'let ', suffix: ';\n'); | 870 var name = field.name.name; |
871 body.add(js.statement('let # = #;', [ | |
872 new JS.VariableDeclaration(name), | |
873 _visitInitializer(field) | |
874 ])); | |
963 if (isPublic(name)) _exports.add(name); | 875 if (isPublic(name)) _exports.add(name); |
964 } else if (_isFieldInitConstant(field)) { | 876 } else if (_isFieldInitConstant(field)) { |
965 _visitNode(field, suffix: ';\n'); | 877 body.add(js.statement( |
878 '# = #;', [field.name.accept(this), _visitInitializer(field)])); | |
966 } else { | 879 } else { |
967 _lazyFields.add(field); | 880 _lazyFields.add(field); |
968 } | 881 } |
969 } | 882 } |
970 } | 883 |
971 | 884 return _statement(body); |
972 @override | 885 } |
973 void visitVariableDeclarationList(VariableDeclarationList node) { | 886 |
974 _visitNodeList(node.variables, prefix: 'let ', separator: ', '); | 887 @override |
975 } | 888 visitVariableDeclarationList(VariableDeclarationList node) { |
976 | 889 var last = node.variables.last; |
977 @override | 890 var lastInitializer = last.initializer; |
978 void visitVariableDeclaration(VariableDeclaration node) { | 891 |
979 node.name.accept(this); | 892 List<JS.VariableInitialization> variables; |
980 out.write(' = '); | 893 if (lastInitializer is CascadeExpression && |
981 if (node.initializer != null) { | 894 node.parent is VariableDeclarationStatement) { |
982 node.initializer.accept(this); | 895 // Special case: cascade as variable initializer |
983 } else { | 896 // |
984 // explicitly initialize to null, so we don't need to worry about | 897 // We can reuse the variable to desugar it: |
985 // `undefined`. | 898 // var result = []..length = length; |
986 // TODO(jmesserly): do this only for vars that aren't definitely assigned. | 899 // becomes: |
987 out.write('null'); | 900 // var result = []; |
988 } | 901 // result.length = length; |
989 } | 902 var savedCascadeTemp = _cascadeTarget; |
990 | 903 _cascadeTarget = last.name; |
991 void _flushLazyFields() { | 904 |
992 if (_lazyFields.isEmpty) return; | 905 variables = _visitList(node.variables.take(node.variables.length - 1)); |
993 | 906 variables.add(new JS.VariableInitialization( |
994 _writeLazyFields(_libraryName, _lazyFields); | 907 new JS.VariableDeclaration(last.name.name), |
995 out.write('\n'); | 908 lastInitializer.target.accept(this))); |
996 | 909 |
910 var result = <JS.Expression>[ | |
911 new JS.VariableDeclarationList('let', variables) | |
912 ]; | |
913 result.addAll(_visitList(lastInitializer.cascadeSections)); | |
914 _cascadeTarget = savedCascadeTemp; | |
915 return _statement(result.map((e) => new JS.ExpressionStatement(e))); | |
916 } else { | |
917 variables = _visitList(node.variables); | |
918 } | |
919 | |
920 return new JS.VariableDeclarationList('let', variables); | |
921 } | |
922 | |
923 @override | |
924 JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) { | |
925 var name = new JS.VariableDeclaration(node.name.name); | |
926 return new JS.VariableInitialization(name, _visitInitializer(node)); | |
927 } | |
928 | |
929 JS.Expression _visitInitializer(VariableDeclaration node) { | |
930 var value = _visit(node.initializer); | |
931 // explicitly initialize to null, to avoid getting `undefined`. | |
932 // TODO(jmesserly): do this only for vars that aren't definitely assigned. | |
933 return value != null ? value : new JS.LiteralNull(); | |
934 } | |
935 | |
936 void _flushLazyFields(List<JS.Statement> body) { | |
937 var code = _emitLazyFields(_libraryName, _lazyFields); | |
938 if (code != null) body.add(code); | |
997 _lazyFields.clear(); | 939 _lazyFields.clear(); |
998 } | 940 } |
999 | 941 |
1000 void _writeLazyFields(String objExpr, List<VariableDeclaration> fields) { | 942 JS.Statement _emitLazyFields( |
1001 if (fields.isEmpty) return; | 943 String objExpr, List<VariableDeclaration> fields) { |
1002 | 944 if (fields.isEmpty) return null; |
1003 out.write('dart.defineLazyProperties($objExpr, {\n', 2); | 945 |
946 var methods = []; | |
1004 for (var node in fields) { | 947 for (var node in fields) { |
1005 var name = node.name.name; | 948 var name = node.name.name; |
1006 out.write('get $name() { return '); | 949 methods.add(new JS.Method(new JS.PropertyName(name), |
1007 node.initializer.accept(this); | 950 js.call('function() { return #; }', node.initializer.accept(this)), |
1008 out.write(' },\n'); | 951 isGetter: true)); |
1009 // TODO(jmesserly): we're using a dummy setter to indicate writable. | 952 |
1010 if (!node.isFinal) out.write('set $name(x) {},\n'); | 953 // TODO(jmesserly): use a dummy setter to indicate writable. |
Jennifer Messerly
2015/02/25 18:36:20
this is the dummy setter code. I agree we should f
| |
1011 } | 954 if (!node.isFinal) { |
1012 out.write('});\n', -2); | 955 methods.add(new JS.Method( |
1013 } | 956 new JS.PropertyName(name), js.call('function() {}'), |
1014 | 957 isSetter: true)); |
1015 void _flushLibraryProperties() { | 958 } |
959 } | |
960 | |
961 return js.statement( | |
962 'dart.defineLazyProperties(#, { # })', [objExpr, methods]); | |
963 } | |
964 | |
965 void _flushLibraryProperties(List<JS.Statement> body) { | |
1016 if (_properties.isEmpty) return; | 966 if (_properties.isEmpty) return; |
1017 | 967 body.add(js.statement('dart.copyProperties(#, { # });', [ |
1018 out.write('dart.copyProperties($_libraryName, {\n', 2); | 968 _libraryName, |
1019 for (var node in _properties) { | 969 _properties.map(_emitTopLevelProperty) |
1020 _writeFunctionDeclaration(node); | 970 ])); |
1021 out.write(',\n'); | |
1022 } | |
1023 out.write('});\n\n', -2); | |
1024 | |
1025 _properties.clear(); | 971 _properties.clear(); |
1026 } | 972 } |
1027 | 973 |
1028 @override | 974 @override |
1029 void visitVariableDeclarationStatement(VariableDeclarationStatement node) { | 975 JS.Statement visitVariableDeclarationStatement( |
1030 _visitNode(node.variables); | 976 VariableDeclarationStatement node) => |
1031 out.write(';\n'); | 977 _expressionStatement(node.variables.accept(this)); |
1032 } | 978 |
1033 | 979 @override |
1034 @override | 980 visitConstructorName(ConstructorName node) { |
1035 void visitConstructorName(ConstructorName node) { | 981 var typeName = node.type.name.accept(this); |
1036 node.type.name.accept(this); | |
1037 if (node.name != null) { | 982 if (node.name != null) { |
1038 out.write('.'); | 983 return js.call('#.#', [typeName, node.name.name]); |
1039 node.name.accept(this); | 984 } |
1040 } | 985 return typeName; |
1041 } | 986 } |
1042 | 987 |
1043 @override | 988 @override |
1044 void visitInstanceCreationExpression(InstanceCreationExpression node) { | 989 visitInstanceCreationExpression(InstanceCreationExpression node) { |
1045 out.write('new '); | 990 return js.call('new #(#)', [ |
1046 node.constructorName.accept(this); | 991 node.constructorName.accept(this), |
1047 out.write('('); | 992 node.argumentList.accept(this) |
1048 node.argumentList.accept(this); | 993 ]); |
1049 out.write(')'); | |
1050 } | 994 } |
1051 | 995 |
1052 /// True if this type is built-in to JS, and we use the values unwrapped. | 996 /// True if this type is built-in to JS, and we use the values unwrapped. |
1053 /// For these types we generate a calling convention via static | 997 /// For these types we generate a calling convention via static |
1054 /// "extension methods". This allows types to be extended without adding | 998 /// "extension methods". This allows types to be extended without adding |
1055 /// extensions directly on the prototype. | 999 /// extensions directly on the prototype. |
1056 bool _isJSBuiltinType(DartType t) => | 1000 bool _isJSBuiltinType(DartType t) => |
1057 rules.isNumType(t) || rules.isStringType(t) || rules.isBoolType(t); | 1001 rules.isNumType(t) || rules.isStringType(t) || rules.isBoolType(t); |
1058 | 1002 |
1059 bool typeIsPrimitiveInJS(DartType t) => !rules.isDynamic(t) && | 1003 bool typeIsPrimitiveInJS(DartType t) => !rules.isDynamic(t) && |
1060 (rules.isIntType(t) || | 1004 (rules.isIntType(t) || |
1061 rules.isDoubleType(t) || | 1005 rules.isDoubleType(t) || |
1062 rules.isBoolType(t) || | 1006 rules.isBoolType(t) || |
1063 rules.isNumType(t)); | 1007 rules.isNumType(t)); |
1064 | 1008 |
1065 bool typeIsNonNullablePrimitiveInJS(DartType t) => | 1009 bool typeIsNonNullablePrimitiveInJS(DartType t) => |
1066 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t); | 1010 typeIsPrimitiveInJS(t) && rules.isNonNullableType(t); |
1067 | 1011 |
1068 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => | 1012 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => |
1069 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); | 1013 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); |
1070 | 1014 |
1071 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); | 1015 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); |
1072 | 1016 |
1073 void notNull(Expression expr) { | 1017 JS.Expression notNull(Expression expr) { |
1074 var type = rules.getStaticType(expr); | 1018 var type = rules.getStaticType(expr); |
1075 if (rules.isNonNullableType(type)) { | 1019 if (rules.isNonNullableType(type)) { |
1076 expr.accept(this); | 1020 return expr.accept(this); |
1077 } else { | 1021 } else { |
1078 out.write('dart.notNull('); | 1022 return js.call('dart.notNull(#)', expr.accept(this)); |
1079 expr.accept(this); | |
1080 out.write(')'); | |
1081 } | 1023 } |
1082 } | 1024 } |
1083 | 1025 |
1084 @override | 1026 @override |
1085 void visitBinaryExpression(BinaryExpression node) { | 1027 JS.Expression visitBinaryExpression(BinaryExpression node) { |
1086 var op = node.operator; | 1028 var op = node.operator; |
1087 var lhs = node.leftOperand; | 1029 var left = node.leftOperand; |
1088 var rhs = node.rightOperand; | 1030 var right = node.rightOperand; |
1031 var leftType = rules.getStaticType(left); | |
1032 var rightType = rules.getStaticType(right); | |
1089 | 1033 |
1090 var dispatchType = rules.getStaticType(lhs); | 1034 var code; |
1091 var otherType = rules.getStaticType(rhs); | |
1092 | |
1093 if (op.type.isEqualityOperator) { | 1035 if (op.type.isEqualityOperator) { |
1094 // If we statically know LHS or RHS is null we can generate a clean check. | 1036 // If we statically know LHS or RHS is null we can generate a clean check. |
1095 // We can also do this if the left hand side is a primitive type, because | 1037 // We can also do this if the left hand side is a primitive type, because |
1096 // we know then it doesn't have an overridden. | 1038 // we know then it doesn't have an overridden. |
1097 if (_isNull(lhs) || _isNull(rhs) || typeIsPrimitiveInJS(dispatchType)) { | 1039 if (_isNull(left) || _isNull(right) || typeIsPrimitiveInJS(leftType)) { |
1098 lhs.accept(this); | |
1099 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equa lity-comparison | 1040 // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equa lity-comparison |
1100 out.write(op.type == TokenType.EQ_EQ ? ' === ' : ' !== '); | 1041 code = op.type == TokenType.EQ_EQ ? '# === #' : '# !== #'; |
1101 rhs.accept(this); | |
1102 } else { | 1042 } else { |
1103 // TODO(jmesserly): it would be nice to use just "equals", perhaps | 1043 var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
1104 // by importing this name. | 1044 code = '${bang}dart.equals(#, #)'; |
1105 if (op.type == TokenType.BANG_EQ) out.write('!'); | |
1106 out.write('dart.equals('); | |
1107 lhs.accept(this); | |
1108 out.write(', '); | |
1109 rhs.accept(this); | |
1110 out.write(')'); | |
1111 } | 1045 } |
1112 } else if (binaryOperationIsPrimitive(dispatchType, otherType)) { | 1046 return js.call(code, [left.accept(this), right.accept(this)]); |
1047 } else if (binaryOperationIsPrimitive(leftType, rightType)) { | |
1113 // special cases where we inline the operation | 1048 // special cases where we inline the operation |
1114 // these values are assumed to be non-null (determined by the checker) | 1049 // these values are assumed to be non-null (determined by the checker) |
1115 // TODO(jmesserly): it would be nice to just inline the method from core, | 1050 // TODO(jmesserly): it would be nice to just inline the method from core, |
1116 // instead of special cases here. | 1051 // instead of special cases here. |
1117 | |
1118 if (op.type == TokenType.TILDE_SLASH) { | 1052 if (op.type == TokenType.TILDE_SLASH) { |
1119 // `a ~/ b` is equivalent to `(a / b).truncate()` | 1053 // `a ~/ b` is equivalent to `(a / b).truncate()` |
1120 out.write('('); | 1054 code = '(# / #).truncate()'; |
1121 notNull(lhs); | |
1122 out.write(' / '); | |
1123 notNull(rhs); | |
1124 out.write(').truncate()'); | |
1125 } else { | 1055 } else { |
1126 // TODO(vsm): When do Dart ops not map to JS? | 1056 // TODO(vsm): When do Dart ops not map to JS? |
1127 notNull(lhs); | 1057 code = '# $op #'; |
1128 out.write(' $op '); | |
1129 notNull(rhs); | |
1130 } | 1058 } |
1131 } else if (rules.isDynamicTarget(lhs)) { | 1059 return js.call(code, [notNull(left), notNull(right)]); |
1132 // dynamic dispatch | |
1133 out.write('dart.dbinary('); | |
1134 lhs.accept(this); | |
1135 out.write(', "${op.lexeme}", '); | |
1136 rhs.accept(this); | |
1137 out.write(')'); | |
1138 } else if (_isJSBuiltinType(dispatchType)) { | |
1139 // TODO(jmesserly): we'd get better readability from the static-dispatch | |
1140 // pattern below. Consider: | |
1141 // | |
1142 // "hello"['+']"world" | |
1143 // vs | |
1144 // core.String['+']("hello", "world") | |
1145 // | |
1146 // Infix notation is much more readable, which is a bit part of why | |
1147 // C# added its extension methods feature. However this would require | |
1148 // adding these methods to String.prototype/Number.prototype in JS. | |
1149 _writeTypeName(dispatchType); | |
1150 out.write(_canonicalMethodInvoke(op.lexeme)); | |
1151 out.write('('); | |
1152 lhs.accept(this); | |
1153 out.write(', '); | |
1154 rhs.accept(this); | |
1155 out.write(')'); | |
1156 } else { | 1060 } else { |
1157 // Generic static-dispatch, user-defined operator code path. | 1061 var opString = js.string(op.lexeme, "'"); |
1158 | 1062 if (rules.isDynamicTarget(left)) { |
1159 // We're going to replace the operator with high-precedence "." or "[]", | 1063 // dynamic dispatch |
1160 // so add parens around the left side if necessary. | 1064 return js.call('dart.dbinary(#, #, #)', [ |
1161 _visitExpression(lhs, _indexExpressionPrecedence); | 1065 left.accept(this), |
1162 out.write(_canonicalMethodInvoke(op.lexeme)); | 1066 opString, |
1163 out.write('('); | 1067 right.accept(this) |
1164 rhs.accept(this); | 1068 ]); |
1165 out.write(')'); | 1069 } else if (_isJSBuiltinType(leftType)) { |
1070 // TODO(jmesserly): we'd get better readability from the static-dispatch | |
1071 // pattern below. Consider: | |
1072 // | |
1073 // "hello"['+']"world" | |
1074 // vs | |
1075 // core.String['+']("hello", "world") | |
1076 // | |
1077 // Infix notation is much more readable, which is a bit part of why | |
1078 // C# added its extension methods feature. However this would require | |
1079 // adding these methods to String.prototype/Number.prototype in JS. | |
1080 return js.call('#.#(#, #)', [ | |
1081 _emitTypeName(leftType), | |
1082 opString, | |
1083 left.accept(this), | |
1084 right.accept(this) | |
1085 ]); | |
1086 } else { | |
1087 // Generic static-dispatch, user-defined operator code path. | |
1088 return js.call( | |
1089 '#.#(#)', [left.accept(this), opString, right.accept(this)]); | |
1090 } | |
1166 } | 1091 } |
1167 } | 1092 } |
1168 | 1093 |
1169 bool _isNull(Expression expr) => expr is NullLiteral; | 1094 bool _isNull(Expression expr) => expr is NullLiteral; |
1170 | 1095 |
1171 @override | 1096 @override |
1172 void visitPostfixExpression(PostfixExpression node) { | 1097 JS.Expression visitPostfixExpression(PostfixExpression node) { |
1173 var op = node.operator; | 1098 var op = node.operator; |
1174 var expr = node.operand; | 1099 var expr = node.operand; |
1175 | 1100 |
1176 var dispatchType = rules.getStaticType(expr); | 1101 var dispatchType = rules.getStaticType(expr); |
1177 if (unaryOperationIsPrimitive(dispatchType)) { | 1102 if (unaryOperationIsPrimitive(dispatchType)) { |
1178 // TODO(vsm): When do Dart ops not map to JS? | 1103 // TODO(vsm): When do Dart ops not map to JS? |
1179 notNull(expr); | 1104 return js.call('#$op', notNull(expr)); |
1180 out.write('$op'); | |
1181 } else { | 1105 } else { |
1182 // TODO(vsm): Figure out operator calling convention / dispatch. | 1106 // TODO(vsm): Figure out operator calling convention / dispatch. |
1183 out.write('/* Unimplemented postfix operator: $node */'); | 1107 return visitExpression(node); |
1184 } | 1108 } |
1185 } | 1109 } |
1186 | 1110 |
1187 @override | 1111 @override |
1188 void visitPrefixExpression(PrefixExpression node) { | 1112 JS.Expression visitPrefixExpression(PrefixExpression node) { |
1189 var op = node.operator; | 1113 var op = node.operator; |
1190 var expr = node.operand; | 1114 var expr = node.operand; |
1191 | 1115 |
1192 var dispatchType = rules.getStaticType(expr); | 1116 var dispatchType = rules.getStaticType(expr); |
1193 if (unaryOperationIsPrimitive(dispatchType)) { | 1117 if (unaryOperationIsPrimitive(dispatchType)) { |
1194 // TODO(vsm): When do Dart ops not map to JS? | 1118 // TODO(vsm): When do Dart ops not map to JS? |
1195 out.write('$op'); | 1119 return js.call('$op#', notNull(expr)); |
1196 notNull(expr); | |
1197 } else { | 1120 } else { |
1198 // TODO(vsm): Figure out operator calling convention / dispatch. | 1121 // TODO(vsm): Figure out operator calling convention / dispatch. |
1199 out.write('/* Unimplemented postfix operator: $node */'); | 1122 return visitExpression(node); |
1200 } | 1123 } |
1201 } | 1124 } |
1202 | 1125 |
1203 // Cascades can contain [IndexExpression], [MethodInvocation] and | 1126 // Cascades can contain [IndexExpression], [MethodInvocation] and |
1204 // [PropertyAccess]. The code generation for those is handled in their | 1127 // [PropertyAccess]. The code generation for those is handled in their |
1205 // respective visit methods. | 1128 // respective visit methods. |
1206 @override | 1129 @override |
1207 void visitCascadeExpression(CascadeExpression node) { | 1130 JS.Node visitCascadeExpression(CascadeExpression node) { |
1208 var savedCascadeTemp = _cascadeTarget; | 1131 var savedCascadeTemp = _cascadeTarget; |
1209 | 1132 |
1210 var parent = node.parent; | 1133 var parent = node.parent; |
1211 var grandparent = parent.parent; | 1134 JS.Node result; |
1212 if (_isStateless(node.target, node)) { | 1135 if (_isStateless(node.target, node)) { |
1213 // Special case: target is stateless, so we can just reuse it. | 1136 // Special case: target is stateless, so we can just reuse it. |
1214 _cascadeTarget = node.target; | 1137 _cascadeTarget = node.target; |
1215 | 1138 |
1216 if (parent is ExpressionStatement) { | 1139 if (parent is ExpressionStatement) { |
1217 _visitNodeList(node.cascadeSections, separator: ';\n'); | 1140 var sections = _visitList(node.cascadeSections); |
1141 result = _statement(sections.map((e) => new JS.ExpressionStatement(e))); | |
1218 } else { | 1142 } else { |
1219 // Use comma expression. For example: | 1143 // Use comma expression. For example: |
1220 // (sb.write(1), sb.write(2), sb) | 1144 // (sb.write(1), sb.write(2), sb) |
1221 out.write('('); | 1145 var sections = _visitListToBinary(node.cascadeSections, ','); |
1222 _visitNodeList(node.cascadeSections, separator: ', ', suffix: ', '); | 1146 result = new JS.Binary(',', sections, _cascadeTarget.accept(this)); |
1223 _cascadeTarget.accept(this); | |
1224 out.write(')'); | |
1225 } | 1147 } |
1226 } else if (parent is AssignmentExpression && | |
1227 grandparent is ExpressionStatement && | |
1228 _isStateless(parent.leftHandSide, node)) { | |
1229 | |
1230 // Special case: assignment to a variable in a statement. | |
1231 // We can reuse the variable to desugar it: | |
1232 // result = []..length = length; | |
1233 // becomes: | |
1234 // result = []; | |
1235 // result.length = length; | |
1236 _cascadeTarget = parent.leftHandSide; | |
1237 node.target.accept(this); | |
1238 out.write(';\n'); | |
1239 _visitNodeList(node.cascadeSections, separator: ';\n'); | |
1240 } else if (parent is VariableDeclaration && | |
1241 grandparent is VariableDeclarationList && | |
1242 grandparent.variables.last == parent) { | |
1243 | |
1244 // Special case: variable declaration | |
1245 // We can reuse the variable to desugar it: | |
1246 // var result = []..length = length; | |
1247 // becomes: | |
1248 // var result = []; | |
1249 // result.length = length; | |
1250 _cascadeTarget = parent.name; | |
1251 node.target.accept(this); | |
1252 out.write(';\n'); | |
1253 _visitNodeList(node.cascadeSections, separator: ';\n'); | |
1254 } else { | 1148 } else { |
1255 // In the general case we need to capture the target expression into | 1149 // In the general case we need to capture the target expression into |
1256 // a temporary. This uses a lambda to get a temporary scope, and it also | 1150 // a temporary. This uses a lambda to get a temporary scope, and it also |
1257 // remains valid in an expression context. | 1151 // remains valid in an expression context. |
1258 // TODO(jmesserly): need a better way to handle temps. | 1152 // TODO(jmesserly): need a better way to handle temps. |
1259 // TODO(jmesserly): special case for parent is ExpressionStatement? | 1153 // TODO(jmesserly): special case for parent is ExpressionStatement? |
1260 _cascadeTarget = | 1154 _cascadeTarget = |
1261 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0)); | 1155 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '_', 0)); |
1262 _cascadeTarget.staticElement = | 1156 _cascadeTarget.staticElement = |
1263 new LocalVariableElementImpl.forNode(_cascadeTarget); | 1157 new LocalVariableElementImpl.forNode(_cascadeTarget); |
1264 _cascadeTarget.staticType = node.target.staticType; | 1158 _cascadeTarget.staticType = node.target.staticType; |
1265 | 1159 |
1266 out.write('((${_cascadeTarget.name}) => {\n', 2); | 1160 var body = _visitList(node.cascadeSections); |
1267 _visitNodeList(node.cascadeSections, separator: ';\n', suffix: ';\n'); | |
1268 if (node.parent is! ExpressionStatement) { | 1161 if (node.parent is! ExpressionStatement) { |
1269 out.write('return ${_cascadeTarget.name};\n'); | 1162 body.add(js.statement('return #;', _cascadeTarget.name)); |
1270 } | 1163 } |
1271 out.write('})', -2); | 1164 |
1272 if (_needsBindThis(node.cascadeSections)) out.write('.bind(this)'); | 1165 var bindThis = _maybeBindThis(node.cascadeSections); |
1273 out.write('('); | 1166 result = js.call('((#) => { # })$bindThis(#)', [ |
1274 node.target.accept(this); | 1167 _cascadeTarget.name, |
1275 out.write(')'); | 1168 body, |
1169 node.target.accept(this) | |
1170 ]); | |
1276 } | 1171 } |
1277 | 1172 |
1278 _cascadeTarget = savedCascadeTemp; | 1173 _cascadeTarget = savedCascadeTemp; |
1174 return result; | |
1279 } | 1175 } |
1280 | 1176 |
1281 /// True is the expression can be evaluated multiple times without causing | 1177 /// True is the expression can be evaluated multiple times without causing |
1282 /// code execution. This is true for final fields. This can be true for local | 1178 /// code execution. This is true for final fields. This can be true for local |
1283 /// variables, if: | 1179 /// variables, if: |
1284 /// * they are not assigned within the [context]. | 1180 /// * they are not assigned within the [context]. |
1285 /// * they are not assigned in a function closure anywhere. | 1181 /// * they are not assigned in a function closure anywhere. |
1286 bool _isStateless(Expression node, [AstNode context]) { | 1182 bool _isStateless(Expression node, [AstNode context]) { |
1287 if (node is SimpleIdentifier) { | 1183 if (node is SimpleIdentifier) { |
1288 var e = node.staticElement; | 1184 var e = node.staticElement; |
1289 if (e is PropertyAccessorElement) e = e.variable; | 1185 if (e is PropertyAccessorElement) e = e.variable; |
1290 if (e is VariableElementImpl && !e.isSynthetic) { | 1186 if (e is VariableElementImpl && !e.isSynthetic) { |
1291 if (e.isFinal) return true; | 1187 if (e.isFinal) return true; |
1292 if (e is LocalVariableElementImpl || e is ParameterElementImpl) { | 1188 if (e is LocalVariableElementImpl || e is ParameterElementImpl) { |
1293 // make sure the local isn't mutated in the context. | 1189 // make sure the local isn't mutated in the context. |
1294 return !_isPotentiallyMutated(e, context); | 1190 return !_isPotentiallyMutated(e, context); |
1295 } | 1191 } |
1296 } | 1192 } |
1297 } | 1193 } |
1298 return false; | 1194 return false; |
1299 } | 1195 } |
1300 | 1196 |
1301 @override | 1197 @override |
1302 void visitParenthesizedExpression(ParenthesizedExpression node) { | 1198 visitParenthesizedExpression(ParenthesizedExpression node) => |
1303 out.write('('); | 1199 // The printer handles precedence so we don't need to. |
1304 node.expression.accept(this); | 1200 node.expression.accept(this); |
1305 out.write(')'); | |
1306 } | |
1307 | 1201 |
1308 @override | 1202 @override |
1309 void visitSimpleFormalParameter(SimpleFormalParameter node) { | 1203 visitSimpleFormalParameter(SimpleFormalParameter node) => |
1310 node.identifier.accept(this); | 1204 node.identifier.accept(this); |
1311 } | |
1312 | 1205 |
1313 @override | 1206 @override |
1314 void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { | 1207 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) => |
1315 node.identifier.accept(this); | 1208 node.identifier.accept(this); |
1316 } | |
1317 | 1209 |
1318 @override | 1210 @override |
1319 void visitThisExpression(ThisExpression node) { | 1211 JS.This visitThisExpression(ThisExpression node) => new JS.This(); |
1320 out.write('this'); | |
1321 } | |
1322 | 1212 |
1323 @override | 1213 @override |
1324 void visitSuperExpression(SuperExpression node) { | 1214 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super(); |
1325 out.write('super'); | |
1326 } | |
1327 | 1215 |
1328 @override | 1216 @override |
1329 void visitPrefixedIdentifier(PrefixedIdentifier node) { | 1217 visitPrefixedIdentifier(PrefixedIdentifier node) { |
1330 if (node.prefix.staticElement is PrefixElement) { | 1218 if (node.prefix.staticElement is PrefixElement) { |
1331 node.identifier.accept(this); | 1219 return node.identifier.accept(this); |
1332 } else { | 1220 } else { |
1333 _visitGet(node.prefix, node.identifier); | 1221 return _visitGet(node.prefix, node.identifier); |
1334 } | 1222 } |
1335 } | 1223 } |
1336 | 1224 |
1337 @override | 1225 @override |
1338 void visitPropertyAccess(PropertyAccess node) { | 1226 visitPropertyAccess(PropertyAccess node) => |
1339 _visitGet(_getTarget(node), node.propertyName); | 1227 _visitGet(_getTarget(node), node.propertyName); |
1340 } | |
1341 | 1228 |
1342 /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. | 1229 /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. |
1343 void _visitGet(Expression target, SimpleIdentifier name) { | 1230 _visitGet(Expression target, SimpleIdentifier name) { |
1344 if (rules.isDynamicTarget(target)) { | 1231 if (rules.isDynamicTarget(target)) { |
1345 // TODO(jmesserly): this won't work if we're left hand side of assignment. | 1232 return js.call( |
1346 out.write('dart.dload('); | 1233 'dart.dload(#, #)', [target.accept(this), js.string(name.name, "'")]); |
1347 target.accept(this); | |
1348 out.write(', "${name.name}")'); | |
1349 } else { | 1234 } else { |
1350 target.accept(this); | 1235 return js.call('#.#', [target.accept(this), name.name]); |
1351 out.write('.${name.name}'); | |
1352 } | 1236 } |
1353 } | 1237 } |
1354 | 1238 |
1355 @override | 1239 @override |
1356 void visitIndexExpression(IndexExpression node) { | 1240 visitIndexExpression(IndexExpression node) { |
1357 var target = _getTarget(node); | 1241 var target = _getTarget(node); |
1242 var code; | |
1358 if (rules.isDynamicTarget(target)) { | 1243 if (rules.isDynamicTarget(target)) { |
1359 out.write('dart.dindex('); | 1244 code = 'dart.dindex(#, #)'; |
1360 target.accept(this); | |
1361 out.write(', '); | |
1362 node.index.accept(this); | |
1363 out.write(')'); | |
1364 } else { | 1245 } else { |
1365 target.accept(this); | 1246 code = '#.get(#)'; |
1366 out.write(_canonicalMethodInvoke('[]')); | |
1367 out.write('('); | |
1368 node.index.accept(this); | |
1369 out.write(')'); | |
1370 } | 1247 } |
1248 return js.call(code, [target.accept(this), node.index.accept(this)]); | |
1371 } | 1249 } |
1372 | 1250 |
1373 /// Gets the target of a [PropertyAccess] or [IndexExpression]. | 1251 /// Gets the target of a [PropertyAccess] or [IndexExpression]. |
1374 /// Those two nodes are special because they're both allowed on left side of | 1252 /// Those two nodes are special because they're both allowed on left side of |
1375 /// an assignment expression and cascades. | 1253 /// an assignment expression and cascades. |
1376 Expression _getTarget(node) { | 1254 Expression _getTarget(node) { |
1377 assert(node is IndexExpression || node is PropertyAccess); | 1255 assert(node is IndexExpression || node is PropertyAccess); |
1378 return node.isCascaded ? _cascadeTarget : node.target; | 1256 return node.isCascaded ? _cascadeTarget : node.target; |
1379 } | 1257 } |
1380 | 1258 |
1381 @override | 1259 @override |
1382 void visitConditionalExpression(ConditionalExpression node) { | 1260 visitConditionalExpression(ConditionalExpression node) { |
1383 node.condition.accept(this); | 1261 return js.call('# ? # : #', [ |
1384 out.write(' ? '); | 1262 node.condition.accept(this), |
1385 node.thenExpression.accept(this); | 1263 node.thenExpression.accept(this), |
1386 out.write(' : '); | 1264 node.elseExpression.accept(this) |
1387 node.elseExpression.accept(this); | 1265 ]); |
1388 } | 1266 } |
1389 | 1267 |
1390 @override | 1268 @override |
1391 void visitThrowExpression(ThrowExpression node) { | 1269 visitThrowExpression(ThrowExpression node) { |
1270 var expr = node.expression.accept(this); | |
1392 if (node.parent is ExpressionStatement) { | 1271 if (node.parent is ExpressionStatement) { |
1393 out.write('throw '); | 1272 return js.statement('throw #;', expr); |
1394 node.expression.accept(this); | |
1395 } else { | 1273 } else { |
1396 // TODO(jmesserly): move this into runtime helper? | 1274 return js.call('dart.throw_(#)', expr); |
1397 out.write('(function(e) { throw e }('); | 1275 } |
1398 node.expression.accept(this); | 1276 } |
1399 out.write(')'); | 1277 |
1400 } | 1278 @override |
1401 } | 1279 JS.If visitIfStatement(IfStatement node) { |
1402 | 1280 return new JS.If(node.condition.accept(this), _visit(node.thenStatement), |
1403 @override | 1281 _visitOrEmpty(node.elseStatement)); |
1404 void visitIfStatement(IfStatement node) { | 1282 } |
1405 out.write('if ('); | 1283 |
1406 node.condition.accept(this); | 1284 @override |
1407 out.write(') '); | 1285 JS.For visitForStatement(ForStatement node) { |
1408 var then = node.thenStatement; | 1286 var init = _visit(node.initialization); |
1409 if (then is Block) { | 1287 if (init == null) init = _visit(node.variables); |
1410 out.write('{\n', 2); | 1288 return new JS.For(init, _visit(node.condition), |
1411 _visitNodeList((then as Block).statements); | 1289 _visitListToBinary(node.updaters, ','), _visit(node.body)); |
1412 out.write('}', -2); | 1290 } |
1413 } else { | 1291 |
1414 _visitNode(then); | 1292 @override |
1415 } | 1293 JS.While visitWhileStatement(WhileStatement node) { |
1416 var elseClause = node.elseStatement; | 1294 return new JS.While(node.condition.accept(this), node.body.accept(this)); |
1417 if (elseClause != null) { | 1295 } |
1418 out.write(' else '); | 1296 |
1419 elseClause.accept(this); | 1297 @override |
1420 } else if (then is Block) { | 1298 JS.Do visitDoStatement(DoStatement node) { |
1421 out.write('\n'); | 1299 return new JS.Do(node.body.accept(this), node.condition.accept(this)); |
1422 } | 1300 } |
1423 } | 1301 |
1424 | 1302 @override |
1425 @override | 1303 JS.ForOf visitForEachStatement(ForEachStatement node) { |
1426 void visitForStatement(ForStatement node) { | 1304 var init = _visit(node.identifier); |
1427 Expression initialization = node.initialization; | 1305 if (init == null) { |
1428 out.write("for ("); | 1306 init = js.call('let #', node.loopVariable.identifier.name); |
1429 if (initialization != null) { | 1307 } |
1430 initialization.accept(this); | 1308 return new JS.ForOf( |
1431 } else if (node.variables != null) { | 1309 init, node.iterable.accept(this), node.body.accept(this)); |
1432 _visitNode(node.variables); | 1310 } |
1433 } | 1311 |
1434 out.write(";"); | 1312 @override |
1435 _visitNode(node.condition, prefix: " "); | 1313 visitBreakStatement(BreakStatement node) { |
1436 out.write(";"); | 1314 var label = node.label; |
1437 _visitNodeList(node.updaters, prefix: " ", separator: ", "); | 1315 return new JS.Break(label != null ? label.name : null); |
1438 out.write(") "); | 1316 } |
1439 _visitNode(node.body); | 1317 |
1440 } | 1318 @override |
1441 | 1319 visitContinueStatement(ContinueStatement node) { |
1442 @override | 1320 var label = node.label; |
1443 void visitWhileStatement(WhileStatement node) { | 1321 return new JS.Continue(label != null ? label.name : null); |
1444 out.write("while ("); | 1322 } |
1445 _visitNode(node.condition); | 1323 |
1446 out.write(") "); | 1324 @override |
1447 _visitNode(node.body); | 1325 visitTryStatement(TryStatement node) { |
1448 } | 1326 return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses), |
1449 | 1327 _visit(node.finallyBlock)); |
1450 @override | 1328 } |
1451 void visitDoStatement(DoStatement node) { | 1329 |
1452 out.write("do "); | 1330 _visitCatch(NodeList<CatchClause> clauses) { |
1453 _visitNode(node.body); | 1331 if (clauses == null || clauses.isEmpty) return null; |
1454 if (node.body is! Block) out.write(' '); | 1332 |
1455 out.write("while ("); | 1333 // TODO(jmesserly): need a better way to get a temporary variable. |
1456 _visitNode(node.condition); | 1334 // This could incorrectly shadow a user's name. |
1457 out.write(");\n"); | 1335 var name = '\$e'; |
1458 } | 1336 |
1459 | 1337 if (clauses.length == 1) { |
1460 @override | 1338 // Special case for a single catch. |
1461 void visitForEachStatement(ForEachStatement node) { | 1339 var clause = clauses.single; |
1462 out.write('for ('); | 1340 if (clause.exceptionParameter != null) { |
1463 if (node.loopVariable != null) { | 1341 name = clause.exceptionParameter.name; |
1464 _visitNode(node.loopVariable.identifier, prefix: 'let '); | 1342 } |
1465 } else { | 1343 } |
1466 _visitNode(node.identifier); | 1344 |
1467 } | 1345 var catchBody = _statement(clauses.map((c) => _visitCatchClause(c, name))); |
1468 out.write(' of '); | 1346 |
1469 _visitNode(node.iterable); | 1347 return new JS.Catch(new JS.VariableDeclaration(name), catchBody); |
1470 out.write(') '); | 1348 } |
1471 _visitNode(node.body); | 1349 |
1472 } | 1350 JS.Statement _statement(Iterable stmts) { |
1473 | 1351 var s = stmts is List ? stmts : new List<JS.Statement>.from(stmts); |
1474 @override | 1352 // TODO(jmesserly): empty block singleton? |
1475 void visitBreakStatement(BreakStatement node) { | 1353 if (s.length == 0) return new JS.Block([]); |
1476 out.write("break"); | 1354 if (s.length == 1) return s[0]; |
1477 _visitNode(node.label, prefix: " "); | 1355 return new JS.Block(s); |
1478 out.write(";\n"); | 1356 } |
1479 } | 1357 |
1480 | 1358 JS.Statement _visitCatchClause(CatchClause node, String varName) { |
1481 @override | 1359 var body = []; |
1482 void visitContinueStatement(ContinueStatement node) { | |
1483 out.write("continue"); | |
1484 _visitNode(node.label, prefix: " "); | |
1485 out.write(";\n"); | |
1486 } | |
1487 | |
1488 @override | |
1489 void visitTryStatement(TryStatement node) { | |
1490 out.write('try '); | |
1491 _visitNode(node.body); | |
1492 if (node.body is! Block) out.write(' '); | |
1493 | |
1494 var clauses = node.catchClauses; | |
1495 if (clauses != null && clauses.isNotEmpty) { | |
1496 // TODO(jmesserly): need a better way to get a temporary variable. | |
1497 // This could incorrectly shadow a user's name. | |
1498 var name = '\$e'; | |
1499 | |
1500 if (clauses.length == 1) { | |
1501 // Special case for a single catch. | |
1502 var clause = clauses.single; | |
1503 if (clause.exceptionParameter != null) { | |
1504 name = clause.exceptionParameter.name; | |
1505 } | |
1506 } | |
1507 | |
1508 out.write('catch ($name) {\n', 2); | |
1509 for (var clause in clauses) { | |
1510 _visitCatchClause(clause, name); | |
1511 } | |
1512 out.write('}\n', -2); | |
1513 } | |
1514 _visitNode(node.finallyBlock, prefix: 'finally '); | |
1515 } | |
1516 | |
1517 void _visitCatchClause(CatchClause node, String varName) { | |
1518 if (node.catchKeyword != null) { | 1360 if (node.catchKeyword != null) { |
1519 if (node.exceptionType != null) { | |
1520 out.write('if (dart.is($varName, '); | |
1521 _writeTypeName(node.exceptionType.type); | |
1522 out.write(')) {\n', 2); | |
1523 } | |
1524 | |
1525 var name = node.exceptionParameter; | 1361 var name = node.exceptionParameter; |
1526 if (name != null && name.name != varName) { | 1362 if (name != null && name.name != varName) { |
1527 out.write('let $name = $varName;\n'); | 1363 body.add(js.statement('let # = #;', [name.accept(this), varName])); |
1528 } | 1364 } |
1529 | |
1530 if (node.stackTraceParameter != null) { | 1365 if (node.stackTraceParameter != null) { |
1531 var stackName = node.stackTraceParameter.name; | 1366 var stackVar = node.stackTraceParameter.name; |
1532 out.write('let $stackName = dart.stackTrace($name);\n'); | 1367 body.add(js.statement( |
1533 } | 1368 'let # = dart.stackTrace(#);', [stackVar, name.accept(this)])); |
1534 } | 1369 } |
1535 | 1370 } |
1536 // If we can, avoid generating a nested { ... } block. | 1371 |
1537 var body = node.body; | 1372 body.add(node.body.accept(this)); |
1538 if (body is Block) { | 1373 |
1539 body.statements.accept(this); | 1374 if (node.exceptionType != null) { |
1375 return js.statement('if (dart.is(#, #)) #;', [ | |
1376 varName, | |
1377 _emitTypeName(node.exceptionType.type), | |
1378 _statement(body) | |
1379 ]); | |
1380 } | |
1381 return _statement(body); | |
1382 } | |
1383 | |
1384 @override | |
1385 JS.Case visitSwitchCase(SwitchCase node) { | |
1386 var expr = node.expression.accept(this); | |
1387 var body = _visitList(node.statements); | |
1388 if (node.labels.isNotEmpty) { | |
1389 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); | |
1390 } | |
1391 // TODO(jmesserly): make sure we are statically checking fall through | |
1392 return new JS.Case(expr, new JS.Block(body)); | |
1393 } | |
1394 | |
1395 @override | |
1396 JS.Default visitSwitchDefault(SwitchDefault node) { | |
1397 var body = _visitList(node.statements); | |
1398 if (node.labels.isNotEmpty) { | |
1399 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); | |
1400 } | |
1401 // TODO(jmesserly): make sure we are statically checking fall through | |
1402 return new JS.Default(new JS.Block(body)); | |
1403 } | |
1404 | |
1405 @override | |
1406 JS.Switch visitSwitchStatement(SwitchStatement node) => | |
1407 new JS.Switch(node.expression.accept(this), _visitList(node.members)); | |
1408 | |
1409 @override | |
1410 JS.Statement visitLabeledStatement(LabeledStatement node) { | |
1411 var result = _visit(node.statement); | |
1412 for (var label in node.labels.reversed) { | |
1413 result = new JS.LabeledStatement(label.label.name, result); | |
1414 } | |
1415 return result; | |
1416 } | |
1417 | |
1418 @override | |
1419 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); | |
1420 | |
1421 @override | |
1422 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); | |
1423 | |
1424 @override | |
1425 visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); | |
1426 | |
1427 @override | |
1428 visitListLiteral(ListLiteral node) { | |
1429 // TODO(jmesserly): make this faster. We're wasting an array. | |
1430 var list = js.call('new List.from(#)', [ | |
1431 new JS.ArrayInitializer(_visitList(node.elements)) | |
1432 ]); | |
1433 if (node.constKeyword != null) { | |
1434 list = js.commentExpression('Unimplemented const', list); | |
1435 } | |
1436 return list; | |
1437 } | |
1438 | |
1439 @override | |
1440 visitMapLiteral(MapLiteral node) { | |
1441 var entries = node.entries; | |
1442 var mapArguments = null; | |
1443 if (entries.isEmpty) return js.call('dart.map()'); | |
1444 | |
1445 // Use JS object literal notation if possible, otherwise use an array. | |
1446 if (entries.every((e) => e.key is SimpleStringLiteral)) { | |
1447 var props = []; | |
1448 for (var e in entries) { | |
1449 var key = (e.key as SimpleStringLiteral).value; | |
1450 var value = e.value.accept(this); | |
1451 props.add(new JS.Property(js.escapedString(key), value)); | |
1452 } | |
1453 mapArguments = new JS.ObjectInitializer(props); | |
1540 } else { | 1454 } else { |
1541 body.accept(this); | 1455 var values = []; |
1542 } | 1456 for (var e in entries) { |
1543 | 1457 values.add(e.key.accept(this)); |
1544 if (node.exceptionType != null) out.write('}\n', -2); | 1458 values.add(e.value.accept(this)); |
1545 } | 1459 } |
1546 | 1460 mapArguments = new JS.ArrayInitializer(values); |
1547 @override | 1461 } |
1548 void visitSwitchCase(SwitchCase node) { | 1462 return js.call('dart.map(#)', [mapArguments]); |
1549 _visitNodeList(node.labels, separator: " ", suffix: " "); | 1463 } |
1550 out.write("case "); | 1464 |
1551 _visitNode(node.expression); | 1465 @override |
1552 out.write(":\n", 2); | 1466 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
1553 _visitNodeList(node.statements); | 1467 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
1554 out.write('', -2); | 1468 |
1555 // TODO(jmesserly): make sure we are statically checking fall through | 1469 @override |
1556 } | 1470 JS.Expression visitAdjacentStrings(AdjacentStrings node) => |
1557 | 1471 _visitListToBinary(node.strings, '+'); |
1558 @override | 1472 |
1559 void visitSwitchDefault(SwitchDefault node) { | 1473 @override |
1560 _visitNodeList(node.labels, separator: " ", suffix: " "); | 1474 JS.TemplateString visitStringInterpolation(StringInterpolation node) { |
1561 out.write("default:\n", 2); | |
1562 _visitNodeList(node.statements); | |
1563 out.write('', -2); | |
1564 } | |
1565 | |
1566 @override | |
1567 void visitSwitchStatement(SwitchStatement node) { | |
1568 out.write("switch ("); | |
1569 _visitNode(node.expression); | |
1570 out.write(") {\n", 2); | |
1571 _visitNodeList(node.members); | |
1572 out.write("}\n", -2); | |
1573 } | |
1574 | |
1575 @override | |
1576 void visitLabel(Label node) { | |
1577 _visitNode(node.label); | |
1578 out.write(':'); | |
1579 } | |
1580 | |
1581 @override | |
1582 void visitLabeledStatement(LabeledStatement node) { | |
1583 _visitNodeList(node.labels, separator: " ", suffix: " "); | |
1584 _visitNode(node.statement); | |
1585 } | |
1586 | |
1587 @override | |
1588 void visitIntegerLiteral(IntegerLiteral node) { | |
1589 out.write('${node.value}'); | |
1590 } | |
1591 | |
1592 @override | |
1593 void visitDoubleLiteral(DoubleLiteral node) { | |
1594 out.write('${node.value}'); | |
1595 } | |
1596 | |
1597 @override | |
1598 void visitNullLiteral(NullLiteral node) { | |
1599 out.write('null'); | |
1600 } | |
1601 | |
1602 @override | |
1603 void visitListLiteral(ListLiteral node) { | |
1604 if (node.constKeyword != null) { | |
1605 out.write('/* Unimplemented const */'); | |
1606 } | |
1607 // TODO(jmesserly): make this faster. | |
1608 out.write('new List.from(['); | |
1609 _visitNodeList(node.elements, separator: ', '); | |
1610 out.write('])'); | |
1611 } | |
1612 | |
1613 @override | |
1614 void visitMapLiteral(MapLiteral node) { | |
1615 out.write('dart.map('); | |
1616 var entries = node.entries; | |
1617 if (entries != null && entries.isNotEmpty) { | |
1618 // Use JS object literal notation if possible, otherwise use an array. | |
1619 if (entries.every((e) => e.key is SimpleStringLiteral)) { | |
1620 out.write('{\n', 2); | |
1621 _visitMapLiteralEntries(entries, separator: ': '); | |
1622 out.write('\n}', -2); | |
1623 } else { | |
1624 out.write('[\n', 2); | |
1625 _visitMapLiteralEntries(entries, separator: ', '); | |
1626 out.write('\n]', -2); | |
1627 } | |
1628 } | |
1629 out.write(')'); | |
1630 } | |
1631 | |
1632 void _visitMapLiteralEntries(NodeList<MapLiteralEntry> nodes, | |
1633 {String separator}) { | |
1634 if (nodes == null) return; | |
1635 int size = nodes.length; | |
1636 if (size == 0) return; | |
1637 | |
1638 for (int i = 0; i < size; i++) { | |
1639 if (i > 0) out.write(',\n'); | |
1640 var node = nodes[i]; | |
1641 node.key.accept(this); | |
1642 out.write(separator); | |
1643 node.value.accept(this); | |
1644 } | |
1645 } | |
1646 | |
1647 @override | |
1648 void visitSimpleStringLiteral(SimpleStringLiteral node) { | |
1649 if (node.isSingleQuoted) { | |
1650 var escaped = _escapeForJs(node.stringValue, "'"); | |
1651 out.write("'$escaped'"); | |
1652 } else { | |
1653 var escaped = _escapeForJs(node.stringValue, '"'); | |
1654 out.write('"$escaped"'); | |
1655 } | |
1656 } | |
1657 | |
1658 @override | |
1659 void visitAdjacentStrings(AdjacentStrings node) { | |
1660 // These are typically used for splitting long strings across lines, so | |
1661 // generate accordingly, with each on its own line and +4 indent. | |
1662 | |
1663 // TODO(jmesserly): we could linebreak before the first string too, but | |
1664 // that means inserting a linebreak in expression context, which might | |
1665 // not be valid and leaves trailing whitespace. | |
1666 for (int i = 0, last = node.strings.length - 1; i <= last; i++) { | |
1667 if (i == 1) { | |
1668 out.write(' +\n', 4); | |
1669 } else if (i > 1) { | |
1670 out.write(' +\n'); | |
1671 } | |
1672 node.strings[i].accept(this); | |
1673 if (i == last && i > 0) { | |
1674 out.write('', -4); | |
1675 } | |
1676 } | |
1677 } | |
1678 | |
1679 @override | |
1680 void visitStringInterpolation(StringInterpolation node) { | |
1681 out.write('`'); | |
1682 _visitNodeList(node.elements); | |
1683 out.write('`'); | |
1684 } | |
1685 | |
1686 @override | |
1687 void visitInterpolationString(InterpolationString node) { | |
1688 out.write(_escapeForJs(node.value, '`')); | |
1689 } | |
1690 | |
1691 /// Escapes the string from [value], handling escape sequences as needed. | |
1692 /// The surrounding [quote] style must be supplied to know which quotes to | |
1693 /// escape, but quotes are not added to the resulting string. | |
1694 String _escapeForJs(String value, String quote) { | |
1695 // Start by escaping the backslashes. | |
1696 String escaped = value.replaceAll('\\', '\\\\'); | |
1697 // Do not escape unicode characters and ' because they are allowed in the | |
1698 // string literal anyway. | |
1699 return escaped.replaceAllMapped(new RegExp('\n|$quote|\b|\t|\v'), (m) { | |
1700 switch (m.group(0)) { | |
1701 case "\n": | |
1702 return r"\n"; | |
1703 case "\b": | |
1704 return r"\b"; | |
1705 case "\t": | |
1706 return r"\t"; | |
1707 case "\f": | |
1708 return r"\f"; | |
1709 case "\v": | |
1710 return r"\v"; | |
1711 // Quotes are only replaced if they conflict with the containing quote | |
1712 case '"': | |
1713 return r'\"'; | |
1714 case "'": | |
1715 return r"\'"; | |
1716 case "`": | |
1717 return r"\`"; | |
1718 } | |
1719 }); | |
1720 } | |
1721 | |
1722 @override | |
1723 void visitInterpolationExpression(InterpolationExpression node) { | |
1724 out.write('\${'); | |
1725 node.expression.accept(this); | |
1726 // Assuming we implement toString() on our objects, we can avoid calling it | 1475 // Assuming we implement toString() on our objects, we can avoid calling it |
1727 // in most cases. Builtin types may differ though. | 1476 // in most cases. Builtin types may differ though. We could handle this with |
1728 // For example, Dart's concrete List type does not have the same toString | 1477 // a tagged template. |
1729 // as Array.prototype.toString(). | 1478 return new JS.TemplateString(_visitList(node.elements)); |
1730 out.write('}'); | 1479 } |
1731 } | 1480 |
1732 | 1481 @override |
1733 @override | 1482 String visitInterpolationString(InterpolationString node) { |
1734 void visitBooleanLiteral(BooleanLiteral node) { | 1483 // TODO(jmesserly): this call adds quotes, and then we strip them off. |
1735 out.write('${node.value}'); | 1484 var str = js.escapedString(node.value, '`').value; |
1736 } | 1485 return str.substring(1, str.length - 1); |
1737 | 1486 } |
1738 @override | 1487 |
1739 void visitDirective(Directive node) {} | 1488 @override |
1740 | 1489 visitInterpolationExpression(InterpolationExpression node) => |
1741 @override | 1490 node.expression.accept(this); |
1742 void visitNode(AstNode node) { | 1491 |
1743 out.write('/* Unimplemented ${node.runtimeType}: $node */'); | 1492 @override |
1493 visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value); | |
1494 | |
1495 @override | |
1496 JS.Statement visitDeclaration(Declaration node) => | |
1497 js.comment('Unimplemented ${node.runtimeType}: $node'); | |
1498 | |
1499 @override | |
1500 JS.Statement visitStatement(Statement node) => | |
1501 js.comment('Unimplemented ${node.runtimeType}: $node'); | |
1502 | |
1503 @override | |
1504 JS.Expression visitExpression(Expression node) => | |
1505 _unimplementedCall('Unimplemented ${node.runtimeType}: $node'); | |
1506 | |
1507 JS.Expression _unimplementedCall(String comment) { | |
1508 return js.call('dart.throw_(#)', [js.escapedString(comment)]); | |
1509 } | |
1510 | |
1511 @override | |
1512 visitNode(AstNode node) { | |
1513 // TODO(jmesserly): verify this is unreachable. | |
1514 throw 'Unimplemented ${node.runtimeType}: $node'; | |
1744 } | 1515 } |
1745 | 1516 |
1746 // TODO(jmesserly): this is used to determine if the field initialization is | 1517 // TODO(jmesserly): this is used to determine if the field initialization is |
1747 // side effect free. We should make the check more general, as things like | 1518 // side effect free. We should make the check more general, as things like |
1748 // list/map literals/regexp are also side effect free and fairly common | 1519 // list/map literals/regexp are also side effect free and fairly common |
1749 // to use as field initializers. | 1520 // to use as field initializers. |
1750 bool _isFieldInitConstant(VariableDeclaration field) => | 1521 bool _isFieldInitConstant(VariableDeclaration field) => |
1751 field.initializer == null || _computeConstant(field).isValid; | 1522 field.initializer == null || _computeConstant(field).isValid; |
1752 | 1523 |
1753 EvaluationResult _computeConstant(VariableDeclaration field) { | 1524 EvaluationResult _computeConstant(VariableDeclaration field) { |
(...skipping 14 matching lines...) Expand all Loading... | |
1768 | 1539 |
1769 /// Returns true if [element] is a getter in JS, therefore needs | 1540 /// Returns true if [element] is a getter in JS, therefore needs |
1770 /// `lib.topLevel` syntax instead of just `topLevel`. | 1541 /// `lib.topLevel` syntax instead of just `topLevel`. |
1771 bool _needsModuleGetter(Element element) { | 1542 bool _needsModuleGetter(Element element) { |
1772 if (element is PropertyAccessorElement) { | 1543 if (element is PropertyAccessorElement) { |
1773 element = (element as PropertyAccessorElement).variable; | 1544 element = (element as PropertyAccessorElement).variable; |
1774 } | 1545 } |
1775 return element is TopLevelVariableElement && !element.isConst; | 1546 return element is TopLevelVariableElement && !element.isConst; |
1776 } | 1547 } |
1777 | 1548 |
1778 /// Safely visit the expression, adding parentheses if necessary | 1549 _visit(AstNode node) => node != null ? node.accept(this) : null; |
1779 void _visitExpression(Expression node, int newPrecedence) { | |
1780 if (node == null) return; | |
1781 | 1550 |
1782 // If we're going to replace an expression with a higher-precedence | 1551 JS.Statement _visitOrEmpty(AstNode node) { |
1783 // operator, add parenthesis around it if needed. | 1552 if (node == null) return new JS.EmptyStatement(); |
1784 var needParens = node.precedence < newPrecedence; | 1553 return node.accept(this); |
1785 if (needParens) out.write('('); | |
1786 node.accept(this); | |
1787 if (needParens) out.write(')'); | |
1788 } | 1554 } |
1789 | 1555 |
1790 /// Safely visit the given node, with an optional prefix or suffix. | 1556 List _visitList(Iterable<AstNode> nodes) { |
1791 void _visitNode(AstNode node, {String prefix: '', String suffix: ''}) { | 1557 if (nodes == null) return null; |
1792 if (node == null) return; | 1558 var result = []; |
1793 | 1559 for (var node in nodes) result.add(node.accept(this)); |
1794 out.write(prefix); | 1560 return result; |
1795 node.accept(this); | |
1796 out.write(suffix); | |
1797 } | 1561 } |
1798 | 1562 |
1799 /// Print a list of nodes, with an optional prefix, suffix, and separator. | 1563 /// Visits a list of expressions, creating a comma expression if needed in JS. |
1800 void _visitNodeList(List<AstNode> nodes, | 1564 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { |
1801 {String prefix: '', String suffix: '', String separator: ''}) { | 1565 if (nodes == null || nodes.isEmpty) return null; |
1802 if (nodes == null) return; | |
1803 | 1566 |
1804 int size = nodes.length; | 1567 JS.Expression result = null; |
1805 if (size == 0) return; | 1568 for (var node in nodes) { |
1806 | 1569 var jsExpr = node.accept(this); |
1807 out.write(prefix); | 1570 if (result == null) { |
1808 for (int i = 0; i < size; i++) { | 1571 result = jsExpr; |
1809 if (i > 0) out.write(separator); | 1572 } else { |
1810 nodes[i].accept(this); | 1573 result = new JS.Binary(operator, result, jsExpr); |
1574 } | |
1811 } | 1575 } |
1812 out.write(suffix); | 1576 return result; |
1813 } | |
1814 | |
1815 /// Safely visit the given node, printing the suffix after the node if it is | |
1816 /// non-`null`. | |
1817 void visitToken(Token token, {String prefix: '', String suffix: ''}) { | |
1818 if (token == null) return; | |
1819 out.write(prefix); | |
1820 out.write(token.lexeme); | |
1821 out.write(suffix); | |
1822 } | 1577 } |
1823 | 1578 |
1824 /// The following names are allowed for user-defined operators: | 1579 /// The following names are allowed for user-defined operators: |
1825 /// | 1580 /// |
1826 /// <, >, <=, >=, ==, -, +, /, ˜/, *, %, |, ˆ, &, <<, >>, []=, [], ˜ | 1581 /// <, >, <=, >=, ==, -, +, /, ˜/, *, %, |, ˆ, &, <<, >>, []=, [], ˜ |
1827 /// | 1582 /// |
1828 /// For the indexing operators, we use `get` and `set` instead: | 1583 /// For the indexing operators, we use `get` and `set` instead: |
1829 /// | 1584 /// |
1830 /// x.get('hi') | 1585 /// x.get('hi') |
1831 /// x.set('hi', 123) | 1586 /// x.set('hi', 123) |
1832 /// | 1587 /// |
1833 /// This follows the same pattern as EcmaScript 6 Map: | 1588 /// This follows the same pattern as EcmaScript 6 Map: |
1834 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map> | 1589 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map> |
1835 /// | 1590 /// |
1836 /// For all others we use the operator name: | 1591 /// For all others we use the operator name: |
1837 /// | 1592 /// |
1838 /// x['+'](y) | 1593 /// x['+'](y) |
1839 /// | 1594 /// |
1840 /// Equality is a bit special, it is generated via the Dart `equals` runtime | 1595 /// Equality is a bit special, it is generated via the Dart `equals` runtime |
1841 /// helper, that checks for null. The user defined method is called '=='. | 1596 /// helper, that checks for null. The user defined method is called '=='. |
1842 String _canonicalMethodName(String name) { | 1597 String _jsMethodName(String name) { |
1843 switch (name) { | 1598 if (name == '[]') return 'get'; |
1844 case '[]': | 1599 if (name == '[]=') return 'set'; |
1845 return 'get'; | 1600 return name; |
1846 case '[]=': | |
1847 return 'set'; | |
1848 case '<': | |
1849 case '>': | |
1850 case '<=': | |
1851 case '>=': | |
1852 case '==': | |
1853 case '-': | |
1854 case '+': | |
1855 case '/': | |
1856 case '~/': | |
1857 case '*': | |
1858 case '%': | |
1859 case '|': | |
1860 case '^': | |
1861 case '&': | |
1862 case '<<': | |
1863 case '>>': | |
1864 case '~': | |
1865 return "['$name']"; | |
1866 default: | |
1867 return name; | |
1868 } | |
1869 } | 1601 } |
1870 | 1602 |
1871 /// The string to invoke a canonical method name, for example: | 1603 String _maybeBindThis(node) { |
1872 /// | 1604 if (currentClass == null) return ''; |
1873 /// "[]" returns ".get" | |
1874 /// "+" returns "['+']" | |
1875 String _canonicalMethodInvoke(String name) { | |
1876 name = _canonicalMethodName(name); | |
1877 return name.startsWith('[') ? name : '.$name'; | |
1878 } | |
1879 | |
1880 bool _needsBindThis(node) { | |
1881 if (currentClass == null) return false; | |
1882 var visitor = _BindThisVisitor._instance; | 1605 var visitor = _BindThisVisitor._instance; |
1883 visitor._bindThis = false; | 1606 visitor._bindThis = false; |
1884 node.accept(visitor); | 1607 node.accept(visitor); |
1885 return visitor._bindThis; | 1608 return visitor._bindThis ? '.bind(this)' : ''; |
1886 } | 1609 } |
1887 | 1610 |
1888 static bool _needsImplicitThis(Element e) => | 1611 static bool _needsImplicitThis(Element e) => |
1889 e is PropertyAccessorElement && !e.variable.isStatic || | 1612 e is PropertyAccessorElement && !e.variable.isStatic || |
1890 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; | 1613 e is ClassMemberElement && !e.isStatic && e is! ConstructorElement; |
1891 } | 1614 } |
1892 | 1615 |
1893 /// Returns true if the local variable is potentially mutated within [context]. | 1616 /// Returns true if the local variable is potentially mutated within [context]. |
1894 /// This accounts for closures that may have been created outside of [context]. | 1617 /// This accounts for closures that may have been created outside of [context]. |
1895 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { | 1618 bool _isPotentiallyMutated(VariableElementImpl e, [AstNode context]) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1957 _bindThis = true; | 1680 _bindThis = true; |
1958 } | 1681 } |
1959 } | 1682 } |
1960 | 1683 |
1961 class JSGenerator extends CodeGenerator { | 1684 class JSGenerator extends CodeGenerator { |
1962 JSGenerator(String outDir, Uri root, TypeRules rules) | 1685 JSGenerator(String outDir, Uri root, TypeRules rules) |
1963 : super(outDir, root, rules); | 1686 : super(outDir, root, rules); |
1964 | 1687 |
1965 void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info, | 1688 void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info, |
1966 CheckerReporter reporter) { | 1689 CheckerReporter reporter) { |
1690 JS.Block jsTree = | |
1691 new JSCodegenVisitor(info, rules).generateLibrary(units, reporter); | |
1692 | |
1967 var outputPath = path.join(outDir, jsOutputPath(info)); | 1693 var outputPath = path.join(outDir, jsOutputPath(info)); |
1968 new Directory(path.dirname(outputPath)).createSync(recursive: true); | 1694 new Directory(path.dirname(outputPath)).createSync(recursive: true); |
1969 var out = new OutWriter(outputPath); | 1695 |
1970 new JSCodegenVisitor(info, rules, out).generateLibrary(units, reporter); | 1696 var context = new JS.SimpleJavaScriptPrintingContext(); |
1971 out.close(); | 1697 var opts = |
1698 new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true); | |
1699 var printer = new JS.Printer(opts, context); | |
1700 printer.blockOutWithoutBraces(jsTree); | |
1701 new File(outputPath).writeAsStringSync(context.getText()); | |
1972 } | 1702 } |
1973 } | 1703 } |
1974 | 1704 |
1975 /// Choose a canonical name from the library element. | 1705 /// Choose a canonical name from the library element. |
1976 /// This never uses the library's name (the identifier in the `library` | 1706 /// This never uses the library's name (the identifier in the `library` |
1977 /// declaration) as it doesn't have any meaningful rules enforced. | 1707 /// declaration) as it doesn't have any meaningful rules enforced. |
1978 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); | 1708 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
1979 | 1709 |
1980 /// Path to file that will be generated for [info]. | 1710 /// Path to file that will be generated for [info]. |
1981 // TODO(jmesserly): library directory should be relative to its package | 1711 // TODO(jmesserly): library directory should be relative to its package |
1982 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b e: | 1712 // root. For example, "package:dev_compiler/src/codegen/js_codegen.dart" would b e: |
1983 // "ddc/src/codegen/js_codegen.js" under the output directory. | 1713 // "ddc/src/codegen/js_codegen.js" under the output directory. |
1984 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; | 1714 String jsOutputPath(LibraryInfo info) => '${info.name}/${info.name}.js'; |
OLD | NEW |