Skip to content

Commit cbe23ee

Browse files
author
John Messerly
committed
implement exports, fixes #141
R=vsm@google.com Review URL: https://codereview.chromium.org/1263583005 .
1 parent 2535238 commit cbe23ee

36 files changed

+441
-65
lines changed

pkg/dev_compiler/lib/runtime/dart/_js_mirrors.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ dart_library.library('dart/_js_mirrors', null, /* Imports */[
118118
namedArgs = null;
119119
dart.assert(getName(constructorName) == "");
120120
dart.assert(namedArgs == null || dart.notNull(namedArgs.isEmpty));
121-
let instance = exports._dart.instantiate(this[_cls], args);
121+
let instance = new this[_cls](...args);
122122
return new JsInstanceMirror._(instance);
123123
}
124124
}

pkg/dev_compiler/lib/runtime/dart_runtime.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ dart_library.library('dart_runtime/dart', null, /* Imports */[
6464
]);
6565

6666
// From dart_utils
67-
exportFrom(dart_utils, ['copyProperties', 'instantiate']);
67+
exportFrom(dart_utils, ['copyProperties', 'export']);
6868
// Renames
6969
exports.defineLazyClass = _export(dart_utils.defineLazy);
7070
exports.defineLazyProperties = _export(dart_utils.defineLazy);

pkg/dev_compiler/lib/runtime/dart_utils.js

+28-28
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,32 @@ var dart_utils =
4949
* Defines a lazy property.
5050
* After initial get or set, it will replace itself with a value property.
5151
*/
52-
// TODO(jmesserly): is this the best implementation for JS engines?
5352
// TODO(jmesserly): reusing descriptor objects has been shown to improve
5453
// performance in other projects (e.g. webcomponents.js ShadowDOM polyfill).
5554
function defineLazyProperty(to, name, desc) {
5655
let init = desc.get;
57-
let writable = !!desc.set;
58-
function lazySetter(value) {
59-
defineProperty(to, name, { value: value, writable: writable });
56+
let value = null;
57+
58+
function lazySetter(x) {
59+
init = null;
60+
value = x;
61+
}
62+
function circularInitError() {
63+
throwError('circular initialization for field ' + name);
6064
}
6165
function lazyGetter() {
62-
// Clear the init function to detect circular initialization.
63-
let f = init;
64-
if (f === null) {
65-
throwError('circular initialization for field ' + name);
66-
}
67-
init = null;
66+
if (init == null) return value;
6867

69-
// Compute and store the value.
70-
let value = f();
71-
lazySetter(value);
68+
// Compute and store the value, guarding against reentry.
69+
let f = init;
70+
init = circularInitError;
71+
lazySetter(f());
7272
return value;
7373
}
7474
desc.get = lazyGetter;
7575
desc.configurable = true;
76-
if (writable) desc.set = lazySetter;
77-
defineProperty(to, name, desc);
76+
if (desc.set) desc.set = lazySetter;
77+
return defineProperty(to, name, desc);
7878
}
7979
dart_utils.defineLazyProperty = defineLazyProperty;
8080

@@ -85,15 +85,8 @@ var dart_utils =
8585
}
8686
dart_utils.defineLazy = defineLazy;
8787

88-
function defineMemoizedGetter(obj, name, get) {
89-
let cache = null;
90-
function getter() {
91-
if (cache != null) return cache;
92-
cache = get();
93-
get = null;
94-
return cache;
95-
}
96-
defineProperty(obj, name, {get: getter, configurable: true});
88+
function defineMemoizedGetter(obj, name, getter) {
89+
return defineLazyProperty(obj, name, {get: getter});
9790
}
9891
dart_utils.defineMemoizedGetter = defineMemoizedGetter;
9992

@@ -114,10 +107,17 @@ var dart_utils =
114107
}
115108
dart_utils.copyProperties = copyProperties;
116109

117-
118-
function instantiate(type, args) {
119-
return new type(...args);
110+
/** Exports from one Dart module to another. */
111+
function export_(to, from, show, hide) {
112+
if (show == void 0) {
113+
show = getOwnNamesAndSymbols(from);
114+
}
115+
if (hide != void 0) {
116+
var hideMap = new Map(hide);
117+
show = show.filter((k) => !hideMap.has(k));
118+
}
119+
return copyTheseProperties(to, from, show);
120120
}
121-
dart_utils.instantiate = instantiate;
121+
dart_utils.export = export_;
122122

123123
})(dart_utils);

pkg/dev_compiler/lib/src/codegen/js_codegen.dart

