Chromium Code Reviews| Index: lib/compiler/implementation/library_loader.dart |
| diff --git a/lib/compiler/implementation/library_loader.dart b/lib/compiler/implementation/library_loader.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..80334447dd4119f8ae93d4141619bcbdf7ca147f |
| --- /dev/null |
| +++ b/lib/compiler/implementation/library_loader.dart |
| @@ -0,0 +1,568 @@ |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +abstract class LibraryLoader implements CompilerTask { |
| + /** |
| + * Loads the library located at [uri] and returns its [LibraryElement]. |
| + * |
| + * If the library is not already loaded, the method creates the |
| + * [LibraryElement] for the library and computes the import/export scope, |
| + * loading and computing the import/export scopes of all required libraries in |
| + * the process. The method handles cyclic dependency between libraries. |
| + * |
| + * This is the main entry point for [LibraryLoader]. |
| + */ |
| + abstract LibraryElement loadLibrary(Uri uri, Node node, Uri canonicalUri); |
| + |
| + // TODO(johnniwinther): Remove this when patches don't need special parsing. |
| + abstract void loadLibraryFromTag(LibraryDependencyHandler handler, |
| + LibraryElement library, |
| + LibraryDependency tag); |
| + |
| + static void importLibrary(Compiler compiler, |
|
Lasse Reichstein Nielsen
2012/10/10 08:07:33
Don't make this static. The caller has compiler, s
Johnni Winther
2012/10/10 09:20:59
Done.
|
| + LibraryElement importingLibrary, |
| + LibraryElement importedLibrary, |
| + Import tag) { |
| + assert(invariant(importingLibrary, |
| + importedLibrary.exportsHandled, |
| + message: 'Exports not handled on $importedLibrary')); |
| + if (!importedLibrary.hasLibraryName()) { |
| + compiler.withCurrentElement(importingLibrary, () { |
| + compiler.reportError(tag === null ? null : tag.uri, |
| + 'no #library tag found in ${importedLibrary.uri}'); |
| + }); |
| + } |
| + if (tag !== null && tag.prefix !== null) { |
| + SourceString prefix = tag.prefix.source; |
| + Element e = importingLibrary.find(prefix); |
| + if (e === null) { |
| + e = new PrefixElement(prefix, importingLibrary.entryCompilationUnit, |
| + tag.getBeginToken()); |
| + importingLibrary.addToScope(e, compiler); |
| + } |
| + if (e.kind !== ElementKind.PREFIX) { |
| + compiler.withCurrentElement(e, () { |
| + compiler.reportWarning(new Identifier(e.position()), |
| + 'duplicated definition'); |
| + }); |
| + compiler.reportError(tag.prefix, 'duplicate definition'); |
| + } |
| + PrefixElement prefixElement = e; |
| + importedLibrary.forEachExport((Element element) { |
| + // TODO(johnniwinther): Handle show and hide combinators. |
| + Element existing = |
| + prefixElement.imported.putIfAbsent(element.name, () => element); |
| + if (existing !== element) { |
| + compiler.withCurrentElement(existing, () { |
| + compiler.reportWarning(new Identifier(existing.position()), |
| + 'duplicated import'); |
| + }); |
| + compiler.withCurrentElement(element, () { |
| + compiler.reportError(new Identifier(element.position()), |
| + 'duplicated import'); |
| + }); |
| + } |
| + }); |
| + } else { |
| + importedLibrary.forEachExport((Element element) { |
| + compiler.withCurrentElement(element, () { |
| + // TODO(johnniwinther): Handle show and hide combinators. |
| + importingLibrary.addImport(element, compiler); |
| + }); |
| + }); |
| + } |
| + } |
| +} |
| + |
| +class LibraryLoaderTask extends CompilerTask implements LibraryLoader { |
|
Lasse Reichstein Nielsen
2012/10/10 08:07:33
Document classes so the difference between Library
Johnni Winther
2012/10/10 09:20:59
Done.
|
| + LibraryLoaderTask(Compiler compiler) : super(compiler); |
| + String get name => 'LibraryLoader'; |
| + |
| + final Map<String, LibraryElement> libraryNames = |
| + new Map<String, LibraryElement>(); |
| + |
| + LibraryDependencyHandler currentHandler; |
| + |
| + /** |
| + * Loads the library located at [uri] and returns its [LibraryElement]. |
| + * |
| + * If the library is not already loaded, the method creates the |
| + * [LibraryElement] for the library and computes the import/export scope, |
| + * loading and computing the import/export scopes of all required libraries in |
| + * the process. The method handles cyclic dependency between libraries. |
| + * |
| + * This is the main entry point for [LibraryLoaderTask]. |
| + */ |
| + LibraryElement loadLibrary(Uri uri, Node node, Uri canonicalUri) { |
| + return measure(() { |
| + assert(currentHandler == null); |
| + currentHandler = new LibraryDependencyHandler(compiler); |
| + LibraryElement library = |
| + loadLibraryInternal(currentHandler, uri, node, canonicalUri); |
| + currentHandler.computeExports(); |
| + currentHandler = null; |
| + return library; |
| + }); |
| + } |
| + |
| + /** |
| + * Processes the library tags in [library]. |
| + * |
| + * The imported/exported libraries are loaded and processed recursively but |
| + * the import/export scopes are not set up. |
| + */ |
| + void processLibraryTags(LibraryDependencyHandler handler, LibraryElement library) { |
|
Lasse Reichstein Nielsen
2012/10/10 08:07:33
long line.
Johnni Winther
2012/10/10 09:20:59
Done.
|
| + int tagState = TagState.NO_TAG_SEEN; |
| + |
| + /** |
| + * If [value] is less than [tagState] complain and return |
| + * [tagState]. Otherwise return the new value for [tagState] |
| + * (transition function for state machine). |
| + */ |
| + int checkTag(int value, LibraryTag tag) { |
| + if (tagState > value) { |
| + compiler.reportError(tag, 'out of order'); |
| + return tagState; |
| + } |
| + return TagState.NEXT[value]; |
| + } |
| + |
| + bool importsDartCore = false; |
| + var libraryDependencies = new LinkBuilder<LibraryDependency>(); |
| + Uri base = library.entryCompilationUnit.script.uri; |
| + for (LibraryTag tag in library.tags.reverse()) { |
| + if (tag.isImport) { |
| + tagState = checkTag(TagState.IMPORT_OR_EXPORT, tag); |
| + if (tag.combinators != null) { |
| + compiler.unimplemented('combinators', node: tag.combinators); |
| + } |
| + // It is not safe to import other libraries at this point as |
| + // another library could then observe the current library |
| + // before it fully declares all the members that are sourced |
| + // in. |
| + if (tag.uri.dartString.slowToString() == 'dart:core') { |
| + importsDartCore = true; |
| + } |
| + libraryDependencies.addLast(tag); |
| + } else if (tag.isExport) { |
| + tagState = checkTag(TagState.IMPORT_OR_EXPORT, tag); |
| + libraryDependencies.addLast(tag); |
| + } else if (tag.isLibraryName) { |
| + tagState = checkTag(TagState.LIBRARY, tag); |
| + if (library.libraryTag !== null) { |
| + compiler.cancel("duplicated library declaration", node: tag); |
| + } else { |
| + library.libraryTag = tag; |
| + } |
| + checkDuplicatedLibraryName(library); |
| + } else if (tag.isPart) { |
| + StringNode uri = tag.uri; |
| + Uri resolved = base.resolve(uri.dartString.slowToString()); |
| + tagState = checkTag(TagState.SOURCE, tag); |
| + loadPart(tag, resolved, library); |
| + } else { |
| + compiler.internalError("Unhandled library tag.", node: tag); |
| + } |
| + } |
| + |
| + // Apply patch, if any. |
| + if (library.uri.scheme == 'dart') { |
| + patchDartLibrary(handler, library, library.uri.path); |
| + } |
| + |
| + // Now that we have processed all the source tags, it is safe to |
| + // start loading other libraries. |
| + |
| + // Import dart:core if not already imported. |
| + if (!importsDartCore && !isDartCore(library.uri)) { |
| + handler.registerDependency(library, null, loadCoreLibrary(handler)); |
| + } |
| + |
| + for (LibraryDependency tag in libraryDependencies.toLink()) { |
| + loadLibraryFromTag(handler, library, tag); |
| + } |
| + } |
| + |
| + void checkDuplicatedLibraryName(LibraryElement library) { |
| + LibraryTag tag = library.libraryTag; |
| + if (tag != null) { |
| + String name = library.getLibraryOrScriptName(); |
| + LibraryElement existing = |
| + libraryNames.putIfAbsent(name, () => library); |
| + if (existing !== library) { |
| + Uri uri = library.entryCompilationUnit.script.uri; |
| + compiler.reportMessage( |
| + compiler.spanFromNode(tag.name, uri), |
| + MessageKind.DUPLICATED_LIBRARY_NAME.error([name]), |
| + api.Diagnostic.WARNING); |
| + Uri existingUri = existing.entryCompilationUnit.script.uri; |
| + compiler.reportMessage( |
| + compiler.spanFromNode(existing.libraryTag.name, existingUri), |
| + MessageKind.DUPLICATED_LIBRARY_NAME.error([name]), |
| + api.Diagnostic.WARNING); |
| + } |
| + } |
| + } |
| + |
| + bool isDartCore(Uri uri) => uri.scheme == "dart" && uri.path == "core"; |
| + |
| + /** |
| + * Lazily loads and returns the [LibraryElement] for the dart:core library. |
| + */ |
| + LibraryElement loadCoreLibrary(LibraryDependencyHandler handler) { |
| + if (compiler.coreLibrary === null) { |
| + Uri coreUri = new Uri.fromComponents(scheme: 'dart', path: 'core'); |
| + compiler.coreLibrary = loadLibraryInternal(handler, coreUri, null, coreUri); |
|
Lasse Reichstein Nielsen
2012/10/10 08:07:33
Long line.
Johnni Winther
2012/10/10 09:20:59
Done.
|
| + } |
| + return compiler.coreLibrary; |
| + } |
| + |
| + void patchDartLibrary(LibraryDependencyHandler handler, |
| + LibraryElement library, String dartLibraryPath) { |
| + if (library.isPatched) return; |
| + Uri patchUri = compiler.resolvePatchUri(dartLibraryPath); |
| + if (patchUri !== null) { |
| + compiler.patchParser.patchLibrary(handler, patchUri, library); |
| + } |
| + } |
| + |
| + /** |
| + * Handle a part tag in the scope of [library]. The [path] given is used as |
| + * is, any resolution should be done beforehand. |
| + */ |
| + void loadPart(Part part, Uri path, LibraryElement library) { |
| + Script sourceScript = compiler.readScript(path, part); |
| + CompilationUnitElement unit = |
| + new CompilationUnitElement(sourceScript, library); |
| + compiler.withCurrentElement(unit, () => compiler.scanner.scan(unit)); |
| + } |
| + |
| + /** |
| + * Handle an import/export tag by loading the referenced library and |
| + * registering its dependency in [handler] for the computation of the import/ |
| + * export scope. |
| + */ |
| + void loadLibraryFromTag(LibraryDependencyHandler handler, |
| + LibraryElement library, |
| + LibraryDependency tag) { |
| + Uri base = library.entryCompilationUnit.script.uri; |
| + Uri resolved = base.resolve(tag.uri.dartString.slowToString()); |
| + LibraryElement loadedLibrary = |
| + loadLibraryInternal(handler, resolved, tag.uri, resolved); |
| + handler.registerDependency(library, tag, loadedLibrary); |
| + |
| + if (!loadedLibrary.hasLibraryName()) { |
| + compiler.withCurrentElement(library, () { |
| + compiler.reportError(tag === null ? null : tag.uri, |
| + 'no library tag found in ${loadedLibrary.uri}'); |
| + }); |
| + } |
| + } |
| + |
| + LibraryElement loadLibraryInternal(LibraryDependencyHandler handler, |
| + Uri uri, Node node, Uri canonicalUri) { |
| + bool newLibrary = false; |
| + LibraryElement createLibrary() { |
| + newLibrary = true; |
| + Script script = compiler.readScript(uri, node); |
| + LibraryElement element = new LibraryElement(script, canonicalUri); |
| + handler.registerNewLibrary(element); |
| + native.maybeEnableNative(compiler, element, uri); |
| + return element; |
| + } |
| + LibraryElement library; |
| + if (canonicalUri === null) { |
| + library = createLibrary(); |
| + } else { |
| + library = compiler.libraries.putIfAbsent(canonicalUri.toString(), |
| + createLibrary); |
| + } |
| + if (newLibrary) { |
| + compiler.withCurrentElement(library, () { |
| + compiler.scanner.scanLibrary(library); |
| + processLibraryTags(handler, library); |
| + handler.registerLibraryExports(library); |
| + compiler.onLibraryLoaded(library, uri); |
| + }); |
| + } |
| + return library; |
| + } |
| +} |
| + |
| + |
| +/** |
| + * The fields of this class models a state machine for checking script |
| + * tags come in the correct order. |
| + */ |
| +class TagState { |
| + static const int NO_TAG_SEEN = 0; |
| + static const int LIBRARY = 1; |
| + static const int IMPORT_OR_EXPORT = 2; |
| + static const int SOURCE = 3; |
| + static const int RESOURCE = 4; |
| + |
| + /** Next state. */ |
| + static const List<int> NEXT = |
| + const <int>[NO_TAG_SEEN, |
| + IMPORT_OR_EXPORT, // Only one library tag is allowed. |
| + IMPORT_OR_EXPORT, |
| + SOURCE, |
| + RESOURCE]; |
| +} |
| + |
| +/** |
| + * An [import] tag and the [library] imported trought [import]. |
|
Lasse Reichstein Nielsen
2012/10/10 08:07:33
"throught" -> "through"
Johnni Winther
2012/10/10 09:20:59
Done.
|
| + */ |
| +class ImportLink { |
|
Lasse Reichstein Nielsen
2012/10/10 08:07:33
Still this data class. Is there really no behavior
Johnni Winther
2012/10/10 09:20:59
Done.
|
| + final Import import; |
| + final LibraryElement library; |
| + |
| + ImportLink(this.import, this.library); |
| +} |
| + |
| +/** |
| + * A node in the library dependency graph. |
| + * |
| + * This class is used to collect the library dependencies expressed through |
| + * import and export tags, and as the work-list entry in computations of library |
| + * exports performed in [LibraryDependencyHandler.computeExports]. |
| + */ |
| +class LibraryDependencyNode { |
| + final LibraryElement library; |
| + |
| + /** |
| + * A linked list of the import tags that import [library] mapped to the |
| + * corresponding libraries. This is used to propagate exports into imports |
| + * after the export scopes have been computed. |
| + */ |
| + Link<ImportLink> imports = const EmptyLink<ImportLink>(); |
| + |
| + /** |
| + * The export tags that export [library] mapped to the nodes for the libraries |
| + * that declared each export tag. This is used to propagete exports during the |
| + * computation of export scopes. |
| + */ |
| + Map<Export, LibraryDependencyNode> dependencyMap = |
| + new Map<Export, LibraryDependencyNode>(); |
| + |
| + /** |
| + * The export scope for [library] which is gradually computed by the work-list |
| + * computation in [LibraryDependencyHandler.computeExports]. |
| + */ |
| + Map<SourceString, Element> exportScope = new Map<SourceString, Element>(); |
| + |
| + /** |
| + * The set of exported elements that need to be propageted to dependent |
| + * libraries as part of the work-list computation performed in |
| + * [LibraryDependencyHandler.computeExports]. |
| + */ |
| + Set<Element> pendingExportSet = new Set<Element>(); |
| + |
| + LibraryDependencyNode(LibraryElement this.library); |
| + |
| + /** |
| + * Registers that the library of this node imports [importLibrary] through the |
| + * [import] tag. |
| + */ |
| + void registerImportDependency(Import import, |
| + LibraryElement importedLibrary) { |
| + imports = imports.prepend(new ImportLink(import, importedLibrary)); |
| + } |
| + |
| + /** |
| + * Registers that the library of this node is exported by |
| + * [exportingLibraryNode] through the [export] tag. |
| + */ |
| + void registerExportDependency(Export export, |
| + LibraryDependencyNode exportingLibraryNode) { |
| + dependencyMap[export] = exportingLibraryNode; |
| + } |
| + |
| + /** |
| + * Registers all non-private locally declared members of the library of this |
| + * node to be exported. This forms the basis for the work-list computation of |
| + * the export scopes performed in [LibraryDependencyHandler.computeExports]. |
| + */ |
| + void registerInitialExports() { |
| + pendingExportSet.addAll( |
| + library.localScope.getValues().filter((Element element) { |
| + // At this point [localScope] only contains members so we don't need |
| + // to check for foreign or prefix elements. |
| + return !element.name.isPrivate(); |
| + })); |
| + } |
| + |
| + /** |
| + * Registers the compute export scope with the node library. |
| + */ |
| + void registerExports() { |
| + library.setExports(exportScope.getValues()); |
| + } |
| + |
| + /** |
| + * Registers the imports of the node library. |
| + */ |
| + void registerImports(Compiler compiler) { |
| + for (ImportLink link in imports) { |
| + LibraryLoader.importLibrary(compiler, library, link.library, link.import); |
| + } |
| + } |
| + |
| + /** |
| + * Copies and clears pending export set for this node. |
| + */ |
| + List<Element> pullPendingExports() { |
| + List<Element> pendingExports = new List.from(pendingExportSet); |
| + pendingExportSet.clear(); |
| + return pendingExports; |
| + } |
| + |
| + /** |
| + * Adds [element] to the export scope for this node. If the [element] name |
| + * is a duplicate, an error element is inserted into the exscope. |
| + */ |
| + Element addElementToExportScope(Compiler compiler, Element element) { |
| + SourceString name = element.name; |
| + Element existingElement = exportScope[name]; |
| + exportScope.putIfAbsent(name, () => element); |
| + if (existingElement !== null) { |
| + if (existingElement.getLibrary() != library) { |
| + // Declared elements hide exported elements. |
| + element = exportScope[name] = new ErroneousElement( |
| + MessageKind.DUPLICATE_EXPORT, [name], name, library); |
| + } |
| + } |
| + return element; |
| + } |
| + |
| + /** |
| + * Propagates the exported [element] to all library nodes that depend upon |
| + * this node. If the propagation updated any pending exports, [:true:] is |
| + * returned. |
| + */ |
| + bool propagateElement(Element element) { |
| + bool change = false; |
| + dependencyMap.forEach((Export export, LibraryDependencyNode exportNode) { |
| + if (exportNode.addElementToPendingExports(export, element)) { |
| + change = true; |
| + } |
| + }); |
| + return change; |
| + } |
| + |
| + /** |
| + * Adds [element] to the pending exports of this node and returns [:true:] if |
| + * the pending export set was modified. The combinators of [export] are used |
| + * to filter the element. |
| + */ |
| + bool addElementToPendingExports(Export export, Element element) { |
| + // TODO(johnniwinther): Use [export] to handle show and hide combinators. |
| + if (exportScope[element.name] !== element) { |
| + if (!pendingExportSet.contains(element)) { |
| + pendingExportSet.add(element); |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| +} |
| + |
| +/** |
| + * Helper class used for computing the possibly cyclic import/export scopes of |
| + * a set of libraries. |
| + * |
| + * This class is used by [ScannerTask.loadLibrary] to collect all newly loaded |
| + * libraries and to compute their import/export scopes through a fixed-point |
| + * algorithm. |
| + */ |
| +class LibraryDependencyHandler { |
| + final Compiler compiler; |
| + |
| + /** |
| + * Newly loaded libraries and their corresponding node in the library |
| + * dependency graph. Libraries that have already been fully loaded are not |
| + * part of the dependency graph of this handler since their export scopes have |
| + * already been computed. |
| + */ |
| + Map<LibraryElement,LibraryDependencyNode> nodeMap = |
| + new Map<LibraryElement,LibraryDependencyNode>(); |
| + |
| + LibraryDependencyHandler(Compiler this.compiler); |
| + |
| + /** |
| + * Performs a fixed-point computation on the export scopes of all registered |
| + * libraries and creates the import/export of the libraries based on the |
| + * fixed-point. |
| + */ |
| + void computeExports() { |
| + bool changed = true; |
| + while (changed) { |
| + changed = false; |
| + nodeMap.forEach((_, LibraryDependencyNode node) { |
| + var pendingExports = node.pullPendingExports(); |
| + pendingExports.forEach((Element element) { |
| + element = node.addElementToExportScope(compiler, element); |
| + if (node.propagateElement(element)) { |
| + changed = true; |
| + } |
| + }); |
| + }); |
| + } |
| + |
| + // Setup export scopes. These have to be set before computing the import |
| + // scopes to avoid accessing uncomputed export scopes during handling of |
| + // imports. |
| + nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { |
| + node.registerExports(); |
| + }); |
| + |
| + // Setup import scopes. |
| + nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { |
| + node.registerImports(compiler); |
| + }); |
| + } |
| + |
| + /** |
| + * Registers that [library] depends on [loadedLibrary] through [tag]. |
| + */ |
| + void registerDependency(LibraryElement library, |
| + LibraryDependency tag, |
| + LibraryElement loadedLibrary) { |
| + if (tag is Export) { |
| + // [loadedLibrary] is exported by [library]. |
| + if (loadedLibrary.exportsHandled) { |
| + // Export scope already computed on [loadedLibrary]. |
| + return; |
| + } |
| + LibraryDependencyNode exportedNode = nodeMap[loadedLibrary]; |
| + LibraryDependencyNode exportingNode = nodeMap[library]; |
| + assert(invariant(loadedLibrary, exportedNode != null, |
| + message: "$loadedLibrary has not been registered")); |
| + assert(invariant(library, exportingNode != null, |
| + message: "$library has not been registered")); |
| + exportedNode.registerExportDependency(tag, exportingNode); |
| + } else if (tag == null || tag is Import) { |
| + // [loadedLibrary] is imported by [library]. |
| + LibraryDependencyNode importingNode = nodeMap[library]; |
| + assert(invariant(library, importingNode != null, |
| + message: "$library has not been registered")); |
| + importingNode.registerImportDependency(tag, loadedLibrary); |
| + } |
| + } |
| + |
| + /** |
| + * Registers [library] for the processing of its import/export scope. |
| + */ |
| + void registerNewLibrary(LibraryElement library) { |
| + nodeMap[library] = new LibraryDependencyNode(library); |
| + } |
| + |
| + /** |
| + * Registers all top-level entities of [library] as starting point for the |
| + * fixed-point computation of the import/export scopes. |
| + */ |
| + void registerLibraryExports(LibraryElement library) { |
| + nodeMap[library].registerInitialExports(); |
| + } |
| +} |