Skip to content

Commit

Permalink
implement exports, fixes #141
Browse files Browse the repository at this point in the history
R=vsm@google.com

Review URL: https://codereview.chromium.org/1263583005 .
  • Loading branch information
John Messerly committed Aug 5, 2015
1 parent 2535238 commit cbe23ee
Show file tree
Hide file tree
Showing 36 changed files with 441 additions and 65 deletions.
2 changes: 1 addition & 1 deletion pkg/dev_compiler/lib/runtime/dart/_js_mirrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ dart_library.library('dart/_js_mirrors', null, /* Imports */[
namedArgs = null;
dart.assert(getName(constructorName) == "");
dart.assert(namedArgs == null || dart.notNull(namedArgs.isEmpty));
let instance = exports._dart.instantiate(this[_cls], args);
let instance = new this[_cls](...args);
return new JsInstanceMirror._(instance);
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/dev_compiler/lib/runtime/dart_runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ dart_library.library('dart_runtime/dart', null, /* Imports */[
]);

// From dart_utils
exportFrom(dart_utils, ['copyProperties', 'instantiate']);
exportFrom(dart_utils, ['copyProperties', 'export']);
// Renames
exports.defineLazyClass = _export(dart_utils.defineLazy);
exports.defineLazyProperties = _export(dart_utils.defineLazy);
Expand Down
56 changes: 28 additions & 28 deletions pkg/dev_compiler/lib/runtime/dart_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,32 @@ var dart_utils =
* Defines a lazy property.
* After initial get or set, it will replace itself with a value property.
*/
// TODO(jmesserly): is this the best implementation for JS engines?
// TODO(jmesserly): reusing descriptor objects has been shown to improve
// performance in other projects (e.g. webcomponents.js ShadowDOM polyfill).
function defineLazyProperty(to, name, desc) {
let init = desc.get;
let writable = !!desc.set;
function lazySetter(value) {
defineProperty(to, name, { value: value, writable: writable });
let value = null;

function lazySetter(x) {
init = null;
value = x;
}
function circularInitError() {
throwError('circular initialization for field ' + name);
}
function lazyGetter() {
// Clear the init function to detect circular initialization.
let f = init;
if (f === null) {
throwError('circular initialization for field ' + name);
}
init = null;
if (init == null) return value;

// Compute and store the value.
let value = f();
lazySetter(value);
// Compute and store the value, guarding against reentry.
let f = init;
init = circularInitError;
lazySetter(f());
return value;
}
desc.get = lazyGetter;
desc.configurable = true;
if (writable) desc.set = lazySetter;
defineProperty(to, name, desc);
if (desc.set) desc.set = lazySetter;
return defineProperty(to, name, desc);
}
dart_utils.defineLazyProperty = defineLazyProperty;

Expand All @@ -85,15 +85,8 @@ var dart_utils =
}
dart_utils.defineLazy = defineLazy;

function defineMemoizedGetter(obj, name, get) {
let cache = null;
function getter() {
if (cache != null) return cache;
cache = get();
get = null;
return cache;
}
defineProperty(obj, name, {get: getter, configurable: true});
function defineMemoizedGetter(obj, name, getter) {
return defineLazyProperty(obj, name, {get: getter});
}
dart_utils.defineMemoizedGetter = defineMemoizedGetter;

Expand All @@ -114,10 +107,17 @@ var dart_utils =
}
dart_utils.copyProperties = copyProperties;


function instantiate(type, args) {
return new type(...args);
/** Exports from one Dart module to another. */
function export_(to, from, show, hide) {
if (show == void 0) {
show = getOwnNamesAndSymbols(from);
}
if (hide != void 0) {
var hideMap = new Map(hide);
show = show.filter((k) => !hideMap.has(k));
}
return copyTheseProperties(to, from, show);
}
dart_utils.instantiate = instantiate;
dart_utils.export = export_;

})(dart_utils);
124 changes: 96 additions & 28 deletions pkg/dev_compiler/lib/src/codegen/js_codegen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import 'package:analyzer/src/generated/scanner.dart'
show StringToken, Token, TokenType;
import 'package:analyzer/src/task/dart.dart' show PublicNamespaceBuilder;

import 'package:dev_compiler/src/codegen/ast_builder.dart' show AstBuilder;
import 'package:dev_compiler/src/codegen/reify_coercions.dart'
Expand Down Expand Up @@ -95,6 +96,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
/// _interceptors.JSArray<E>, used for List literals.
ClassElement _jsArray;

/// The default value of the module object. See [visitLibraryDirective].
String _jsModuleValue;

Map<String, DartType> _objectMembers;

JSCodegenVisitor(AbstractCompiler compiler, this.currentLibrary,
Expand All @@ -115,42 +119,41 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
TypeProvider get types => rules.provider;

JS.Program emitLibrary(LibraryUnit library) {
String jsDefaultValue = null;

// Modify the AST to make coercions explicit.
new CoercionReifier(library, compiler).reify();

var unit = library.library;
if (unit.directives.isNotEmpty) {
var libraryDir = unit.directives.first;
if (libraryDir is LibraryDirective) {
var jsName = findAnnotation(libraryDir.element, _isJsNameAnnotation);
jsDefaultValue =
getConstantField(jsName, 'name', types.stringType) as String;
}
// Build the public namespace for this library. This allows us to do
// constant time lookups (contrast with `Element.getChild(name)`).
if (currentLibrary.publicNamespace == null) {
(currentLibrary as LibraryElementImpl).publicNamespace =
new PublicNamespaceBuilder().build(currentLibrary);
}

// TODO(jmesserly): visit scriptTag, directives?
library.library.directives.forEach(_visit);

// Rather than directly visit declarations, we instead use [_loader] to
// visit them. It has the ability to sort elements on demand, so
// dependencies between top level items are handled with a minimal
// reordering of the user's input code. The loader will call back into
// this visitor via [_emitModuleItem] when it's ready to visit the item
// for real.
_loader.collectElements(currentLibrary, library.partsThenLibrary);

for (var unit in library.partsThenLibrary) {
_constField = new ConstFieldVisitor(types, unit);

for (var decl in unit.declarations) {
if (decl is TopLevelVariableDeclaration) {
_visit(decl);
visitTopLevelVariableDeclaration(decl);
} else {
_loader.loadDeclaration(decl, decl.element);
}
if (decl is ClassDeclaration) {
// Static fields can be emitted into the top-level code, so they need
// to potentially be ordered independently of the class.
for (var member in decl.members) {
if (member is FieldDeclaration && member.isStatic) {
for (var f in member.fields.variables) {
_loader.loadDeclaration(f, f.element);
}
if (member is FieldDeclaration) {
visitFieldDeclaration(member);
}
}
}
Expand Down Expand Up @@ -208,19 +211,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
var module = js.call("function(#) { 'use strict'; #; #; }",
[params, dartxImport, _moduleItems]);

var program = <JS.Statement>[
js.statement("dart_library.library(#, #, #, #, #)", [
js.string(jsPath, "'"),
jsDefaultValue ?? new JS.LiteralNull(),
js.commentExpression(
"Imports", new JS.ArrayInitializer(imports, multiline: true)),
js.commentExpression("Lazy imports",
new JS.ArrayInitializer(lazyImports, multiline: true)),
module
])
];
var moduleDef = js.statement("dart_library.library(#, #, #, #, #)", [
js.string(jsPath, "'"),
_jsModuleValue ?? new JS.LiteralNull(),
js.commentExpression(
"Imports", new JS.ArrayInitializer(imports, multiline: true)),
js.commentExpression("Lazy imports",
new JS.ArrayInitializer(lazyImports, multiline: true)),
module
]);

var jsBin = compiler.options.runnerOptions.v8Binary;

return new JS.Program(program);
String scriptTag = null;
if (library.library.scriptTag != null) scriptTag = '/usr/bin/env $jsBin';
return new JS.Program(<JS.Statement>[moduleDef], scriptTag: scriptTag);
}

void _emitModuleItem(AstNode node) {
Expand All @@ -232,6 +237,54 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
if (code != null) _moduleItems.add(code);
}

@override
void visitLibraryDirective(LibraryDirective node) {
assert(_jsModuleValue == null);

var jsName = findAnnotation(node.element, _isJsNameAnnotation);
_jsModuleValue =
getConstantField(jsName, 'name', types.stringType) as String;
}

@override
void visitImportDirective(ImportDirective node) {
// Nothing to do yet, but we'll want to convert this to an ES6 import once
// we have support for modules.
}

@override void visitPartDirective(PartDirective node) {}
@override void visitPartOfDirective(PartOfDirective node) {}

@override
void visitExportDirective(ExportDirective node) {
var exportName = _libraryName(node.uriElement);

var currentLibNames = currentLibrary.publicNamespace.definedNames;

var args = [_exportsVar, exportName];
if (node.combinators.isNotEmpty) {
var shownNames = <JS.Expression>[];
var hiddenNames = <JS.Expression>[];

var show = node.combinators.firstWhere((c) => c is ShowCombinator,
orElse: () => null) as ShowCombinator;
var hide = node.combinators.firstWhere((c) => c is HideCombinator,
orElse: () => null) as HideCombinator;
if (show != null) {
shownNames.addAll(show.shownNames
.map((i) => i.name)
.where((s) => !currentLibNames.containsKey(s))
.map((s) => js.string(s, "'")));
}
if (hide != null) {
hiddenNames.addAll(hide.hiddenNames.map((i) => js.string(i.name, "'")));
}
args.add(new JS.ArrayInitializer(shownNames));
args.add(new JS.ArrayInitializer(hiddenNames));
}
_moduleItems.add(js.statement('dart.export(#);', [args]));
}

JS.Identifier _initSymbol(JS.Identifier id) {
var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]);
_moduleItems.add(s);
Expand Down Expand Up @@ -1779,6 +1832,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
}
}

/// Emits static fields.
///
/// Instance fields are emitted in [_initializeFields].
///
/// These are generally treated the same as top-level fields, see
/// [visitTopLevelVariableDeclaration].
@override
visitFieldDeclaration(FieldDeclaration node) {
if (!node.isStatic) return;

for (var f in node.fields.variables) {
_loader.loadDeclaration(f, f.element);
}
}

_addExport(String name) {
if (!_exports.add(name)) throw 'Duplicate top level name found: $name';
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/dev_compiler/lib/src/codegen/js_printer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

library dev_compiler.src.codegen.js_printer;

import 'dart:io' show Directory, File;
import 'dart:io' show Directory, File, Platform, Process;
import 'package:analyzer/src/generated/ast.dart';
import 'package:path/path.dart' as path;
import 'package:source_maps/source_maps.dart' as srcmaps show Printer;
Expand Down Expand Up @@ -48,6 +48,12 @@ String writeJsLibrary(JS.Program jsTree, String outputPath,
text = (context as JS.SimpleJavaScriptPrintingContext).getText();
}
new File(outputPath).writeAsStringSync(text);
if (jsTree.scriptTag != null) {
// Mark executable.
// TODO(jmesserly): should only do this if the input file was executable?
if (!Platform.isWindows) Process.runSync('chmod', ['+x', outputPath]);
}

return computeHash(text);
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/dev_compiler/lib/src/js/nodes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,13 @@ abstract class Node {
}

class Program extends Node {
/// Script tag hash-bang, e.g. `#!/usr/bin/env node`
final String scriptTag;

/// Top-level statements in the program.
final List<Statement> body;
Program(this.body);

Program(this.body, {this.scriptTag});

accept(NodeVisitor visitor) => visitor.visitProgram(this);
void visitChildren(NodeVisitor visitor) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/dev_compiler/lib/src/js/printer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ class Printer implements NodeVisitor {
}

visitProgram(Program program) {
if (program.scriptTag != null) {
out('#!${program.scriptTag}\n');
}
visitAll(program.body);
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/dev_compiler/test/browser/language_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,19 @@
'asyncstar_yieldstar_test'
]);
});

suite('export', () => {
dartLanguageTests([
'duplicate_export_test',
'export_cyclic_test',
'export_double_same_main_test',
'export_main_override_test',
'export_main_test',
'export_test',
'local_export_test',
'reexport_core_test',
'top_level_entry_test'
]);
});

})();
6 changes: 4 additions & 2 deletions pkg/dev_compiler/test/codegen/expect/dir/html_input_b.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
dart_library.library('dir/html_input_b', null, /* Imports */[
"dart_runtime/dart"
"dart_runtime/dart",
'dir/html_input_d'
], /* Lazy imports */[
], function(exports, dart) {
], function(exports, dart, html_input_d) {
'use strict';
let dartx = dart.dartx;
dart.export(exports, html_input_d);
exports.x = 3;
});
18 changes: 18 additions & 0 deletions pkg/dev_compiler/test/codegen/expect/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env iojs
dart_library.library('script', null, /* Imports */[
"dart_runtime/dart",
'dart/core'
], /* Lazy imports */[
], function(exports, dart, core) {
'use strict';
let dartx = dart.dartx;
function main(args) {
let name = args[dartx.join](' ');
if (name == '')
name = 'world';
core.print(`hello ${name}`);
}
dart.fn(main, dart.void, [core.List$(core.String)]);
// Exports:
exports.main = main;
});
1 change: 1 addition & 0 deletions pkg/dev_compiler/test/codegen/expect/script.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Messages from compiling script.dart
Loading

0 comments on commit cbe23ee

Please sign in to comment.