Files
sdk/pkg/dev_compiler/lib/codegenerator.dart
T
Justin Fagnani 3546759833 More constructor codegen:
* constructor parameters
* named constructors
* initializer lists
* superclasses and super constructor calls
2014-12-05 15:21:28 -08:00

659 lines
18 KiB
Dart

library codegenerator;
import 'dart:io';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'src/static_info.dart';
import 'src/type_rules.dart';
import 'typechecker.dart';
class OutWriter {
IOSink _sink;
int _indent = 0;
String _prefix = "";
bool newline = true;
OutWriter(String path) {
var file = new File(path);
file.createSync();
_sink = file.openWrite();
}
void write(String string, [int indent = 0]) {
if (indent < 0) inc(indent);
var lines = string.split('\n');
var length = lines.length;
for (var i = 0; i < length - 1; ++i) {
var prefix = (lines[i].isNotEmpty && (newline || i > 0)) ? _prefix : '';
_sink.write('$prefix${lines[i]}\n');
}
var last = lines.last;
if (last.isNotEmpty && (newline && length == 1 || length > 1)) {
_sink.write(_prefix);
}
_sink.write(last);
newline = last.isEmpty;
if (indent > 0) inc(indent);
}
void inc([int n = 2]) {
_indent = _indent + n;
assert(_indent >= 0);
_prefix = "".padRight(_indent);
}
void dec([int n = 2]) {
_indent = _indent - n;
assert(_indent >= 0);
_prefix = "".padRight(_indent);
}
void close() {
_sink.close();
}
}
class UnitGenerator extends GeneralizingAstVisitor {
final Uri uri;
final Directory directory;
final String libName;
final CompilationUnit unit;
final Map<AstNode, List<StaticInfo>> infoMap;
final TypeRules rules;
OutWriter out = null;
UnitGenerator(
this.uri, this.unit, this.directory, this.libName, this.infoMap,
this.rules);
DynamicInvoke _processDynamicInvoke(AstNode node) {
DynamicInvoke result = null;
if (infoMap.containsKey(node)) {
var infos = infoMap[node];
for (var info in infos) {
if (info is DynamicInvoke) {
assert(result == null);
result = info;
}
}
if (result != null) infos.remove(result);
}
return result;
}
void _reportUnimplementedConversions(AstNode node) {
if (infoMap.containsKey(node) && infoMap[node].isNotEmpty) {
out.write('/* Unimplemented: [ ');
for (var info in infoMap[node]) {
assert(info is Conversion);
out.write('${info.description}');
}
out.write('] */ ');
}
}
String path() {
var tail = uri.pathSegments.last;
return directory.path + Platform.pathSeparator + tail + '.js';
}
void generate() {
out = new OutWriter(path());
out.write("""
var $libName;
(function ($libName) {
""", 2);
unit.visitChildren(this);
out.write("""
})($libName || ($libName = {}));
""", -2);
out.close();
}
bool isPublic(String name) => !name.startsWith('_');
visitFunctionTypeAlias(FunctionTypeAlias node) {
// TODO(vsm): Do we need to record type info the generated code for a
// typedef?
_reportUnimplementedConversions(node);
return node;
}
void generateInitializer(ClassDeclaration node,
List<String> initializedFields) {
// TODO: generate one field initialzing function per-class and one
// initializer list function per constructor. Inline if possible.
var params = (['_this']..addAll(initializedFields)).join(', ');
out.write("""
var _initializer = (function ($params) {
""", 2);
var members = node.members;
for (var member in members) {
if (member is FieldDeclaration) {
if (!member.isStatic) {
for (var field in member.fields.variables) {
var name = field.name.name;
var initializer = field.initializer;
if (initializer != null) {
// TODO(vsm): Check for conversion.
out.write("_this.$name = ");
initializer.accept(this);
out.write(";\n");
} else if (!initializedFields.contains(name)) {
out.write("_this.$name = null;\n");
}
if (initializedFields.contains(name)) {
// TODO: track whether this field is always constructor
// initialized, if so, don't emit the check for undefined
out.write("_this.$name = ($name === void 0) ? null : $name;\n");
}
}
}
}
}
out.write("""
});
""", -2);
}
Iterable<String> getFieldFormalParameters(ConstructorDeclaration ctor) =>
ctor.parameters.parameters
.where((p) => p is FieldFormalParameter)
.map((FieldFormalParameter p) => p.identifier.name);
void generateConstructor(ConstructorDeclaration ctor, String name,
List<String> initializedFields, bool needsInitializer) {
var fieldParameters = ctor == null
? []
: getFieldFormalParameters(ctor);
var initializers = ctor == null
? {}
: new Map.fromIterable(
ctor.initializers.where((i) => i is ConstructorFieldInitializer),
key: (i) => i.fieldName.name);
out.write("function $name(");
if (ctor != null) ctor.parameters.accept(this);
out.write(") {\n");
if (needsInitializer) {
out.write(" _initializer(this");
for (var field in initializedFields) {
out.write(", ");
if (fieldParameters.contains(field)) {
out.write(field);
} else if (initializers.containsKey(field)) {
initializers[field].expression.accept(this);
} else {
out.write('undefined');
}
}
out.write(");\n");
}
if (ctor != null) {
var superCall = ctor.initializers.firstWhere(
(i) => i is SuperConstructorInvocation,
orElse: () => null);
if (superCall != null) {
var superName = superCall.constructorName;
var args = superCall.argumentList.arguments
.map((a) => a.toString());
var superArgs = (['this']..addAll(args)).join(', ');
var superSelector = superName == null ? '' : '.$superName';
out.write(" _super$superSelector.call($superArgs);\n");
}
}
// ctor.body.accept(this);
out.write("};\n");
}
void generateDefaultConstructor(ClassDeclaration node,
List<String> initializedFields, bool needsInitializer) {
var name = node.name.name;
var ctors = node.members.where((m) => m is ConstructorDeclaration);
var ctor = ctors.firstWhere((m) => m.name == null, orElse: () => null);
if (ctor == null && ctors.isNotEmpty) {
out.write("""
var constructor = function $name() {
throw "no default constructor";
}\n""");
} else {
out.write("var constructor = ");
generateConstructor(ctor, name, initializedFields, needsInitializer);
}
out.write("dart_runtime.dextend(constructor, _super);\n");
}
void generateNamedConstructors(ClassDeclaration node,
List<String> initializedFields, bool needsInitializer) {
var ctors = node.members
.where((m) => m is ConstructorDeclaration && m.name != null);
for (var ctor in ctors) {
var name = ctor.name.name;
out.write("constructor.$name = ");
generateConstructor(ctor, node.name.name, initializedFields,
needsInitializer);
out.write("""
constructor.$name.prototype = constructor.prototype;
""");
}
}
/**
* Returns a list of fields set via constructors. This forms the parameter
* list for _initialize().
*/
Set<String> getConstructorInitializedFields(ClassDeclaration node) {
var ctors = node.members.where((m) => m is ConstructorDeclaration);
var fields = new Set<String>();
for (var ctor in ctors) {
ConstructorDeclaration c = ctor;
// initializer list
var initializers = c.initializers
.where((i) => i is ConstructorFieldInitializer)
.map((ConstructorFieldInitializer i) => i.fieldName.name);
fields.addAll(initializers);
// field parameters: this.foo
var parameters = getFieldFormalParameters(ctor);
fields.addAll(parameters);
}
return fields;
}
String getSuperclassName(ClassDeclaration node) {
var element = node.element;
var superclass = node.element.supertype.element;
if (superclass.library == element.library) return superclass.name;
var libName = superclass.library.name;
if (libName == 'dart.core') libName = 'dart_core';
return '$libName.${superclass.name}';
}
AstNode visitClassDeclaration(ClassDeclaration node) {
_reportUnimplementedConversions(node);
var name = node.name.name;
var superclassName = getSuperclassName(node);
out.write("""
// Class $name
var $name = (function (_super) {
""", 2);
// TODO(vsm): Process constructors, fields, and methods properly.
// Generate default only when needed.
var needsInitializer = node.members.where((m) => m is FieldDeclaration)
.isNotEmpty;
var initializedFields = getConstructorInitializedFields(node).toList();
if (needsInitializer) {
generateInitializer(node, initializedFields);
}
generateDefaultConstructor(node, initializedFields,
needsInitializer);
generateNamedConstructors(node, initializedFields, needsInitializer);
// TODO(vsm): What should we generate if there is no unnamed constructor
// for this class?
out.write("""
return constructor;
})($superclassName);
""", -2);
if (isPublic(name)) out.write("$libName.$name = $name;\n");
out.write("\n");
return node;
}
AstNode visitFunctionDeclaration(FunctionDeclaration node) {
_reportUnimplementedConversions(node);
var name = node.name.name;
assert(node.parent is CompilationUnit);
out.write("// Function $name: ${node.element.type}\n");
out.write("function $name(");
node.functionExpression.parameters.accept(this);
out.write(") {\n", 2);
node.functionExpression.body.accept(this);
out.write("}\n", -2);
if (isPublic(name)) out.write("$libName.$name = $name;\n");
out.write("\n");
return node;
}
AstNode visitFunctionExpression(FunctionExpression node) {
_reportUnimplementedConversions(node);
// Bind all free variables.
out.write('/* Unimplemented: bind any free variables. */');
out.write("function (");
node.parameters.accept(this);
out.write(") {\n", 2);
node.body.accept(this);
out.write("}\n", -2);
return node;
}
AstNode visitSimpleIdentifier(SimpleIdentifier node) {
_reportUnimplementedConversions(node);
out.write(node.name);
return node;
}
AstNode visitExpressionFunctionBody(ExpressionFunctionBody node) {
_reportUnimplementedConversions(node);
out.write("return ");
// TODO(vsm): Check for conversion.
node.expression.accept(this);
out.write(";\n");
return node;
}
AstNode visitMethodInvocation(MethodInvocation node) {
// TODO(vsm): Check dynamic.
_reportUnimplementedConversions(node);
writeQualifiedName(node.target, node.methodName);
out.write('(');
node.argumentList.accept(this);
out.write(')');
return node;
}
AstNode visitArgumentList(ArgumentList node) {
_reportUnimplementedConversions(node);
// TODO(vsm): Optional parameters.
var arguments = node.arguments;
var length = arguments.length;
if (length > 0) {
// TODO(vsm): Check for conversion.
arguments[0].accept(this);
for (var i = 1; i < length; ++i) {
out.write(', ');
// TODO(vsm): Check for conversion.
arguments[i].accept(this);
}
}
return node;
}
AstNode visitFormalParameterList(FormalParameterList node) {
_reportUnimplementedConversions(node);
// TODO(vsm): Optional parameters.
var arguments = node.parameters;
var length = arguments.length;
if (length > 0) {
arguments[0].accept(this);
for (var i = 1; i < length; ++i) {
out.write(', ');
arguments[i].accept(this);
}
}
return node;
}
AstNode visitFieldFormalParameter(FieldFormalParameter node) {
_reportUnimplementedConversions(node);
out.write(node.identifier.name);
return node;
}
AstNode visitBlockFunctionBody(BlockFunctionBody node) {
_reportUnimplementedConversions(node);
var statements = node.block.statements;
for (var statement in statements) statement.accept(this);
return node;
}
AstNode visitExpressionStatement(ExpressionStatement node) {
_reportUnimplementedConversions(node);
node.expression.accept(this);
out.write(';\n');
return node;
}
void _generateVariableList(VariableDeclarationList list, bool lazy) {
// TODO(vsm): Detect when we can avoid wrapping in function.
var prefix = lazy ? 'function () { return ' : '';
var postfix = lazy ? '; }()' : '';
var declarations = list.variables;
for (var declaration in declarations) {
var name = declaration.name.name;
var initializer = declaration.initializer;
if (initializer == null) {
out.write('var $name;\n');
} else {
out.write('var $name = $prefix');
initializer.accept(this);
out.write('$postfix;\n');
}
}
}
AstNode visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_reportUnimplementedConversions(node);
_generateVariableList(node.variables, true);
return node;
}
AstNode visitVariableDeclarationStatement(VariableDeclarationStatement node) {
_reportUnimplementedConversions(node);
_generateVariableList(node.variables, false);
return node;
}
AstNode visitConstructorName(ConstructorName node) {
_reportUnimplementedConversions(node);
node.type.name.accept(this);
if (node.name != null) {
out.write('.');
node.name.accept(this);
}
return node;
}
AstNode visitInstanceCreationExpression(InstanceCreationExpression node) {
_reportUnimplementedConversions(node);
out.write('new ');
node.constructorName.accept(this);
out.write('(');
node.argumentList.accept(this);
out.write(')');
return node;
}
AstNode visitBinaryExpression(BinaryExpression node) {
_reportUnimplementedConversions(node);
var op = node.operator;
var lhs = node.leftOperand;
var rhs = node.rightOperand;
var dispatchType = rules.getStaticType(lhs);
if (rules.isPrimitive(dispatchType)) {
// TODO(vsm): When do Dart ops not map to JS?
assert(rules.isPrimitive(rules.getStaticType(rhs)));
lhs.accept(this);
out.write(' $op ');
rhs.accept(this);
} else {
// TODO(vsm): Figure out operator calling convention / dispatch.
out.write('/* Unimplemented binary operator: $node */');
}
return node;
}
AstNode visitParenthesizedExpression(ParenthesizedExpression node) {
_reportUnimplementedConversions(node);
out.write('(');
node.expression.accept(this);
out.write(')');
return node;
}
AstNode visitSimpleFormalParameter(SimpleFormalParameter node) {
_reportUnimplementedConversions(node);
node.identifier.accept(this);
return node;
}
AstNode visitPrefixedIdentifier(PrefixedIdentifier node) {
var dynamicInvoke = _processDynamicInvoke(node);
_reportUnimplementedConversions(node);
if (dynamicInvoke != null) {
out.write('dart_runtime.dload(');
node.prefix.accept(this);
out.write(', "');
node.identifier.accept(this);
out.write('")');
} else {
node.prefix.accept(this);
out.write('.');
node.identifier.accept(this);
}
return node;
}
AstNode visitIntegerLiteral(IntegerLiteral node) {
_reportUnimplementedConversions(node);
out.write('${node.value}');
return node;
}
AstNode visitStringLiteral(StringLiteral node) {
_reportUnimplementedConversions(node);
out.write('"${node.stringValue}"');
return node;
}
AstNode visitBooleanLiteral(BooleanLiteral node) {
_reportUnimplementedConversions(node);
out.write('${node.value}');
return node;
}
AstNode visitDirective(Directive node) {
_reportUnimplementedConversions(node);
return node;
}
AstNode visitNode(AstNode node) {
_reportUnimplementedConversions(node);
out.write('/* Unimplemented ${node.runtimeType}: $node */');
return node;
}
static const Map<String, String> _builtins = const <String, String>{
'dart.core': 'dart_core',
};
void writeQualifiedName(Expression target, SimpleIdentifier id) {
if (target != null) {
target.accept(this);
out.write('.');
} else {
var element = id.staticElement;
if (element.enclosingElement is CompilationUnitElement) {
var library = element.enclosingElement.enclosingElement;
assert(library is LibraryElement);
var package = library.name;
var libname = _builtins.containsKey(package) ?
_builtins[package] : package;
out.write('$libname.');
}
}
id.accept(this);
}
}
class LibraryGenerator {
final String name;
final Library library;
final Directory dir;
final Map<AstNode, List<StaticInfo>> info;
final TypeRules rules;
LibraryGenerator(this.name, this.library, this.dir, this.info, this.rules);
void generateUnit(Uri uri, CompilationUnit unit) {
var unitGen = new UnitGenerator(uri, unit, dir, name, info, rules);
unitGen.generate();
}
void generate() {
generateUnit(library.uri, library.lib);
library.parts.forEach((Uri uri, CompilationUnit unit) {
generateUnit(uri, unit);
});
}
}
class CodeGenerator {
final String outDir;
final Uri root;
final Map<Uri, Library> libraries;
final Map<AstNode, List<StaticInfo>> info;
final TypeRules rules;
CodeGenerator(this.outDir, this.root, this.libraries, this.info, this.rules);
String _libName(Library lib) {
for (var directive in lib.lib.directives) {
if (directive is LibraryDirective) return directive.name.toString();
}
// Fall back on the file name.
var tail = lib.uri.pathSegments.last;
if (tail.endsWith('.dart')) tail = tail.substring(0, tail.length - 5);
return tail;
}
void generate() {
var base = Uri.base;
var out = base.resolve(outDir + '/');
var top = new Directory.fromUri(out);
top.createSync();
libraries.forEach((Uri uri, Library lib) {
var name = _libName(lib);
var dir = new Directory.fromUri(out.resolve(name));
dir.createSync();
var libgen = new LibraryGenerator(name, lib, dir, info, rules);
libgen.generate();
});
}
}