| Index: lib/src/compiler/element_loader.dart
|
| diff --git a/lib/src/compiler/element_loader.dart b/lib/src/compiler/element_loader.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e782103623affc666f93d6881abf65dea7599385
|
| --- /dev/null
|
| +++ b/lib/src/compiler/element_loader.dart
|
| @@ -0,0 +1,142 @@
|
| +// Copyright (c) 2015, 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.
|
| +
|
| +import 'dart:collection' show HashMap, HashSet;
|
| +
|
| +import 'package:analyzer/dart/ast/ast.dart';
|
| +import 'package:analyzer/dart/element/element.dart';
|
| +import 'package:func/func.dart';
|
| +import '../js_ast/js_ast.dart' as JS;
|
| +
|
| +typedef void ModuleItemEmitter(AstNode item);
|
| +
|
| +/// Helper that tracks order of elements visited by the compiler, detecting
|
| +/// if the top level item can be loaded eagerly or not.
|
| +class ElementLoader {
|
| + /// Whether an item has been loaded (emitted) already.
|
| + final _loaded = new HashSet<Element>();
|
| +
|
| + final HashMap<Element, AstNode> _declarationNodes;
|
| +
|
| + /// The stack of currently emitting elements, if generating top-level code
|
| + /// for them. This is not used when inside method bodies, because order does
|
| + /// not matter for those.
|
| + final _topLevelElements = new List<Element>();
|
| +
|
| + /// The current element being loaded.
|
| + /// We can use this to determine if we're loading top-level code or not:
|
| + ///
|
| + /// _currentElements.last == _topLevelElements.last
|
| + final _currentElements = new List<Element>();
|
| +
|
| + bool _checkReferences;
|
| +
|
| + final ModuleItemEmitter _emitModuleItem;
|
| +
|
| + ElementLoader(this._emitModuleItem, this._declarationNodes) {
|
| + assert(!_declarationNodes.containsKey(null));
|
| + }
|
| +
|
| + Element get currentElement => _currentElements.last;
|
| +
|
| + bool isLoaded(Element e) =>
|
| + !_declarationNodes.containsKey(e) || _loaded.contains(e);
|
| +
|
| + /// True if the element is currently being loaded.
|
| + bool _isLoading(Element e) => _currentElements.contains(e);
|
| +
|
| + /// Start generating top-level code for the element [e].
|
| + ///
|
| + /// Subsequent [emitDeclaration] calls will cause those elements to be
|
| + /// generated before this one, until [finishTopLevel] is called.
|
| + void startTopLevel(Element e) {
|
| + assert(identical(e, currentElement));
|
| + _topLevelElements.add(e);
|
| + }
|
| +
|
| + /// Finishes the top-level code for the element [e].
|
| + void finishTopLevel(Element e) {
|
| + var last = _topLevelElements.removeLast();
|
| + assert(identical(e, last));
|
| + }
|
| +
|
| + /// Starts recording calls to [declareBeforeUse], until
|
| + /// [finishCheckingReferences] is called.
|
| + void startCheckingReferences() {
|
| + // This function should not be reentrant, and we should not current be
|
| + // emitting top-level code.
|
| + assert(_checkReferences == null);
|
| + assert(_topLevelElements.isEmpty ||
|
| + !identical(currentElement, _topLevelElements.last));
|
| + // Assume true until proven otherwise
|
| + _checkReferences = true;
|
| + }
|
| +
|
| + /// Finishes recording references, and returns `true` if all referenced
|
| + /// items were loaded (or if no items were referenced).
|
| + bool finishCheckingReferences() {
|
| + var result = _checkReferences;
|
| + _checkReferences = null;
|
| + return result;
|
| + }
|
| +
|
| + /// Ensures a top-level declaration is generated, and returns `true` if it
|
| + /// is part of the current module.
|
| + void emitDeclaration(Element e) {
|
| + var node = _declarationNodes[e];
|
| + if (node == null) return; // not from this module.
|
| +
|
| + // If we already tried to load it, see if we succeeded or not.
|
| + // Otherwise try and load now.
|
| + if (_loaded.contains(e)) return;
|
| +
|
| + _loaded.add(e);
|
| + _currentElements.add(e);
|
| +
|
| + _emitModuleItem(node);
|
| +
|
| + var last = _currentElements.removeLast();
|
| + assert(identical(e, last));
|
| + }
|
| +
|
| + /// Used to immediately emit a declaration and return the result.
|
| + ///
|
| + /// This will operate regardless of whether the node is currently loaded.
|
| + /// We use this when we encounter a getter/setter, to immediately emit the
|
| + /// pair and combine them.
|
| + /*=T*/ customEmitDeclaration/*<T extends JS.Node>*/(
|
| + Element e, Func1<AstNode, JS.Node/*=T*/ > visit) {
|
| + _loaded.add(e);
|
| + _currentElements.add(e);
|
| + var result = visit(_declarationNodes[e]);
|
| + var last = _currentElements.removeLast();
|
| + assert(identical(e, last));
|
| + return result;
|
| + }
|
| +
|
| + /// To emit top-level module items, we sometimes need to reorder them.
|
| + ///
|
| + /// This function takes care of that, and also detects cases where reordering
|
| + /// failed, and we need to resort to lazy loading, by marking the element as
|
| + /// lazy. All elements need to be aware of this possibility and generate code
|
| + /// accordingly.
|
| + ///
|
| + /// If we are not emitting top-level code, this does nothing, because all
|
| + /// declarations are assumed to be available before we start execution.
|
| + /// See [startTopLevel].
|
| + void declareBeforeUse(Element e) {
|
| + if (e == null) return;
|
| +
|
| + if (_checkReferences != null) {
|
| + _checkReferences = _checkReferences && isLoaded(e) && !_isLoading(e);
|
| + return;
|
| + }
|
| +
|
| + var topLevel = _topLevelElements;
|
| + if (topLevel.isNotEmpty && identical(currentElement, topLevel.last)) {
|
| + // If the item is from our library, try to emit it now.
|
| + emitDeclaration(e);
|
| + }
|
| + }
|
| +}
|
|
|