Index: lib/src/codegen/js_codegen.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
index bd74796a10f24f925584dc85cfa76fe2054ae7c9..aa3bb2c53edd7c6b2d95275c59dedf8181b045c0 100644 |
--- a/lib/src/codegen/js_codegen.dart |
+++ b/lib/src/codegen/js_codegen.dart |
@@ -4,7 +4,7 @@ |
library ddc.src.codegen.js_codegen; |
-import 'dart:io' show Directory; |
+import 'dart:io' show Directory, File; |
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
@@ -14,6 +14,10 @@ import 'package:analyzer/src/generated/scanner.dart' |
show StringToken, Token, TokenType; |
import 'package:path/path.dart' as path; |
+// TODO(jmesserly): import from its own package |
+import 'package:dev_compiler/src/js/js_ast.dart' as JS; |
+import 'package:dev_compiler/src/js/js_ast.dart' show js; |
+ |
import 'package:dev_compiler/src/checker/rules.dart'; |
import 'package:dev_compiler/src/info.dart'; |
import 'package:dev_compiler/src/report.dart'; |
@@ -21,12 +25,11 @@ import 'package:dev_compiler/src/utils.dart'; |
import 'code_generator.dart'; |
// This must match the optional parameter name used in runtime.js |
-const String optionalParameters = r'opt$'; |
+const String _jsNamedParameterName = r'opt$'; |
class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
final LibraryInfo libraryInfo; |
final TypeRules rules; |
- final OutWriter out; |
final String _libraryName; |
/// The variable for the target of the current `..` cascade expression. |
@@ -39,72 +42,69 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
final _lazyFields = <VariableDeclaration>[]; |
final _properties = <FunctionDeclaration>[]; |
- static final int _indexExpressionPrecedence = |
- new IndexExpression.forTarget(null, null, null, null).precedence; |
- |
- static final int _prefixExpressionPrecedence = |
- new PrefixExpression(null, null).precedence; |
- |
- JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules, this.out) |
+ JSCodegenVisitor(LibraryInfo libraryInfo, TypeRules rules) |
: libraryInfo = libraryInfo, |
rules = rules, |
_libraryName = jsLibraryName(libraryInfo.library); |
Element get currentLibrary => libraryInfo.library; |
- void generateLibrary( |
+ JS.Block generateLibrary( |
Iterable<CompilationUnit> units, CheckerReporter reporter) { |
- out.write(""" |
-var $_libraryName; |
-(function ($_libraryName) { |
- 'use strict'; |
-""", 2); |
- |
+ var body = <JS.Statement>[]; |
for (var unit in units) { |
// TODO(jmesserly): this is needed because RestrictedTypeRules can send |
// messages to CheckerReporter, for things like missing types. |
// We should probably refactor so this can't happen. |
- |
var source = unit.element.source; |
_constEvaluator = new ConstantEvaluator(source, rules.provider); |
reporter.enterSource(source); |
- unit.accept(this); |
+ body.add(unit.accept(this)); |
reporter.leaveSource(); |
} |
- if (_exports.isNotEmpty) out.write('// Exports:\n'); |
+ if (_exports.isNotEmpty) body.add(js.comment('Exports:')); |
// TODO(jmesserly): make these immutable in JS? |
for (var name in _exports) { |
- out.write('${_libraryName}.$name = $name;\n'); |
+ body.add(js.statement('#.# = #;', [_libraryName, name, name])); |
} |
- out.write(""" |
-})($_libraryName || ($_libraryName = {})); |
-""", -2); |
+ var name = _libraryName; |
+ return new JS.Block([ |
+ js.statement('var #;', name), |
+ js.statement(''' |
+ (function (#) { |
+ 'use strict'; |
+ #; |
+ })(# || (# = {})); |
+ ''', [name, body, name, name]) |
+ ]); |
} |
@override |
- void visitCompilationUnit(CompilationUnit node) { |
- _visitNode(node.scriptTag); |
- _visitNodeList(node.directives); |
+ JS.Statement visitCompilationUnit(CompilationUnit node) { |
+ // TODO(jmesserly): scriptTag, directives. |
+ var body = <JS.Statement>[]; |
for (var child in node.declarations) { |
// Attempt to group adjacent fields/properties. |
- if (child is! TopLevelVariableDeclaration) _flushLazyFields(); |
- if (child is! FunctionDeclaration) _flushLibraryProperties(); |
+ if (child is! TopLevelVariableDeclaration) _flushLazyFields(body); |
+ if (child is! FunctionDeclaration) _flushLibraryProperties(body); |
- child.accept(this); |
+ var code = child.accept(this); |
+ if (code != null) body.add(code); |
} |
// Flush any unwritten fields/properties. |
- _flushLazyFields(); |
- _flushLibraryProperties(); |
+ _flushLazyFields(body); |
+ _flushLibraryProperties(body); |
+ return _statement(body); |
} |
bool isPublic(String name) => !name.startsWith('_'); |
/// Conversions that we don't handle end up here. |
@override |
- void visitConversion(Conversion node) { |
+ visitConversion(Conversion node) { |
var from = node.baseType; |
var to = node.convertedType; |
@@ -118,56 +118,41 @@ var $_libraryName; |
if (!rules.isNonNullableType(from) && rules.isNonNullableType(to)) { |
// Converting from a nullable number to a non-nullable number |
// only requires a null check. |
- out.write('dart.notNull('); |
- node.expression.accept(this); |
- out.write(')'); |
+ return js.call('dart.notNull(#)', node.expression.accept(this)); |
} else { |
// A no-op in JavaScript. |
- node.expression.accept(this); |
+ return node.expression.accept(this); |
} |
- return; |
} |
- _writeCast(node.expression, to); |
+ return _emitCast(node.expression, to); |
} |
@override |
- void visitAsExpression(AsExpression node) { |
- _writeCast(node.expression, node.type.type); |
- } |
+ visitAsExpression(AsExpression node) => |
+ _emitCast(node.expression, node.type.type); |
- void _writeCast(Expression node, DartType type) { |
- out.write('dart.as('); |
- node.accept(this); |
- out.write(', '); |
- _writeTypeName(type); |
- out.write(')'); |
- } |
+ _emitCast(Expression node, DartType type) => |
+ js.call('dart.as(#)', [[node.accept(this), _emitTypeName(type)]]); |
@override |
- void visitIsExpression(IsExpression node) { |
- // Generate `is` as `instanceof` or `typeof` depending on the RHS type. |
- if (node.notOperator != null) out.write('!'); |
- |
+ visitIsExpression(IsExpression node) { |
+ // Generate `is` as `dart.is` or `typeof` depending on the RHS type. |
+ JS.Expression result; |
var type = node.type.type; |
- var lhs = node.expression; |
+ var lhs = node.expression.accept(this); |
var typeofName = _jsTypeofName(type); |
if (typeofName != null) { |
- if (node.notOperator != null) out.write('('); |
- out.write('typeof '); |
- // We're going to replace the `is` operator with higher-precedence prefix |
- // `typeof` operator, so add parens around the left side if necessary. |
- _visitExpression(lhs, _prefixExpressionPrecedence); |
- out.write(' == "$typeofName"'); |
- if (node.notOperator != null) out.write(')'); |
+ result = js.call('typeof # == #', [lhs, typeofName]); |
} else { |
// Always go through a runtime helper, because implicit interfaces. |
- out.write('dart.is('); |
- lhs.accept(this); |
- out.write(', '); |
- _writeTypeName(type); |
- out.write(')'); |
+ result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]); |
} |
+ |
+ if (node.notOperator != null) { |
+ return js.call('!#', result); |
+ } |
+ return result; |
} |
String _jsTypeofName(DartType t) { |
@@ -178,93 +163,64 @@ var $_libraryName; |
} |
@override |
- void visitFunctionTypeAlias(FunctionTypeAlias node) { |
+ visitFunctionTypeAlias(FunctionTypeAlias node) { |
// TODO(vsm): Do we need to record type info the generated code for a |
// typedef? |
} |
@override |
- void visitTypeName(TypeName node) { |
Jennifer Messerly
2015/02/25 18:36:20
types used to be handled in two ways, here and _wr
|
- _visitNode(node.name); |
- _visitNode(node.typeArguments); |
- } |
- |
- @override |
- void visitTypeArgumentList(TypeArgumentList node) { |
- out.write('\$('); |
- _visitNodeList(node.arguments, separator: ', '); |
- out.write(')'); |
- } |
+ JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); |
@override |
- void visitClassTypeAlias(ClassTypeAlias node) { |
+ JS.Statement visitClassTypeAlias(ClassTypeAlias node) { |
var name = node.name.name; |
- _beginTypeParameters(node.typeParameters, name); |
- out.write('class $name extends dart.mixin('); |
- _visitNodeList(node.withClause.mixinTypes, separator: ', '); |
- out.write(') {}\n\n'); |
- _endTypeParameters(node.typeParameters, name); |
+ var heritage = |
+ js.call('dart.mixin(#)', [_visitList(node.withClause.mixinTypes)]); |
+ var classDecl = new JS.ClassDeclaration( |
+ new JS.ClassExpression(new JS.VariableDeclaration(name), heritage, [])); |
if (isPublic(name)) _exports.add(name); |
+ return _addTypeParameters(node.typeParameters, name, classDecl); |
} |
@override |
- void visitTypeParameter(TypeParameter node) { |
- out.write(node.name.name); |
- } |
+ visitTypeParameter(TypeParameter node) => new JS.Parameter(node.name.name); |
- void _beginTypeParameters(TypeParameterList node, String name) { |
- if (node == null) return; |
- out.write('let ${name}\$ = dart.generic(function('); |
- _visitNodeList(node.typeParameters, separator: ', '); |
- out.write(') {\n', 2); |
- } |
+ JS.Statement _addTypeParameters( |
+ TypeParameterList node, String name, JS.Statement clazz) { |
+ if (node == null) return clazz; |
+ |
+ var genericName = '$name\$'; |
+ var genericDef = js.statement( |
+ 'let # = dart.generic(function(#) { #; return #; });', [ |
+ genericName, |
+ _visitList(node.typeParameters), |
+ clazz, |
+ name |
+ ]); |
+ |
+ // TODO(jmesserly): we may not want this to be `dynamic` if the generic |
+ // has a lower bound, e.g. `<T extends SomeType>`. |
+ // https://github.com/dart-lang/dart-dev-compiler/issues/38 |
+ var typeArgs = new List.filled(node.typeParameters.length, 'dynamic'); |
+ |
+ var dynInst = js.statement('let # = #(#);', [name, genericName, typeArgs]); |
- void _endTypeParameters(TypeParameterList node, String name) { |
- if (node == null) return; |
- // Return the specialized class. |
- out.write('return $name;\n'); |
- out.write('});\n', -2); |
- // Construct the "default" version of the generic type for easy interop. |
- out.write('let $name = ${name}\$('); |
- for (int i = 0, len = node.typeParameters.length; i < len; i++) { |
- if (i > 0) out.write(', '); |
- // TODO(jmesserly): we may not want this to be `dynamic` if the generic |
- // has a lower bound, e.g. `<T extends SomeType>`. |
- // https://github.com/dart-lang/dart-dev-compiler/issues/38 |
- out.write('dynamic'); |
- } |
- out.write(');\n'); |
// TODO(jmesserly): is it worth exporting both names? Alternatively we could |
// put the generic type constructor on the <dynamic> instance. |
if (isPublic(name)) _exports.add('${name}\$'); |
+ return new JS.Block([genericDef, dynInst]); |
} |
@override |
- void visitClassDeclaration(ClassDeclaration node) { |
+ JS.Statement visitClassDeclaration(ClassDeclaration node) { |
currentClass = node; |
- var name = node.name.name; |
- _beginTypeParameters(node.typeParameters, name); |
- out.write('class $name extends '); |
- |
- if (node.withClause != null) { |
- out.write('dart.mixin('); |
- } |
- if (node.extendsClause != null) { |
- _visitNode(node.extendsClause.superclass); |
- } else { |
- out.write('dart.Object'); |
- } |
- if (node.withClause != null) { |
- _visitNodeList(node.withClause.mixinTypes, prefix: ', ', separator: ', '); |
- out.write(')'); |
- } |
- |
- out.write(' {\n', 2); |
+ var body = <JS.Statement>[]; |
- var ctors = new List<ConstructorDeclaration>(); |
- var fields = new List<FieldDeclaration>(); |
- var staticFields = new List<FieldDeclaration>(); |
+ var name = node.name.name; |
+ var ctors = <ConstructorDeclaration>[]; |
+ var fields = <FieldDeclaration>[]; |
+ var staticFields = <FieldDeclaration>[]; |
for (var member in node.members) { |
if (member is ConstructorDeclaration) { |
ctors.add(member); |
@@ -273,29 +229,61 @@ var $_libraryName; |
} |
} |
+ var jsMethods = <JS.Method>[]; |
// Iff no constructor is specified for a class C, it implicitly has a |
// default constructor `C() : super() {}`, unless C is class Object. |
if (ctors.isEmpty && !node.element.type.isObject) { |
- _generateImplicitConstructor(node, name, fields); |
+ jsMethods.add(_emitImplicitConstructor(node, name, fields)); |
} |
for (var member in node.members) { |
if (member is ConstructorDeclaration) { |
- _generateConstructor(member, name, fields); |
+ jsMethods.add(_emitConstructor(member, name, fields)); |
} else if (member is MethodDeclaration) { |
- member.accept(this); |
+ jsMethods.add(member.accept(this)); |
} |
} |
- out.write('}\n', -2); |
+ // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
+ // This lets them use for-of loops transparently. |
+ // https://github.com/lukehoban/es6features#iterators--forof |
+ if (node.element.library.isDartCore && node.element.name == 'Iterable') { |
+ JS.Fun body = js.call('''function() { |
+ var iterator = this.iterator; |
+ return { |
+ next() { |
+ var done = iterator.moveNext(); |
+ return { done: done, current: done ? void 0 : iterator.current }; |
+ } |
+ }; |
+ }'''); |
+ jsMethods.add(new JS.Method(js.call('Symbol.iterator'), body)); |
+ } |
+ |
+ JS.Expression heritage; |
+ if (node.extendsClause != null) { |
+ heritage = _visit(node.extendsClause.superclass); |
+ } else { |
+ heritage = js.call('dart.Object'); |
+ } |
+ if (node.withClause != null) { |
+ var mixins = _visitList(node.withClause.mixinTypes); |
+ mixins.insert(0, heritage); |
+ heritage = js.call('dart.mixin(#)', [mixins]); |
+ } |
+ body.add(new JS.ClassDeclaration(new JS.ClassExpression( |
+ new JS.VariableDeclaration(name), heritage, |
+ jsMethods.where((m) => m != null).toList(growable: false)))); |
if (isPublic(name)) _exports.add(name); |
// Named constructors |
for (ConstructorDeclaration member in ctors) { |
if (member.name != null) { |
- var ctorName = member.name.name; |
- out.write('dart.defineNamedConstructor($name, "$ctorName");\n'); |
+ body.add(js.statement('dart.defineNamedConstructor(#, #);', [ |
+ name, |
+ js.string(member.name.name, "'") |
+ ])); |
} |
} |
@@ -303,96 +291,72 @@ var $_libraryName; |
var lazyStatics = <VariableDeclaration>[]; |
for (FieldDeclaration member in staticFields) { |
for (VariableDeclaration field in member.fields.variables) { |
- var prefix = '$name.${field.name.name}'; |
- if (field.initializer == null) { |
- out.write('$prefix = null;\n'); |
- } else if (field.isConst || _isFieldInitConstant(field)) { |
- out.write('$prefix = '); |
- field.initializer.accept(this); |
- out.write(';\n'); |
+ var fieldName = field.name.name; |
+ if (field.isConst || _isFieldInitConstant(field)) { |
+ var init = _visit(field.initializer); |
+ if (init == null) init = new JS.LiteralNull(); |
+ body.add(js.statement('#.# = #;', [name, fieldName, init])); |
} else { |
lazyStatics.add(field); |
} |
} |
} |
- _writeLazyFields(name, lazyStatics); |
- |
- // Support for adapting dart:core Iterator/Iterable to ES6 versions. |
- // This lets them use for-of loops transparently. |
- // https://github.com/lukehoban/es6features#iterators--forof |
- // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-iterable-interface |
- // TODO(jmesserly): put this straight in the class as an instance method, |
- // once V8 supports symbols for method names: `[Symbol.iterator]() { ... }`. |
- if (node.element.library.isDartCore && node.element.name == 'Iterable') { |
- out.write(''' |
-$name.prototype[Symbol.iterator] = function() { |
- var iterator = this.iterator; |
- return { |
- next: function() { |
- var done = iterator.moveNext(); |
- return { done: done, current: done ? void 0 : iterator.current }; |
- } |
- }; |
-}; |
-'''); |
- } |
+ var lazy = _emitLazyFields(name, lazyStatics); |
+ if (lazy != null) body.add(lazy); |
- _endTypeParameters(node.typeParameters, name); |
- |
- out.write('\n'); |
currentClass = null; |
+ return _addTypeParameters(node.typeParameters, name, _statement(body)); |
} |
/// Generates the implicit default constructor for class C of the form |
/// `C() : super() {}`. |
- void _generateImplicitConstructor( |
+ JS.Method _emitImplicitConstructor( |
ClassDeclaration node, String name, List<FieldDeclaration> fields) { |
// If we don't have a method body, skip this. |
- if (fields.isEmpty) return; |
+ if (fields.isEmpty) return null; |
- out.write('$name() {\n', 2); |
- _initializeFields(fields); |
- _superConstructorCall(node); |
- out.write('}\n', -2); |
+ var body = _initializeFields(fields); |
+ var superCall = _superConstructorCall(node); |
+ if (superCall != null) body = [[body, superCall]]; |
+ return new JS.Method( |
+ new JS.PropertyName(name), js.call('function() { #; }', body)); |
} |
- void _generateConstructor(ConstructorDeclaration node, String className, |
+ JS.Method _emitConstructor(ConstructorDeclaration node, String className, |
List<FieldDeclaration> fields) { |
- if (node.externalKeyword != null) { |
- out.write('/* Unimplemented $node */\n'); |
- return; |
- } |
+ if (node.externalKeyword != null) return null; |
+ |
+ var name = _constructorName(className, node.name); |
// We generate constructors as initializer methods in the class; |
// this allows use of `super` for instance methods/properties. |
// It also avoids V8 restrictions on `super` in default constructors. |
- out.write(className); |
- if (node.name != null) { |
- out.write('\$${node.name.name}'); |
- } |
- out.write('('); |
- _visitNode(node.parameters); |
- out.write(') {\n', 2); |
- _generateConstructorBody(node, fields); |
- out.write('}\n', -2); |
+ return new JS.Method(new JS.PropertyName(name), new JS.Fun( |
+ node.parameters.accept(this), _emitConstructorBody(node, fields))); |
+ } |
+ |
+ String _constructorName(String className, SimpleIdentifier name) { |
+ if (name == null) return className; |
+ return '$className\$${name.name}'; |
} |
- void _generateConstructorBody( |
+ JS.Block _emitConstructorBody( |
ConstructorDeclaration node, List<FieldDeclaration> fields) { |
// Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz; |
if (node.redirectedConstructor != null) { |
- out.write('return new '); |
- node.redirectedConstructor.accept(this); |
- out.write('('); |
- _visitNode(node.parameters); |
- out.write(');\n'); |
- return; |
+ return js.statement('{ return new #(#); }', [ |
+ node.redirectedConstructor.accept(this), |
+ node.parameters.accept(this) |
+ ]); |
} |
+ var body = <JS.Statement>[]; |
+ |
// Generate optional/named argument value assignment. These can not have |
// side effects, and may be used by the constructor's initializers, so it's |
// nice to do them first. |
- _generateArgumentInitializers(node.parameters); |
+ var init = _emitArgumentInitializers(node.parameters); |
+ if (init != null) body.add(init); |
// Redirecting constructors: these are not allowed to have initializers, |
// and the redirecting ctor invocation runs before field initializers. |
@@ -400,8 +364,8 @@ $name.prototype[Symbol.iterator] = function() { |
(i) => i is RedirectingConstructorInvocation, orElse: () => null); |
if (redirectCall != null) { |
- redirectCall.accept(this); |
- return; |
+ body.add(redirectCall.accept(this)); |
+ return new JS.Block(body); |
} |
// Initializers only run for non-factory constructors. |
@@ -410,7 +374,7 @@ $name.prototype[Symbol.iterator] = function() { |
// These are expanded into each non-redirecting constructor. |
// In the future we may want to create an initializer function if we have |
// multiple constructors, but it needs to be balanced against readability. |
- _initializeFields(fields, node.parameters, node.initializers); |
+ body.add(_initializeFields(fields, node.parameters, node.initializers)); |
var superCall = node.initializers.firstWhere( |
(i) => i is SuperConstructorInvocation, orElse: () => null); |
@@ -418,53 +382,39 @@ $name.prototype[Symbol.iterator] = function() { |
// If no superinitializer is provided, an implicit superinitializer of the |
// form `super()` is added at the end of the initializer list, unless the |
// enclosing class is class Object. |
- if (superCall == null) { |
- _superConstructorCall(node.parent); |
- } else { |
- _superConstructorCall(node.parent, node.name, superCall.constructorName, |
- superCall.argumentList); |
- } |
+ var jsSuper = _superConstructorCall(node.parent, superCall); |
+ if (jsSuper != null) body.add(jsSuper); |
} |
- var body = node.body; |
- if (body is BlockFunctionBody) { |
- body.block.statements.accept(this); |
- } else if (body is ExpressionFunctionBody) { |
- _visitNode(body.expression, prefix: 'return ', suffix: ';\n'); |
- } else { |
- assert(body is EmptyFunctionBody); |
- } |
+ body.add(node.body.accept(this)); |
+ return new JS.Block(body); |
} |
@override |
- void visitRedirectingConstructorInvocation( |
+ JS.Statement visitRedirectingConstructorInvocation( |
RedirectingConstructorInvocation node) { |
- var parent = node.parent as ConstructorDeclaration; |
+ ClassDeclaration classDecl = node.parent.parent; |
+ var className = classDecl.name.name; |
- if (parent.name != null) { |
- out.write(parent.name.name); |
- } else { |
- _writeTypeName((parent.parent as ClassDeclaration).element.type); |
- } |
- out.write('.call(this'); |
- _visitArgumentsWithCommaPrefix(node.argumentList); |
- out.write(');\n'); |
+ var name = _constructorName(className, node.constructorName); |
+ 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
|
} |
- void _superConstructorCall(ClassDeclaration clazz, [SimpleIdentifier ctorName, |
- SimpleIdentifier superCtorName, ArgumentList args]) { |
+ JS.Statement _superConstructorCall(ClassDeclaration clazz, |
+ [SuperConstructorInvocation node]) { |
+ var superCtorName = node != null ? node.constructorName : null; |
+ |
var element = clazz.element; |
if (superCtorName == null && |
(element.type.isObject || element.supertype.isObject)) { |
- return; |
+ return null; |
} |
var supertypeName = element.supertype.name; |
- out.write('super.$supertypeName'); |
- if (superCtorName != null) out.write('\$${superCtorName.name}'); |
- out.write('('); |
- _visitNode(args); |
- out.write(');\n'); |
+ var name = _constructorName(supertypeName, superCtorName); |
+ |
+ var args = node != null ? node.argumentList.accept(this) : []; |
+ return js.statement('super.#(#);', [name, args]); |
} |
/// Initialize fields. They follow the sequence: |
@@ -473,9 +423,10 @@ $name.prototype[Symbol.iterator] = function() { |
/// 2. field initializing parameters, |
/// 3. constructor field initializers, |
/// 4. initialize fields not covered in 1-3 |
- void _initializeFields(List<FieldDeclaration> fields, |
+ JS.Statement _initializeFields(List<FieldDeclaration> fields, |
[FormalParameterList parameters, |
NodeList<ConstructorInitializer> initializers]) { |
+ var body = <JS.Statement>[]; |
// Run field initializers if they can have side-effects. |
var unsetFields = new Map<String, VariableDeclaration>(); |
@@ -484,7 +435,8 @@ $name.prototype[Symbol.iterator] = function() { |
if (_isFieldInitConstant(field)) { |
unsetFields[field.name.name] = field; |
} else { |
- _visitNode(field, suffix: ';\n'); |
+ body.add(js.statement( |
+ '# = #;', [field.name.accept(this), _visitInitializer(field)])); |
} |
} |
} |
@@ -495,7 +447,7 @@ $name.prototype[Symbol.iterator] = function() { |
if (p is DefaultFormalParameter) p = p.parameter; |
if (p is FieldFormalParameter) { |
var name = p.identifier.name; |
- out.write('this.$name = $name;\n'); |
+ body.add(js.statement('this.# = #;', [name, name])); |
unsetFields.remove(name); |
} |
} |
@@ -505,10 +457,10 @@ $name.prototype[Symbol.iterator] = function() { |
if (initializers != null) { |
for (var init in initializers) { |
if (init is ConstructorFieldInitializer) { |
- init.fieldName.accept(this); |
- out.write(' = '); |
- init.expression.accept(this); |
- out.write(';\n'); |
+ body.add(js.statement('# = #;', [ |
+ init.fieldName.accept(this), |
+ init.expression.accept(this) |
+ ])); |
unsetFields.remove(init.fieldName.name); |
} |
} |
@@ -516,25 +468,29 @@ $name.prototype[Symbol.iterator] = function() { |
// Initialize all remaining fields |
unsetFields.forEach((name, field) { |
- out.write('this.$name = '); |
- var expression = field.initializer; |
- if (expression != null) { |
- expression.accept(this); |
+ JS.Expression value; |
+ if (field.initializer != null) { |
+ value = field.initializer.accept(this); |
} else { |
var type = rules.elementType(field.element); |
if (rules.maybeNonNullableType(type)) { |
- out.write('dart.as(null, '); |
- _writeTypeName(type); |
- out.write(')'); |
+ value = js.call('dart.as(null, #)', _emitTypeName(type)); |
} else { |
- out.write('null'); |
+ value = new JS.LiteralNull(); |
} |
} |
- out.write(';\n'); |
+ body.add(js.statement('this.# = #;', [name, value])); |
}); |
+ |
+ return _statement(body); |
} |
FormalParameterList _parametersOf(node) { |
+ // Note: ConstructorDeclaration is intentionally skipped here so we can |
+ // emit the argument initializers in a different place. |
+ // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we |
+ // could handle argument initializers more consistently in a separate |
+ // lowering pass. |
if (node is MethodDeclaration) return node.parameters; |
if (node is FunctionDeclaration) node = node.functionExpression; |
if (node is FunctionExpression) return node.parameters; |
@@ -546,507 +502,495 @@ $name.prototype[Symbol.iterator] = function() { |
return parameters.parameters.any((p) => p.kind != ParameterKind.REQUIRED); |
} |
- void _generateArgumentInitializers(FormalParameterList parameters) { |
- if (parameters == null) return; |
+ JS.Statement _emitArgumentInitializers(FormalParameterList parameters) { |
+ if (parameters == null || !_hasArgumentInitializers(parameters)) { |
+ return null; |
+ } |
+ |
+ var body = []; |
for (var param in parameters.parameters) { |
// TODO(justinfagnani): rename identifier if necessary |
var name = param.identifier.name; |
if (param.kind == ParameterKind.NAMED) { |
- out.write('let $name = opt\$.$name === undefined ? '); |
- if (param is DefaultFormalParameter && param.defaultValue != null) { |
- param.defaultValue.accept(this); |
- } else { |
- out.write('null'); |
- } |
- out.write(' : opt\$.$name;\n'); |
+ body.add(js.statement('let # = opt\$.# === void 0 ? # : opt\$.#;', [ |
+ name, |
+ name, |
+ _defaultParamValue(param), |
+ name |
+ ])); |
} else if (param.kind == ParameterKind.POSITIONAL) { |
- out.write('if ($name === undefined) $name = '); |
- if (param is DefaultFormalParameter && param.defaultValue != null) { |
- param.defaultValue.accept(this); |
- } else { |
- out.write('null'); |
- } |
- out.write(';\n'); |
+ body.add(js.statement('if (# === void 0) # = #;', [ |
+ name, |
+ name, |
+ _defaultParamValue(param) |
+ ])); |
} |
} |
+ return _statement(body); |
} |
- @override |
- void visitMethodDeclaration(MethodDeclaration node) { |
- if (node.isAbstract) return; |
- if (node.externalKeyword != null) { |
- out.write('/* Unimplemented $node */\n'); |
- return; |
+ JS.Expression _defaultParamValue(FormalParameter param) { |
+ if (param is DefaultFormalParameter && param.defaultValue != null) { |
+ return param.defaultValue.accept(this); |
+ } else { |
+ return new JS.LiteralNull(); |
} |
+ } |
- if (node.isStatic) { |
- out.write('static '); |
- } |
- if (node.isGetter) { |
- out.write('get '); |
- } else if (node.isSetter) { |
- out.write('set '); |
- } |
+ @override |
+ JS.Method visitMethodDeclaration(MethodDeclaration node) { |
+ if (node.isAbstract || node.externalKeyword != null) return null; |
+ |
+ var params = _visit(node.parameters); |
+ if (params == null) params = []; |
- var name = _canonicalMethodName(node.name.name); |
- out.write('$name('); |
- _visitNode(node.parameters); |
- out.write(') '); |
- _visitNode(node.body); |
- out.write('\n'); |
+ return new JS.Method(new JS.PropertyName(_jsMethodName(node.name.name)), |
+ new JS.Fun(params, node.body.accept(this)), |
+ isGetter: node.isGetter, |
+ isSetter: node.isSetter, |
+ isStatic: node.isStatic); |
} |
@override |
- void visitFunctionDeclaration(FunctionDeclaration node) { |
+ JS.Statement visitFunctionDeclaration(FunctionDeclaration node) { |
assert(node.parent is CompilationUnit); |
- if (node.externalKeyword != null) { |
- // TODO(jmesserly): the toString visitor in Analyzer doesn't include the |
- // external keyword for FunctionDeclaration. |
- out.write('/* Unimplemented external $node */\n'); |
- return; |
- } |
+ if (node.externalKeyword != null) return null; |
if (node.isGetter || node.isSetter) { |
// Add these later so we can use getter/setter syntax. |
_properties.add(node); |
- } else { |
- _flushLibraryProperties(); |
- _writeFunctionDeclaration(node); |
+ return null; |
} |
- } |
- void _writeFunctionDeclaration(FunctionDeclaration node) { |
+ var body = <JS.Statement>[]; |
+ _flushLibraryProperties(body); |
+ |
var name = node.name.name; |
+ body.add(js.comment('Function $name: ${node.element.type}')); |
- if (node.isGetter) { |
- out.write('get '); |
- } else if (node.isSetter) { |
- out.write('set '); |
- } else { |
- out.write("// Function $name: ${node.element.type}\n"); |
- out.write('function '); |
- } |
+ body.add(new JS.FunctionDeclaration(new JS.VariableDeclaration(name), |
+ node.functionExpression.accept(this))); |
- out.write('$name'); |
- node.functionExpression.accept(this); |
+ if (isPublic(name)) _exports.add(name); |
+ return _statement(body); |
+ } |
- if (!node.isGetter && !node.isSetter) { |
- out.write('\n'); |
- if (isPublic(name)) _exports.add(name); |
- out.write('\n'); |
- } |
+ JS.Method _emitTopLevelProperty(FunctionDeclaration node) { |
+ var name = node.name.name; |
+ if (isPublic(name)) _exports.add(name); |
+ return new JS.Method( |
+ new JS.PropertyName(name), node.functionExpression.accept(this), |
+ isGetter: node.isGetter, isSetter: node.isSetter); |
} |
@override |
- void visitFunctionExpression(FunctionExpression node) { |
+ JS.Expression visitFunctionExpression(FunctionExpression node) { |
+ var params = _visit(node.parameters); |
+ if (params == null) params = []; |
+ |
if (node.parent is FunctionDeclaration) { |
- out.write('('); |
- _visitNode(node.parameters); |
- out.write(') '); |
- node.body.accept(this); |
+ return new JS.Fun(params, node.body.accept(this)); |
} else { |
- var bindThis = _needsBindThis(node.body); |
- if (bindThis) out.write("("); |
- out.write("("); |
- _visitNode(node.parameters); |
- out.write(") => "); |
- var body = node.body; |
- if (body is ExpressionFunctionBody) body = body.expression; |
- body.accept(this); |
- if (bindThis) out.write(").bind(this)"); |
+ var bindThis = _maybeBindThis(node.body); |
+ |
+ String code; |
+ AstNode body; |
+ var nodeBody = node.body; |
+ if (nodeBody is ExpressionFunctionBody) { |
+ code = '(#) => #'; |
+ body = nodeBody.expression; |
+ } else { |
+ code = '(#) => { #; }'; |
+ body = nodeBody; |
+ } |
+ return js.call('($code)$bindThis', [params, body.accept(this)]); |
} |
} |
@override |
- void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { |
+ JS.Statement visitFunctionDeclarationStatement( |
+ FunctionDeclarationStatement node) { |
var func = node.functionDeclaration; |
if (func.isGetter || func.isSetter) { |
- out.write('/* Unimplemented function get/set statement: $node */'); |
- return; |
+ return js.comment('Unimplemented function get/set statement: $node'); |
} |
- var name = func.name.name; |
- out.write("// Function $name: ${func.element.type}\n"); |
- out.write('function $name'); |
- func.functionExpression.accept(this); |
- out.write('\n'); |
+ var name = new JS.VariableDeclaration(func.name.name); |
+ return new JS.Block([ |
+ js.comment("// Function ${func.name.name}: ${func.element.type}\n"), |
+ new JS.FunctionDeclaration(name, func.functionExpression.accept(this)) |
+ ]); |
} |
/// Writes a simple identifier. This can handle implicit `this` as well as |
/// going through the qualified library name if necessary. |
@override |
- void visitSimpleIdentifier(SimpleIdentifier node) { |
+ JS.Expression visitSimpleIdentifier(SimpleIdentifier node) { |
var e = node.staticElement; |
if (e == null) { |
- out.write('/* Unimplemented unknown name */'); |
- } else { |
- if (e.enclosingElement is CompilationUnitElement && |
- (e.library != libraryInfo.library || _needsModuleGetter(e))) { |
- out.write('${jsLibraryName(e.library)}.'); |
- } else if (currentClass != null && _needsImplicitThis(e)) { |
- out.write('this.'); |
- } |
+ return js.commentExpression( |
+ 'Unimplemented unknown name', new JS.VariableUse(node.name)); |
} |
- out.write(node.name); |
+ var name = node.name; |
+ if (e.enclosingElement is CompilationUnitElement && |
+ (e.library != libraryInfo.library || _needsModuleGetter(e))) { |
+ return js.call('#.#', [jsLibraryName(e.library), name]); |
+ } else if (currentClass != null && _needsImplicitThis(e)) { |
+ return js.call('this.#', name); |
+ } |
+ return new JS.VariableUse(name); |
} |
- void _writeTypeName(DartType type) { |
+ JS.Expression _emitTypeName(DartType type) { |
var name = type.name; |
var lib = type.element.library; |
if (name == '') { |
// TODO(jmesserly): remove when we're using coercion reifier. |
- out.write('/* Unimplemented type $type */'); |
- return; |
- } |
- |
- if (lib != currentLibrary && lib != null) { |
- out.write(jsLibraryName(lib)); |
- out.write('.'); |
+ return _unimplementedCall('Unimplemented type $type'); |
} |
- out.write(name); |
+ var typeArgs = null; |
if (type is ParameterizedType) { |
// TODO(jmesserly): this is a workaround for an analyzer bug, see: |
// https://github.com/dart-lang/dart-dev-compiler/commit/a212d59ad046085a626dd8d16881cdb8e8b9c3fa |
if (type is! FunctionType || type.element is FunctionTypeAlias) { |
var args = type.typeArguments; |
if (args.any((a) => a != rules.provider.dynamicType)) { |
- out.write('\$('); |
- for (var arg in args) { |
- if (arg != args.first) out.write(', '); |
- _writeTypeName(arg); |
- } |
- out.write(')'); |
+ name = '$name\$'; |
+ typeArgs = args.map(_emitTypeName); |
} |
} |
} |
+ |
+ JS.Expression result; |
+ if (lib != currentLibrary && lib != null) { |
+ result = js.call('#.#', [jsLibraryName(lib), name]); |
+ } else { |
+ result = new JS.VariableUse(name); |
+ } |
+ |
+ if (typeArgs != null) { |
+ result = js.call('#(#)', [result, typeArgs]); |
+ } |
+ return result; |
} |
@override |
- void visitAssignmentExpression(AssignmentExpression node) { |
+ JS.Node visitAssignmentExpression(AssignmentExpression node) { |
var lhs = node.leftHandSide; |
var rhs = node.rightHandSide; |
if (lhs is IndexExpression) { |
+ String code; |
var target = _getTarget(lhs); |
if (rules.isDynamicTarget(target)) { |
- out.write('dart.dsetindex('); |
- target.accept(this); |
- out.write(', '); |
- lhs.index.accept(this); |
- out.write(', '); |
- rhs.accept(this); |
- out.write(')'); |
+ code = 'dart.dsetindex(#, #, #)'; |
} else { |
- target.accept(this); |
- out.write('.set('); |
- lhs.index.accept(this); |
- out.write(', '); |
- rhs.accept(this); |
- out.write(')'); |
+ code = '#.set(#, #)'; |
} |
- return; |
+ return js.call(code, [ |
+ target.accept(this), |
+ lhs.index.accept(this), |
+ rhs.accept(this) |
+ ]); |
} |
if (lhs is PropertyAccess) { |
var target = _getTarget(lhs); |
if (rules.isDynamicTarget(target)) { |
- out.write('dart.dput('); |
- target.accept(this); |
- out.write(', "${lhs.propertyName.name}", '); |
- rhs.accept(this); |
- out.write(')'); |
- return; |
+ return js.call('dart.dput(#, #, #)', [ |
+ target.accept(this), |
+ js.string(lhs.propertyName.name, "'"), |
+ rhs.accept(this) |
+ ]); |
} |
} |
- lhs.accept(this); |
- out.write(' = '); |
- rhs.accept(this); |
- } |
+ if (node.parent is ExpressionStatement && |
+ rhs is CascadeExpression && |
+ _isStateless(lhs, rhs)) { |
+ // Special case: cascade assignment to a variable in a statement. |
+ // We can reuse the variable to desugar it: |
+ // result = []..length = length; |
+ // becomes: |
+ // result = []; |
+ // result.length = length; |
+ var savedCascadeTemp = _cascadeTarget; |
+ _cascadeTarget = lhs; |
+ |
+ var body = []; |
+ body.add( |
+ js.statement('# = #;', [lhs.accept(this), rhs.target.accept(this)])); |
+ for (var section in rhs.cascadeSections) { |
+ body.add(new JS.ExpressionStatement(section.accept(this))); |
+ } |
- @override |
- void visitExpressionFunctionBody(ExpressionFunctionBody node) { |
- var parameters = _parametersOf(node.parent); |
- var initArgs = parameters != null && _hasArgumentInitializers(parameters); |
- if (initArgs) { |
- out.write('{\n', 2); |
- _generateArgumentInitializers(parameters); |
- } else { |
- out.write('{ '); |
- } |
- out.write('return '); |
- node.expression.accept(this); |
- if (initArgs) { |
- out.write('\n}', -2); |
- } else { |
- out.write('; }'); |
+ _cascadeTarget = savedCascadeTemp; |
+ return _statement(body); |
} |
+ |
+ return js.call('# = #', [lhs.accept(this), rhs.accept(this)]); |
} |
@override |
- void visitEmptyFunctionBody(EmptyFunctionBody node) { |
- out.write('{}'); |
+ JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { |
+ var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
+ var ret = new JS.Return(node.expression.accept(this)); |
+ return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); |
} |
@override |
- void visitBlockFunctionBody(BlockFunctionBody node) { |
- out.write('{\n', 2); |
- _generateArgumentInitializers(_parametersOf(node.parent)); |
- _visitNodeList(node.block.statements); |
- out.write('}', -2); |
- } |
+ JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); |
@override |
- void visitBlock(Block node) { |
- out.write("{\n", 2); |
- node.statements.accept(this); |
- out.write("}\n", -2); |
+ JS.Block visitBlockFunctionBody(BlockFunctionBody node) { |
+ var initArgs = _emitArgumentInitializers(_parametersOf(node.parent)); |
+ var block = visitBlock(node.block); |
+ if (initArgs != null) return new JS.Block([initArgs, block]); |
+ return block; |
} |
@override |
- void visitMethodInvocation(MethodInvocation node) { |
+ JS.Block visitBlock(Block node) => new JS.Block(_visitList(node.statements)); |
+ |
+ @override |
+ JS.Expression visitMethodInvocation(MethodInvocation node) { |
var target = node.isCascaded ? _cascadeTarget : node.target; |
if (rules.isDynamicCall(node.methodName)) { |
+ var args = node.argumentList.accept(this); |
if (target != null) { |
- out.write('dart.dinvoke('); |
- target.accept(this); |
- out.write(', "${node.methodName.name}"'); |
+ return js.call('dart.dinvoke(#, #, #)', [ |
+ target.accept(this), |
+ js.string(node.methodName.name, "'"), |
+ args |
+ ]); |
} else { |
- out.write('dart.dinvokef('); |
- node.methodName.accept(this); |
+ return js.call( |
+ 'dart.dinvokef(#, #)', [node.methodName.accept(this), args]); |
} |
- |
- _visitArgumentsWithCommaPrefix(node.argumentList); |
- out.write(')'); |
- return; |
} |
// TODO(jmesserly): if this resolves to a getter returning a function with |
// a call method, we don't generate the `.call` correctly. |
+ |
+ var targetJs; |
if (target != null) { |
- target.accept(this); |
- out.write('.${node.methodName.name}'); |
+ targetJs = js.call('#.#', [target.accept(this), node.methodName.name]); |
} else { |
- node.methodName.accept(this); |
+ targetJs = node.methodName.accept(this); |
} |
- out.write('('); |
- node.argumentList.accept(this); |
- out.write(')'); |
+ return js.call('#(#)', [targetJs, node.argumentList.accept(this)]); |
} |
@override |
- void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
+ JS.Expression visitFunctionExpressionInvocation( |
+ FunctionExpressionInvocation node) { |
+ var code; |
if (rules.isDynamicCall(node.function)) { |
- out.write('dart.dinvokef('); |
- node.function.accept(this); |
- _visitArgumentsWithCommaPrefix(node.argumentList); |
- out.write(')'); |
+ code = 'dart.dinvokef(#, #)'; |
} else { |
- node.function.accept(this); |
- out.write('('); |
- node.argumentList.accept(this); |
- out.write(')'); |
+ code = '#(#)'; |
} |
+ return js.call( |
+ code, [node.function.accept(this), node.argumentList.accept(this)]); |
} |
- /// Writes an argument list. This does not write the parens, because sometimes |
- /// a parameter will need to be added before the start of the list, so |
- /// writing parens is the responsibility of the parent node. |
@override |
- void visitArgumentList(ArgumentList node) { |
- var args = node.arguments; |
- |
- bool hasNamed = false; |
- for (int i = 0; i < args.length; i++) { |
- if (i > 0) out.write(', '); |
- |
- var arg = args[i]; |
+ List<JS.Expression> visitArgumentList(ArgumentList node) { |
+ var args = <JS.Expression>[]; |
+ var named = <JS.Property>[]; |
+ for (var arg in node.arguments) { |
if (arg is NamedExpression) { |
- if (!hasNamed) out.write('{'); |
- hasNamed = true; |
+ named.add(visitNamedExpression(arg)); |
+ } else { |
+ args.add(arg.accept(this)); |
} |
- arg.accept(this); |
} |
- if (hasNamed) out.write('}'); |
- } |
- |
- void _visitArgumentsWithCommaPrefix(ArgumentList node) { |
- if (node == null) return; |
- if (node.arguments.isNotEmpty) out.write(', '); |
- visitArgumentList(node); |
+ if (named.isNotEmpty) { |
+ args.add(new JS.ObjectInitializer(named)); |
+ } |
+ return args; |
} |
@override |
- void visitNamedExpression(NamedExpression node) { |
+ JS.Property visitNamedExpression(NamedExpression node) { |
assert(node.parent is ArgumentList); |
- node.name.accept(this); |
- out.write(' '); |
- node.expression.accept(this); |
+ return new JS.Property(new JS.PropertyName(node.name.label.name), |
+ node.expression.accept(this)); |
} |
@override |
- void visitFormalParameterList(FormalParameterList node) { |
- int length = node.parameters.length; |
- bool hasOptionalParameters = false; |
- bool hasPositionalParameters = false; |
- |
- for (int i = 0; i < length; i++) { |
- var param = node.parameters[i]; |
+ List<JS.Parameter> visitFormalParameterList(FormalParameterList node) { |
+ var result = <JS.Parameter>[]; |
+ for (FormalParameter param in node.parameters) { |
if (param.kind == ParameterKind.NAMED) { |
- hasOptionalParameters = true; |
- } else { |
- if (hasPositionalParameters) out.write(', '); |
- hasPositionalParameters = true; |
- param.accept(this); |
+ result.add(new JS.Parameter(_jsNamedParameterName)); |
+ break; |
} |
+ result.add(new JS.Parameter(param.identifier.name)); |
} |
- if (hasOptionalParameters) { |
- if (hasPositionalParameters) out.write(', '); |
- out.write(optionalParameters); |
- } |
+ return result; |
} |
@override |
- void visitFieldFormalParameter(FieldFormalParameter node) { |
- // Named parameters are handled as a single object, so we skip individual |
- // parameters |
- if (node.kind != ParameterKind.NAMED) { |
- out.write(node.identifier.name); |
- } |
- } |
+ JS.Statement visitExpressionStatement(ExpressionStatement node) => |
+ _expressionStatement(node.expression.accept(this)); |
- @override |
- void visitDefaultFormalParameter(DefaultFormalParameter node) { |
- // Named parameters are handled as a single object, so we skip individual |
- // parameters |
- if (node.kind != ParameterKind.NAMED) { |
- out.write(node.identifier.name); |
- } |
- } |
+ // Some expressions may choose to generate themselves as JS statements |
+ // if their parent is in a statement context. |
+ // TODO(jmesserly): refactor so we handle the special cases here, and |
+ // can use better return types on the expression visit methods. |
+ JS.Statement _expressionStatement(expr) => |
+ expr is JS.Statement ? expr : new JS.ExpressionStatement(expr); |
@override |
- void visitExpressionStatement(ExpressionStatement node) { |
- node.expression.accept(this); |
- out.write(';\n'); |
- } |
+ JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => |
+ new JS.EmptyStatement(); |
@override |
- void visitEmptyStatement(EmptyStatement node) { |
- out.write(';\n'); |
- } |
+ JS.Statement visitAssertStatement(AssertStatement node) => |
+ // TODO(jmesserly): only emit in checked mode. |
+ js.statement('dart.assert(#);', node.condition.accept(this)); |
@override |
- void visitAssertStatement(AssertStatement node) { |
- // TODO(jmesserly): only emit in checked mode. |
- _visitNode(node.condition, prefix: 'dart.assert(', suffix: ');\n'); |
- } |
+ JS.Return visitReturnStatement(ReturnStatement node) => |
+ new JS.Return(_visit(node.expression)); |
@override |
- void visitReturnStatement(ReturnStatement node) { |
- out.write('return'); |
- _visitNode(node.expression, prefix: ' '); |
- out.write(';\n'); |
- } |
+ visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
+ var body = <JS.Statement>[]; |
- @override |
- void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
for (var field in node.variables.variables) { |
- var name = field.name.name; |
if (field.isConst) { |
// constant fields don't change, so we can generate them as `let` |
// but add them to the module's exports |
- _visitNode(field, prefix: 'let ', suffix: ';\n'); |
+ var name = field.name.name; |
+ body.add(js.statement('let # = #;', [ |
+ new JS.VariableDeclaration(name), |
+ _visitInitializer(field) |
+ ])); |
if (isPublic(name)) _exports.add(name); |
} else if (_isFieldInitConstant(field)) { |
- _visitNode(field, suffix: ';\n'); |
+ body.add(js.statement( |
+ '# = #;', [field.name.accept(this), _visitInitializer(field)])); |
} else { |
_lazyFields.add(field); |
} |
} |
- } |
- @override |
- void visitVariableDeclarationList(VariableDeclarationList node) { |
- _visitNodeList(node.variables, prefix: 'let ', separator: ', '); |
+ return _statement(body); |
} |
@override |
- void visitVariableDeclaration(VariableDeclaration node) { |
- node.name.accept(this); |
- out.write(' = '); |
- if (node.initializer != null) { |
- node.initializer.accept(this); |
+ visitVariableDeclarationList(VariableDeclarationList node) { |
+ var last = node.variables.last; |
+ var lastInitializer = last.initializer; |
+ |
+ List<JS.VariableInitialization> variables; |
+ if (lastInitializer is CascadeExpression && |
+ node.parent is VariableDeclarationStatement) { |
+ // Special case: cascade as variable initializer |
+ // |
+ // We can reuse the variable to desugar it: |
+ // var result = []..length = length; |
+ // becomes: |
+ // var result = []; |
+ // result.length = length; |
+ var savedCascadeTemp = _cascadeTarget; |
+ _cascadeTarget = last.name; |
+ |
+ variables = _visitList(node.variables.take(node.variables.length - 1)); |
+ variables.add(new JS.VariableInitialization( |
+ new JS.VariableDeclaration(last.name.name), |
+ lastInitializer.target.accept(this))); |
+ |
+ var result = <JS.Expression>[ |
+ new JS.VariableDeclarationList('let', variables) |
+ ]; |
+ result.addAll(_visitList(lastInitializer.cascadeSections)); |
+ _cascadeTarget = savedCascadeTemp; |
+ return _statement(result.map((e) => new JS.ExpressionStatement(e))); |
} else { |
- // explicitly initialize to null, so we don't need to worry about |
- // `undefined`. |
- // TODO(jmesserly): do this only for vars that aren't definitely assigned. |
- out.write('null'); |
+ variables = _visitList(node.variables); |
} |
+ |
+ return new JS.VariableDeclarationList('let', variables); |
} |
- void _flushLazyFields() { |
- if (_lazyFields.isEmpty) return; |
+ @override |
+ JS.VariableInitialization visitVariableDeclaration(VariableDeclaration node) { |
+ var name = new JS.VariableDeclaration(node.name.name); |
+ return new JS.VariableInitialization(name, _visitInitializer(node)); |
+ } |
- _writeLazyFields(_libraryName, _lazyFields); |
- out.write('\n'); |
+ JS.Expression _visitInitializer(VariableDeclaration node) { |
+ var value = _visit(node.initializer); |
+ // explicitly initialize to null, to avoid getting `undefined`. |
+ // TODO(jmesserly): do this only for vars that aren't definitely assigned. |
+ return value != null ? value : new JS.LiteralNull(); |
+ } |
+ void _flushLazyFields(List<JS.Statement> body) { |
+ var code = _emitLazyFields(_libraryName, _lazyFields); |
+ if (code != null) body.add(code); |
_lazyFields.clear(); |
} |
- void _writeLazyFields(String objExpr, List<VariableDeclaration> fields) { |
- if (fields.isEmpty) return; |
+ JS.Statement _emitLazyFields( |
+ String objExpr, List<VariableDeclaration> fields) { |
+ if (fields.isEmpty) return null; |
- out.write('dart.defineLazyProperties($objExpr, {\n', 2); |
+ var methods = []; |
for (var node in fields) { |
var name = node.name.name; |
- out.write('get $name() { return '); |
- node.initializer.accept(this); |
- out.write(' },\n'); |
- // TODO(jmesserly): we're using a dummy setter to indicate writable. |
- if (!node.isFinal) out.write('set $name(x) {},\n'); |
Jennifer Messerly
2015/02/25 18:36:20
this is the dummy setter code. I agree we should f
|
+ methods.add(new JS.Method(new JS.PropertyName(name), |
+ js.call('function() { return #; }', node.initializer.accept(this)), |
+ isGetter: true)); |
+ |
+ // TODO(jmesserly): use a dummy setter to indicate writable. |
+ if (!node.isFinal) { |
+ methods.add(new JS.Method( |
+ new JS.PropertyName(name), js.call('function() {}'), |
+ isSetter: true)); |
+ } |
} |
- out.write('});\n', -2); |
+ |
+ return js.statement( |
+ 'dart.defineLazyProperties(#, { # })', [objExpr, methods]); |
} |
- void _flushLibraryProperties() { |
+ void _flushLibraryProperties(List<JS.Statement> body) { |
if (_properties.isEmpty) return; |
- |
- out.write('dart.copyProperties($_libraryName, {\n', 2); |
- for (var node in _properties) { |
- _writeFunctionDeclaration(node); |
- out.write(',\n'); |
- } |
- out.write('});\n\n', -2); |
- |
+ body.add(js.statement('dart.copyProperties(#, { # });', [ |
+ _libraryName, |
+ _properties.map(_emitTopLevelProperty) |
+ ])); |
_properties.clear(); |
} |
@override |
- void visitVariableDeclarationStatement(VariableDeclarationStatement node) { |
- _visitNode(node.variables); |
- out.write(';\n'); |
- } |
+ JS.Statement visitVariableDeclarationStatement( |
+ VariableDeclarationStatement node) => |
+ _expressionStatement(node.variables.accept(this)); |
@override |
- void visitConstructorName(ConstructorName node) { |
- node.type.name.accept(this); |
+ visitConstructorName(ConstructorName node) { |
+ var typeName = node.type.name.accept(this); |
if (node.name != null) { |
- out.write('.'); |
- node.name.accept(this); |
+ return js.call('#.#', [typeName, node.name.name]); |
} |
+ return typeName; |
} |
@override |
- void visitInstanceCreationExpression(InstanceCreationExpression node) { |
- out.write('new '); |
- node.constructorName.accept(this); |
- out.write('('); |
- node.argumentList.accept(this); |
- out.write(')'); |
+ visitInstanceCreationExpression(InstanceCreationExpression node) { |
+ return js.call('new #(#)', [ |
+ node.constructorName.accept(this), |
+ node.argumentList.accept(this) |
+ ]); |
} |
/// True if this type is built-in to JS, and we use the values unwrapped. |
@@ -1070,133 +1014,112 @@ $name.prototype[Symbol.iterator] = function() { |
bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); |
- void notNull(Expression expr) { |
+ JS.Expression notNull(Expression expr) { |
var type = rules.getStaticType(expr); |
if (rules.isNonNullableType(type)) { |
- expr.accept(this); |
+ return expr.accept(this); |
} else { |
- out.write('dart.notNull('); |
- expr.accept(this); |
- out.write(')'); |
+ return js.call('dart.notNull(#)', expr.accept(this)); |
} |
} |
@override |
- void visitBinaryExpression(BinaryExpression node) { |
+ JS.Expression visitBinaryExpression(BinaryExpression node) { |
var op = node.operator; |
- var lhs = node.leftOperand; |
- var rhs = node.rightOperand; |
- |
- var dispatchType = rules.getStaticType(lhs); |
- var otherType = rules.getStaticType(rhs); |
+ var left = node.leftOperand; |
+ var right = node.rightOperand; |
+ var leftType = rules.getStaticType(left); |
+ var rightType = rules.getStaticType(right); |
+ var code; |
if (op.type.isEqualityOperator) { |
// If we statically know LHS or RHS is null we can generate a clean check. |
// We can also do this if the left hand side is a primitive type, because |
// we know then it doesn't have an overridden. |
- if (_isNull(lhs) || _isNull(rhs) || typeIsPrimitiveInJS(dispatchType)) { |
- lhs.accept(this); |
+ if (_isNull(left) || _isNull(right) || typeIsPrimitiveInJS(leftType)) { |
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-strict-equality-comparison |
- out.write(op.type == TokenType.EQ_EQ ? ' === ' : ' !== '); |
- rhs.accept(this); |
+ code = op.type == TokenType.EQ_EQ ? '# === #' : '# !== #'; |
} else { |
- // TODO(jmesserly): it would be nice to use just "equals", perhaps |
- // by importing this name. |
- if (op.type == TokenType.BANG_EQ) out.write('!'); |
- out.write('dart.equals('); |
- lhs.accept(this); |
- out.write(', '); |
- rhs.accept(this); |
- out.write(')'); |
+ var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
+ code = '${bang}dart.equals(#, #)'; |
} |
- } else if (binaryOperationIsPrimitive(dispatchType, otherType)) { |
+ return js.call(code, [left.accept(this), right.accept(this)]); |
+ } else if (binaryOperationIsPrimitive(leftType, rightType)) { |
// special cases where we inline the operation |
// these values are assumed to be non-null (determined by the checker) |
// TODO(jmesserly): it would be nice to just inline the method from core, |
// instead of special cases here. |
- |
if (op.type == TokenType.TILDE_SLASH) { |
// `a ~/ b` is equivalent to `(a / b).truncate()` |
- out.write('('); |
- notNull(lhs); |
- out.write(' / '); |
- notNull(rhs); |
- out.write(').truncate()'); |
+ code = '(# / #).truncate()'; |
} else { |
// TODO(vsm): When do Dart ops not map to JS? |
- notNull(lhs); |
- out.write(' $op '); |
- notNull(rhs); |
+ code = '# $op #'; |
} |
- } else if (rules.isDynamicTarget(lhs)) { |
- // dynamic dispatch |
- out.write('dart.dbinary('); |
- lhs.accept(this); |
- out.write(', "${op.lexeme}", '); |
- rhs.accept(this); |
- out.write(')'); |
- } else if (_isJSBuiltinType(dispatchType)) { |
- // TODO(jmesserly): we'd get better readability from the static-dispatch |
- // pattern below. Consider: |
- // |
- // "hello"['+']"world" |
- // vs |
- // core.String['+']("hello", "world") |
- // |
- // Infix notation is much more readable, which is a bit part of why |
- // C# added its extension methods feature. However this would require |
- // adding these methods to String.prototype/Number.prototype in JS. |
- _writeTypeName(dispatchType); |
- out.write(_canonicalMethodInvoke(op.lexeme)); |
- out.write('('); |
- lhs.accept(this); |
- out.write(', '); |
- rhs.accept(this); |
- out.write(')'); |
+ return js.call(code, [notNull(left), notNull(right)]); |
} else { |
- // Generic static-dispatch, user-defined operator code path. |
- |
- // We're going to replace the operator with high-precedence "." or "[]", |
- // so add parens around the left side if necessary. |
- _visitExpression(lhs, _indexExpressionPrecedence); |
- out.write(_canonicalMethodInvoke(op.lexeme)); |
- out.write('('); |
- rhs.accept(this); |
- out.write(')'); |
+ var opString = js.string(op.lexeme, "'"); |
+ if (rules.isDynamicTarget(left)) { |
+ // dynamic dispatch |
+ return js.call('dart.dbinary(#, #, #)', [ |
+ left.accept(this), |
+ opString, |
+ right.accept(this) |
+ ]); |
+ } else if (_isJSBuiltinType(leftType)) { |
+ // TODO(jmesserly): we'd get better readability from the static-dispatch |
+ // pattern below. Consider: |
+ // |
+ // "hello"['+']"world" |
+ // vs |
+ // core.String['+']("hello", "world") |
+ // |
+ // Infix notation is much more readable, which is a bit part of why |
+ // C# added its extension methods feature. However this would require |
+ // adding these methods to String.prototype/Number.prototype in JS. |
+ return js.call('#.#(#, #)', [ |
+ _emitTypeName(leftType), |
+ opString, |
+ left.accept(this), |
+ right.accept(this) |
+ ]); |
+ } else { |
+ // Generic static-dispatch, user-defined operator code path. |
+ return js.call( |
+ '#.#(#)', [left.accept(this), opString, right.accept(this)]); |
+ } |
} |
} |
bool _isNull(Expression expr) => expr is NullLiteral; |
@override |
- void visitPostfixExpression(PostfixExpression node) { |
+ JS.Expression visitPostfixExpression(PostfixExpression node) { |
var op = node.operator; |
var expr = node.operand; |
var dispatchType = rules.getStaticType(expr); |
if (unaryOperationIsPrimitive(dispatchType)) { |
// TODO(vsm): When do Dart ops not map to JS? |
- notNull(expr); |
- out.write('$op'); |
+ return js.call('#$op', notNull(expr)); |
} else { |
// TODO(vsm): Figure out operator calling convention / dispatch. |
- out.write('/* Unimplemented postfix operator: $node */'); |
+ return visitExpression(node); |
} |
} |
@override |
- void visitPrefixExpression(PrefixExpression node) { |
+ JS.Expression visitPrefixExpression(PrefixExpression node) { |
var op = node.operator; |
var expr = node.operand; |
var dispatchType = rules.getStaticType(expr); |
if (unaryOperationIsPrimitive(dispatchType)) { |
// TODO(vsm): When do Dart ops not map to JS? |
- out.write('$op'); |
- notNull(expr); |
+ return js.call('$op#', notNull(expr)); |
} else { |
// TODO(vsm): Figure out operator calling convention / dispatch. |
- out.write('/* Unimplemented postfix operator: $node */'); |
+ return visitExpression(node); |
} |
} |
@@ -1204,53 +1127,24 @@ $name.prototype[Symbol.iterator] = function() { |
// [PropertyAccess]. The code generation for those is handled in their |
// respective visit methods. |
@override |
- void visitCascadeExpression(CascadeExpression node) { |
+ JS.Node visitCascadeExpression(CascadeExpression node) { |
var savedCascadeTemp = _cascadeTarget; |
var parent = node.parent; |
- var grandparent = parent.parent; |
+ JS.Node result; |
if (_isStateless(node.target, node)) { |
// Special case: target is stateless, so we can just reuse it. |
_cascadeTarget = node.target; |
if (parent is ExpressionStatement) { |
- _visitNodeList(node.cascadeSections, separator: ';\n'); |
+ var sections = _visitList(node.cascadeSections); |
+ result = _statement(sections.map((e) => new JS.ExpressionStatement(e))); |
} else { |
// Use comma expression. For example: |
// (sb.write(1), sb.write(2), sb) |
- out.write('('); |
- _visitNodeList(node.cascadeSections, separator: ', ', suffix: ', '); |
- _cascadeTarget.accept(this); |
- out.write(')'); |
+ var sections = _visitListToBinary(node.cascadeSections, ','); |
+ result = new JS.Binary(',', sections, _cascadeTarget.accept(this)); |
} |
- } else if (parent is AssignmentExpression && |
- grandparent is ExpressionStatement && |
- _isStateless(parent.leftHandSide, node)) { |
- |
- // Special case: assignment to a variable in a statement. |
- // We can reuse the variable to desugar it: |
- // result = []..length = length; |
- // becomes: |
- // result = []; |
- // result.length = length; |
- _cascadeTarget = parent.leftHandSide; |
- node.target.accept(this); |
- out.write(';\n'); |
- _visitNodeList(node.cascadeSections, separator: ';\n'); |
- } else if (parent is VariableDeclaration && |
- grandparent is VariableDeclarationList && |
- grandparent.variables.last == parent) { |
- |
- // Special case: variable declaration |
- // We can reuse the variable to desugar it: |
- // var result = []..length = length; |
- // becomes: |
- // var result = []; |
- // result.length = length; |
- _cascadeTarget = parent.name; |
- node.target.accept(this); |
- out.write(';\n'); |
- _visitNodeList(node.cascadeSections, separator: ';\n'); |
} else { |
// In the general case we need to capture the target expression into |
// a temporary. This uses a lambda to get a temporary scope, and it also |
@@ -1263,19 +1157,21 @@ $name.prototype[Symbol.iterator] = function() { |
new LocalVariableElementImpl.forNode(_cascadeTarget); |
_cascadeTarget.staticType = node.target.staticType; |
- out.write('((${_cascadeTarget.name}) => {\n', 2); |
- _visitNodeList(node.cascadeSections, separator: ';\n', suffix: ';\n'); |
+ var body = _visitList(node.cascadeSections); |
if (node.parent is! ExpressionStatement) { |
- out.write('return ${_cascadeTarget.name};\n'); |
+ body.add(js.statement('return #;', _cascadeTarget.name)); |
} |
- out.write('})', -2); |
- if (_needsBindThis(node.cascadeSections)) out.write('.bind(this)'); |
- out.write('('); |
- node.target.accept(this); |
- out.write(')'); |
+ |
+ var bindThis = _maybeBindThis(node.cascadeSections); |
+ result = js.call('((#) => { # })$bindThis(#)', [ |
+ _cascadeTarget.name, |
+ body, |
+ node.target.accept(this) |
+ ]); |
} |
_cascadeTarget = savedCascadeTemp; |
+ return result; |
} |
/// True is the expression can be evaluated multiple times without causing |
@@ -1299,75 +1195,57 @@ $name.prototype[Symbol.iterator] = function() { |
} |
@override |
- void visitParenthesizedExpression(ParenthesizedExpression node) { |
- out.write('('); |
- node.expression.accept(this); |
- out.write(')'); |
- } |
+ visitParenthesizedExpression(ParenthesizedExpression node) => |
+ // The printer handles precedence so we don't need to. |
+ node.expression.accept(this); |
@override |
- void visitSimpleFormalParameter(SimpleFormalParameter node) { |
- node.identifier.accept(this); |
- } |
+ visitSimpleFormalParameter(SimpleFormalParameter node) => |
+ node.identifier.accept(this); |
@override |
- void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
- node.identifier.accept(this); |
- } |
+ visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) => |
+ node.identifier.accept(this); |
@override |
- void visitThisExpression(ThisExpression node) { |
- out.write('this'); |
- } |
+ JS.This visitThisExpression(ThisExpression node) => new JS.This(); |
@override |
- void visitSuperExpression(SuperExpression node) { |
- out.write('super'); |
- } |
+ JS.Super visitSuperExpression(SuperExpression node) => new JS.Super(); |
@override |
- void visitPrefixedIdentifier(PrefixedIdentifier node) { |
+ visitPrefixedIdentifier(PrefixedIdentifier node) { |
if (node.prefix.staticElement is PrefixElement) { |
- node.identifier.accept(this); |
+ return node.identifier.accept(this); |
} else { |
- _visitGet(node.prefix, node.identifier); |
+ return _visitGet(node.prefix, node.identifier); |
} |
} |
@override |
- void visitPropertyAccess(PropertyAccess node) { |
- _visitGet(_getTarget(node), node.propertyName); |
- } |
+ visitPropertyAccess(PropertyAccess node) => |
+ _visitGet(_getTarget(node), node.propertyName); |
/// Shared code for [PrefixedIdentifier] and [PropertyAccess]. |
- void _visitGet(Expression target, SimpleIdentifier name) { |
+ _visitGet(Expression target, SimpleIdentifier name) { |
if (rules.isDynamicTarget(target)) { |
- // TODO(jmesserly): this won't work if we're left hand side of assignment. |
- out.write('dart.dload('); |
- target.accept(this); |
- out.write(', "${name.name}")'); |
+ return js.call( |
+ 'dart.dload(#, #)', [target.accept(this), js.string(name.name, "'")]); |
} else { |
- target.accept(this); |
- out.write('.${name.name}'); |
+ return js.call('#.#', [target.accept(this), name.name]); |
} |
} |
@override |
- void visitIndexExpression(IndexExpression node) { |
+ visitIndexExpression(IndexExpression node) { |
var target = _getTarget(node); |
+ var code; |
if (rules.isDynamicTarget(target)) { |
- out.write('dart.dindex('); |
- target.accept(this); |
- out.write(', '); |
- node.index.accept(this); |
- out.write(')'); |
+ code = 'dart.dindex(#, #)'; |
} else { |
- target.accept(this); |
- out.write(_canonicalMethodInvoke('[]')); |
- out.write('('); |
- node.index.accept(this); |
- out.write(')'); |
+ code = '#.get(#)'; |
} |
+ return js.call(code, [target.accept(this), node.index.accept(this)]); |
} |
/// Gets the target of a [PropertyAccess] or [IndexExpression]. |
@@ -1379,368 +1257,261 @@ $name.prototype[Symbol.iterator] = function() { |
} |
@override |
- void visitConditionalExpression(ConditionalExpression node) { |
- node.condition.accept(this); |
- out.write(' ? '); |
- node.thenExpression.accept(this); |
- out.write(' : '); |
- node.elseExpression.accept(this); |
+ visitConditionalExpression(ConditionalExpression node) { |
+ return js.call('# ? # : #', [ |
+ node.condition.accept(this), |
+ node.thenExpression.accept(this), |
+ node.elseExpression.accept(this) |
+ ]); |
} |
@override |
- void visitThrowExpression(ThrowExpression node) { |
+ visitThrowExpression(ThrowExpression node) { |
+ var expr = node.expression.accept(this); |
if (node.parent is ExpressionStatement) { |
- out.write('throw '); |
- node.expression.accept(this); |
+ return js.statement('throw #;', expr); |
} else { |
- // TODO(jmesserly): move this into runtime helper? |
- out.write('(function(e) { throw e }('); |
- node.expression.accept(this); |
- out.write(')'); |
+ return js.call('dart.throw_(#)', expr); |
} |
} |
@override |
- void visitIfStatement(IfStatement node) { |
- out.write('if ('); |
- node.condition.accept(this); |
- out.write(') '); |
- var then = node.thenStatement; |
- if (then is Block) { |
- out.write('{\n', 2); |
- _visitNodeList((then as Block).statements); |
- out.write('}', -2); |
- } else { |
- _visitNode(then); |
- } |
- var elseClause = node.elseStatement; |
- if (elseClause != null) { |
- out.write(' else '); |
- elseClause.accept(this); |
- } else if (then is Block) { |
- out.write('\n'); |
- } |
+ JS.If visitIfStatement(IfStatement node) { |
+ return new JS.If(node.condition.accept(this), _visit(node.thenStatement), |
+ _visitOrEmpty(node.elseStatement)); |
} |
@override |
- void visitForStatement(ForStatement node) { |
- Expression initialization = node.initialization; |
- out.write("for ("); |
- if (initialization != null) { |
- initialization.accept(this); |
- } else if (node.variables != null) { |
- _visitNode(node.variables); |
- } |
- out.write(";"); |
- _visitNode(node.condition, prefix: " "); |
- out.write(";"); |
- _visitNodeList(node.updaters, prefix: " ", separator: ", "); |
- out.write(") "); |
- _visitNode(node.body); |
+ JS.For visitForStatement(ForStatement node) { |
+ var init = _visit(node.initialization); |
+ if (init == null) init = _visit(node.variables); |
+ return new JS.For(init, _visit(node.condition), |
+ _visitListToBinary(node.updaters, ','), _visit(node.body)); |
} |
@override |
- void visitWhileStatement(WhileStatement node) { |
- out.write("while ("); |
- _visitNode(node.condition); |
- out.write(") "); |
- _visitNode(node.body); |
+ JS.While visitWhileStatement(WhileStatement node) { |
+ return new JS.While(node.condition.accept(this), node.body.accept(this)); |
} |
@override |
- void visitDoStatement(DoStatement node) { |
- out.write("do "); |
- _visitNode(node.body); |
- if (node.body is! Block) out.write(' '); |
- out.write("while ("); |
- _visitNode(node.condition); |
- out.write(");\n"); |
+ JS.Do visitDoStatement(DoStatement node) { |
+ return new JS.Do(node.body.accept(this), node.condition.accept(this)); |
} |
@override |
- void visitForEachStatement(ForEachStatement node) { |
- out.write('for ('); |
- if (node.loopVariable != null) { |
- _visitNode(node.loopVariable.identifier, prefix: 'let '); |
- } else { |
- _visitNode(node.identifier); |
+ JS.ForOf visitForEachStatement(ForEachStatement node) { |
+ var init = _visit(node.identifier); |
+ if (init == null) { |
+ init = js.call('let #', node.loopVariable.identifier.name); |
} |
- out.write(' of '); |
- _visitNode(node.iterable); |
- out.write(') '); |
- _visitNode(node.body); |
+ return new JS.ForOf( |
+ init, node.iterable.accept(this), node.body.accept(this)); |
} |
@override |
- void visitBreakStatement(BreakStatement node) { |
- out.write("break"); |
- _visitNode(node.label, prefix: " "); |
- out.write(";\n"); |
+ visitBreakStatement(BreakStatement node) { |
+ var label = node.label; |
+ return new JS.Break(label != null ? label.name : null); |
} |
@override |
- void visitContinueStatement(ContinueStatement node) { |
- out.write("continue"); |
- _visitNode(node.label, prefix: " "); |
- out.write(";\n"); |
+ visitContinueStatement(ContinueStatement node) { |
+ var label = node.label; |
+ return new JS.Continue(label != null ? label.name : null); |
} |
@override |
- void visitTryStatement(TryStatement node) { |
- out.write('try '); |
- _visitNode(node.body); |
- if (node.body is! Block) out.write(' '); |
+ visitTryStatement(TryStatement node) { |
+ return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses), |
+ _visit(node.finallyBlock)); |
+ } |
- var clauses = node.catchClauses; |
- if (clauses != null && clauses.isNotEmpty) { |
- // TODO(jmesserly): need a better way to get a temporary variable. |
- // This could incorrectly shadow a user's name. |
- var name = '\$e'; |
+ _visitCatch(NodeList<CatchClause> clauses) { |
+ if (clauses == null || clauses.isEmpty) return null; |
- if (clauses.length == 1) { |
- // Special case for a single catch. |
- var clause = clauses.single; |
- if (clause.exceptionParameter != null) { |
- name = clause.exceptionParameter.name; |
- } |
- } |
+ // TODO(jmesserly): need a better way to get a temporary variable. |
+ // This could incorrectly shadow a user's name. |
+ var name = '\$e'; |
- out.write('catch ($name) {\n', 2); |
- for (var clause in clauses) { |
- _visitCatchClause(clause, name); |
+ if (clauses.length == 1) { |
+ // Special case for a single catch. |
+ var clause = clauses.single; |
+ if (clause.exceptionParameter != null) { |
+ name = clause.exceptionParameter.name; |
} |
- out.write('}\n', -2); |
} |
- _visitNode(node.finallyBlock, prefix: 'finally '); |
+ |
+ var catchBody = _statement(clauses.map((c) => _visitCatchClause(c, name))); |
+ |
+ return new JS.Catch(new JS.VariableDeclaration(name), catchBody); |
} |
- void _visitCatchClause(CatchClause node, String varName) { |
- if (node.catchKeyword != null) { |
- if (node.exceptionType != null) { |
- out.write('if (dart.is($varName, '); |
- _writeTypeName(node.exceptionType.type); |
- out.write(')) {\n', 2); |
- } |
+ JS.Statement _statement(Iterable stmts) { |
+ var s = stmts is List ? stmts : new List<JS.Statement>.from(stmts); |
+ // TODO(jmesserly): empty block singleton? |
+ if (s.length == 0) return new JS.Block([]); |
+ if (s.length == 1) return s[0]; |
+ return new JS.Block(s); |
+ } |
+ JS.Statement _visitCatchClause(CatchClause node, String varName) { |
+ var body = []; |
+ if (node.catchKeyword != null) { |
var name = node.exceptionParameter; |
if (name != null && name.name != varName) { |
- out.write('let $name = $varName;\n'); |
+ body.add(js.statement('let # = #;', [name.accept(this), varName])); |
} |
- |
if (node.stackTraceParameter != null) { |
- var stackName = node.stackTraceParameter.name; |
- out.write('let $stackName = dart.stackTrace($name);\n'); |
+ var stackVar = node.stackTraceParameter.name; |
+ body.add(js.statement( |
+ 'let # = dart.stackTrace(#);', [stackVar, name.accept(this)])); |
} |
} |
- // If we can, avoid generating a nested { ... } block. |
- var body = node.body; |
- if (body is Block) { |
- body.statements.accept(this); |
- } else { |
- body.accept(this); |
- } |
+ body.add(node.body.accept(this)); |
- if (node.exceptionType != null) out.write('}\n', -2); |
+ if (node.exceptionType != null) { |
+ return js.statement('if (dart.is(#, #)) #;', [ |
+ varName, |
+ _emitTypeName(node.exceptionType.type), |
+ _statement(body) |
+ ]); |
+ } |
+ return _statement(body); |
} |
@override |
- void visitSwitchCase(SwitchCase node) { |
- _visitNodeList(node.labels, separator: " ", suffix: " "); |
- out.write("case "); |
- _visitNode(node.expression); |
- out.write(":\n", 2); |
- _visitNodeList(node.statements); |
- out.write('', -2); |
+ JS.Case visitSwitchCase(SwitchCase node) { |
+ var expr = node.expression.accept(this); |
+ var body = _visitList(node.statements); |
+ if (node.labels.isNotEmpty) { |
+ body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); |
+ } |
// TODO(jmesserly): make sure we are statically checking fall through |
+ return new JS.Case(expr, new JS.Block(body)); |
} |
@override |
- void visitSwitchDefault(SwitchDefault node) { |
- _visitNodeList(node.labels, separator: " ", suffix: " "); |
- out.write("default:\n", 2); |
- _visitNodeList(node.statements); |
- out.write('', -2); |
- } |
- |
- @override |
- void visitSwitchStatement(SwitchStatement node) { |
- out.write("switch ("); |
- _visitNode(node.expression); |
- out.write(") {\n", 2); |
- _visitNodeList(node.members); |
- out.write("}\n", -2); |
+ JS.Default visitSwitchDefault(SwitchDefault node) { |
+ var body = _visitList(node.statements); |
+ if (node.labels.isNotEmpty) { |
+ body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); |
+ } |
+ // TODO(jmesserly): make sure we are statically checking fall through |
+ return new JS.Default(new JS.Block(body)); |
} |
@override |
- void visitLabel(Label node) { |
- _visitNode(node.label); |
- out.write(':'); |
- } |
+ JS.Switch visitSwitchStatement(SwitchStatement node) => |
+ new JS.Switch(node.expression.accept(this), _visitList(node.members)); |
@override |
- void visitLabeledStatement(LabeledStatement node) { |
- _visitNodeList(node.labels, separator: " ", suffix: " "); |
- _visitNode(node.statement); |
+ JS.Statement visitLabeledStatement(LabeledStatement node) { |
+ var result = _visit(node.statement); |
+ for (var label in node.labels.reversed) { |
+ result = new JS.LabeledStatement(label.label.name, result); |
+ } |
+ return result; |
} |
@override |
- void visitIntegerLiteral(IntegerLiteral node) { |
- out.write('${node.value}'); |
- } |
+ visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); |
@override |
- void visitDoubleLiteral(DoubleLiteral node) { |
- out.write('${node.value}'); |
- } |
+ visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); |
@override |
- void visitNullLiteral(NullLiteral node) { |
- out.write('null'); |
- } |
+ visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); |
@override |
- void visitListLiteral(ListLiteral node) { |
+ visitListLiteral(ListLiteral node) { |
+ // TODO(jmesserly): make this faster. We're wasting an array. |
+ var list = js.call('new List.from(#)', [ |
+ new JS.ArrayInitializer(_visitList(node.elements)) |
+ ]); |
if (node.constKeyword != null) { |
- out.write('/* Unimplemented const */'); |
+ list = js.commentExpression('Unimplemented const', list); |
} |
- // TODO(jmesserly): make this faster. |
- out.write('new List.from(['); |
- _visitNodeList(node.elements, separator: ', '); |
- out.write('])'); |
+ return list; |
} |
@override |
- void visitMapLiteral(MapLiteral node) { |
- out.write('dart.map('); |
+ visitMapLiteral(MapLiteral node) { |
var entries = node.entries; |
- if (entries != null && entries.isNotEmpty) { |
- // Use JS object literal notation if possible, otherwise use an array. |
- if (entries.every((e) => e.key is SimpleStringLiteral)) { |
- out.write('{\n', 2); |
- _visitMapLiteralEntries(entries, separator: ': '); |
- out.write('\n}', -2); |
- } else { |
- out.write('[\n', 2); |
- _visitMapLiteralEntries(entries, separator: ', '); |
- out.write('\n]', -2); |
+ var mapArguments = null; |
+ if (entries.isEmpty) return js.call('dart.map()'); |
+ |
+ // Use JS object literal notation if possible, otherwise use an array. |
+ if (entries.every((e) => e.key is SimpleStringLiteral)) { |
+ var props = []; |
+ for (var e in entries) { |
+ var key = (e.key as SimpleStringLiteral).value; |
+ var value = e.value.accept(this); |
+ props.add(new JS.Property(js.escapedString(key), value)); |
+ } |
+ mapArguments = new JS.ObjectInitializer(props); |
+ } else { |
+ var values = []; |
+ for (var e in entries) { |
+ values.add(e.key.accept(this)); |
+ values.add(e.value.accept(this)); |
} |
+ mapArguments = new JS.ArrayInitializer(values); |
} |
- out.write(')'); |
+ return js.call('dart.map(#)', [mapArguments]); |
} |
- void _visitMapLiteralEntries(NodeList<MapLiteralEntry> nodes, |
- {String separator}) { |
- if (nodes == null) return; |
- int size = nodes.length; |
- if (size == 0) return; |
+ @override |
+ JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
+ js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
- for (int i = 0; i < size; i++) { |
- if (i > 0) out.write(',\n'); |
- var node = nodes[i]; |
- node.key.accept(this); |
- out.write(separator); |
- node.value.accept(this); |
- } |
- } |
+ @override |
+ JS.Expression visitAdjacentStrings(AdjacentStrings node) => |
+ _visitListToBinary(node.strings, '+'); |
@override |
- void visitSimpleStringLiteral(SimpleStringLiteral node) { |
- if (node.isSingleQuoted) { |
- var escaped = _escapeForJs(node.stringValue, "'"); |
- out.write("'$escaped'"); |
- } else { |
- var escaped = _escapeForJs(node.stringValue, '"'); |
- out.write('"$escaped"'); |
- } |
+ JS.TemplateString visitStringInterpolation(StringInterpolation node) { |
+ // Assuming we implement toString() on our objects, we can avoid calling it |
+ // in most cases. Builtin types may differ though. We could handle this with |
+ // a tagged template. |
+ return new JS.TemplateString(_visitList(node.elements)); |
} |
@override |
- void visitAdjacentStrings(AdjacentStrings node) { |
- // These are typically used for splitting long strings across lines, so |
- // generate accordingly, with each on its own line and +4 indent. |
- |
- // TODO(jmesserly): we could linebreak before the first string too, but |
- // that means inserting a linebreak in expression context, which might |
- // not be valid and leaves trailing whitespace. |
- for (int i = 0, last = node.strings.length - 1; i <= last; i++) { |
- if (i == 1) { |
- out.write(' +\n', 4); |
- } else if (i > 1) { |
- out.write(' +\n'); |
- } |
- node.strings[i].accept(this); |
- if (i == last && i > 0) { |
- out.write('', -4); |
- } |
- } |
+ String visitInterpolationString(InterpolationString node) { |
+ // TODO(jmesserly): this call adds quotes, and then we strip them off. |
+ var str = js.escapedString(node.value, '`').value; |
+ return str.substring(1, str.length - 1); |
} |
@override |
- void visitStringInterpolation(StringInterpolation node) { |
- out.write('`'); |
- _visitNodeList(node.elements); |
- out.write('`'); |
- } |
- |
- @override |
- void visitInterpolationString(InterpolationString node) { |
- out.write(_escapeForJs(node.value, '`')); |
- } |
- |
- /// Escapes the string from [value], handling escape sequences as needed. |
- /// The surrounding [quote] style must be supplied to know which quotes to |
- /// escape, but quotes are not added to the resulting string. |
- String _escapeForJs(String value, String quote) { |
- // Start by escaping the backslashes. |
- String escaped = value.replaceAll('\\', '\\\\'); |
- // Do not escape unicode characters and ' because they are allowed in the |
- // string literal anyway. |
- return escaped.replaceAllMapped(new RegExp('\n|$quote|\b|\t|\v'), (m) { |
- switch (m.group(0)) { |
- case "\n": |
- return r"\n"; |
- case "\b": |
- return r"\b"; |
- case "\t": |
- return r"\t"; |
- case "\f": |
- return r"\f"; |
- case "\v": |
- return r"\v"; |
- // Quotes are only replaced if they conflict with the containing quote |
- case '"': |
- return r'\"'; |
- case "'": |
- return r"\'"; |
- case "`": |
- return r"\`"; |
- } |
- }); |
- } |
+ visitInterpolationExpression(InterpolationExpression node) => |
+ node.expression.accept(this); |
@override |
- void visitInterpolationExpression(InterpolationExpression node) { |
- out.write('\${'); |
- node.expression.accept(this); |
- // Assuming we implement toString() on our objects, we can avoid calling it |
- // in most cases. Builtin types may differ though. |
- // For example, Dart's concrete List type does not have the same toString |
- // as Array.prototype.toString(). |
- out.write('}'); |
- } |
+ visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value); |
@override |
- void visitBooleanLiteral(BooleanLiteral node) { |
- out.write('${node.value}'); |
- } |
+ JS.Statement visitDeclaration(Declaration node) => |
+ js.comment('Unimplemented ${node.runtimeType}: $node'); |
+ |
+ @override |
+ JS.Statement visitStatement(Statement node) => |
+ js.comment('Unimplemented ${node.runtimeType}: $node'); |
@override |
- void visitDirective(Directive node) {} |
+ JS.Expression visitExpression(Expression node) => |
+ _unimplementedCall('Unimplemented ${node.runtimeType}: $node'); |
+ |
+ JS.Expression _unimplementedCall(String comment) { |
+ return js.call('dart.throw_(#)', [js.escapedString(comment)]); |
+ } |
@override |
- void visitNode(AstNode node) { |
- out.write('/* Unimplemented ${node.runtimeType}: $node */'); |
+ visitNode(AstNode node) { |
+ // TODO(jmesserly): verify this is unreachable. |
+ throw 'Unimplemented ${node.runtimeType}: $node'; |
} |
// TODO(jmesserly): this is used to determine if the field initialization is |
@@ -1775,50 +1546,34 @@ $name.prototype[Symbol.iterator] = function() { |
return element is TopLevelVariableElement && !element.isConst; |
} |
- /// Safely visit the expression, adding parentheses if necessary |
- void _visitExpression(Expression node, int newPrecedence) { |
- if (node == null) return; |
+ _visit(AstNode node) => node != null ? node.accept(this) : null; |
- // If we're going to replace an expression with a higher-precedence |
- // operator, add parenthesis around it if needed. |
- var needParens = node.precedence < newPrecedence; |
- if (needParens) out.write('('); |
- node.accept(this); |
- if (needParens) out.write(')'); |
+ JS.Statement _visitOrEmpty(AstNode node) { |
+ if (node == null) return new JS.EmptyStatement(); |
+ return node.accept(this); |
} |
- /// Safely visit the given node, with an optional prefix or suffix. |
- void _visitNode(AstNode node, {String prefix: '', String suffix: ''}) { |
- if (node == null) return; |
- |
- out.write(prefix); |
- node.accept(this); |
- out.write(suffix); |
+ List _visitList(Iterable<AstNode> nodes) { |
+ if (nodes == null) return null; |
+ var result = []; |
+ for (var node in nodes) result.add(node.accept(this)); |
+ return result; |
} |
- /// Print a list of nodes, with an optional prefix, suffix, and separator. |
- void _visitNodeList(List<AstNode> nodes, |
- {String prefix: '', String suffix: '', String separator: ''}) { |
- if (nodes == null) return; |
+ /// Visits a list of expressions, creating a comma expression if needed in JS. |
+ JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { |
+ if (nodes == null || nodes.isEmpty) return null; |
- int size = nodes.length; |
- if (size == 0) return; |
- |
- out.write(prefix); |
- for (int i = 0; i < size; i++) { |
- if (i > 0) out.write(separator); |
- nodes[i].accept(this); |
+ JS.Expression result = null; |
+ for (var node in nodes) { |
+ var jsExpr = node.accept(this); |
+ if (result == null) { |
+ result = jsExpr; |
+ } else { |
+ result = new JS.Binary(operator, result, jsExpr); |
+ } |
} |
- out.write(suffix); |
- } |
- |
- /// Safely visit the given node, printing the suffix after the node if it is |
- /// non-`null`. |
- void visitToken(Token token, {String prefix: '', String suffix: ''}) { |
- if (token == null) return; |
- out.write(prefix); |
- out.write(token.lexeme); |
- out.write(suffix); |
+ return result; |
} |
/// The following names are allowed for user-defined operators: |
@@ -1839,50 +1594,18 @@ $name.prototype[Symbol.iterator] = function() { |
/// |
/// Equality is a bit special, it is generated via the Dart `equals` runtime |
/// helper, that checks for null. The user defined method is called '=='. |
- String _canonicalMethodName(String name) { |
- switch (name) { |
- case '[]': |
- return 'get'; |
- case '[]=': |
- return 'set'; |
- case '<': |
- case '>': |
- case '<=': |
- case '>=': |
- case '==': |
- case '-': |
- case '+': |
- case '/': |
- case '~/': |
- case '*': |
- case '%': |
- case '|': |
- case '^': |
- case '&': |
- case '<<': |
- case '>>': |
- case '~': |
- return "['$name']"; |
- default: |
- return name; |
- } |
- } |
- |
- /// The string to invoke a canonical method name, for example: |
- /// |
- /// "[]" returns ".get" |
- /// "+" returns "['+']" |
- String _canonicalMethodInvoke(String name) { |
- name = _canonicalMethodName(name); |
- return name.startsWith('[') ? name : '.$name'; |
+ String _jsMethodName(String name) { |
+ if (name == '[]') return 'get'; |
+ if (name == '[]=') return 'set'; |
+ return name; |
} |
- bool _needsBindThis(node) { |
- if (currentClass == null) return false; |
+ String _maybeBindThis(node) { |
+ if (currentClass == null) return ''; |
var visitor = _BindThisVisitor._instance; |
visitor._bindThis = false; |
node.accept(visitor); |
- return visitor._bindThis; |
+ return visitor._bindThis ? '.bind(this)' : ''; |
} |
static bool _needsImplicitThis(Element e) => |
@@ -1964,11 +1687,18 @@ class JSGenerator extends CodeGenerator { |
void generateLibrary(Iterable<CompilationUnit> units, LibraryInfo info, |
CheckerReporter reporter) { |
+ JS.Block jsTree = |
+ new JSCodegenVisitor(info, rules).generateLibrary(units, reporter); |
+ |
var outputPath = path.join(outDir, jsOutputPath(info)); |
new Directory(path.dirname(outputPath)).createSync(recursive: true); |
- var out = new OutWriter(outputPath); |
- new JSCodegenVisitor(info, rules, out).generateLibrary(units, reporter); |
- out.close(); |
+ |
+ var context = new JS.SimpleJavaScriptPrintingContext(); |
+ var opts = |
+ new JS.JavaScriptPrintingOptions(avoidKeywordsInIdentifiers: true); |
+ var printer = new JS.Printer(opts, context); |
+ printer.blockOutWithoutBraces(jsTree); |
+ new File(outputPath).writeAsStringSync(context.getText()); |
} |
} |