| 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) {
|
| - _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)]);
|
| }
|
|
|
| - 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');
|
| + 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());
|
| }
|
| }
|
|
|
|
|