+96-28
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:analyzer/src/generated/element.dart';
1313
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
1414
import 'package:analyzer/src/generated/scanner.dart'
1515
show StringToken, Token, TokenType;
16+
import 'package:analyzer/src/task/dart.dart' show PublicNamespaceBuilder;
1617

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

99+
/// The default value of the module object. See [visitLibraryDirective].
100+
String _jsModuleValue;
101+
98102
Map<String, DartType> _objectMembers;
99103

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

117121
JS.Program emitLibrary(LibraryUnit library) {
118-
String jsDefaultValue = null;
119-
120122
// Modify the AST to make coercions explicit.
121123
new CoercionReifier(library, compiler).reify();
122124

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

133-
// TODO(jmesserly): visit scriptTag, directives?
132+
library.library.directives.forEach(_visit);
134133

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

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

140145
for (var decl in unit.declarations) {
141146
if (decl is TopLevelVariableDeclaration) {
142-
_visit(decl);
147+
visitTopLevelVariableDeclaration(decl);
143148
} else {
144149
_loader.loadDeclaration(decl, decl.element);
145150
}
146151
if (decl is ClassDeclaration) {
147152
// Static fields can be emitted into the top-level code, so they need
148153
// to potentially be ordered independently of the class.
149154
for (var member in decl.members) {
150-
if (member is FieldDeclaration && member.isStatic) {
151-
for (var f in member.fields.variables) {
152-
_loader.loadDeclaration(f, f.element);
153-
}
155+
if (member is FieldDeclaration) {
156+
visitFieldDeclaration(member);
154157
}
155158
}
156159
}
@@ -208,19 +211,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
208211
var module = js.call("function(#) { 'use strict'; #; #; }",
209212
[params, dartxImport, _moduleItems]);
210213

211-
var program = <JS.Statement>[
212-
js.statement("dart_library.library(#, #, #, #, #)", [
213-
js.string(jsPath, "'"),
214-
jsDefaultValue ?? new JS.LiteralNull(),
215-
js.commentExpression(
216-
"Imports", new JS.ArrayInitializer(imports, multiline: true)),
217-
js.commentExpression("Lazy imports",
218-
new JS.ArrayInitializer(lazyImports, multiline: true)),
219-
module
220-
])
221-
];
214+
var moduleDef = js.statement("dart_library.library(#, #, #, #, #)", [
215+
js.string(jsPath, "'"),
216+
_jsModuleValue ?? new JS.LiteralNull(),
217+
js.commentExpression(
218+
"Imports", new JS.ArrayInitializer(imports, multiline: true)),
219+
js.commentExpression("Lazy imports",
220+
new JS.ArrayInitializer(lazyImports, multiline: true)),
221+
module
222+
]);
223+
224+
var jsBin = compiler.options.runnerOptions.v8Binary;
222225

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

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

240+
@override
241+
void visitLibraryDirective(LibraryDirective node) {
242+
assert(_jsModuleValue == null);
243+
244+
var jsName = findAnnotation(node.element, _isJsNameAnnotation);
245+
_jsModuleValue =
246+
getConstantField(jsName, 'name', types.stringType) as String;
247+
}
248+
249+
@override
250+
void visitImportDirective(ImportDirective node) {
251+
// Nothing to do yet, but we'll want to convert this to an ES6 import once
252+
// we have support for modules.
253+
}
254+
255+
@override void visitPartDirective(PartDirective node) {}
256+
@override void visitPartOfDirective(PartOfDirective node) {}
257+
258+
@override
259+
void visitExportDirective(ExportDirective node) {
260+
var exportName = _libraryName(node.uriElement);
261+
262+
var currentLibNames = currentLibrary.publicNamespace.definedNames;
263+
264+
var args = [_exportsVar, exportName];
265+
if (node.combinators.isNotEmpty) {
266+
var shownNames = <JS.Expression>[];
267+
var hiddenNames = <JS.Expression>[];
268+
269+
var show = node.combinators.firstWhere((c) => c is ShowCombinator,
270+
orElse: () => null) as ShowCombinator;
271+
var hide = node.combinators.firstWhere((c) => c is HideCombinator,
272+
orElse: () => null) as HideCombinator;
273+
if (show != null) {
274+
shownNames.addAll(show.shownNames
275+
.map((i) => i.name)
276+
.where((s) => !currentLibNames.containsKey(s))
277+
.map((s) => js.string(s, "'")));
278+
}
279+
if (hide != null) {
280+
hiddenNames.addAll(hide.hiddenNames.map((i) => js.string(i.name, "'")));
281+
}
282+
args.add(new JS.ArrayInitializer(shownNames));
283+
args.add(new JS.ArrayInitializer(hiddenNames));
284+
}
285+
_moduleItems.add(js.statement('dart.export(#);', [args]));
286+
}
287+
235288
JS.Identifier _initSymbol(JS.Identifier id) {
236289
var s = js.statement('let # = $_SYMBOL(#);', [id, js.string(id.name, "'")]);
237290
_moduleItems.add(s);
@@ -1779,6 +1832,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor {
17791832
}
17801833
}
17811834

1835+
/// Emits static fields.
1836+
///
1837+
/// Instance fields are emitted in [_initializeFields].
1838+
///
1839+
/// These are generally treated the same as top-level fields, see
1840+
/// [visitTopLevelVariableDeclaration].
1841+
@override
1842+
visitFieldDeclaration(FieldDeclaration node) {
1843+
if (!node.isStatic) return;
1844+
1845+
for (var f in node.fields.variables) {
1846+
_loader.loadDeclaration(f, f.element);
1847+
}
1848+
}
1849+
17821850
_addExport(String name) {
17831851
if (!_exports.add(name)) throw 'Duplicate top level name found: $name';
17841852
}

pkg/dev_compiler/lib/src/codegen/js_printer.dart

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
library dev_compiler.src.codegen.js_printer;
66

7-
import 'dart:io' show Directory, File;
7+
import 'dart:io' show Directory, File, Platform, Process;
88
import 'package:analyzer/src/generated/ast.dart';
99
import 'package:path/path.dart' as path;
1010
import 'package:source_maps/source_maps.dart' as srcmaps show Printer;
@@ -48,6 +48,12 @@ String writeJsLibrary(JS.Program jsTree, String outputPath,
4848
text = (context as JS.SimpleJavaScriptPrintingContext).getText();
4949
}
5050
new File(outputPath).writeAsStringSync(text);
51+
if (jsTree.scriptTag != null) {
52+
// Mark executable.
53+
// TODO(jmesserly): should only do this if the input file was executable?
54+
if (!Platform.isWindows) Process.runSync('chmod', ['+x', outputPath]);
55+
}
56+
5157
return computeHash(text);
5258
}
5359

pkg/dev_compiler/lib/src/js/nodes.dart

+6-1
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,13 @@ abstract class Node {
245245
}
246246

247247
class Program extends Node {
248+
/// Script tag hash-bang, e.g. `#!/usr/bin/env node`
249+
final String scriptTag;
250+
251+
/// Top-level statements in the program.
248252
final List<Statement> body;
249-
Program(this.body);
253+
254+
Program(this.body, {this.scriptTag});
250255

251256
accept(NodeVisitor visitor) => visitor.visitProgram(this);
252257
void visitChildren(NodeVisitor visitor) {

pkg/dev_compiler/lib/src/js/printer.dart

+3
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ class Printer implements NodeVisitor {
212212
}
213213

214214
visitProgram(Program program) {
215+
if (program.scriptTag != null) {
216+
out('#!${program.scriptTag}\n');
217+
}
215218
visitAll(program.body);
216219
}
217220

pkg/dev_compiler/test/browser/language_tests.js

+15
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,19 @@
6868
'asyncstar_yieldstar_test'
6969
]);
7070
});
71+
72+
suite('export', () => {
73+
dartLanguageTests([
74+
'duplicate_export_test',
75+
'export_cyclic_test',
76+
'export_double_same_main_test',
77+
'export_main_override_test',
78+
'export_main_test',
79+
'export_test',
80+
'local_export_test',
81+
'reexport_core_test',
82+
'top_level_entry_test'
83+
]);
84+
});
85+
7186
})();
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
dart_library.library('dir/html_input_b', null, /* Imports */[
2-
"dart_runtime/dart"
2+
"dart_runtime/dart",
3+
'dir/html_input_d'
34
], /* Lazy imports */[
4-
], function(exports, dart) {
5+
], function(exports, dart, html_input_d) {
56
'use strict';
67
let dartx = dart.dartx;
8+
dart.export(exports, html_input_d);
79
exports.x = 3;
810
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env iojs
2+
dart_library.library('script', null, /* Imports */[
3+
"dart_runtime/dart",
4+
'dart/core'
5+
], /* Lazy imports */[
6+
], function(exports, dart, core) {
7+
'use strict';
8+
let dartx = dart.dartx;
9+
function main(args) {
10+
let name = args[dartx.join](' ');
11+
if (name == '')
12+
name = 'world';
13+
core.print(`hello ${name}`);
14+
}
15+
dart.fn(main, dart.void, [core.List$(core.String)]);
16+
// Exports:
17+
exports.main = main;
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Messages from compiling script.dart

0 commit comments

Comments
 (0)