Imported markdown library from dartdoc
diff --git a/pkgs/markdown/.gitignore b/pkgs/markdown/.gitignore
new file mode 100644
index 0000000..aeb4e13
--- /dev/null
+++ b/pkgs/markdown/.gitignore
@@ -0,0 +1,5 @@
+packages
+pubspec.lock
+.project
+.children
+out
\ No newline at end of file
diff --git a/pkgs/markdown/LICENSE b/pkgs/markdown/LICENSE
new file mode 100644
index 0000000..81764fd
--- /dev/null
+++ b/pkgs/markdown/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2012, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/pkgs/markdown/lib/classify.dart b/pkgs/markdown/lib/classify.dart
new file mode 100644
index 0000000..2838fbd
--- /dev/null
+++ b/pkgs/markdown/lib/classify.dart
@@ -0,0 +1,209 @@
+// 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.
+
+library classify;
+
+import 'src/compiler/implementation/scanner/scannerlib.dart';
+// TODO(rnystrom): Use "package:" URL (#4968).
+import 'markdown.dart' as md;
+
+/**
+ * Kinds of tokens that we care to highlight differently. The values of the
+ * fields here will be used as CSS class names for the generated spans.
+ */
+class Classification {
+  static const NONE = null;
+  static const ERROR = "e";
+  static const COMMENT = "c";
+  static const IDENTIFIER = "i";
+  static const KEYWORD = "k";
+  static const OPERATOR = "o";
+  static const STRING = "s";
+  static const NUMBER = "n";
+  static const PUNCTUATION = "p";
+
+  // A few things that are nice to make different:
+  static const TYPE_IDENTIFIER = "t";
+
+  // Between a keyword and an identifier
+  static const SPECIAL_IDENTIFIER = "r";
+
+  static const ARROW_OPERATOR = "a";
+
+  static const STRING_INTERPOLATION = 'si';
+}
+
+/// Returns a marked up HTML string. If the code does not appear to be valid
+/// Dart code, returns the original [text].
+String classifySource(String text) {
+  try {
+    var html = new StringBuffer();
+    var tokenizer = new StringScanner(text, includeComments: true);
+
+    var whitespaceOffset = 0;
+    var token = tokenizer.tokenize();
+    var inString = false;
+    while (token.kind != EOF_TOKEN) {
+      html.add(text.substring(whitespaceOffset, token.charOffset));
+      whitespaceOffset = token.charOffset + token.slowCharCount;
+
+      // Track whether or not we're in a string.
+      switch (token.kind) {
+        case STRING_TOKEN:
+        case STRING_INTERPOLATION_TOKEN:
+          inString = true;
+          break;
+      }
+
+      final kind = classify(token);
+      final escapedText = md.escapeHtml(token.slowToString());
+      if (kind != null) {
+        // Add a secondary class to tokens appearing within a string so that
+        // we can highlight tokens in an interpolation specially.
+        var stringClass = inString ? Classification.STRING_INTERPOLATION : '';
+        html.add('<span class="$kind $stringClass">$escapedText</span>');
+      } else {
+        html.add(escapedText);
+      }
+
+      // Track whether or not we're in a string.
+      if (token.kind == STRING_TOKEN) {
+        inString = false;
+      }
+      token = token.next;
+    }
+    return html.toString();
+  } catch (e) {
+    return text;
+  }
+}
+
+bool _looksLikeType(String name) {
+  // If the name looks like an UppercaseName, assume it's a type.
+  return _looksLikePublicType(name) || _looksLikePrivateType(name);
+}
+
+bool _looksLikePublicType(String name) {
+  // If the name looks like an UppercaseName, assume it's a type.
+  return name.length >= 2 && isUpper(name[0]) && isLower(name[1]);
+}
+
+bool _looksLikePrivateType(String name) {
+  // If the name looks like an _UppercaseName, assume it's a type.
+  return (name.length >= 3 && name[0] == '_' && isUpper(name[1])
+    && isLower(name[2]));
+}
+
+// These ensure that they don't return "true" if the string only has symbols.
+bool isUpper(String s) => s.toLowerCase() != s;
+bool isLower(String s) => s.toUpperCase() != s;
+
+String classify(Token token) {
+  switch (token.kind) {
+    case UNKNOWN_TOKEN:
+      return Classification.ERROR;
+
+    case IDENTIFIER_TOKEN:
+      // Special case for names that look like types.
+      final text = token.slowToString();
+      if (_looksLikeType(text)
+          || text == 'num'
+          || text == 'bool'
+          || text == 'int'
+          || text == 'double') {
+        return Classification.TYPE_IDENTIFIER;
+      }
+      return Classification.IDENTIFIER;
+
+    case STRING_TOKEN:
+    case STRING_INTERPOLATION_TOKEN:
+      return Classification.STRING;
+
+    case INT_TOKEN:
+    case HEXADECIMAL_TOKEN:
+    case DOUBLE_TOKEN:
+      return Classification.NUMBER;
+
+    case COMMENT_TOKEN:
+      return Classification.COMMENT;
+
+    // => is so awesome it is in a class of its own.
+    case FUNCTION_TOKEN:
+      return Classification.ARROW_OPERATOR;
+
+    case OPEN_PAREN_TOKEN:
+    case CLOSE_PAREN_TOKEN:
+    case OPEN_SQUARE_BRACKET_TOKEN:
+    case CLOSE_SQUARE_BRACKET_TOKEN:
+    case OPEN_CURLY_BRACKET_TOKEN:
+    case CLOSE_CURLY_BRACKET_TOKEN:
+    case COLON_TOKEN:
+    case SEMICOLON_TOKEN:
+    case COMMA_TOKEN:
+    case PERIOD_TOKEN:
+    case PERIOD_PERIOD_TOKEN:
+      return Classification.PUNCTUATION;
+
+    case PLUS_PLUS_TOKEN:
+    case MINUS_MINUS_TOKEN:
+    case TILDE_TOKEN:
+    case BANG_TOKEN:
+    case EQ_TOKEN:
+    case BAR_EQ_TOKEN:
+    case CARET_EQ_TOKEN:
+    case AMPERSAND_EQ_TOKEN:
+    case LT_LT_EQ_TOKEN:
+    case GT_GT_EQ_TOKEN:
+    case PLUS_EQ_TOKEN:
+    case MINUS_EQ_TOKEN:
+    case STAR_EQ_TOKEN:
+    case SLASH_EQ_TOKEN:
+    case TILDE_SLASH_EQ_TOKEN:
+    case PERCENT_EQ_TOKEN:
+    case QUESTION_TOKEN:
+    case BAR_BAR_TOKEN:
+    case AMPERSAND_AMPERSAND_TOKEN:
+    case BAR_TOKEN:
+    case CARET_TOKEN:
+    case AMPERSAND_TOKEN:
+    case LT_LT_TOKEN:
+    case GT_GT_TOKEN:
+    case PLUS_TOKEN:
+    case MINUS_TOKEN:
+    case STAR_TOKEN:
+    case SLASH_TOKEN:
+    case TILDE_SLASH_TOKEN:
+    case PERCENT_TOKEN:
+    case EQ_EQ_TOKEN:
+    case BANG_EQ_TOKEN:
+    case EQ_EQ_EQ_TOKEN:
+    case BANG_EQ_EQ_TOKEN:
+    case LT_TOKEN:
+    case GT_TOKEN:
+    case LT_EQ_TOKEN:
+    case GT_EQ_TOKEN:
+    case INDEX_TOKEN:
+    case INDEX_EQ_TOKEN:
+      return Classification.OPERATOR;
+
+    // Color keyword token. Most are colored as keywords.
+    case HASH_TOKEN:
+    case KEYWORD_TOKEN:
+      if (token.stringValue == 'void') {
+        // Color "void" as a type.
+        return Classification.TYPE_IDENTIFIER;
+      }
+      if (token.stringValue == 'this' || token.stringValue == 'super') {
+        // Color "this" and "super" as identifiers.
+        return Classification.SPECIAL_IDENTIFIER;
+      }
+      return Classification.KEYWORD;
+
+    case EOF_TOKEN:
+      return Classification.NONE;
+
+    default:
+      return Classification.NONE;
+  }
+}
diff --git a/pkgs/markdown/lib/markdown.dart b/pkgs/markdown/lib/markdown.dart
new file mode 100644
index 0000000..ef111fb
--- /dev/null
+++ b/pkgs/markdown/lib/markdown.dart
@@ -0,0 +1,118 @@
+// 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.
+
+/// Parses text in a markdown-like format and renders to HTML.
+library markdown;
+
+import 'classify.dart';
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+part 'src/markdown/ast.dart';
+part 'src/markdown/block_parser.dart';
+part 'src/markdown/html_renderer.dart';
+part 'src/markdown/inline_parser.dart';
+
+/// Converts the given string of markdown to HTML.
+String markdownToHtml(String markdown) {
+  final document = new Document();
+
+  // Replace windows line endings with unix line endings, and split.
+  final lines = markdown.replaceAll('\r\n','\n').split('\n');
+  document.parseRefLinks(lines);
+  final blocks = document.parseLines(lines);
+  return renderToHtml(blocks);
+}
+
+/// Replaces `<`, `&`, and `>`, with their HTML entity equivalents.
+String escapeHtml(String html) {
+  return html.replaceAll('&', '&amp;')
+             .replaceAll('<', '&lt;')
+             .replaceAll('>', '&gt;');
+}
+
+var _implicitLinkResolver;
+
+Node setImplicitLinkResolver(Node resolver(String text)) {
+  _implicitLinkResolver = resolver;
+}
+
+/// Maintains the context needed to parse a markdown document.
+class Document {
+  final Map<String, Link> refLinks;
+
+  Document()
+    : refLinks = <String, Link>{};
+
+  parseRefLinks(List<String> lines) {
+    // This is a hideous regex. It matches:
+    // [id]: http:foo.com "some title"
+    // Where there may whitespace in there, and where the title may be in
+    // single quotes, double quotes, or parentheses.
+    final indent = r'^[ ]{0,3}'; // Leading indentation.
+    final id = r'\[([^\]]+)\]';  // Reference id in [brackets].
+    final quote = r'"[^"]+"';    // Title in "double quotes".
+    final apos = r"'[^']+'";     // Title in 'single quotes'.
+    final paren = r"\([^)]+\)";  // Title in (parentheses).
+    final pattern = new RegExp(
+        '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
+
+    for (int i = 0; i < lines.length; i++) {
+      final match = pattern.firstMatch(lines[i]);
+      if (match != null) {
+        // Parse the link.
+        var id = match[1];
+        var url = match[2];
+        var title = match[3];
+
+        if (title == '') {
+          // No title.
+          title = null;
+        } else {
+          // Remove "", '', or ().
+          title = title.substring(1, title.length - 1);
+        }
+
+        // References are case-insensitive.
+        id = id.toLowerCase();
+
+        refLinks[id] = new Link(id, url, title);
+
+        // Remove it from the output. We replace it with a blank line which will
+        // get consumed by later processing.
+        lines[i] = '';
+      }
+    }
+  }
+
+  /// Parse the given [lines] of markdown to a series of AST nodes.
+  List<Node> parseLines(List<String> lines) {
+    final parser = new BlockParser(lines, this);
+
+    final blocks = [];
+    while (!parser.isDone) {
+      for (final syntax in BlockSyntax.syntaxes) {
+        if (syntax.canParse(parser)) {
+          final block = syntax.parse(parser);
+          if (block != null) blocks.add(block);
+          break;
+        }
+      }
+    }
+
+    return blocks;
+  }
+
+  /// Takes a string of raw text and processes all inline markdown tags,
+  /// returning a list of AST nodes. For example, given ``"*this **is** a*
+  /// `markdown`"``, returns:
+  /// `<em>this <strong>is</strong> a</em> <code>markdown</code>`.
+  List<Node> parseInline(String text) => new InlineParser(text, this).parse();
+}
+
+class Link {
+  final String id;
+  final String url;
+  final String title;
+  Link(this.id, this.url, this.title);
+}
diff --git a/pkgs/markdown/lib/src/compiler/compiler.dart b/pkgs/markdown/lib/src/compiler/compiler.dart
new file mode 100644
index 0000000..bbee705
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/compiler.dart
@@ -0,0 +1,177 @@
+// 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.
+
+library compiler;
+
+import 'dart:async';
+import 'dart:uri';
+import 'implementation/apiimpl.dart';
+
+// Unless explicitly allowed, passing [:null:] for any argument to the
+// methods of library will result in an Error being thrown.
+
+/**
+ * Returns a future that completes to the source corresponding to
+ * [uri]. If an exception occurs, the future completes with this
+ * exception.
+ */
+typedef Future<String> CompilerInputProvider(Uri uri);
+
+/// Deprecated, please use [CompilerInputProvider] instead.
+typedef Future<String> ReadStringFromUri(Uri uri);
+
+/**
+ * Returns a [StreamSink] that will serve as compiler output for the given
+ * component.
+ *
+ * Components are identified by [name] and [extension]. By convention,
+ * the empty string [:"":] will represent the main script
+ * (corresponding to the script parameter of [compile]) even if the
+ * main script is a library. For libraries that are compiled
+ * separately, the library name is used.
+ *
+ * At least the following extensions can be expected:
+ *
+ * * "js" for JavaScript output.
+ * * "js.map" for source maps.
+ * * "dart" for Dart output.
+ * * "dart.map" for source maps.
+ *
+ * As more features are added to the compiler, new names and
+ * extensions may be introduced.
+ */
+typedef StreamSink<String> CompilerOutputProvider(String name,
+                                                  String extension);
+
+/**
+ * Invoked by the compiler to report diagnostics. If [uri] is
+ * [:null:], so are [begin] and [end]. No other arguments may be
+ * [:null:]. If [uri] is not [:null:], neither are [begin] and
+ * [end]. [uri] indicates the compilation unit from where the
+ * diagnostic originates. [begin] and [end] are zero-based character
+ * offsets from the beginning of the compilaton unit. [message] is the
+ * diagnostic message, and [kind] indicates indicates what kind of
+ * diagnostic it is.
+ */
+typedef void DiagnosticHandler(Uri uri, int begin, int end,
+                               String message, Diagnostic kind);
+
+/**
+ * Returns a future that completes to a non-null String when [script]
+ * has been successfully compiled.
+ *
+ * The compiler output is obtained by providing an [outputProvider].
+ *
+ * If the compilation fails, the future's value will be [:null:] and
+ * [handler] will have been invoked at least once with [:kind ==
+ * Diagnostic.ERROR:] or [:kind == Diagnostic.CRASH:].
+ *
+ * Deprecated: if no [outputProvider] is given, the future completes
+ * to the compiled script. This behavior will be removed in the future
+ * as the compiler may create multiple files to support lazy loading
+ * of libraries.
+ */
+Future<String> compile(Uri script,
+                       Uri libraryRoot,
+                       Uri packageRoot,
+                       CompilerInputProvider inputProvider,
+                       DiagnosticHandler handler,
+                       [List<String> options = const [],
+                        CompilerOutputProvider outputProvider]) {
+  if (!libraryRoot.path.endsWith("/")) {
+    throw new ArgumentError("libraryRoot must end with a /");
+  }
+  if (packageRoot != null && !packageRoot.path.endsWith("/")) {
+    throw new ArgumentError("packageRoot must end with a /");
+  }
+  // TODO(ahe): Consider completing the future with an exception if
+  // code is null.
+  Compiler compiler = new Compiler(inputProvider,
+                                   outputProvider,
+                                   handler,
+                                   libraryRoot,
+                                   packageRoot,
+                                   options);
+  compiler.run(script);
+  String code = compiler.assembledCode;
+  if (code != null && outputProvider != null) {
+    String outputType = 'js';
+    if (options.contains('--output-type=dart')) {
+      outputType = 'dart';
+    }
+    outputProvider('', outputType)
+        ..add(code)
+        ..close();
+    code = ''; // Non-null signals success.
+  }
+  return new Future.immediate(code);
+}
+
+/**
+ * Kind of diagnostics that the compiler can report.
+ */
+class Diagnostic {
+  /**
+   * An error as identified by the "Dart Programming Language
+   * Specification" [http://www.dartlang.org/docs/spec/].
+   *
+   * Note: the compiler may still produce an executable result after
+   * reporting a compilation error. The specification says:
+   *
+   * "A compile-time error must be reported by a Dart compiler before
+   * the erroneous code is executed." and "If a compile-time error
+   * occurs within the code of a running isolate A, A is immediately
+   * suspended."
+   *
+   * This means that the compiler can generate code that when executed
+   * terminates execution.
+   */
+  static const Diagnostic ERROR = const Diagnostic(1, 'error');
+
+  /**
+   * A warning as identified by the "Dart Programming Language
+   * Specification" [http://www.dartlang.org/docs/spec/].
+   */
+  static const Diagnostic WARNING = const Diagnostic(2, 'warning');
+
+  /**
+   * Any other warning that is not covered by [WARNING].
+   */
+  static const Diagnostic LINT = const Diagnostic(4, 'lint');
+
+  /**
+   * Informational messages.
+   */
+  static const Diagnostic INFO = const Diagnostic(8, 'info');
+
+  /**
+   * Informational messages that shouldn't be printed unless
+   * explicitly requested by the user of a compiler.
+   */
+  static const Diagnostic VERBOSE_INFO = const Diagnostic(16, 'verbose info');
+
+  /**
+   * An internal error in the compiler.
+   */
+  static const Diagnostic CRASH = const Diagnostic(32, 'crash');
+
+  /**
+   * An [int] representation of this kind. The ordinals are designed
+   * to be used as bitsets.
+   */
+  final int ordinal;
+
+  /**
+   * The name of this kind.
+   */
+  final String name;
+
+  /**
+   * This constructor is not private to support user-defined
+   * diagnostic kinds.
+   */
+  const Diagnostic(this.ordinal, this.name);
+
+  String toString() => name;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/README.txt b/pkgs/markdown/lib/src/compiler/implementation/README.txt
new file mode 100644
index 0000000..8fe0a29
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/README.txt
@@ -0,0 +1,12 @@
+Dart2JS is the Dart-to-JavaScript compiler for Dart. This compiler
+will provide high-quality translation from Dart to JavaScript.
+
+Some areas that will explored in this project are:
+
+   * high-performance extensible scanner and parser
+   * concrete type inferencing
+   * fancy language tool support
+   * programming environment integration
+   * SSA-based intermediate representation
+   * adaptive compilation on the client
+
diff --git a/pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart b/pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart
new file mode 100644
index 0000000..663e003
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart
@@ -0,0 +1,266 @@
+// 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.
+
+library leg_apiimpl;
+
+import 'dart:uri';
+import 'dart:async';
+
+import '../compiler.dart' as api;
+import 'dart2jslib.dart' as leg;
+import 'tree/tree.dart' as tree;
+import 'elements/elements.dart' as elements;
+import 'ssa/tracer.dart' as ssa;
+import '../../libraries.dart';
+import 'source_file.dart';
+
+class Compiler extends leg.Compiler {
+  api.ReadStringFromUri provider;
+  api.DiagnosticHandler handler;
+  final Uri libraryRoot;
+  final Uri packageRoot;
+  List<String> options;
+  bool mockableLibraryUsed = false;
+  final Set<String> allowedLibraryCategories;
+
+  Compiler(this.provider,
+           api.CompilerOutputProvider outputProvider,
+           this.handler,
+           this.libraryRoot,
+           this.packageRoot,
+           List<String> options)
+      : this.options = options,
+        this.allowedLibraryCategories = getAllowedLibraryCategories(options),
+        super(
+            tracer: new ssa.HTracer(),
+            outputProvider: outputProvider,
+            enableTypeAssertions: hasOption(options, '--enable-checked-mode'),
+            enableUserAssertions: hasOption(options, '--enable-checked-mode'),
+            enableMinification: hasOption(options, '--minify'),
+            enableNativeLiveTypeAnalysis:
+                !hasOption(options, '--disable-native-live-type-analysis'),
+            emitJavaScript: !hasOption(options, '--output-type=dart'),
+            disallowUnsafeEval: hasOption(options, '--disallow-unsafe-eval'),
+            analyzeAll: hasOption(options, '--analyze-all'),
+            analyzeOnly: hasOption(options, '--analyze-only'),
+            rejectDeprecatedFeatures:
+                hasOption(options, '--reject-deprecated-language-features'),
+            checkDeprecationInSdk:
+                hasOption(options,
+                          '--report-sdk-use-of-deprecated-language-features'),
+            strips: getStrips(options),
+            enableConcreteTypeInference:
+                hasOption(options, '--enable-concrete-type-inference'),
+            preserveComments: hasOption(options, '--preserve-comments')) {
+    if (!libraryRoot.path.endsWith("/")) {
+      throw new ArgumentError("libraryRoot must end with a /");
+    }
+    if (packageRoot != null && !packageRoot.path.endsWith("/")) {
+      throw new ArgumentError("packageRoot must end with a /");
+    }
+  }
+
+  static List<String> getStrips(List<String> options) {
+    for (String option in options) {
+      if (option.startsWith('--force-strip=')) {
+        return option.substring('--force-strip='.length).split(',');
+      }
+    }
+    return const <String>[];
+  }
+
+  static Set<String> getAllowedLibraryCategories(List<String> options) {
+    for (String option in options) {
+      if (option.startsWith('--categories=')) {
+        var result = option.substring('--categories='.length).split(',');
+        result.add('Shared');
+        result.add('Internal');
+        return new Set<String>.from(result);
+      }
+    }
+    return new Set<String>.from(['Client', 'Shared', 'Internal']);
+  }
+
+  static bool hasOption(List<String> options, String option) {
+    return options.indexOf(option) >= 0;
+  }
+
+  // TODO(johnniwinther): Merge better with [translateDartUri] when
+  // [scanBuiltinLibrary] is removed.
+  String lookupLibraryPath(String dartLibraryName) {
+    LibraryInfo info = LIBRARIES[dartLibraryName];
+    if (info == null) return null;
+    if (!info.isDart2jsLibrary) return null;
+    if (!allowedLibraryCategories.contains(info.category)) return null;
+    String path = info.dart2jsPath;
+    if (path == null) {
+      path = info.path;
+    }
+    return "lib/$path";
+  }
+
+  String lookupPatchPath(String dartLibraryName) {
+    LibraryInfo info = LIBRARIES[dartLibraryName];
+    if (info == null) return null;
+    if (!info.isDart2jsLibrary) return null;
+    String path = info.dart2jsPatchPath;
+    if (path == null) return null;
+    return "lib/$path";
+  }
+
+  elements.LibraryElement scanBuiltinLibrary(String path) {
+    Uri uri = libraryRoot.resolve(lookupLibraryPath(path));
+    Uri canonicalUri = new Uri.fromComponents(scheme: "dart", path: path);
+    elements.LibraryElement library =
+        libraryLoader.loadLibrary(uri, null, canonicalUri);
+    return library;
+  }
+
+  void log(message) {
+    handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO);
+  }
+
+  /// See [leg.Compiler.translateResolvedUri].
+  Uri translateResolvedUri(elements.LibraryElement importingLibrary,
+                           Uri resolvedUri, tree.Node node) {
+    if (resolvedUri.scheme == 'dart') {
+      return translateDartUri(importingLibrary, resolvedUri, node);
+    }
+    return resolvedUri;
+  }
+
+  /**
+   * Reads the script designated by [readableUri].
+   */
+  leg.Script readScript(Uri readableUri, [tree.Node node]) {
+    if (!readableUri.isAbsolute()) {
+      internalError('Relative uri $readableUri provided to readScript(Uri)',
+                    node: node);
+    }
+    return fileReadingTask.measure(() {
+      Uri resourceUri = translateUri(readableUri, node);
+      String text = "";
+      try {
+        // TODO(ahe): We expect the future to be complete and call value
+        // directly. In effect, we don't support truly asynchronous API.
+        text = deprecatedFutureValue(provider(resourceUri));
+      } catch (exception) {
+        if (node != null) {
+          cancel("$exception", node: node);
+        } else {
+          reportDiagnostic(null, "$exception", api.Diagnostic.ERROR);
+          throw new leg.CompilerCancelledException("$exception");
+        }
+      }
+      SourceFile sourceFile = new SourceFile(resourceUri.toString(), text);
+      // We use [readableUri] as the URI for the script since need to preserve
+      // the scheme in the script because [Script.uri] is used for resolving
+      // relative URIs mentioned in the script. See the comment on
+      // [LibraryLoader] for more details.
+      return new leg.Script(readableUri, sourceFile);
+    });
+  }
+
+  /**
+   * Translates a readable URI into a resource URI.
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  Uri translateUri(Uri readableUri, tree.Node node) {
+    switch (readableUri.scheme) {
+      case 'package': return translatePackageUri(readableUri, node);
+      default: return readableUri;
+    }
+  }
+
+  Uri translateDartUri(elements.LibraryElement importingLibrary,
+                       Uri resolvedUri, tree.Node node) {
+    LibraryInfo libraryInfo = LIBRARIES[resolvedUri.path];
+    String path = lookupLibraryPath(resolvedUri.path);
+    if (libraryInfo != null &&
+        libraryInfo.category == "Internal") {
+      bool allowInternalLibraryAccess = false;
+      if (importingLibrary != null) {
+        if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) {
+          allowInternalLibraryAccess = true;
+        } else if (importingLibrary.canonicalUri.path.contains(
+                       'dart/tests/compiler/dart2js_native')) {
+          allowInternalLibraryAccess = true;
+        }
+      }
+      if (!allowInternalLibraryAccess) {
+        if (node != null && importingLibrary != null) {
+          reportDiagnostic(spanFromNode(node),
+              'Error: Internal library $resolvedUri is not accessible from '
+              '${importingLibrary.canonicalUri}.',
+              api.Diagnostic.ERROR);
+        } else {
+          reportDiagnostic(null,
+              'Error: Internal library $resolvedUri is not accessible.',
+              api.Diagnostic.ERROR);
+        }
+        //path = null;
+      }
+    }
+    if (path == null) {
+      if (node != null) {
+        reportError(node, 'library not found ${resolvedUri}');
+      } else {
+        reportDiagnostic(null, 'library not found ${resolvedUri}',
+                         api.Diagnostic.ERROR);
+      }
+      return null;
+    }
+    if (resolvedUri.path == 'html' ||
+        resolvedUri.path == 'io') {
+      // TODO(ahe): Get rid of mockableLibraryUsed when test.dart
+      // supports this use case better.
+      mockableLibraryUsed = true;
+    }
+    return libraryRoot.resolve(path);
+  }
+
+  Uri resolvePatchUri(String dartLibraryPath) {
+    String patchPath = lookupPatchPath(dartLibraryPath);
+    if (patchPath == null) return null;
+    return libraryRoot.resolve(patchPath);
+  }
+
+  translatePackageUri(Uri uri, tree.Node node) => packageRoot.resolve(uri.path);
+
+  bool run(Uri uri) {
+    log('Allowed library categories: $allowedLibraryCategories');
+    bool success = super.run(uri);
+    int cumulated = 0;
+    for (final task in tasks) {
+      cumulated += task.timing;
+      log('${task.name} took ${task.timing}msec');
+    }
+    int total = totalCompileTime.elapsedMilliseconds;
+    log('Total compile-time ${total}msec;'
+        ' unaccounted ${total - cumulated}msec');
+    return success;
+  }
+
+  void reportDiagnostic(leg.SourceSpan span, String message,
+                        api.Diagnostic kind) {
+    if (identical(kind, api.Diagnostic.ERROR)
+        || identical(kind, api.Diagnostic.CRASH)) {
+      compilationFailed = true;
+    }
+    // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For
+    // instance in the [Types] constructor in typechecker.dart.
+    if (span == null || span.uri == null) {
+      handler(null, null, null, message, kind);
+    } else {
+      handler(translateUri(span.uri, null), span.begin, span.end,
+              message, kind);
+    }
+  }
+
+  bool get isMockCompilation {
+    return mockableLibraryUsed
+      && (options.indexOf('--allow-mock-compilation') != -1);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/closure.dart b/pkgs/markdown/lib/src/compiler/implementation/closure.dart
new file mode 100644
index 0000000..4ed1564
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/closure.dart
@@ -0,0 +1,663 @@
+// 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.
+
+library closureToClassMapper;
+
+import "elements/elements.dart";
+import "dart2jslib.dart";
+import "dart_types.dart";
+import "scanner/scannerlib.dart" show Token;
+import "tree/tree.dart";
+import "util/util.dart";
+import "elements/modelx.dart" show ElementX, FunctionElementX, ClassElementX;
+
+abstract class ClosureNamer {
+  SourceString getClosureVariableName(SourceString name, int id);
+}
+
+
+class ClosureTask extends CompilerTask {
+  Map<Node, ClosureClassMap> closureMappingCache;
+  ClosureNamer namer;
+  ClosureTask(Compiler compiler, this.namer)
+      : closureMappingCache = new Map<Node, ClosureClassMap>(),
+        super(compiler);
+
+  String get name => "Closure Simplifier";
+
+  ClosureClassMap computeClosureToClassMapping(Element element,
+                                               Expression node,
+                                               TreeElements elements) {
+    return measure(() {
+      ClosureClassMap cached = closureMappingCache[node];
+      if (cached != null) return cached;
+
+      ClosureTranslator translator =
+          new ClosureTranslator(compiler, elements, closureMappingCache, namer);
+
+      // The translator will store the computed closure-mappings inside the
+      // cache. One for given node and one for each nested closure.
+      if (node is FunctionExpression) {
+        translator.translateFunction(element, node);
+      } else {
+        // Must be the lazy initializer of a static.
+        assert(node is SendSet);
+        translator.translateLazyInitializer(element, node);
+      }
+      assert(closureMappingCache[node] != null);
+      return closureMappingCache[node];
+    });
+  }
+
+  ClosureClassMap getMappingForNestedFunction(FunctionExpression node) {
+    return measure(() {
+      ClosureClassMap nestedClosureData = closureMappingCache[node];
+      if (nestedClosureData == null) {
+        compiler.internalError("No closure cache", node: node);
+      }
+      return nestedClosureData;
+    });
+  }
+}
+
+class ClosureFieldElement extends ElementX {
+  ClosureFieldElement(SourceString name, ClassElement enclosing)
+      : super(name, ElementKind.FIELD, enclosing);
+
+  bool isInstanceMember() => true;
+  bool isAssignable() => false;
+  // The names of closure variables don't need renaming, since their use is very
+  // simple and they have 1-character names in the minified mode.
+  bool hasFixedBackendName() => true;
+  String fixedBackendName() => name.slowToString();
+
+  DartType computeType(Compiler compiler) => compiler.types.dynamicType;
+
+  String toString() => "ClosureFieldElement($name)";
+}
+
+class ClosureClassElement extends ClassElementX {
+  ClosureClassElement(SourceString name,
+                      Compiler compiler,
+                      this.methodElement,
+                      Element enclosingElement)
+      : super(name,
+              enclosingElement,
+              // By assigning a fresh class-id we make sure that the hashcode
+              // is unique, but also emit closure classes after all other
+              // classes (since the emitter sorts classes by their id).
+              compiler.getNextFreeClassId(),
+              STATE_DONE) {
+    compiler.closureClass.ensureResolved(compiler);
+    supertype = compiler.closureClass.computeType(compiler);
+    interfaces = const Link<DartType>();
+    allSupertypes = const Link<DartType>().prepend(supertype);
+  }
+
+  bool isClosure() => true;
+
+  /**
+   * The most outer method this closure is declared into.
+   */
+  Element methodElement;
+}
+
+class BoxElement extends ElementX {
+  BoxElement(SourceString name, Element enclosingElement)
+      : super(name, ElementKind.VARIABLE, enclosingElement);
+}
+
+class ThisElement extends ElementX {
+  ThisElement(Element enclosing)
+      : super(const SourceString('this'), ElementKind.PARAMETER, enclosing);
+
+  bool isAssignable() => false;
+
+  // Since there is no declaration corresponding to 'this', use the position of
+  // the enclosing method.
+  Token position() => enclosingElement.position();
+}
+
+class CheckVariableElement extends ElementX {
+  Element parameter;
+  CheckVariableElement(SourceString name, this.parameter, Element enclosing)
+      : super(name, ElementKind.VARIABLE, enclosing);
+
+  // Since there is no declaration for the synthetic 'check' variable, use
+  // parameter.
+  Token position() => parameter.position();
+}
+
+// The box-element for a scope, and the captured variables that need to be
+// stored in the box.
+class ClosureScope {
+  Element boxElement;
+  Map<Element, Element> capturedVariableMapping;
+  // If the scope is attached to a [For] contains the variables that are
+  // declared in the initializer of the [For] and that need to be boxed.
+  // Otherwise contains the empty List.
+  List<Element> boxedLoopVariables;
+
+  ClosureScope(this.boxElement, this.capturedVariableMapping)
+      : boxedLoopVariables = const <Element>[];
+
+  bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty;
+}
+
+class ClosureClassMap {
+  // The closure's element before any translation. Will be null for methods.
+  final Element closureElement;
+  // The closureClassElement will be null for methods that are not local
+  // closures.
+  final ClassElement closureClassElement;
+  // The callElement will be null for methods that are not local closures.
+  final FunctionElement callElement;
+  // The [thisElement] makes handling 'this' easier by treating it like any
+  // other argument. It is only set for instance-members.
+  final ThisElement thisElement;
+
+  // Maps free locals, arguments and function elements to their captured
+  // copies.
+  final Map<Element, Element> freeVariableMapping;
+  // Maps closure-fields to their captured elements. This is somehow the inverse
+  // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does
+  // not deal with boxes, here we map instance-fields (which might represent
+  // boxes) to their boxElement.
+  final Map<Element, Element> capturedFieldMapping;
+
+  // Maps scopes ([Loop] and [FunctionExpression] nodes) to their
+  // [ClosureScope] which contains their box and the
+  // captured variables that are stored in the box.
+  // This map will be empty if the method/closure of this [ClosureData] does not
+  // contain any nested closure.
+  final Map<Node, ClosureScope> capturingScopes;
+
+  final Set<Element> usedVariablesInTry;
+
+  // A map from the parameter element to the variable element that
+  // holds the sentinel check.
+  final Map<Element, Element> parametersWithSentinel;
+
+  ClosureClassMap(this.closureElement,
+                  this.closureClassElement,
+                  this.callElement,
+                  this.thisElement)
+      : this.freeVariableMapping = new Map<Element, Element>(),
+        this.capturedFieldMapping = new Map<Element, Element>(),
+        this.capturingScopes = new Map<Node, ClosureScope>(),
+        this.usedVariablesInTry = new Set<Element>(),
+        this.parametersWithSentinel = new Map<Element, Element>();
+
+  bool isClosure() => closureElement != null;
+}
+
+class ClosureTranslator extends Visitor {
+  final Compiler compiler;
+  final TreeElements elements;
+  int closureFieldCounter = 0;
+  int boxedFieldCounter = 0;
+  bool inTryStatement = false;
+  final Map<Node, ClosureClassMap> closureMappingCache;
+
+  // Map of captured variables. Initially they will map to themselves. If
+  // a variable needs to be boxed then the scope declaring the variable
+  // will update this mapping.
+  Map<Element, Element> capturedVariableMapping;
+  // List of encountered closures.
+  List<Expression> closures;
+
+  // The variables that have been declared in the current scope.
+  List<Element> scopeVariables;
+
+  // Keep track of the mutated variables so that we don't need to box
+  // non-mutated variables.
+  Set<Element> mutatedVariables;
+
+  Element outermostElement;
+  Element currentElement;
+
+  // The closureData of the currentFunctionElement.
+  ClosureClassMap closureData;
+
+  ClosureNamer namer;
+
+  bool insideClosure = false;
+
+  ClosureTranslator(this.compiler, this.elements, this.closureMappingCache,
+                    this.namer)
+      : capturedVariableMapping = new Map<Element, Element>(),
+        closures = <Expression>[],
+        mutatedVariables = new Set<Element>();
+
+  void translateFunction(Element element, FunctionExpression node) {
+    // For constructors the [element] and the [:elements[node]:] may differ.
+    // The [:elements[node]:] always points to the generative-constructor
+    // element, whereas the [element] might be the constructor-body element.
+    visit(node);  // [visitFunctionExpression] will call [visitInvokable].
+    // When variables need to be boxed their [capturedVariableMapping] is
+    // updated, but we delay updating the similar freeVariableMapping in the
+    // closure datas that capture these variables.
+    // The closures don't have their fields (in the closure class) set, either.
+    updateClosures();
+  }
+
+  void translateLazyInitializer(Element element, SendSet node) {
+    assert(node.assignmentOperator.source == const SourceString("="));
+    Expression initialValue = node.argumentsNode.nodes.head;
+    visitInvokable(element, node, () { visit(initialValue); });
+    updateClosures();
+  }
+
+  // This function runs through all of the existing closures and updates their
+  // free variables to the boxed value. It also adds the field-elements to the
+  // class representing the closure. At the same time it fills the
+  // [capturedFieldMapping].
+  void updateClosures() {
+    for (Expression closure in closures) {
+      // The captured variables that need to be stored in a field of the closure
+      // class.
+      Set<Element> fieldCaptures = new Set<Element>();
+      Set<Element> boxes = new Set<Element>();
+      ClosureClassMap data = closureMappingCache[closure];
+      Map<Element, Element> freeVariableMapping = data.freeVariableMapping;
+      // We get a copy of the keys and iterate over it, to avoid modifications
+      // to the map while iterating over it.
+      freeVariableMapping.keys.toList().forEach((Element fromElement) {
+        assert(fromElement == freeVariableMapping[fromElement]);
+        Element updatedElement = capturedVariableMapping[fromElement];
+        assert(updatedElement != null);
+        if (fromElement == updatedElement) {
+          assert(freeVariableMapping[fromElement] == updatedElement);
+          assert(Elements.isLocal(updatedElement)
+                 || updatedElement.isTypeVariable());
+          // The variable has not been boxed.
+          fieldCaptures.add(updatedElement);
+        } else {
+          // A boxed element.
+          freeVariableMapping[fromElement] = updatedElement;
+          Element boxElement = updatedElement.enclosingElement;
+          assert(boxElement.kind == ElementKind.VARIABLE);
+          boxes.add(boxElement);
+        }
+      });
+      ClassElement closureElement = data.closureClassElement;
+      assert(closureElement != null ||
+             (fieldCaptures.isEmpty && boxes.isEmpty));
+      void addElement(Element element, SourceString name) {
+        Element fieldElement = new ClosureFieldElement(name, closureElement);
+        closureElement.addBackendMember(fieldElement);
+        data.capturedFieldMapping[fieldElement] = element;
+        freeVariableMapping[element] = fieldElement;
+      }
+      // Add the box elements first so we get the same ordering.
+      // TODO(sra): What is the canonical order of multiple boxes?
+      for (Element capturedElement in boxes) {
+        addElement(capturedElement, capturedElement.name);
+      }
+      for (Element capturedElement in
+               Elements.sortedByPosition(fieldCaptures)) {
+        int id = closureFieldCounter++;
+        SourceString name =
+            namer.getClosureVariableName(capturedElement.name, id);
+        addElement(capturedElement, name);
+      }
+      closureElement.reverseBackendMembers();
+    }
+  }
+
+  void useLocal(Element element) {
+    // If the element is not declared in the current function and the element
+    // is not the closure itself we need to mark the element as free variable.
+    // Note that the check on [insideClosure] is not just an
+    // optimization: factories have type parameters as function
+    // parameters, and type parameters are declared in the class, not
+    // the factory.
+    if (insideClosure &&
+        element.enclosingElement != currentElement &&
+        element != currentElement) {
+      assert(closureData.freeVariableMapping[element] == null ||
+             closureData.freeVariableMapping[element] == element);
+      closureData.freeVariableMapping[element] = element;
+    } else if (inTryStatement) {
+      // Don't mark the this-element. This would complicate things in the
+      // builder.
+      if (element != closureData.thisElement) {
+        // TODO(ngeoffray): only do this if the variable is mutated.
+        closureData.usedVariablesInTry.add(element);
+      }
+    }
+  }
+
+  void declareLocal(Element element) {
+    scopeVariables.add(element);
+  }
+
+  visit(Node node) => node.accept(this);
+
+  visitNode(Node node) => node.visitChildren(this);
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    for (Link<Node> link = node.definitions.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      Node definition = link.head;
+      Element element = elements[definition];
+      assert(element != null);
+      declareLocal(element);
+      // We still need to visit the right-hand sides of the init-assignments.
+      // For SendSets don't visit the left again. Otherwise it would be marked
+      // as mutated.
+      if (definition is Send) {
+        Send assignment = definition;
+        Node arguments = assignment.argumentsNode;
+        if (arguments != null) {
+          visit(arguments);
+        }
+      } else {
+        visit(definition);
+      }
+    }
+  }
+
+  visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      useLocal(closureData.thisElement);
+    }
+    node.visitChildren(this);
+  }
+
+  visitSend(Send node) {
+    Element element = elements[node];
+    if (Elements.isLocal(element)) {
+      useLocal(element);
+    } else if (node.receiver == null &&
+               Elements.isInstanceSend(node, elements)) {
+      useLocal(closureData.thisElement);
+    } else if (node.isSuperCall) {
+      useLocal(closureData.thisElement);
+    } else if (node.isParameterCheck) {
+      Element parameter = elements[node.receiver];
+      FunctionElement enclosing = parameter.enclosingElement;
+      FunctionExpression function = enclosing.parseNode(compiler);
+      ClosureClassMap cached = closureMappingCache[function];
+      if (!cached.parametersWithSentinel.containsKey(parameter)) {
+        SourceString parameterName = parameter.name;
+        String name = '${parameterName.slowToString()}_check';
+        Element newElement = new CheckVariableElement(new SourceString(name),
+                                                      parameter,
+                                                      enclosing);
+        useLocal(newElement);
+        cached.parametersWithSentinel[parameter] = newElement;
+      }
+    }
+    node.visitChildren(this);
+  }
+
+  visitSendSet(SendSet node) {
+    Element element = elements[node];
+    if (Elements.isLocal(element)) {
+      mutatedVariables.add(element);
+    }
+    super.visitSendSet(node);
+  }
+
+  visitNewExpression(NewExpression node) {
+    DartType type = elements.getType(node);
+
+    bool hasTypeVariable(DartType type) {
+      if (type is TypeVariableType) {
+        return true;
+      } else if (type is InterfaceType) {
+        InterfaceType ifcType = type;
+        for (DartType argument in ifcType.typeArguments) {
+          if (hasTypeVariable(argument)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    void analyzeTypeVariables(DartType type) {
+      if (type is TypeVariableType) {
+        useLocal(type.element);
+      } else if (type is InterfaceType) {
+        InterfaceType ifcType = type;
+        for (DartType argument in ifcType.typeArguments) {
+          analyzeTypeVariables(argument);
+        }
+      }
+    }
+    if (outermostElement.isMember() &&
+        compiler.world.needsRti(outermostElement.getEnclosingClass())) {
+      if (outermostElement.isInstanceMember()
+          || outermostElement.isGenerativeConstructor()) {
+        if (hasTypeVariable(type)) useLocal(closureData.thisElement);
+      } else if (outermostElement.isFactoryConstructor()) {
+        analyzeTypeVariables(type);
+      }
+    }
+
+    node.visitChildren(this);
+  }
+
+  // If variables that are declared in the [node] scope are captured and need
+  // to be boxed create a box-element and update the [capturingScopes] in the
+  // current [closureData].
+  // The boxed variables are updated in the [capturedVariableMapping].
+  void attachCapturedScopeVariables(Node node) {
+    Element box = null;
+    Map<Element, Element> scopeMapping = new Map<Element, Element>();
+    for (Element element in scopeVariables) {
+      // No need to box non-assignable elements.
+      if (!element.isAssignable()) continue;
+      if (!mutatedVariables.contains(element)) continue;
+      if (capturedVariableMapping.containsKey(element)) {
+        if (box == null) {
+          // TODO(floitsch): construct better box names.
+          SourceString boxName =
+              namer.getClosureVariableName(const SourceString('box'),
+                                           closureFieldCounter++);
+          box = new BoxElement(boxName, currentElement);
+        }
+        String elementName = element.name.slowToString();
+        SourceString boxedName =
+            namer.getClosureVariableName(new SourceString(elementName),
+                                         boxedFieldCounter++);
+        // TODO(kasperl): Should this be a FieldElement instead?
+        Element boxed = new ElementX(boxedName, ElementKind.FIELD, box);
+        // No need to rename the fields of a box, so we give them a native name
+        // right now.
+        boxed.setFixedBackendName(boxedName.slowToString());
+        scopeMapping[element] = boxed;
+        capturedVariableMapping[element] = boxed;
+      }
+    }
+    if (!scopeMapping.isEmpty) {
+      ClosureScope scope = new ClosureScope(box, scopeMapping);
+      closureData.capturingScopes[node] = scope;
+    }
+  }
+
+  void inNewScope(Node node, Function action) {
+    List<Element> oldScopeVariables = scopeVariables;
+    scopeVariables = new List<Element>();
+    action();
+    attachCapturedScopeVariables(node);
+    for (Element element in scopeVariables) {
+      mutatedVariables.remove(element);
+    }
+    scopeVariables = oldScopeVariables;
+  }
+
+  visitLoop(Loop node) {
+    inNewScope(node, () {
+      node.visitChildren(this);
+    });
+  }
+
+  visitFor(For node) {
+    visitLoop(node);
+    // See if we have declared loop variables that need to be boxed.
+    if (node.initializer == null) return;
+    VariableDefinitions definitions = node.initializer.asVariableDefinitions();
+    if (definitions == null) return;
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    List<Element> result = <Element>[];
+    for (Link<Node> link = definitions.definitions.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      Node definition = link.head;
+      Element element = elements[definition];
+      if (capturedVariableMapping.containsKey(element)) {
+        result.add(element);
+      };
+    }
+    scopeData.boxedLoopVariables = result;
+  }
+
+  /** Returns a non-unique name for the given closure element. */
+  String computeClosureName(Element element) {
+    Link<String> parts = const Link<String>();
+    SourceString ownName = element.name;
+    if (ownName == null || ownName.stringValue == "") {
+      parts = parts.prepend("anon");
+    } else {
+      parts = parts.prepend(ownName.slowToString());
+    }
+    for (Element enclosingElement = element.enclosingElement;
+         enclosingElement != null &&
+             (identical(enclosingElement.kind,
+                        ElementKind.GENERATIVE_CONSTRUCTOR_BODY)
+              || identical(enclosingElement.kind, ElementKind.CLASS)
+              || identical(enclosingElement.kind, ElementKind.FUNCTION)
+              || identical(enclosingElement.kind, ElementKind.GETTER)
+              || identical(enclosingElement.kind, ElementKind.SETTER));
+         enclosingElement = enclosingElement.enclosingElement) {
+      SourceString surroundingName =
+          Elements.operatorNameToIdentifier(enclosingElement.name);
+      parts = parts.prepend(surroundingName.slowToString());
+    }
+    StringBuffer sb = new StringBuffer();
+    parts.printOn(sb, '_');
+    return sb.toString();
+  }
+
+  ClosureClassMap globalizeClosure(FunctionExpression node, Element element) {
+    SourceString closureName = new SourceString(computeClosureName(element));
+    ClassElement globalizedElement = new ClosureClassElement(
+        closureName, compiler, element, element.getCompilationUnit());
+    FunctionElement callElement =
+        new FunctionElementX.from(Compiler.CALL_OPERATOR_NAME,
+                                  element,
+                                  globalizedElement);
+    globalizedElement.addBackendMember(callElement);
+    // The nested function's 'this' is the same as the one for the outer
+    // function. It could be [null] if we are inside a static method.
+    Element thisElement = closureData.thisElement;
+    return new ClosureClassMap(element, globalizedElement,
+                               callElement, thisElement);
+  }
+
+  void visitInvokable(Element element, Expression node, void visitChildren()) {
+    bool oldInsideClosure = insideClosure;
+    Element oldFunctionElement = currentElement;
+    ClosureClassMap oldClosureData = closureData;
+
+    insideClosure = outermostElement != null;
+    currentElement = element;
+    if (insideClosure) {
+      closures.add(node);
+      closureData = globalizeClosure(node, element);
+    } else {
+      outermostElement = element;
+      Element thisElement = null;
+      if (element.isInstanceMember() || element.isGenerativeConstructor()) {
+        thisElement = new ThisElement(element);
+      }
+      closureData = new ClosureClassMap(null, null, null, thisElement);
+    }
+    closureMappingCache[node] = closureData;
+
+    inNewScope(node, () {
+      // We have to declare the implicit 'this' parameter.
+      if (!insideClosure && closureData.thisElement != null) {
+        declareLocal(closureData.thisElement);
+      }
+      // If we are inside a named closure we have to declare ourselve. For
+      // simplicity we declare the local even if the closure does not have a
+      // name.
+      // It will simply not be used.
+      if (insideClosure) {
+        declareLocal(element);
+      }
+
+      if (currentElement.isFactoryConstructor()
+          && compiler.world.needsRti(currentElement.enclosingElement)) {
+        // Declare the type parameters in the scope. Generative
+        // constructors just use 'this'.
+        ClassElement cls = currentElement.enclosingElement;
+        cls.typeVariables.forEach((TypeVariableType typeVariable) {
+          declareLocal(typeVariable.element);
+        });
+      }
+
+      visitChildren();
+    });
+
+
+    ClosureClassMap savedClosureData = closureData;
+    bool savedInsideClosure = insideClosure;
+
+    // Restore old values.
+    insideClosure = oldInsideClosure;
+    closureData = oldClosureData;
+    currentElement = oldFunctionElement;
+
+    // Mark all free variables as captured and use them in the outer function.
+    Iterable<Element> freeVariables = savedClosureData.freeVariableMapping.keys;
+    assert(freeVariables.isEmpty || savedInsideClosure);
+    for (Element freeElement in freeVariables) {
+      if (capturedVariableMapping[freeElement] != null &&
+          capturedVariableMapping[freeElement] != freeElement) {
+        compiler.internalError('In closure analyzer', node: node);
+      }
+      capturedVariableMapping[freeElement] = freeElement;
+      useLocal(freeElement);
+    }
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    Element element = elements[node];
+
+    if (element.isParameter()) {
+      // TODO(ahe): This is a hack. This method should *not* call
+      // visitChildren.
+      return node.name.accept(this);
+    }
+
+    visitInvokable(element, node, () {
+      // TODO(ahe): This is problematic. The backend should not repeat
+      // the work of the resolver. It is the resolver's job to create
+      // parameters, etc. Other phases should only visit statements.
+      if (node.parameters != null) node.parameters.accept(this);
+      if (node.initializers != null) node.initializers.accept(this);
+      if (node.body != null) node.body.accept(this);
+    });
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    node.visitChildren(this);
+    declareLocal(elements[node]);
+  }
+
+  visitTryStatement(TryStatement node) {
+    // TODO(ngeoffray): implement finer grain state.
+    bool oldInTryStatement = inTryStatement;
+    inTryStatement = true;
+    node.visitChildren(this);
+    inTryStatement = oldInTryStatement;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart b/pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart
new file mode 100644
index 0000000..c3465e8
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart
@@ -0,0 +1,106 @@
+// 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.
+
+part of dart2js;
+
+class CodeBuffer implements StringBuffer {
+  StringBuffer buffer;
+  List<CodeBufferMarker> markers;
+  int lastBufferOffset = 0;
+  int mappedRangeCounter = 0;
+
+  CodeBuffer()
+      : buffer = new StringBuffer(),
+        markers = new List<CodeBufferMarker>();
+
+  int get length => buffer.length;
+
+  bool get isEmpty {
+    return buffer.isEmpty;
+  }
+
+  /**
+   * Converts [object] to a string and adds it to the buffer. If [object] is a
+   * [CodeBuffer], adds its markers to [markers].
+   */
+  CodeBuffer add(var object) {
+    if (object is CodeBuffer) {
+      return addBuffer(object);
+    }
+    if (mappedRangeCounter == 0) setSourceLocation(null);
+    buffer.add(object.toString());
+    return this;
+  }
+
+  CodeBuffer addBuffer(CodeBuffer other) {
+    if (other.markers.length > 0) {
+      CodeBufferMarker firstMarker = other.markers[0];
+      int offsetDelta =
+          buffer.length + firstMarker.offsetDelta - lastBufferOffset;
+      markers.add(new CodeBufferMarker(offsetDelta,
+                                       firstMarker.sourcePosition));
+      for (int i = 1; i < other.markers.length; ++i) {
+        markers.add(other.markers[i]);
+      }
+      lastBufferOffset = buffer.length + other.lastBufferOffset;
+    }
+    buffer.add(other.getText());
+  }
+
+  CodeBuffer addAll(Iterable<Object> iterable) {
+    for (Object obj in iterable) {
+      add(obj);
+    }
+    return this;
+  }
+
+  CodeBuffer addCharCode(int charCode) {
+    return add(new String.fromCharCodes([charCode]));
+  }
+
+  CodeBuffer clear() {
+    buffer.clear();
+    markers.clear();
+    lastBufferOffset = 0;
+    return this;
+  }
+
+  String toString() {
+    throw "Don't use CodeBuffer.toString() since it drops sourcemap data.";
+  }
+
+  String getText() {
+    return buffer.toString();
+  }
+
+  void beginMappedRange() {
+    ++mappedRangeCounter;
+  }
+
+  void endMappedRange() {
+    assert(mappedRangeCounter > 0);
+    --mappedRangeCounter;
+  }
+
+  void setSourceLocation(var sourcePosition) {
+    int offsetDelta = buffer.length - lastBufferOffset;
+    markers.add(new CodeBufferMarker(offsetDelta, sourcePosition));
+    lastBufferOffset = buffer.length;
+  }
+
+  void forEachSourceLocation(void f(int targetOffset, var sourcePosition)) {
+    int targetOffset = 0;
+    markers.forEach((marker) {
+      targetOffset += marker.offsetDelta;
+      f(targetOffset, marker.sourcePosition);
+    });
+  }
+}
+
+class CodeBufferMarker {
+  final int offsetDelta;
+  final sourcePosition;
+
+  CodeBufferMarker(this.offsetDelta, this.sourcePosition);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/colors.dart b/pkgs/markdown/lib/src/compiler/implementation/colors.dart
new file mode 100644
index 0000000..42b3e7f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/colors.dart
@@ -0,0 +1,15 @@
+// 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.
+
+library colors;
+
+const String GREEN_COLOR = '\u001b[32m';
+const String RED_COLOR = '\u001b[31m';
+const String MAGENTA_COLOR = '\u001b[35m';
+const String NO_COLOR = '\u001b[0m';
+
+String wrap(String string, String color) => "${color}$string${NO_COLOR}";
+String green(String string) => wrap(string, GREEN_COLOR);
+String red(String string) => wrap(string, RED_COLOR);
+String magenta(String string) => wrap(string, MAGENTA_COLOR);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart b/pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart
new file mode 100644
index 0000000..691cbac
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart
@@ -0,0 +1,888 @@
+// 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.
+
+part of dart2js;
+
+/**
+ * The [ConstantHandler] keeps track of compile-time constants,
+ * initializations of global and static fields, and default values of
+ * optional parameters.
+ */
+class ConstantHandler extends CompilerTask {
+  final ConstantSystem constantSystem;
+  final bool isMetadata;
+
+  /**
+   * Contains the initial value of fields. Must contain all static and global
+   * initializations of const fields. May contain eagerly compiled values for
+   * statics and instance fields.
+   */
+  final Map<VariableElement, Constant> initialVariableValues;
+
+  /** Set of all registered compiled constants. */
+  final Set<Constant> compiledConstants;
+
+  /** The set of variable elements that are in the process of being computed. */
+  final Set<VariableElement> pendingVariables;
+
+  /** Caches the statics where the initial value cannot be eagerly compiled. */
+  final Set<VariableElement> lazyStatics;
+
+  /** Caches the createRuntimeType function if registered. */
+  Element createRuntimeTypeFunction = null;
+
+  ConstantHandler(Compiler compiler, this.constantSystem,
+                  { bool this.isMetadata: false })
+      : initialVariableValues = new Map<VariableElement, dynamic>(),
+        compiledConstants = new Set<Constant>(),
+        pendingVariables = new Set<VariableElement>(),
+        lazyStatics = new Set<VariableElement>(),
+        super(compiler);
+
+  String get name => 'ConstantHandler';
+
+  void registerCompileTimeConstant(Constant constant) {
+    registerInstantiatedClass(constant.computeType(compiler).element);
+    if (constant.isFunction()) {
+      FunctionConstant function = constant;
+      registerGetOfStaticFunction(function.element);
+    }
+    compiledConstants.add(constant);
+  }
+
+  void registerInstantiatedClass(ClassElement element) {
+    if (isMetadata) return;
+    compiler.enqueuer.codegen.registerInstantiatedClass(element);
+  }
+
+  void registerStaticUse(Element element) {
+    if (isMetadata) return;
+    compiler.enqueuer.codegen.registerStaticUse(element);
+  }
+
+  void registerGetOfStaticFunction(FunctionElement element) {
+    if (isMetadata) return;
+    compiler.enqueuer.codegen.registerGetOfStaticFunction(element);
+  }
+
+  void registerStringInstance() {
+    registerInstantiatedClass(compiler.stringClass);
+  }
+
+  void registerCreateRuntimeTypeFunction() {
+    if (createRuntimeTypeFunction != null) return;
+    SourceString helperName = const SourceString('createRuntimeType');
+    createRuntimeTypeFunction = compiler.findHelper(helperName);
+    registerStaticUse(createRuntimeTypeFunction);
+  }
+
+  /**
+   * Compiles the initial value of the given field and stores it in an internal
+   * map. Returns the initial value (a constant) if it can be computed
+   * statically. Returns [:null:] if the variable must be initialized lazily.
+   *
+   * [work] must contain a [VariableElement] refering to a global or
+   * static field.
+   */
+  Constant compileWorkItem(CodegenWorkItem work) {
+    return measure(() {
+      assert(work.element.kind == ElementKind.FIELD
+             || work.element.kind == ElementKind.PARAMETER
+             || work.element.kind == ElementKind.FIELD_PARAMETER);
+      VariableElement element = work.element;
+      // Shortcut if it has already been compiled.
+      Constant result = initialVariableValues[element];
+      if (result != null) return result;
+      if (lazyStatics.contains(element)) return null;
+      result = compileVariableWithDefinitions(element, work.resolutionTree);
+      assert(pendingVariables.isEmpty);
+      return result;
+    });
+  }
+
+  /**
+   * Returns a compile-time constant, or reports an error if the element is not
+   * a compile-time constant.
+   */
+  Constant compileConstant(VariableElement element) {
+    return compileVariable(element, isConst: true);
+  }
+
+  /**
+   * Returns the a compile-time constant if the variable could be compiled
+   * eagerly. Otherwise returns `null`.
+   */
+  Constant compileVariable(VariableElement element, {bool isConst: false}) {
+    return measure(() {
+      if (initialVariableValues.containsKey(element)) {
+        Constant result = initialVariableValues[element];
+        return result;
+      }
+      return compiler.withCurrentElement(element, () {
+        TreeElements definitions = compiler.analyzeElement(element);
+        Constant constant = compileVariableWithDefinitions(
+            element, definitions, isConst: isConst);
+        return constant;
+      });
+    });
+  }
+
+  /**
+   * Returns the a compile-time constant if the variable could be compiled
+   * eagerly. If the variable needs to be initialized lazily returns `null`.
+   * If the variable is `const` but cannot be compiled eagerly reports an
+   * error.
+   */
+  Constant compileVariableWithDefinitions(VariableElement element,
+                                          TreeElements definitions,
+                                          {bool isConst: false}) {
+    return measure(() {
+      // Initializers for parameters must be const.
+      isConst = isConst || element.modifiers.isConst()
+          || !Elements.isStaticOrTopLevel(element);
+      if (!isConst && lazyStatics.contains(element)) return null;
+
+      Node node = element.parseNode(compiler);
+      if (pendingVariables.contains(element)) {
+        if (isConst) {
+          MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS;
+          compiler.reportError(node,
+                               new CompileTimeConstantError(kind));
+        } else {
+          lazyStatics.add(element);
+          return null;
+        }
+      }
+      pendingVariables.add(element);
+
+      SendSet assignment = node.asSendSet();
+      Constant value;
+      if (assignment == null) {
+        // No initial value.
+        value = new NullConstant();
+      } else {
+        Node right = assignment.arguments.head;
+        value =
+            compileNodeWithDefinitions(right, definitions, isConst: isConst);
+        if (compiler.enableTypeAssertions
+            && value != null
+            && element.isField()) {
+          DartType elementType = element.computeType(compiler);
+          DartType constantType = value.computeType(compiler);
+          if (elementType.isMalformed || constantType.isMalformed ||
+              !constantSystem.isSubtype(compiler, constantType, elementType)) {
+            if (isConst) {
+              compiler.reportError(node, new CompileTimeConstantError(
+                  MessageKind.NOT_ASSIGNABLE,
+                  {'fromType': elementType, 'toType': constantType}));
+            } else {
+              // If the field can be lazily initialized, we will throw
+              // the exception at runtime.
+              value = null;
+            }
+          }
+        }
+      }
+      if (value != null) {
+        initialVariableValues[element] = value;
+      } else {
+        assert(!isConst);
+        lazyStatics.add(element);
+      }
+      pendingVariables.remove(element);
+      return value;
+    });
+  }
+
+  Constant compileNodeWithDefinitions(Node node,
+                                      TreeElements definitions,
+                                      {bool isConst: false}) {
+    return measure(() {
+      assert(node != null);
+      CompileTimeConstantEvaluator evaluator = new CompileTimeConstantEvaluator(
+          this, definitions, compiler, isConst: isConst);
+      return evaluator.evaluate(node);
+    });
+  }
+
+  /** Attempts to compile a constant expression. Returns null if not possible */
+  Constant tryCompileNodeWithDefinitions(Node node, TreeElements definitions) {
+    return measure(() {
+      assert(node != null);
+      try {
+        TryCompileTimeConstantEvaluator evaluator =
+            new TryCompileTimeConstantEvaluator(this, definitions, compiler);
+        return evaluator.evaluate(node);
+      } on CompileTimeConstantError catch (exn) {
+        return null;
+      }
+    });
+  }
+
+  /**
+   * Returns an [Iterable] of static non final fields that need to be
+   * initialized. The fields list must be evaluated in order since they might
+   * depend on each other.
+   */
+  Iterable<VariableElement> getStaticNonFinalFieldsForEmission() {
+    return initialVariableValues.keys.where((element) {
+      return element.kind == ElementKind.FIELD
+          && !element.isInstanceMember()
+          && !element.modifiers.isFinal()
+          // The const fields are all either emitted elsewhere or inlined.
+          && !element.modifiers.isConst();
+    });
+  }
+
+  /**
+   * Returns an [Iterable] of static const fields that need to be initialized.
+   * The fields must be evaluated in order since they might depend on each
+   * other.
+   */
+  Iterable<VariableElement> getStaticFinalFieldsForEmission() {
+    return initialVariableValues.keys.where((element) {
+      return element.kind == ElementKind.FIELD
+          && !element.isInstanceMember()
+          && element.modifiers.isFinal();
+    });
+  }
+
+  List<VariableElement> getLazilyInitializedFieldsForEmission() {
+    return new List<VariableElement>.from(lazyStatics);
+  }
+
+  List<Constant> getConstantsForEmission() {
+    // We must emit dependencies before their uses.
+    Set<Constant> seenConstants = new Set<Constant>();
+    List<Constant> result = new List<Constant>();
+
+    void addConstant(Constant constant) {
+      if (!seenConstants.contains(constant)) {
+        constant.getDependencies().forEach(addConstant);
+        assert(!seenConstants.contains(constant));
+        result.add(constant);
+        seenConstants.add(constant);
+      }
+    }
+
+    compiledConstants.forEach(addConstant);
+    return result;
+  }
+
+  Constant getInitialValueFor(VariableElement element) {
+    Constant initialValue = initialVariableValues[element];
+    if (initialValue == null) {
+      compiler.internalError("No initial value for given element",
+                             element: element);
+    }
+    return initialValue;
+  }
+}
+
+class CompileTimeConstantEvaluator extends Visitor {
+  bool isEvaluatingConstant;
+  final ConstantHandler handler;
+  final TreeElements elements;
+  final Compiler compiler;
+
+  CompileTimeConstantEvaluator(this.handler,
+                               this.elements,
+                               this.compiler,
+                               {bool isConst: false})
+      : this.isEvaluatingConstant = isConst;
+
+  ConstantSystem get constantSystem => handler.constantSystem;
+
+  Constant evaluate(Node node) {
+    return node.accept(this);
+  }
+
+  Constant evaluateConstant(Node node) {
+    bool oldIsEvaluatingConstant = isEvaluatingConstant;
+    isEvaluatingConstant = true;
+    Constant result = node.accept(this);
+    isEvaluatingConstant = oldIsEvaluatingConstant;
+    assert(result != null);
+    return result;
+  }
+
+  Constant visitNode(Node node) {
+    return signalNotCompileTimeConstant(node);
+  }
+
+  Constant visitLiteralBool(LiteralBool node) {
+    handler.registerInstantiatedClass(compiler.boolClass);
+    return constantSystem.createBool(node.value);
+  }
+
+  Constant visitLiteralDouble(LiteralDouble node) {
+    handler.registerInstantiatedClass(compiler.doubleClass);
+    return constantSystem.createDouble(node.value);
+  }
+
+  Constant visitLiteralInt(LiteralInt node) {
+    handler.registerInstantiatedClass(compiler.intClass);
+    return constantSystem.createInt(node.value);
+  }
+
+  Constant visitLiteralList(LiteralList node) {
+    if (!node.isConst())  {
+      return signalNotCompileTimeConstant(node);
+    }
+    List<Constant> arguments = <Constant>[];
+    for (Link<Node> link = node.elements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      arguments.add(evaluateConstant(link.head));
+    }
+    // TODO(floitsch): get type parameters.
+    DartType type = new InterfaceType(compiler.listClass);
+    Constant constant = new ListConstant(type, arguments);
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  Constant visitLiteralMap(LiteralMap node) {
+    if (!node.isConst()) {
+      return signalNotCompileTimeConstant(node);
+    }
+    List<StringConstant> keys = <StringConstant>[];
+    Map<StringConstant, Constant> map = new Map<StringConstant, Constant>();
+    for (Link<Node> link = node.entries.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      LiteralMapEntry entry = link.head;
+      Constant key = evaluateConstant(entry.key);
+      if (!key.isString() || entry.key.asStringNode() == null) {
+        MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL;
+        compiler.reportError(entry.key, new ResolutionError(kind));
+      }
+      StringConstant keyConstant = key;
+      if (!map.containsKey(key)) keys.add(key);
+      map[key] = evaluateConstant(entry.value);
+    }
+    List<Constant> values = <Constant>[];
+    Constant protoValue = null;
+    for (StringConstant key in keys) {
+      if (key.value == MapConstant.PROTO_PROPERTY) {
+        protoValue = map[key];
+      } else {
+        values.add(map[key]);
+      }
+    }
+    bool hasProtoKey = (protoValue != null);
+    // TODO(floitsch): this should be a List<String> type.
+    DartType keysType = new InterfaceType(compiler.listClass);
+    ListConstant keysList = new ListConstant(keysType, keys);
+    handler.registerCompileTimeConstant(keysList);
+    SourceString className = hasProtoKey
+                             ? MapConstant.DART_PROTO_CLASS
+                             : MapConstant.DART_CLASS;
+    ClassElement classElement = compiler.jsHelperLibrary.find(className);
+    classElement.ensureResolved(compiler);
+    // TODO(floitsch): copy over the generic type.
+    DartType type = new InterfaceType(classElement);
+    handler.registerInstantiatedClass(classElement);
+    Constant constant = new MapConstant(type, keysList, values, protoValue);
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  Constant visitLiteralNull(LiteralNull node) {
+    return constantSystem.createNull();
+  }
+
+  Constant visitLiteralString(LiteralString node) {
+    handler.registerStringInstance();
+    return constantSystem.createString(node.dartString, node);
+  }
+
+  Constant visitStringJuxtaposition(StringJuxtaposition node) {
+    StringConstant left = evaluate(node.first);
+    StringConstant right = evaluate(node.second);
+    if (left == null || right == null) return null;
+    handler.registerStringInstance();
+    return constantSystem.createString(
+        new DartString.concat(left.value, right.value), node);
+  }
+
+  Constant visitStringInterpolation(StringInterpolation node) {
+    StringConstant initialString = evaluate(node.string);
+    if (initialString == null) return null;
+    DartString accumulator = initialString.value;
+    for (StringInterpolationPart part in node.parts) {
+      Constant expression = evaluate(part.expression);
+      DartString expressionString;
+      if (expression == null) {
+        return signalNotCompileTimeConstant(part.expression);
+      } else if (expression.isNum() || expression.isBool()) {
+        PrimitiveConstant primitive = expression;
+        expressionString = new DartString.literal(primitive.value.toString());
+      } else if (expression.isString()) {
+        PrimitiveConstant primitive = expression;
+        expressionString = primitive.value;
+      } else {
+        return signalNotCompileTimeConstant(part.expression);
+      }
+      accumulator = new DartString.concat(accumulator, expressionString);
+      StringConstant partString = evaluate(part.string);
+      if (partString == null) return null;
+      accumulator = new DartString.concat(accumulator, partString.value);
+    };
+    handler.registerStringInstance();
+    return constantSystem.createString(accumulator, node);
+  }
+
+  Constant makeTypeConstant(Element element) {
+    DartType elementType = element.computeType(compiler).asRaw();
+    DartType constantType = compiler.typeClass.computeType(compiler);
+    Constant constant = new TypeConstant(elementType, constantType);
+    // If we use a type literal in a constant, the compile time
+    // constant emitter will generate a call to the createRuntimeType
+    // helper so we register a use of that.
+    handler.registerCreateRuntimeTypeFunction();
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  // TODO(floitsch): provide better error-messages.
+  Constant visitSend(Send send) {
+    Element element = elements[send];
+    if (send.isPropertyAccess) {
+      if (Elements.isStaticOrTopLevelFunction(element)) {
+        Constant constant = new FunctionConstant(element);
+        handler.registerCompileTimeConstant(constant);
+        return constant;
+      } else if (Elements.isStaticOrTopLevelField(element)) {
+        Constant result;
+        if (element.modifiers.isConst()) {
+          result = handler.compileConstant(element);
+        } else if (element.modifiers.isFinal() && !isEvaluatingConstant) {
+          result = handler.compileVariable(element);
+        }
+        if (result != null) return result;
+      } else if (Elements.isClass(element) || Elements.isTypedef(element)) {
+        return makeTypeConstant(element);
+      } else if (send.receiver != null) {
+        // Fall through to error handling.
+      } else if (!Elements.isUnresolved(element)
+                 && element.isVariable()
+                 && element.modifiers.isConst()) {
+        Constant result = handler.compileConstant(element);
+        if (result != null) return result;
+      }
+      return signalNotCompileTimeConstant(send);
+    } else if (send.isCall) {
+      if (identical(element, compiler.identicalFunction)
+          && send.argumentCount() == 2) {
+        Constant left = evaluate(send.argumentsNode.nodes.head);
+        Constant right = evaluate(send.argumentsNode.nodes.tail.head);
+        Constant result = constantSystem.identity.fold(left, right);
+        if (result != null) return result;
+      } else if (Elements.isClass(element) || Elements.isTypedef(element)) {
+        return makeTypeConstant(element);
+      }
+      return signalNotCompileTimeConstant(send);
+    } else if (send.isPrefix) {
+      assert(send.isOperator);
+      Constant receiverConstant = evaluate(send.receiver);
+      if (receiverConstant == null) return null;
+      Operator op = send.selector;
+      Constant folded;
+      switch (op.source.stringValue) {
+        case "!":
+          folded = constantSystem.not.fold(receiverConstant);
+          break;
+        case "-":
+          folded = constantSystem.negate.fold(receiverConstant);
+          break;
+        case "~":
+          folded = constantSystem.bitNot.fold(receiverConstant);
+          break;
+        default:
+          compiler.internalError("Unexpected operator.", node: op);
+          break;
+      }
+      if (folded == null) return signalNotCompileTimeConstant(send);
+      return folded;
+    } else if (send.isOperator && !send.isPostfix) {
+      assert(send.argumentCount() == 1);
+      Constant left = evaluate(send.receiver);
+      Constant right = evaluate(send.argumentsNode.nodes.head);
+      if (left == null || right == null) return null;
+      Operator op = send.selector.asOperator();
+      Constant folded = null;
+      switch (op.source.stringValue) {
+        case "+":
+          folded = constantSystem.add.fold(left, right);
+          break;
+        case "-":
+          folded = constantSystem.subtract.fold(left, right);
+          break;
+        case "*":
+          folded = constantSystem.multiply.fold(left, right);
+          break;
+        case "/":
+          folded = constantSystem.divide.fold(left, right);
+          break;
+        case "%":
+          folded = constantSystem.modulo.fold(left, right);
+          break;
+        case "~/":
+          folded = constantSystem.truncatingDivide.fold(left, right);
+          break;
+        case "|":
+          folded = constantSystem.bitOr.fold(left, right);
+          break;
+        case "&":
+          folded = constantSystem.bitAnd.fold(left, right);
+          break;
+        case "^":
+          folded = constantSystem.bitXor.fold(left, right);
+          break;
+        case "||":
+          folded = constantSystem.booleanOr.fold(left, right);
+          break;
+        case "&&":
+          folded = constantSystem.booleanAnd.fold(left, right);
+          break;
+        case "<<":
+          folded = constantSystem.shiftLeft.fold(left, right);
+          break;
+        case ">>":
+          folded = constantSystem.shiftRight.fold(left, right);
+          break;
+        case "<":
+          folded = constantSystem.less.fold(left, right);
+          break;
+        case "<=":
+          folded = constantSystem.lessEqual.fold(left, right);
+          break;
+        case ">":
+          folded = constantSystem.greater.fold(left, right);
+          break;
+        case ">=":
+          folded = constantSystem.greaterEqual.fold(left, right);
+          break;
+        case "==":
+          if (left.isPrimitive() && right.isPrimitive()) {
+            folded = constantSystem.equal.fold(left, right);
+          }
+          break;
+        case "===":
+          folded = constantSystem.identity.fold(left, right);
+          break;
+        case "!=":
+          if (left.isPrimitive() && right.isPrimitive()) {
+            BoolConstant areEquals = constantSystem.equal.fold(left, right);
+            if (areEquals == null) {
+              folded = null;
+            } else {
+              folded = areEquals.negate();
+            }
+          }
+          break;
+        case "!==":
+          BoolConstant areIdentical =
+              constantSystem.identity.fold(left, right);
+          if (areIdentical == null) {
+            folded = null;
+          } else {
+            folded = areIdentical.negate();
+          }
+          break;
+      }
+      if (folded == null) return signalNotCompileTimeConstant(send);
+      return folded;
+    }
+    return signalNotCompileTimeConstant(send);
+  }
+
+  Constant visitSendSet(SendSet node) {
+    return signalNotCompileTimeConstant(node);
+  }
+
+  /**
+   * Returns the list of constants that are passed to the static function.
+   *
+   * Invariant: [target] must be an implementation element.
+   */
+  List<Constant> evaluateArgumentsToConstructor(Node node,
+                                                Selector selector,
+                                                Link<Node> arguments,
+                                                FunctionElement target) {
+    assert(invariant(node, target.isImplementation));
+    List<Constant> compiledArguments = <Constant>[];
+
+    Function compileArgument = evaluateConstant;
+    Function compileConstant = handler.compileConstant;
+    bool succeeded = selector.addArgumentsToList(arguments,
+                                                 compiledArguments,
+                                                 target,
+                                                 compileArgument,
+                                                 compileConstant,
+                                                 compiler);
+    if (!succeeded) {
+      MessageKind kind = MessageKind.INVALID_ARGUMENTS;
+      compiler.reportError(node,
+          new CompileTimeConstantError(kind, {'methodName': target.name}));
+    }
+    return compiledArguments;
+  }
+
+  Constant visitNewExpression(NewExpression node) {
+    if (!node.isConst()) {
+      return signalNotCompileTimeConstant(node);
+    }
+
+    Send send = node.send;
+    FunctionElement constructor = elements[send];
+    constructor = constructor.redirectionTarget;
+    ClassElement classElement = constructor.getEnclosingClass();
+    if (classElement.isInterface()) {
+      compiler.resolver.resolveMethodElement(constructor);
+      constructor = constructor.defaultImplementation;
+      classElement = constructor.getEnclosingClass();
+    }
+    // The constructor must be an implementation to ensure that field
+    // initializers are handled correctly.
+    constructor = constructor.implementation;
+    assert(invariant(node, constructor.isImplementation));
+
+    Selector selector = elements.getSelector(send);
+    List<Constant> arguments = evaluateArgumentsToConstructor(
+        node, selector, send.arguments, constructor);
+    ConstructorEvaluator evaluator =
+        new ConstructorEvaluator(node, constructor, handler, compiler);
+    evaluator.evaluateConstructorFieldValues(arguments);
+    List<Constant> jsNewArguments = evaluator.buildJsNewArguments(classElement);
+
+    handler.registerInstantiatedClass(classElement);
+    // TODO(floitsch): take generic types into account.
+    DartType type = classElement.computeType(compiler);
+    Constant constant = new ConstructedConstant(type, jsNewArguments);
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  Constant visitParenthesizedExpression(ParenthesizedExpression node) {
+    return node.expression.accept(this);
+  }
+
+  error(Node node) {
+    // TODO(floitsch): get the list of constants that are currently compiled
+    // and present some kind of stack-trace.
+    MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT;
+    compiler.reportError(node, new CompileTimeConstantError(kind));
+  }
+
+  Constant signalNotCompileTimeConstant(Node node) {
+    if (isEvaluatingConstant) {
+      error(node);
+    }
+    // Else we don't need to do anything. The final handler is only
+    // optimistically trying to compile constants. So it is normal that we
+    // sometimes see non-compile time constants.
+    // Simply return [:null:] which is used to propagate a failing
+    // compile-time compilation.
+    return null;
+  }
+}
+
+class TryCompileTimeConstantEvaluator extends CompileTimeConstantEvaluator {
+  TryCompileTimeConstantEvaluator(ConstantHandler handler,
+                                  TreeElements elements,
+                                  Compiler compiler)
+      : super(handler, elements, compiler, isConst: true);
+
+  error(Node node) {
+    // Just fail without reporting it anywhere.
+    throw new CompileTimeConstantError(
+        MessageKind.NOT_A_COMPILE_TIME_CONSTANT);
+  }
+}
+
+class ConstructorEvaluator extends CompileTimeConstantEvaluator {
+  final FunctionElement constructor;
+  final Map<Element, Constant> definitions;
+  final Map<Element, Constant> fieldValues;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [constructor] must be an implementation element.
+   */
+  ConstructorEvaluator(Node node,
+                       FunctionElement constructor,
+                       ConstantHandler handler,
+                       Compiler compiler)
+      : this.constructor = constructor,
+        this.definitions = new Map<Element, Constant>(),
+        this.fieldValues = new Map<Element, Constant>(),
+        super(handler,
+              compiler.resolver.resolveMethodElement(constructor.declaration),
+              compiler,
+              isConst: true) {
+    assert(invariant(node, constructor.isImplementation));
+  }
+
+  Constant visitSend(Send send) {
+    Element element = elements[send];
+    if (Elements.isLocal(element)) {
+      Constant constant = definitions[element];
+      if (constant == null) {
+        compiler.internalError("Local variable without value", node: send);
+      }
+      return constant;
+    }
+    return super.visitSend(send);
+  }
+
+  void potentiallyCheckType(Node node, Element element, Constant constant) {
+    if (compiler.enableTypeAssertions) {
+      DartType elementType = element.computeType(compiler);
+      DartType constantType = constant.computeType(compiler);
+      // TODO(ngeoffray): Handle type parameters.
+      if (elementType.element.isTypeVariable()) return;
+      if (elementType.isMalformed || constantType.isMalformed ||
+          !constantSystem.isSubtype(compiler, constantType, elementType)) {
+        compiler.reportError(node, new CompileTimeConstantError(
+            MessageKind.NOT_ASSIGNABLE,
+            {'fromType': elementType, 'toType': constantType}));
+      }
+    }
+  }
+
+  void updateFieldValue(Node node, Element element, Constant constant) {
+    potentiallyCheckType(node, element, constant);
+    fieldValues[element] = constant;
+  }
+
+  /**
+   * Given the arguments (a list of constants) assigns them to the parameters,
+   * updating the definitions map. If the constructor has field-initializer
+   * parameters (like [:this.x:]), also updates the [fieldValues] map.
+   */
+  void assignArgumentsToParameters(List<Constant> arguments) {
+    // Assign arguments to parameters.
+    FunctionSignature parameters = constructor.computeSignature(compiler);
+    int index = 0;
+    parameters.orderedForEachParameter((Element parameter) {
+      Constant argument = arguments[index++];
+      Node node = parameter.parseNode(compiler);
+      potentiallyCheckType(node, parameter, argument);
+      definitions[parameter] = argument;
+      if (parameter.kind == ElementKind.FIELD_PARAMETER) {
+        FieldParameterElement fieldParameterElement = parameter;
+        updateFieldValue(node, fieldParameterElement.fieldElement, argument);
+      }
+    });
+  }
+
+  void evaluateSuperOrRedirectSend(Node currentNode,
+                                   Selector selector,
+                                   Link<Node> arguments,
+                                   FunctionElement targetConstructor) {
+    List<Constant> compiledArguments = evaluateArgumentsToConstructor(
+        currentNode, selector, arguments, targetConstructor);
+
+    ConstructorEvaluator evaluator = new ConstructorEvaluator(
+        currentNode, targetConstructor, handler, compiler);
+    evaluator.evaluateConstructorFieldValues(compiledArguments);
+    // Copy over the fieldValues from the super/redirect-constructor.
+    // No need to go through [updateFieldValue] because the
+    // assignments have already been checked in checked mode.
+    evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value);
+  }
+
+  /**
+   * Runs through the initializers of the given [constructor] and updates
+   * the [fieldValues] map.
+   */
+  void evaluateConstructorInitializers() {
+    FunctionExpression functionNode = constructor.parseNode(compiler);
+    NodeList initializerList = functionNode.initializers;
+
+    bool foundSuperOrRedirect = false;
+
+    if (initializerList != null) {
+      for (Link<Node> link = initializerList.nodes;
+           !link.isEmpty;
+           link = link.tail) {
+        assert(link.head is Send);
+        if (link.head is !SendSet) {
+          // A super initializer or constructor redirection.
+          Send call = link.head;
+          FunctionElement targetConstructor = elements[call];
+          Selector selector = elements.getSelector(call);
+          Link<Node> arguments = call.arguments;
+          evaluateSuperOrRedirectSend(
+              call, selector, arguments, targetConstructor);
+          foundSuperOrRedirect = true;
+        } else {
+          // A field initializer.
+          SendSet init = link.head;
+          Link<Node> initArguments = init.arguments;
+          assert(!initArguments.isEmpty && initArguments.tail.isEmpty);
+          Constant fieldValue = evaluate(initArguments.head);
+          updateFieldValue(init, elements[init], fieldValue);
+        }
+      }
+    }
+
+    if (!foundSuperOrRedirect) {
+      // No super initializer found. Try to find the default constructor if
+      // the class is not Object.
+      ClassElement enclosingClass = constructor.getEnclosingClass();
+      ClassElement superClass = enclosingClass.superclass;
+      if (enclosingClass != compiler.objectClass) {
+        assert(superClass != null);
+        assert(superClass.resolutionState == STATE_DONE);
+
+        Selector selector =
+            new Selector.callDefaultConstructor(enclosingClass.getLibrary());
+
+        FunctionElement targetConstructor =
+            superClass.lookupConstructor(selector);
+        if (targetConstructor == null) {
+          compiler.internalError("no default constructor available",
+                                 node: functionNode);
+        }
+
+        evaluateSuperOrRedirectSend(functionNode,
+                                    selector,
+                                    const Link<Node>(),
+                                    targetConstructor);
+      }
+    }
+  }
+
+  /**
+   * Simulates the execution of the [constructor] with the given
+   * [arguments] to obtain the field values that need to be passed to the
+   * native JavaScript constructor.
+   */
+  void evaluateConstructorFieldValues(List<Constant> arguments) {
+    compiler.withCurrentElement(constructor, () {
+      assignArgumentsToParameters(arguments);
+      evaluateConstructorInitializers();
+    });
+  }
+
+  List<Constant> buildJsNewArguments(ClassElement classElement) {
+    List<Constant> jsNewArguments = <Constant>[];
+    classElement.implementation.forEachInstanceField(
+        (ClassElement enclosing, Element field) {
+          Constant fieldValue = fieldValues[field];
+          if (fieldValue == null) {
+            // Use the default value.
+            fieldValue = handler.compileConstant(field);
+          }
+          jsNewArguments.add(fieldValue);
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: true);
+    return jsNewArguments;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/compiler.dart b/pkgs/markdown/lib/src/compiler/implementation/compiler.dart
new file mode 100644
index 0000000..4fef805
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/compiler.dart
@@ -0,0 +1,1130 @@
+// 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.
+
+part of dart2js;
+
+/**
+ * If true, print a warning for each method that was resolved, but not
+ * compiled.
+ */
+const bool REPORT_EXCESS_RESOLUTION = false;
+
+/**
+ * If true, dump the inferred types after compilation.
+ */
+const bool DUMP_INFERRED_TYPES = false;
+
+/**
+ * A string to identify the revision or build.
+ *
+ * This ID is displayed if the compiler crashes and in verbose mode, and is
+ * an aid in reproducing bug reports.
+ *
+ * The actual string is rewritten during the SDK build process.
+ */
+const String BUILD_ID = '0.3.5.1_r18300';
+
+/**
+ * Contains backend-specific data that is used throughout the compilation of
+ * one work item.
+ */
+class ItemCompilationContext {
+}
+
+abstract class WorkItem {
+  final ItemCompilationContext compilationContext;
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  final Element element;
+  TreeElements get resolutionTree;
+
+  WorkItem(this.element, this.compilationContext) {
+    assert(invariant(element, element.isDeclaration));
+  }
+
+  bool isAnalyzed() => resolutionTree != null;
+
+  void run(Compiler compiler, Enqueuer world);
+}
+
+/// [WorkItem] used exclusively by the [ResolutionEnqueuer].
+class ResolutionWorkItem extends WorkItem {
+  TreeElements resolutionTree;
+
+  ResolutionWorkItem(Element element,
+                     ItemCompilationContext compilationContext)
+      : super(element, compilationContext);
+
+  void run(Compiler compiler, ResolutionEnqueuer world) {
+    resolutionTree = compiler.analyze(this, world);
+  }
+}
+
+/// [WorkItem] used exclusively by the [CodegenEnqueuer].
+class CodegenWorkItem extends WorkItem {
+  final TreeElements resolutionTree;
+
+  bool allowSpeculativeOptimization = true;
+  List<HTypeGuard> guards = const <HTypeGuard>[];
+
+  CodegenWorkItem(Element element,
+                  TreeElements this.resolutionTree,
+                  ItemCompilationContext compilationContext)
+      : super(element, compilationContext) {
+    assert(invariant(element, resolutionTree != null,
+        message: 'Resolution tree is null for $element in codegen work item'));
+  }
+
+  void run(Compiler compiler, CodegenEnqueuer world) {
+    if (world.isProcessed(element)) return;
+    compiler.codegen(this, world);
+  }
+}
+
+class ReadingFilesTask extends CompilerTask {
+  ReadingFilesTask(Compiler compiler) : super(compiler);
+  String get name => 'Reading input files';
+}
+
+abstract class Backend {
+  final Compiler compiler;
+  final ConstantSystem constantSystem;
+
+  Backend(this.compiler,
+          [ConstantSystem constantSystem = DART_CONSTANT_SYSTEM])
+      : this.constantSystem = constantSystem;
+
+  void enqueueAllTopLevelFunctions(LibraryElement lib, Enqueuer world) {
+    lib.forEachExport((Element e) {
+      if (e.isFunction()) world.addToWorkList(e);
+    });
+  }
+
+  void enqueueHelpers(ResolutionEnqueuer world);
+  void codegen(CodegenWorkItem work);
+
+  // The backend determines the native resolution enqueuer, with a no-op
+  // default, so tools like dart2dart can ignore the native classes.
+  native.NativeEnqueuer nativeResolutionEnqueuer(world) {
+    return new native.NativeEnqueuer();
+  }
+  native.NativeEnqueuer nativeCodegenEnqueuer(world) {
+    return new native.NativeEnqueuer();
+  }
+
+  void assembleProgram();
+  List<CompilerTask> get tasks;
+
+  // TODO(ahe,karlklose): rename this?
+  void dumpInferredTypes() {}
+
+  ItemCompilationContext createItemCompilationContext() {
+    return new ItemCompilationContext();
+  }
+
+  SourceString getCheckedModeHelper(DartType type) => null;
+  void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {}
+}
+
+/**
+ * Key class used in [TokenMap] in which the hash code for a token is based
+ * on the [charOffset].
+ */
+class TokenKey {
+  final Token token;
+  TokenKey(this.token);
+  int get hashCode => token.charOffset;
+  operator==(other) => other is TokenKey && token == other.token;
+}
+
+/// Map of tokens and the first associated comment.
+/*
+ * This implementation was chosen among several candidates for its space/time
+ * efficiency by empirical tests of running dartdoc on dartdoc itself. Time
+ * measurements for the use of [Compiler.commentMap]:
+ *
+ * 1) Using [TokenKey] as key (this class): ~80 msec
+ * 2) Using [TokenKey] as key + storing a separate map in each script: ~120 msec
+ * 3) Using [Token] as key in a [Map]: ~38000 msec
+ * 4) Storing comments is new field in [Token]: ~20 msec
+ *    (Abandoned due to the increased memory usage)
+ * 5) Storing comments in an [Expando]: ~14000 msec
+ * 6) Storing token/comments pairs in a linked list: ~5400 msec
+ */
+class TokenMap {
+  Map<TokenKey,Token> comments = new Map<TokenKey,Token>();
+
+  Token operator[] (Token key) {
+    if (key == null) return null;
+    return comments[new TokenKey(key)];
+  }
+
+  void operator[]= (Token key, Token value) {
+    if (key == null) return;
+    comments[new TokenKey(key)] = value;
+  }
+}
+
+abstract class Compiler implements DiagnosticListener {
+  final Map<String, LibraryElement> libraries;
+  final Stopwatch totalCompileTime = new Stopwatch();
+  int nextFreeClassId = 0;
+  World world;
+  String assembledCode;
+  Types types;
+
+  /**
+   * Map from token to the first preceeding comment token.
+   */
+  final TokenMap commentMap = new TokenMap();
+
+  final bool enableMinification;
+  final bool enableTypeAssertions;
+  final bool enableUserAssertions;
+  final bool enableConcreteTypeInference;
+  /**
+   * The maximum size of a concrete type before it widens to dynamic during
+   * concrete type inference.
+   */
+  final int maxConcreteTypeSize;
+  final bool analyzeAll;
+  final bool analyzeOnly;
+  final bool enableNativeLiveTypeAnalysis;
+  final bool rejectDeprecatedFeatures;
+  final bool checkDeprecationInSdk;
+
+  /**
+   * If [:true:], comment tokens are collected in [commentMap] during scanning.
+   */
+  final bool preserveComments;
+
+  final api.CompilerOutputProvider outputProvider;
+
+  bool disableInlining = false;
+
+  List<Uri> librariesToAnalyzeWhenRun;
+
+  final Tracer tracer;
+
+  CompilerTask measuredTask;
+  Element _currentElement;
+  LibraryElement coreLibrary;
+  LibraryElement isolateLibrary;
+  LibraryElement isolateHelperLibrary;
+  LibraryElement jsHelperLibrary;
+  LibraryElement interceptorsLibrary;
+  LibraryElement foreignLibrary;
+  LibraryElement mainApp;
+
+  ClassElement objectClass;
+  ClassElement closureClass;
+  ClassElement dynamicClass;
+  ClassElement boolClass;
+  ClassElement numClass;
+  ClassElement intClass;
+  ClassElement doubleClass;
+  ClassElement stringClass;
+  ClassElement functionClass;
+  ClassElement nullClass;
+  ClassElement listClass;
+  ClassElement typeClass;
+  ClassElement mapClass;
+  ClassElement jsInvocationMirrorClass;
+  /// Document class from dart:mirrors.
+  ClassElement documentClass;
+  Element assertMethod;
+  Element identicalFunction;
+  Element functionApplyMethod;
+  Element invokeOnMethod;
+  Element createInvocationMirrorElement;
+
+  Element get currentElement => _currentElement;
+  withCurrentElement(Element element, f()) {
+    Element old = currentElement;
+    _currentElement = element;
+    try {
+      return f();
+    } on SpannableAssertionFailure catch (ex) {
+      if (!hasCrashed) {
+        SourceSpan span = spanFromSpannable(ex.node);
+        reportDiagnostic(span, ex.message, api.Diagnostic.ERROR);
+        pleaseReportCrash();
+      }
+      hasCrashed = true;
+      throw;
+    } on CompilerCancelledException catch (ex) {
+      throw;
+    } on StackOverflowError catch (ex) {
+      // We cannot report anything useful in this case, because we
+      // do not have enough stack space.
+      throw;
+    } catch (ex) {
+      try {
+        unhandledExceptionOnElement(element);
+      } catch (doubleFault) {
+        // Ignoring exceptions in exception handling.
+      }
+      throw;
+    } finally {
+      _currentElement = old;
+    }
+  }
+
+  List<CompilerTask> tasks;
+  ScannerTask scanner;
+  DietParserTask dietParser;
+  ParserTask parser;
+  PatchParserTask patchParser;
+  LibraryLoader libraryLoader;
+  TreeValidatorTask validator;
+  ResolverTask resolver;
+  closureMapping.ClosureTask closureToClassMapper;
+  TypeCheckerTask checker;
+  ti.TypesTask typesTask;
+  Backend backend;
+  ConstantHandler constantHandler;
+  ConstantHandler metadataHandler;
+  EnqueueTask enqueuer;
+  CompilerTask fileReadingTask;
+
+  static const SourceString MAIN = const SourceString('main');
+  static const SourceString CALL_OPERATOR_NAME = const SourceString('call');
+  static const SourceString NO_SUCH_METHOD = const SourceString('noSuchMethod');
+  static const int NO_SUCH_METHOD_ARG_COUNT = 1;
+  static const SourceString CREATE_INVOCATION_MIRROR =
+      const SourceString('createInvocationMirror');
+  static const SourceString INVOKE_ON = const SourceString('invokeOn');
+  static const SourceString RUNTIME_TYPE = const SourceString('runtimeType');
+  static const SourceString START_ROOT_ISOLATE =
+      const SourceString('startRootIsolate');
+  bool enabledNoSuchMethod = false;
+  bool enabledRuntimeType = false;
+  bool enabledFunctionApply = false;
+  bool enabledInvokeOn = false;
+
+  Stopwatch progress;
+
+  static const int PHASE_SCANNING = 0;
+  static const int PHASE_RESOLVING = 1;
+  static const int PHASE_COMPILING = 2;
+  int phase;
+
+  bool compilationFailed = false;
+
+  bool hasCrashed = false;
+
+  Compiler({this.tracer: const Tracer(),
+            this.enableTypeAssertions: false,
+            this.enableUserAssertions: false,
+            this.enableConcreteTypeInference: false,
+            this.maxConcreteTypeSize: 5,
+            this.enableMinification: false,
+            this.enableNativeLiveTypeAnalysis: false,
+            bool emitJavaScript: true,
+            bool generateSourceMap: true,
+            bool disallowUnsafeEval: false,
+            this.analyzeAll: false,
+            this.analyzeOnly: false,
+            this.rejectDeprecatedFeatures: false,
+            this.checkDeprecationInSdk: false,
+            this.preserveComments: false,
+            outputProvider,
+            List<String> strips: const []})
+      : libraries = new Map<String, LibraryElement>(),
+        progress = new Stopwatch(),
+        this.outputProvider =
+            (outputProvider == null) ? NullSink.outputProvider : outputProvider
+  {
+    progress.start();
+    world = new World(this);
+
+    closureMapping.ClosureNamer closureNamer;
+    if (emitJavaScript) {
+      js_backend.JavaScriptBackend jsBackend =
+          new js_backend.JavaScriptBackend(this, generateSourceMap,
+                                           disallowUnsafeEval);
+      closureNamer = jsBackend.namer;
+      backend = jsBackend;
+    } else {
+      backend = new dart_backend.DartBackend(this, strips);
+    }
+
+    // No-op in production mode.
+    validator = new TreeValidatorTask(this);
+
+    tasks = [
+      fileReadingTask = new ReadingFilesTask(this),
+      libraryLoader = new LibraryLoaderTask(this),
+      scanner = new ScannerTask(this),
+      dietParser = new DietParserTask(this),
+      parser = new ParserTask(this),
+      patchParser = new PatchParserTask(this),
+      resolver = new ResolverTask(this),
+      closureToClassMapper = new closureMapping.ClosureTask(this, closureNamer),
+      checker = new TypeCheckerTask(this),
+      typesTask = new ti.TypesTask(this),
+      constantHandler = new ConstantHandler(this, backend.constantSystem),
+      enqueuer = new EnqueueTask(this)];
+
+    tasks.addAll(backend.tasks);
+    metadataHandler = new ConstantHandler(
+        this, backend.constantSystem, isMetadata: true);
+  }
+
+  Universe get resolverWorld => enqueuer.resolution.universe;
+  Universe get codegenWorld => enqueuer.codegen.universe;
+
+  int getNextFreeClassId() => nextFreeClassId++;
+
+  void ensure(bool condition) {
+    if (!condition) cancel('failed assertion in leg');
+  }
+
+  void unimplemented(String methodName,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element}) {
+    internalError("$methodName not implemented",
+                  node: node, token: token,
+                  instruction: instruction, element: element);
+  }
+
+  void internalError(String message,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element}) {
+    cancel('Internal error: $message',
+           node: node, token: token,
+           instruction: instruction, element: element);
+  }
+
+  void internalErrorOnElement(Element element, String message) {
+    internalError(message, element: element);
+  }
+
+  void unhandledExceptionOnElement(Element element) {
+    if (hasCrashed) return;
+    hasCrashed = true;
+    reportDiagnostic(spanFromElement(element),
+                     MessageKind.COMPILER_CRASHED.error().toString(),
+                     api.Diagnostic.CRASH);
+    pleaseReportCrash();
+  }
+
+  void pleaseReportCrash() {
+    print(MessageKind.PLEASE_REPORT_THE_CRASH.message({'buildId': BUILD_ID}));
+  }
+
+  void cancel(String reason, {Node node, Token token,
+               HInstruction instruction, Element element}) {
+    assembledCode = null; // Compilation failed. Make sure that we
+                          // don't return a bogus result.
+    SourceSpan span = null;
+    if (node != null) {
+      span = spanFromNode(node);
+    } else if (token != null) {
+      span = spanFromTokens(token, token);
+    } else if (instruction != null) {
+      span = spanFromHInstruction(instruction);
+    } else if (element != null) {
+      span = spanFromElement(element);
+    } else {
+      throw 'No error location for error: $reason';
+    }
+    reportDiagnostic(span, reason, api.Diagnostic.ERROR);
+    throw new CompilerCancelledException(reason);
+  }
+
+  SourceSpan spanFromSpannable(Spannable node, [Uri uri]) {
+    if (node == CURRENT_ELEMENT_SPANNABLE) {
+      node = currentElement;
+    }
+    if (node is Node) {
+      return spanFromNode(node, uri);
+    } else if (node is Token) {
+      return spanFromTokens(node, node, uri);
+    } else if (node is HInstruction) {
+      return spanFromHInstruction(node);
+    } else if (node is Element) {
+      return spanFromElement(node);
+    } else if (node is MetadataAnnotation) {
+      return spanFromTokens(node.beginToken, node.endToken);
+    } else {
+      throw 'No error location.';
+    }
+  }
+
+  void reportFatalError(String reason, Element element,
+                        {Node node, Token token, HInstruction instruction}) {
+    withCurrentElement(element, () {
+      cancel(reason, node: node, token: token, instruction: instruction,
+             element: element);
+    });
+  }
+
+  void log(message) {
+    reportDiagnostic(null, message, api.Diagnostic.VERBOSE_INFO);
+  }
+
+  bool run(Uri uri) {
+    totalCompileTime.start();
+    try {
+      runCompiler(uri);
+    } on CompilerCancelledException catch (exception) {
+      log('Error: $exception');
+      return false;
+    } finally {
+      tracer.close();
+      totalCompileTime.stop();
+    }
+    return true;
+  }
+
+  bool hasIsolateSupport() => isolateLibrary != null;
+
+  /**
+   * This method is called before [library] import and export scopes have been
+   * set up.
+   */
+  void onLibraryScanned(LibraryElement library, Uri uri) {
+    if (dynamicClass != null) {
+      // When loading the built-in libraries, dynamicClass is null. We
+      // take advantage of this as core imports js_helper and sees [dynamic]
+      // this way.
+      withCurrentElement(dynamicClass, () {
+        library.addToScope(dynamicClass, this);
+      });
+    }
+  }
+
+  LibraryElement scanBuiltinLibrary(String filename);
+
+  void initializeSpecialClasses() {
+    final List missingCoreClasses = [];
+    ClassElement lookupCoreClass(String name) {
+      ClassElement result = coreLibrary.find(new SourceString(name));
+      if (result == null) {
+        missingCoreClasses.add(name);
+      }
+      return result;
+    }
+    objectClass = lookupCoreClass('Object');
+    boolClass = lookupCoreClass('bool');
+    numClass = lookupCoreClass('num');
+    intClass = lookupCoreClass('int');
+    doubleClass = lookupCoreClass('double');
+    stringClass = lookupCoreClass('String');
+    functionClass = lookupCoreClass('Function');
+    listClass = lookupCoreClass('List');
+    typeClass = lookupCoreClass('Type');
+    mapClass = lookupCoreClass('Map');
+    if (!missingCoreClasses.isEmpty) {
+      internalErrorOnElement(coreLibrary,
+          'dart:core library does not contain required classes: '
+          '$missingCoreClasses');
+    }
+
+    final List missingHelperClasses = [];
+    ClassElement lookupHelperClass(String name) {
+      ClassElement result = jsHelperLibrary.find(new SourceString(name));
+      if (result == null) {
+        missingHelperClasses.add(name);
+      }
+      return result;
+    }
+    jsInvocationMirrorClass = lookupHelperClass('JSInvocationMirror');
+    closureClass = lookupHelperClass('Closure');
+    dynamicClass = lookupHelperClass('Dynamic_');
+    nullClass = lookupHelperClass('Null');
+    if (!missingHelperClasses.isEmpty) {
+      internalErrorOnElement(jsHelperLibrary,
+          'dart:_js_helper library does not contain required classes: '
+          '$missingHelperClasses');
+    }
+
+    types = new Types(this, dynamicClass);
+  }
+
+  void scanBuiltinLibraries() {
+    jsHelperLibrary = scanBuiltinLibrary('_js_helper');
+    interceptorsLibrary = scanBuiltinLibrary('_interceptors');
+    foreignLibrary = scanBuiltinLibrary('_foreign_helper');
+    isolateHelperLibrary = scanBuiltinLibrary('_isolate_helper');
+    // The helper library does not use the native language extension,
+    // so we manually set the native classes this library defines.
+    // TODO(ngeoffray): Enable annotations on these classes.
+    ClassElement cls =
+        isolateHelperLibrary.find(const SourceString('_WorkerStub'));
+    cls.setNative('"*Worker"');
+
+    assertMethod = jsHelperLibrary.find(const SourceString('assertHelper'));
+    identicalFunction = coreLibrary.find(const SourceString('identical'));
+
+    initializeSpecialClasses();
+
+    functionClass.ensureResolved(this);
+    functionApplyMethod =
+        functionClass.lookupLocalMember(const SourceString('apply'));
+    jsInvocationMirrorClass.ensureResolved(this);
+    invokeOnMethod = jsInvocationMirrorClass.lookupLocalMember(
+        const SourceString('invokeOn'));
+
+    if (preserveComments) {
+      var uri = new Uri.fromComponents(scheme: 'dart', path: 'mirrors');
+      LibraryElement libraryElement =
+          libraryLoader.loadLibrary(uri, null, uri);
+      documentClass = libraryElement.find(const SourceString('Comment'));
+    }
+  }
+
+  void importHelperLibrary(LibraryElement library) {
+    if (jsHelperLibrary != null) {
+      libraryLoader.importLibrary(library, jsHelperLibrary, null);
+    }
+  }
+
+  /**
+   * Get an [Uri] pointing to a patch for the dart: library with
+   * the given path. Returns null if there is no patch.
+   */
+  Uri resolvePatchUri(String dartLibraryPath);
+
+  void runCompiler(Uri uri) {
+    assert(uri != null || analyzeOnly);
+    scanBuiltinLibraries();
+    if (librariesToAnalyzeWhenRun != null) {
+      for (Uri libraryUri in librariesToAnalyzeWhenRun) {
+        log('analyzing $libraryUri ($BUILD_ID)');
+        libraryLoader.loadLibrary(libraryUri, null, libraryUri);
+      }
+    }
+    if (uri != null) {
+      if (analyzeOnly) {
+        log('analyzing $uri ($BUILD_ID)');
+      } else {
+        log('compiling $uri ($BUILD_ID)');
+      }
+      mainApp = libraryLoader.loadLibrary(uri, null, uri);
+    }
+    Element main = null;
+    if (mainApp != null) {
+      main = mainApp.find(MAIN);
+      if (main == null) {
+        if (!analyzeOnly) {
+          // Allow analyze only of libraries with no main.
+          reportFatalError('Could not find $MAIN', mainApp);
+        } else if (!analyzeAll) {
+          reportFatalError(
+              "Could not find $MAIN. "
+              "No source will be analyzed. "
+              "Use '--analyze-all' to analyze all code in the library.",
+              mainApp);
+        }
+      } else {
+        if (!main.isFunction()) {
+          reportFatalError('main is not a function', main);
+        }
+        FunctionElement mainMethod = main;
+        FunctionSignature parameters = mainMethod.computeSignature(this);
+        parameters.forEachParameter((Element parameter) {
+          reportFatalError('main cannot have parameters', parameter);
+        });
+      }
+    }
+
+    log('Resolving...');
+    phase = PHASE_RESOLVING;
+    if (analyzeAll) {
+      libraries.forEach((_, lib) => fullyEnqueueLibrary(lib));
+    }
+    backend.enqueueHelpers(enqueuer.resolution);
+    processQueue(enqueuer.resolution, main);
+    enqueuer.resolution.logSummary(log);
+
+    if (compilationFailed) return;
+    if (analyzeOnly) return;
+    assert(main != null);
+
+    log('Inferring types...');
+    typesTask.onResolutionComplete(main);
+
+    // TODO(ahe): Remove this line. Eventually, enqueuer.resolution
+    // should know this.
+    world.populate();
+
+    log('Compiling...');
+    phase = PHASE_COMPILING;
+    // TODO(johnniwinther): Move these to [CodegenEnqueuer].
+    if (hasIsolateSupport()) {
+      enqueuer.codegen.addToWorkList(
+          isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE));
+    }
+    if (enabledNoSuchMethod) {
+      Selector selector = new Selector.noSuchMethod();
+      enqueuer.codegen.registerInvocation(NO_SUCH_METHOD, selector);
+      enqueuer.codegen.addToWorkList(createInvocationMirrorElement);
+    }
+    processQueue(enqueuer.codegen, main);
+    enqueuer.codegen.logSummary(log);
+
+    if (compilationFailed) return;
+
+    backend.assembleProgram();
+
+    checkQueues();
+  }
+
+  void fullyEnqueueLibrary(LibraryElement library) {
+    library.forEachLocalMember(fullyEnqueueTopLevelElement);
+  }
+
+  void fullyEnqueueTopLevelElement(Element element) {
+    if (element.isClass()) {
+      ClassElement cls = element;
+      cls.ensureResolved(this);
+      cls.forEachLocalMember(enqueuer.resolution.addToWorkList);
+    } else {
+      enqueuer.resolution.addToWorkList(element);
+    }
+  }
+
+  void processQueue(Enqueuer world, Element main) {
+    world.nativeEnqueuer.processNativeClasses(libraries.values);
+    if (main != null) {
+      world.addToWorkList(main);
+    }
+    progress.reset();
+    world.forEach((WorkItem work) {
+      withCurrentElement(work.element, () => work.run(this, world));
+    });
+    world.queueIsClosed = true;
+    if (compilationFailed) return;
+    assert(world.checkNoEnqueuedInvokedInstanceMethods());
+    if (DUMP_INFERRED_TYPES && phase == PHASE_COMPILING) {
+      backend.dumpInferredTypes();
+    }
+  }
+
+  /**
+   * Perform various checks of the queues. This includes checking that
+   * the queues are empty (nothing was added after we stopped
+   * processing the queues). Also compute the number of methods that
+   * were resolved, but not compiled (aka excess resolution).
+   */
+  checkQueues() {
+    for (Enqueuer world in [enqueuer.resolution, enqueuer.codegen]) {
+      world.forEach((WorkItem work) {
+        internalErrorOnElement(work.element, "Work list is not empty.");
+      });
+    }
+    if (!REPORT_EXCESS_RESOLUTION) return;
+    var resolved = new Set.from(enqueuer.resolution.resolvedElements.keys);
+    for (Element e in enqueuer.codegen.generatedCode.keys) {
+      resolved.remove(e);
+    }
+    for (Element e in new Set.from(resolved)) {
+      if (e.isClass() ||
+          e.isField() ||
+          e.isTypeVariable() ||
+          e.isTypedef() ||
+          identical(e.kind, ElementKind.ABSTRACT_FIELD)) {
+        resolved.remove(e);
+      }
+      if (identical(e.kind, ElementKind.GENERATIVE_CONSTRUCTOR)) {
+        ClassElement enclosingClass = e.getEnclosingClass();
+        if (enclosingClass.isInterface()) {
+          resolved.remove(e);
+        }
+        resolved.remove(e);
+
+      }
+      if (identical(e.getLibrary(), jsHelperLibrary)) {
+        resolved.remove(e);
+      }
+      if (identical(e.getLibrary(), interceptorsLibrary)) {
+        resolved.remove(e);
+      }
+    }
+    log('Excess resolution work: ${resolved.length}.');
+    for (Element e in resolved) {
+      SourceSpan span = spanFromElement(e);
+      reportDiagnostic(span, 'Warning: $e resolved but not compiled.',
+                       api.Diagnostic.WARNING);
+    }
+  }
+
+  TreeElements analyzeElement(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    TreeElements elements = enqueuer.resolution.getCachedElements(element);
+    if (elements != null) return elements;
+    assert(parser != null);
+    Node tree = parser.parse(element);
+    validator.validate(tree);
+    elements = resolver.resolve(element);
+    if (elements != null) {
+      // Only analyze nodes with a corresponding [TreeElements].
+      checker.check(tree, elements);
+      typesTask.analyze(tree, elements);
+    }
+    return elements;
+  }
+
+  TreeElements analyze(ResolutionWorkItem work, ResolutionEnqueuer world) {
+    assert(invariant(work.element, identical(world, enqueuer.resolution)));
+    assert(invariant(work.element, !work.isAnalyzed(),
+        message: 'Element ${work.element} has already been analyzed'));
+    if (progress.elapsedMilliseconds > 500) {
+      // TODO(ahe): Add structured diagnostics to the compiler API and
+      // use it to separate this from the --verbose option.
+      if (phase == PHASE_RESOLVING) {
+        log('Resolved ${enqueuer.resolution.resolvedElements.length} '
+            'elements.');
+        progress.reset();
+      }
+    }
+    Element element = work.element;
+    TreeElements result = world.getCachedElements(element);
+    if (result != null) return result;
+    result = analyzeElement(element);
+    assert(invariant(element, element.isDeclaration));
+    world.resolvedElements[element] = result;
+    return result;
+  }
+
+  void codegen(CodegenWorkItem work, CodegenEnqueuer world) {
+    assert(invariant(work.element, identical(world, enqueuer.codegen)));
+    if (progress.elapsedMilliseconds > 500) {
+      // TODO(ahe): Add structured diagnostics to the compiler API and
+      // use it to separate this from the --verbose option.
+      log('Compiled ${enqueuer.codegen.generatedCode.length} methods.');
+      progress.reset();
+    }
+    backend.codegen(work);
+  }
+
+  DartType resolveTypeAnnotation(Element element,
+                                 TypeAnnotation annotation) {
+    return resolver.resolveTypeAnnotation(element, annotation);
+  }
+
+  DartType resolveReturnType(Element element,
+                             TypeAnnotation annotation) {
+    return resolver.resolveReturnType(element, annotation);
+  }
+
+  FunctionSignature resolveSignature(FunctionElement element) {
+    return withCurrentElement(element,
+                              () => resolver.resolveSignature(element));
+  }
+
+  FunctionSignature resolveFunctionExpression(Element element,
+                                              FunctionExpression node) {
+    return withCurrentElement(element,
+        () => resolver.resolveFunctionExpression(element, node));
+  }
+
+  void resolveTypedef(TypedefElement element) {
+    withCurrentElement(element,
+                       () => resolver.resolveTypedef(element));
+  }
+
+  FunctionType computeFunctionType(Element element,
+                                   FunctionSignature signature) {
+    return withCurrentElement(element,
+        () => resolver.computeFunctionType(element, signature));
+  }
+
+  reportWarning(Node node, var message) {
+    if (message is TypeWarning) {
+      // TODO(ahe): Don't supress these warning when the type checker
+      // is more complete.
+      if (identical(message.message.kind, MessageKind.NOT_ASSIGNABLE)) return;
+      if (identical(message.message.kind, MessageKind.MISSING_RETURN)) return;
+      if (identical(message.message.kind, MessageKind.MAYBE_MISSING_RETURN)) return;
+      if (identical(message.message.kind, MessageKind.METHOD_NOT_FOUND)) return;
+    }
+    SourceSpan span = spanFromNode(node);
+
+    reportDiagnostic(span, 'Warning: $message', api.Diagnostic.WARNING);
+  }
+
+  // TODO(ahe): Remove this method.
+  reportError(Node node, var message) {
+    SourceSpan span = spanFromNode(node);
+    reportDiagnostic(span, 'Error: $message', api.Diagnostic.ERROR);
+    throw new CompilerCancelledException(message.toString());
+  }
+
+  // TODO(ahe): Rename to reportError when that method has been removed.
+  void reportErrorCode(Spannable node, MessageKind errorCode,
+                       [Map arguments = const {}]) {
+    reportMessage(spanFromSpannable(node),
+                  errorCode.error(arguments),
+                  api.Diagnostic.ERROR);
+  }
+
+  void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) {
+    // TODO(ahe): The names Diagnostic and api.Diagnostic are in
+    // conflict. Fix it.
+    reportDiagnostic(span, "$message", kind);
+  }
+
+  /// Returns true if a diagnostic was emitted.
+  bool onDeprecatedFeature(Spannable span, String feature) {
+    if (currentElement == null)
+      throw new SpannableAssertionFailure(span, feature);
+    if (!checkDeprecationInSdk &&
+        currentElement.getLibrary().isPlatformLibrary) {
+      return false;
+    }
+    var kind = rejectDeprecatedFeatures
+        ? api.Diagnostic.ERROR : api.Diagnostic.WARNING;
+    var message = rejectDeprecatedFeatures
+        ? MessageKind.DEPRECATED_FEATURE_ERROR.error({'featureName': feature})
+        : MessageKind.DEPRECATED_FEATURE_WARNING.error(
+            {'featureName': feature});
+    reportMessage(spanFromSpannable(span), message, kind);
+    return true;
+  }
+
+  void reportDiagnostic(SourceSpan span, String message, api.Diagnostic kind);
+
+  SourceSpan spanFromTokens(Token begin, Token end, [Uri uri]) {
+    if (begin == null || end == null) {
+      // TODO(ahe): We can almost always do better. Often it is only
+      // end that is null. Otherwise, we probably know the current
+      // URI.
+      throw 'Cannot find tokens to produce error message.';
+    }
+    if (uri == null && currentElement != null) {
+      uri = currentElement.getCompilationUnit().script.uri;
+    }
+    return SourceSpan.withCharacterOffsets(begin, end,
+      (beginOffset, endOffset) => new SourceSpan(uri, beginOffset, endOffset));
+  }
+
+  SourceSpan spanFromNode(Node node, [Uri uri]) {
+    return spanFromTokens(node.getBeginToken(), node.getEndToken(), uri);
+  }
+
+  SourceSpan spanFromElement(Element element) {
+    if (Elements.isErroneousElement(element)) {
+      element = element.enclosingElement;
+    }
+    if (element.position() == null && !element.isCompilationUnit()) {
+      // Sometimes, the backend fakes up elements that have no
+      // position. So we use the enclosing element instead. It is
+      // not a good error location, but cancel really is "internal
+      // error" or "not implemented yet", so the vicinity is good
+      // enough for now.
+      element = element.enclosingElement;
+      // TODO(ahe): I plan to overhaul this infrastructure anyways.
+    }
+    if (element == null) {
+      element = currentElement;
+    }
+    Token position = element.position();
+    Uri uri = element.getCompilationUnit().script.uri;
+    return (position == null)
+        ? new SourceSpan(uri, 0, 0)
+        : spanFromTokens(position, position, uri);
+  }
+
+  SourceSpan spanFromHInstruction(HInstruction instruction) {
+    Element element = instruction.sourceElement;
+    if (element == null) element = currentElement;
+    var position = instruction.sourcePosition;
+    if (position == null) return spanFromElement(element);
+    Token token = position.token;
+    if (token == null) return spanFromElement(element);
+    Uri uri = element.getCompilationUnit().script.uri;
+    return spanFromTokens(token, token, uri);
+  }
+
+  /**
+   * Translates the [resolvedUri] into a readable URI.
+   *
+   * The [importingLibrary] holds the library importing [resolvedUri] or
+   * [:null:] if [resolvedUri] is loaded as the main library. The
+   * [importingLibrary] is used to grant access to internal libraries from
+   * platform libraries and patch libraries.
+   *
+   * If the [resolvedUri] is not accessible from [importingLibrary], this method
+   * is responsible for reporting errors.
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  Uri translateResolvedUri(LibraryElement importingLibrary,
+                           Uri resolvedUri, Node node) {
+    unimplemented('Compiler.translateResolvedUri');
+  }
+
+  /**
+   * Reads the script specified by the [readableUri].
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  Script readScript(Uri readableUri, [Node node]) {
+    unimplemented('Compiler.readScript');
+  }
+
+  String get legDirectory {
+    unimplemented('Compiler.legDirectory');
+  }
+
+  // TODO(karlklose): split into findHelperFunction and findHelperClass and
+  // add a check that the element has the expected kind.
+  Element findHelper(SourceString name)
+      => jsHelperLibrary.findLocal(name);
+  Element findInterceptor(SourceString name)
+      => interceptorsLibrary.findLocal(name);
+
+  Element lookupElementIn(ScopeContainerElement container, SourceString name) {
+    Element element = container.localLookup(name);
+    if (element == null) {
+      throw 'Could not find ${name.slowToString()} in $container';
+    }
+    return element;
+  }
+
+  bool get isMockCompilation => false;
+
+  Token processAndStripComments(Token currentToken) {
+    Token firstToken = currentToken;
+    Token prevToken;
+    while (currentToken.kind != EOF_TOKEN) {
+      if (identical(currentToken.kind, COMMENT_TOKEN)) {
+        Token firstCommentToken = currentToken;
+        while (identical(currentToken.kind, COMMENT_TOKEN)) {
+          currentToken = currentToken.next;
+        }
+        commentMap[currentToken] = firstCommentToken;
+        if (prevToken == null) {
+          firstToken = currentToken;
+        } else {
+          prevToken.next = currentToken;
+        }
+      }
+      prevToken = currentToken;
+      currentToken = currentToken.next;
+    }
+    return firstToken;
+  }
+}
+
+class CompilerTask {
+  final Compiler compiler;
+  final Stopwatch watch;
+
+  CompilerTask(this.compiler) : watch = new Stopwatch();
+
+  String get name => 'Unknown task';
+  int get timing => watch.elapsedMilliseconds;
+
+  measure(Function action) {
+    CompilerTask previous = compiler.measuredTask;
+    if (identical(this, previous)) return action();
+    compiler.measuredTask = this;
+    if (previous != null) previous.watch.stop();
+    watch.start();
+    try {
+      return action();
+    } finally {
+      watch.stop();
+      if (previous != null) previous.watch.start();
+      compiler.measuredTask = previous;
+    }
+  }
+}
+
+class CompilerCancelledException implements Exception {
+  final String reason;
+  CompilerCancelledException(this.reason);
+
+  String toString() {
+    String banner = 'compiler cancelled';
+    return (reason != null) ? '$banner: $reason' : '$banner';
+  }
+}
+
+class Tracer {
+  final bool enabled = false;
+
+  const Tracer();
+
+  void traceCompilation(String methodName, ItemCompilationContext context) {
+  }
+
+  void traceGraph(String name, var graph) {
+  }
+
+  void close() {
+  }
+}
+
+class SourceSpan {
+  final Uri uri;
+  final int begin;
+  final int end;
+
+  const SourceSpan(this.uri, this.begin, this.end);
+
+  static withCharacterOffsets(Token begin, Token end,
+                     f(int beginOffset, int endOffset)) {
+    final beginOffset = begin.charOffset;
+    final endOffset = end.charOffset + end.slowCharCount;
+
+    // [begin] and [end] might be the same for the same empty token. This
+    // happens for instance when scanning '$$'.
+    assert(endOffset >= beginOffset);
+    return f(beginOffset, endOffset);
+  }
+
+  String toString() => 'SourceSpan($uri, $begin, $end)';
+}
+
+/**
+ * Throws an [InvariantException] if [condition] is [:false:]. [condition] must
+ * be either a [:bool:] or a no-arg function returning a [:bool:].
+ *
+ * Use this method to provide better information for assertion by calling
+ * [invariant] as the argument to an [:assert:] statement:
+ *
+ *     assert(invariant(position, isValid));
+ *
+ * [spannable] must be non-null and will be used to provide positional
+ * information in the generated error message.
+ */
+bool invariant(Spannable spannable, var condition, {String message: null}) {
+  // TODO(johnniwinther): Use [spannable] and [message] to provide better
+  // information on assertion errors.
+  if (condition is Function){
+    condition = condition();
+  }
+  if (spannable == null || !condition) {
+    throw new SpannableAssertionFailure(spannable, message);
+  }
+  return true;
+}
+
+/// A sink that drains into /dev/null.
+class NullSink extends StreamSink<String> {
+  final String name;
+
+  NullSink(this.name);
+
+  add(String value) {}
+
+  void signalError(AsyncError error) {}
+
+  void close() {}
+
+  toString() => name;
+
+  /// Convenience method for getting an [api.CompilerOutputProvider].
+  static NullSink outputProvider(String name, String extension) {
+    return new NullSink('$name.$extension');
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/constant_system.dart b/pkgs/markdown/lib/src/compiler/implementation/constant_system.dart
new file mode 100644
index 0000000..0fff580
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/constant_system.dart
@@ -0,0 +1,80 @@
+// 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.
+
+part of dart2js;
+
+abstract class Operation {
+  SourceString get name;
+  bool isUserDefinable();
+}
+
+abstract class UnaryOperation extends Operation {
+  /** Returns [:null:] if it was unable to fold the operation. */
+  Constant fold(Constant constant);
+  apply(value);
+}
+
+abstract class BinaryOperation extends Operation {
+  /** Returns [:null:] if it was unable to fold the operation. */
+  Constant fold(Constant left, Constant right);
+  apply(left, right);
+}
+
+/**
+ * A [ConstantSystem] is responsible for creating constants and folding them.
+ */
+abstract class ConstantSystem {
+  BinaryOperation get add;
+  BinaryOperation get bitAnd;
+  UnaryOperation get bitNot;
+  BinaryOperation get bitOr;
+  BinaryOperation get bitXor;
+  BinaryOperation get booleanAnd;
+  BinaryOperation get booleanOr;
+  BinaryOperation get divide;
+  BinaryOperation get equal;
+  BinaryOperation get greaterEqual;
+  BinaryOperation get greater;
+  BinaryOperation get identity;
+  BinaryOperation get lessEqual;
+  BinaryOperation get less;
+  BinaryOperation get modulo;
+  BinaryOperation get multiply;
+  UnaryOperation get negate;
+  UnaryOperation get not;
+  BinaryOperation get shiftLeft;
+  BinaryOperation get shiftRight;
+  BinaryOperation get subtract;
+  BinaryOperation get truncatingDivide;
+
+  const ConstantSystem();
+
+  Constant createInt(int i);
+  Constant createDouble(double d);
+  // We need a diagnostic node to report errors in case the string is malformed.
+  Constant createString(DartString string, Node diagnosticNode);
+  Constant createBool(bool value);
+  Constant createNull();
+
+  // We need to special case the subtype check for JavaScript constant
+  // system because an int is a double at runtime.
+  bool isSubtype(Compiler compiler, DartType s, DartType t);
+
+  /** Returns true if the [constant] is an integer at runtime. */
+  bool isInt(Constant constant);
+  /** Returns true if the [constant] is a double at runtime. */
+  bool isDouble(Constant constant);
+  /** Returns true if the [constant] is a string at runtime. */
+  bool isString(Constant constant);
+  /** Returns true if the [constant] is a boolean at runtime. */
+  bool isBool(Constant constant);
+  /** Returns true if the [constant] is null at runtime. */
+  bool isNull(Constant constant);
+
+  Operation lookupUnary(SourceString operator) {
+    if (operator == const SourceString('-')) return negate;
+    if (operator == const SourceString('~')) return bitNot;
+    return null;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart b/pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart
new file mode 100644
index 0000000..6732b8c
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart
@@ -0,0 +1,380 @@
+// 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.
+
+part of dart2js;
+
+const DART_CONSTANT_SYSTEM = const DartConstantSystem();
+
+class BitNotOperation implements UnaryOperation {
+  final SourceString name = const SourceString('~');
+  bool isUserDefinable() => true;
+  const BitNotOperation();
+  Constant fold(Constant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      return DART_CONSTANT_SYSTEM.createInt(~intConstant.value);
+    }
+    return null;
+  }
+  apply(value) => ~value;
+}
+
+class NegateOperation implements UnaryOperation {
+  final SourceString name = const SourceString('negate');
+  bool isUserDefinable() => true;
+  const NegateOperation();
+  Constant fold(Constant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      return DART_CONSTANT_SYSTEM.createInt(-intConstant.value);
+    }
+    if (constant.isDouble()) {
+      DoubleConstant doubleConstant = constant;
+      return DART_CONSTANT_SYSTEM.createDouble(-doubleConstant.value);
+    }
+    return null;
+  }
+  apply(value) => -value;
+}
+
+class NotOperation implements UnaryOperation {
+  final SourceString name = const SourceString('!');
+  bool isUserDefinable() => true;
+  const NotOperation();
+  Constant fold(Constant constant) {
+    if (constant.isBool()) {
+      BoolConstant boolConstant = constant;
+      return DART_CONSTANT_SYSTEM.createBool(!boolConstant.value);
+    }
+    return null;
+  }
+  apply(value) => !value;
+}
+
+/**
+ * Operations that only work if both arguments are integers.
+ */
+abstract class BinaryBitOperation implements BinaryOperation {
+  bool isUserDefinable() => true;
+  const BinaryBitOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isInt() && right.isInt()) {
+      IntConstant leftInt = left;
+      IntConstant rightInt = right;
+      int resultValue = foldInts(leftInt.value, rightInt.value);
+      if (resultValue == null) return null;
+      return DART_CONSTANT_SYSTEM.createInt(resultValue);
+    }
+    return null;
+  }
+
+  int foldInts(int left, int right);
+}
+
+class BitOrOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('|');
+  const BitOrOperation();
+  int foldInts(int left, int right)  => left | right;
+  apply(left, right) => left | right;
+}
+
+class BitAndOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('&');
+  const BitAndOperation();
+  int foldInts(int left, int right) => left & right;
+  apply(left, right) => left & right;
+}
+
+class BitXorOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('^');
+  const BitXorOperation();
+  int foldInts(int left, int right) => left ^ right;
+  apply(left, right) => left ^ right;
+}
+
+class ShiftLeftOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('<<');
+  const ShiftLeftOperation();
+  int foldInts(int left, int right) {
+    // TODO(floitsch): find a better way to guard against excessive shifts to
+    // the left.
+    if (right > 100 || right < 0) return null;
+    return left << right;
+  }
+  apply(left, right) => left << right;
+}
+
+class ShiftRightOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('>>');
+  const ShiftRightOperation();
+  int foldInts(int left, int right) {
+    if (right < 0) return null;
+    return left >> right;
+  }
+  apply(left, right) => left >> right;
+}
+
+abstract class BinaryBoolOperation implements BinaryOperation {
+  bool isUserDefinable() => false;
+  const BinaryBoolOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isBool() && right.isBool()) {
+      BoolConstant leftBool = left;
+      BoolConstant rightBool = right;
+      bool resultValue = foldBools(leftBool.value, rightBool.value);
+      return DART_CONSTANT_SYSTEM.createBool(resultValue);
+    }
+    return null;
+  }
+
+  bool foldBools(bool left, bool right);
+}
+
+class BooleanAndOperation extends BinaryBoolOperation {
+  final SourceString name = const SourceString('&&');
+  const BooleanAndOperation();
+  bool foldBools(bool left, bool right) => left && right;
+  apply(left, right) => left && right;
+}
+
+class BooleanOrOperation extends BinaryBoolOperation {
+  final SourceString name = const SourceString('||');
+  const BooleanOrOperation();
+  bool foldBools(bool left, bool right) => left || right;
+  apply(left, right) => left || right;
+}
+
+abstract class ArithmeticNumOperation implements BinaryOperation {
+  bool isUserDefinable() => true;
+  const ArithmeticNumOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      num foldedValue;
+      if (left.isInt() && right.isInt()) {
+        foldedValue = foldInts(leftNum.value, rightNum.value);
+      } else {
+        foldedValue = foldNums(leftNum.value, rightNum.value);
+      }
+      // A division by 0 means that we might not have a folded value.
+      if (foldedValue == null) return null;
+      if (left.isInt() && right.isInt() && !isDivide() ||
+          isTruncatingDivide()) {
+        assert(foldedValue is int);
+        return DART_CONSTANT_SYSTEM.createInt(foldedValue);
+      } else {
+        return DART_CONSTANT_SYSTEM.createDouble(foldedValue);
+      }
+    }
+    return null;
+  }
+
+  bool isDivide() => false;
+  bool isTruncatingDivide() => false;
+  num foldInts(int left, int right) => foldNums(left, right);
+  num foldNums(num left, num right);
+}
+
+class SubtractOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('-');
+  const SubtractOperation();
+  num foldNums(num left, num right) => left - right;
+  apply(left, right) => left - right;
+}
+
+class MultiplyOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('*');
+  const MultiplyOperation();
+  num foldNums(num left, num right) => left * right;
+  apply(left, right) => left * right;
+}
+
+class ModuloOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('%');
+  const ModuloOperation();
+  int foldInts(int left, int right) {
+    if (right == 0) return null;
+    return left % right;
+  }
+  num foldNums(num left, num right) => left % right;
+  apply(left, right) => left % right;
+}
+
+class TruncatingDivideOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('~/');
+  const TruncatingDivideOperation();
+  int foldInts(int left, int right) {
+    if (right == 0) return null;
+    return left ~/ right;
+  }
+  num foldNums(num left, num right) {
+    num ratio = left / right;
+    if (ratio.isNaN || ratio.isInfinite) return null;
+    return ratio.truncate().toInt();
+  }
+  apply(left, right) => left ~/ right;
+  bool isTruncatingDivide() => true;
+}
+
+class DivideOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('/');
+  const DivideOperation();
+  num foldNums(num left, num right) => left / right;
+  bool isDivide() => true;
+  apply(left, right) => left / right;
+}
+
+class AddOperation implements BinaryOperation {
+  final SourceString name = const SourceString('+');
+  bool isUserDefinable() => true;
+  const AddOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isInt() && right.isInt()) {
+      IntConstant leftInt = left;
+      IntConstant rightInt = right;
+      int result = leftInt.value + rightInt.value;
+      return DART_CONSTANT_SYSTEM.createInt(result);
+    } else if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      double result = leftNum.value + rightNum.value;
+      return DART_CONSTANT_SYSTEM.createDouble(result);
+    } else {
+      return null;
+    }
+  }
+  apply(left, right) => left + right;
+}
+
+abstract class RelationalNumOperation implements BinaryOperation {
+  bool isUserDefinable() => true;
+  const RelationalNumOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      bool foldedValue = foldNums(leftNum.value, rightNum.value);
+      assert(foldedValue != null);
+      return DART_CONSTANT_SYSTEM.createBool(foldedValue);
+    }
+  }
+
+  bool foldNums(num left, num right);
+}
+
+class LessOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('<');
+  const LessOperation();
+  bool foldNums(num left, num right) => left < right;
+  apply(left, right) => left < right;
+}
+
+class LessEqualOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('<=');
+  const LessEqualOperation();
+  bool foldNums(num left, num right) => left <= right;
+  apply(left, right) => left <= right;
+}
+
+class GreaterOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('>');
+  const GreaterOperation();
+  bool foldNums(num left, num right) => left > right;
+  apply(left, right) => left > right;
+}
+
+class GreaterEqualOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('>=');
+  const GreaterEqualOperation();
+  bool foldNums(num left, num right) => left >= right;
+  apply(left, right) => left >= right;
+}
+
+class EqualsOperation implements BinaryOperation {
+  final SourceString name = const SourceString('==');
+  bool isUserDefinable() => true;
+  const EqualsOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isNum() && right.isNum()) {
+      // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0,
+      // and 1 == 1.0.
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      bool result = leftNum.value == rightNum.value;
+      return DART_CONSTANT_SYSTEM.createBool(result);
+    }
+    if (left.isConstructedObject()) {
+      // Unless we know that the user-defined object does not implement the
+      // equality operator we cannot fold here.
+      return null;
+    }
+    return DART_CONSTANT_SYSTEM.createBool(left == right);
+  }
+  apply(left, right) => left == right;
+}
+
+class IdentityOperation implements BinaryOperation {
+  final SourceString name = const SourceString('===');
+  bool isUserDefinable() => false;
+  const IdentityOperation();
+  BoolConstant fold(Constant left, Constant right) {
+    // In order to preserve runtime semantics which says that NaN !== NaN don't
+    // constant fold NaN === NaN. Otherwise the output depends on inlined
+    // variables and other optimizations.
+    if (left.isNaN() && right.isNaN()) return null;
+    return DART_CONSTANT_SYSTEM.createBool(left == right);
+  }
+  apply(left, right) => identical(left, right);
+}
+
+/**
+ * A constant system implementing the Dart semantics. This system relies on
+ * the underlying runtime-system. That is, if dart2js is run in an environment
+ * that doesn't correctly implement Dart's semantics this constant system will
+ * not return the correct values.
+ */
+class DartConstantSystem extends ConstantSystem {
+  const add = const AddOperation();
+  const bitAnd = const BitAndOperation();
+  const bitNot = const BitNotOperation();
+  const bitOr = const BitOrOperation();
+  const bitXor = const BitXorOperation();
+  const booleanAnd = const BooleanAndOperation();
+  const booleanOr = const BooleanOrOperation();
+  const divide = const DivideOperation();
+  const equal = const EqualsOperation();
+  const greaterEqual = const GreaterEqualOperation();
+  const greater = const GreaterOperation();
+  const identity = const IdentityOperation();
+  const lessEqual = const LessEqualOperation();
+  const less = const LessOperation();
+  const modulo = const ModuloOperation();
+  const multiply = const MultiplyOperation();
+  const negate = const NegateOperation();
+  const not = const NotOperation();
+  const shiftLeft = const ShiftLeftOperation();
+  const shiftRight = const ShiftRightOperation();
+  const subtract = const SubtractOperation();
+  const truncatingDivide = const TruncatingDivideOperation();
+
+  const DartConstantSystem();
+
+  IntConstant createInt(int i) => new IntConstant(i);
+  DoubleConstant createDouble(double d) => new DoubleConstant(d);
+  StringConstant createString(DartString string, Node diagnosticNode)
+      => new StringConstant(string, diagnosticNode);
+  BoolConstant createBool(bool value) => new BoolConstant(value);
+  NullConstant createNull() => new NullConstant();
+
+  bool isInt(Constant constant) => constant.isInt();
+  bool isDouble(Constant constant) => constant.isDouble();
+  bool isString(Constant constant) => constant.isString();
+  bool isBool(Constant constant) => constant.isBool();
+  bool isNull(Constant constant) => constant.isNull();
+
+  bool isSubtype(Compiler compiler, DartType s, DartType t) {
+    return compiler.types.isSubtype(s, t);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/constants.dart b/pkgs/markdown/lib/src/compiler/implementation/constants.dart
new file mode 100644
index 0000000..ad5dc74
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/constants.dart
@@ -0,0 +1,473 @@
+// 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.
+
+part of dart2js;
+
+abstract class ConstantVisitor<R> {
+  R visitSentinel(SentinelConstant constant);
+  R visitFunction(FunctionConstant constant);
+  R visitNull(NullConstant constant);
+  R visitInt(IntConstant constant);
+  R visitDouble(DoubleConstant constant);
+  R visitTrue(TrueConstant constant);
+  R visitFalse(FalseConstant constant);
+  R visitString(StringConstant constant);
+  R visitList(ListConstant constant);
+  R visitMap(MapConstant constant);
+  R visitConstructed(ConstructedConstant constant);
+  R visitType(TypeConstant constant);
+}
+
+abstract class Constant {
+  const Constant();
+
+  bool isNull() => false;
+  bool isBool() => false;
+  bool isTrue() => false;
+  bool isFalse() => false;
+  bool isInt() => false;
+  bool isDouble() => false;
+  bool isNum() => false;
+  bool isString() => false;
+  bool isList() => false;
+  bool isMap() => false;
+  bool isConstructedObject() => false;
+  bool isFunction() => false;
+  /** Returns true if the constant is null, a bool, a number or a string. */
+  bool isPrimitive() => false;
+  /** Returns true if the constant is a list, a map or a constructed object. */
+  bool isObject() => false;
+  bool isType() => false;
+  bool isSentinel() => false;
+
+  bool isNaN() => false;
+  bool isMinusZero() => false;
+
+  DartType computeType(Compiler compiler);
+
+  List<Constant> getDependencies();
+
+  accept(ConstantVisitor visitor);
+}
+
+class SentinelConstant extends Constant {
+  const SentinelConstant();
+  static final SENTINEL = const SentinelConstant();
+
+  List<Constant> getDependencies() => const <Constant>[];
+
+  // Just use a random value.
+  int get hashCode => 24297418;
+
+  bool isSentinel() => true;
+
+  accept(ConstantVisitor visitor) => visitor.visitSentinel(this);
+
+  DartType computeType(Compiler compiler) => compiler.types.dynamicType;
+}
+
+class FunctionConstant extends Constant {
+  Element element;
+
+  FunctionConstant(this.element);
+
+  bool isFunction() => true;
+
+  bool operator ==(var other) {
+    if (other is !FunctionConstant) return false;
+    return identical(other.element, element);
+  }
+
+  String toString() => element.toString();
+  List<Constant> getDependencies() => const <Constant>[];
+  DartString toDartString() {
+    return new DartString.literal(element.name.slowToString());
+  }
+
+  DartType computeType(Compiler compiler) {
+    return compiler.functionClass.computeType(compiler);
+  }
+
+  int get hashCode => (17 * element.hashCode) & 0x7fffffff;
+
+  accept(ConstantVisitor visitor) => visitor.visitFunction(this);
+}
+
+abstract class PrimitiveConstant extends Constant {
+  get value;
+  const PrimitiveConstant();
+  bool isPrimitive() => true;
+
+  bool operator ==(var other) {
+    if (other is !PrimitiveConstant) return false;
+    PrimitiveConstant otherPrimitive = other;
+    // We use == instead of 'identical' so that DartStrings compare correctly.
+    return value == otherPrimitive.value;
+  }
+
+  String toString() => value.toString();
+  // Primitive constants don't have dependencies.
+  List<Constant> getDependencies() => const <Constant>[];
+  DartString toDartString();
+}
+
+class NullConstant extends PrimitiveConstant {
+  /** The value a Dart null is compiled to in JavaScript. */
+  static const String JsNull = "null";
+
+  factory NullConstant() => const NullConstant._internal();
+  const NullConstant._internal();
+  bool isNull() => true;
+  get value => null;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.nullClass.computeType(compiler);
+  }
+
+  void _writeJsCode(CodeBuffer buffer, ConstantHandler handler) {
+    buffer.add(JsNull);
+  }
+
+  // The magic constant has no meaning. It is just a random value.
+  int get hashCode => 785965825;
+  DartString toDartString() => const LiteralDartString("null");
+
+  accept(ConstantVisitor visitor) => visitor.visitNull(this);
+}
+
+abstract class NumConstant extends PrimitiveConstant {
+  num get value;
+  const NumConstant();
+  bool isNum() => true;
+}
+
+class IntConstant extends NumConstant {
+  final int value;
+  factory IntConstant(int value) {
+    switch (value) {
+      case 0: return const IntConstant._internal(0);
+      case 1: return const IntConstant._internal(1);
+      case 2: return const IntConstant._internal(2);
+      case 3: return const IntConstant._internal(3);
+      case 4: return const IntConstant._internal(4);
+      case 5: return const IntConstant._internal(5);
+      case 6: return const IntConstant._internal(6);
+      case 7: return const IntConstant._internal(7);
+      case 8: return const IntConstant._internal(8);
+      case 9: return const IntConstant._internal(9);
+      case 10: return const IntConstant._internal(10);
+      case -1: return const IntConstant._internal(-1);
+      case -2: return const IntConstant._internal(-2);
+      default: return new IntConstant._internal(value);
+    }
+  }
+  const IntConstant._internal(this.value);
+  bool isInt() => true;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.intClass.computeType(compiler);
+  }
+
+  // We have to override the equality operator so that ints and doubles are
+  // treated as separate constants.
+  // The is [:!IntConstant:] check at the beginning of the function makes sure
+  // that we compare only equal to integer constants.
+  bool operator ==(var other) {
+    if (other is !IntConstant) return false;
+    IntConstant otherInt = other;
+    return value == otherInt.value;
+  }
+
+  int get hashCode => value.hashCode;
+  DartString toDartString() => new DartString.literal(value.toString());
+
+  accept(ConstantVisitor visitor) => visitor.visitInt(this);
+}
+
+class DoubleConstant extends NumConstant {
+  final double value;
+  factory DoubleConstant(double value) {
+    if (value.isNaN) {
+      return const DoubleConstant._internal(double.NAN);
+    } else if (value == double.INFINITY) {
+      return const DoubleConstant._internal(double.INFINITY);
+    } else if (value == -double.INFINITY) {
+      return const DoubleConstant._internal(-double.INFINITY);
+    } else if (value == 0.0 && !value.isNegative) {
+      return const DoubleConstant._internal(0.0);
+    } else if (value == 1.0) {
+      return const DoubleConstant._internal(1.0);
+    } else {
+      return new DoubleConstant._internal(value);
+    }
+  }
+  const DoubleConstant._internal(this.value);
+  bool isDouble() => true;
+  bool isNaN() => value.isNaN;
+  // We need to check for the negative sign since -0.0 == 0.0.
+  bool isMinusZero() => value == 0.0 && value.isNegative;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.doubleClass.computeType(compiler);
+  }
+
+  bool operator ==(var other) {
+    if (other is !DoubleConstant) return false;
+    DoubleConstant otherDouble = other;
+    double otherValue = otherDouble.value;
+    if (value == 0.0 && otherValue == 0.0) {
+      return value.isNegative == otherValue.isNegative;
+    } else if (value.isNaN) {
+      return otherValue.isNaN;
+    } else {
+      return value == otherValue;
+    }
+  }
+
+  int get hashCode => value.hashCode;
+  DartString toDartString() => new DartString.literal(value.toString());
+
+  accept(ConstantVisitor visitor) => visitor.visitDouble(this);
+}
+
+abstract class BoolConstant extends PrimitiveConstant {
+  factory BoolConstant(value) {
+    return value ? new TrueConstant() : new FalseConstant();
+  }
+  const BoolConstant._internal();
+  bool isBool() => true;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.boolClass.computeType(compiler);
+  }
+
+  BoolConstant negate();
+}
+
+class TrueConstant extends BoolConstant {
+  final bool value = true;
+
+  factory TrueConstant() => const TrueConstant._internal();
+  const TrueConstant._internal() : super._internal();
+  bool isTrue() => true;
+
+  FalseConstant negate() => new FalseConstant();
+
+  bool operator ==(var other) => identical(this, other);
+  // The magic constant is just a random value. It does not have any
+  // significance.
+  int get hashCode => 499;
+  DartString toDartString() => const LiteralDartString("true");
+
+  accept(ConstantVisitor visitor) => visitor.visitTrue(this);
+}
+
+class FalseConstant extends BoolConstant {
+  final bool value = false;
+
+  factory FalseConstant() => const FalseConstant._internal();
+  const FalseConstant._internal() : super._internal();
+  bool isFalse() => true;
+
+  TrueConstant negate() => new TrueConstant();
+
+  bool operator ==(var other) => identical(this, other);
+  // The magic constant is just a random value. It does not have any
+  // significance.
+  int get hashCode => 536555975;
+  DartString toDartString() => const LiteralDartString("false");
+
+  accept(ConstantVisitor visitor) => visitor.visitFalse(this);
+}
+
+class StringConstant extends PrimitiveConstant {
+  final DartString value;
+  final int hashCode;
+  final Node node;
+
+  // TODO(floitsch): cache StringConstants.
+  // TODO(floitsch): compute hashcode without calling toString() on the
+  // DartString.
+  StringConstant(DartString value, this.node)
+      : this.value = value,
+        this.hashCode = value.slowToString().hashCode;
+  bool isString() => true;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.stringClass.computeType(compiler);
+  }
+
+  bool operator ==(var other) {
+    if (other is !StringConstant) return false;
+    StringConstant otherString = other;
+    return (hashCode == otherString.hashCode) && (value == otherString.value);
+  }
+
+  DartString toDartString() => value;
+  int get length => value.length;
+
+  accept(ConstantVisitor visitor) => visitor.visitString(this);
+}
+
+abstract class ObjectConstant extends Constant {
+  final DartType type;
+
+  ObjectConstant(this.type);
+  bool isObject() => true;
+
+  DartType computeType(Compiler compiler) => type;
+}
+
+class TypeConstant extends ObjectConstant {
+  /// The user type that this constant represents.
+  final DartType representedType;
+
+  TypeConstant(this.representedType, type) : super(type);
+
+  bool isType() => true;
+
+  bool operator ==(other) {
+    return other is TypeConstant && representedType == other.representedType;
+  }
+
+  int get hashCode => representedType.hashCode * 13;
+
+  List<Constant> getDependencies() => const <Constant>[];
+
+  accept(ConstantVisitor visitor) => visitor.visitType(this);
+}
+
+class ListConstant extends ObjectConstant {
+  final List<Constant> entries;
+  final int hashCode;
+
+  ListConstant(DartType type, List<Constant> entries)
+      : this.entries = entries,
+        hashCode = _computeHash(entries),
+        super(type);
+  bool isList() => true;
+
+  static int _computeHash(List<Constant> entries) {
+    // TODO(floitsch): create a better hash.
+    int hash = 0;
+    for (Constant input in entries) hash ^= input.hashCode;
+    return hash;
+  }
+
+  bool operator ==(var other) {
+    if (other is !ListConstant) return false;
+    ListConstant otherList = other;
+    if (hashCode != otherList.hashCode) return false;
+    // TODO(floitsch): verify that the generic types are the same.
+    if (entries.length != otherList.entries.length) return false;
+    for (int i = 0; i < entries.length; i++) {
+      if (entries[i] != otherList.entries[i]) return false;
+    }
+    return true;
+  }
+
+  List<Constant> getDependencies() => entries;
+
+  int get length => entries.length;
+
+  accept(ConstantVisitor visitor) => visitor.visitList(this);
+}
+
+class MapConstant extends ObjectConstant {
+  /**
+   * The [PROTO_PROPERTY] must not be used as normal property in any JavaScript
+   * object. It would change the prototype chain.
+   */
+  static const LiteralDartString PROTO_PROPERTY =
+      const LiteralDartString("__proto__");
+
+  /** The dart class implementing constant map literals. */
+  static const SourceString DART_CLASS = const SourceString("ConstantMap");
+  static const SourceString DART_PROTO_CLASS =
+      const SourceString("ConstantProtoMap");
+  static const SourceString LENGTH_NAME = const SourceString("length");
+  static const SourceString JS_OBJECT_NAME = const SourceString("_jsObject");
+  static const SourceString KEYS_NAME = const SourceString("_keys");
+  static const SourceString PROTO_VALUE = const SourceString("_protoValue");
+
+  final ListConstant keys;
+  final List<Constant> values;
+  final Constant protoValue;
+  final int hashCode;
+
+  MapConstant(DartType type, this.keys, List<Constant> values, this.protoValue)
+      : this.values = values,
+        this.hashCode = computeHash(values),
+        super(type);
+  bool isMap() => true;
+
+  static int computeHash(List<Constant> values) {
+    // TODO(floitsch): create a better hash.
+    int hash = 0;
+    for (Constant value in values) hash ^= value.hashCode;
+    return hash;
+  }
+
+  bool operator ==(var other) {
+    if (other is !MapConstant) return false;
+    MapConstant otherMap = other;
+    if (hashCode != otherMap.hashCode) return false;
+    // TODO(floitsch): verify that the generic types are the same.
+    if (keys != otherMap.keys) return false;
+    for (int i = 0; i < values.length; i++) {
+      if (values[i] != otherMap.values[i]) return false;
+    }
+    return true;
+  }
+
+  List<Constant> getDependencies() {
+    List<Constant> result = <Constant>[keys];
+    result.addAll(values);
+    return result;
+  }
+
+  int get length => keys.length;
+
+  accept(ConstantVisitor visitor) => visitor.visitMap(this);
+}
+
+class ConstructedConstant extends ObjectConstant {
+  final List<Constant> fields;
+  final int hashCode;
+
+  ConstructedConstant(DartType type, List<Constant> fields)
+    : this.fields = fields,
+      hashCode = computeHash(type, fields),
+      super(type) {
+    assert(type != null);
+  }
+  bool isConstructedObject() => true;
+
+  static int computeHash(DartType type, List<Constant> fields) {
+    // TODO(floitsch): create a better hash.
+    int hash = 0;
+    for (Constant field in fields) {
+      hash ^= field.hashCode;
+    }
+    hash ^= type.element.hashCode;
+    return hash;
+  }
+
+  bool operator ==(var otherVar) {
+    if (otherVar is !ConstructedConstant) return false;
+    ConstructedConstant other = otherVar;
+    if (hashCode != other.hashCode) return false;
+    // TODO(floitsch): verify that the (generic) types are the same.
+    if (type.element != other.type.element) return false;
+    if (fields.length != other.fields.length) return false;
+    for (int i = 0; i < fields.length; i++) {
+      if (fields[i] != other.fields[i]) return false;
+    }
+    return true;
+  }
+
+  List<Constant> getDependencies() => fields;
+
+  accept(ConstantVisitor visitor) => visitor.visitConstructed(this);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart
new file mode 100644
index 0000000..ac7d422
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart
@@ -0,0 +1,497 @@
+// 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.
+
+library dart2js;
+
+import 'dart:async';
+import 'dart:collection' show Queue, LinkedHashMap;
+import 'dart:io';
+import 'dart:uri';
+import 'dart:utf';
+
+import '../compiler.dart' as api;
+import 'source_file.dart';
+import 'source_file_provider.dart';
+import 'filenames.dart';
+import 'util/uri_extras.dart';
+import '../../libraries.dart';
+
+const String LIBRARY_ROOT = '../../../../..';
+const String OUTPUT_LANGUAGE_DART = 'Dart';
+
+typedef void HandleOption(String option);
+
+class OptionHandler {
+  String pattern;
+  HandleOption handle;
+
+  OptionHandler(this.pattern, this.handle);
+}
+
+/**
+ * Extract the parameter of an option.
+ *
+ * For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters
+ * are ['fisk.js'] and ['hest.js'], respectively.
+ */
+String extractParameter(String argument) {
+  // m[0] is the entire match (which will be equal to argument). m[1]
+  // is something like "-o" or "--out=", and m[2] is the parameter.
+  Match m = new RegExp('^(-[a-z]|--.+=)(.*)').firstMatch(argument);
+  if (m == null) helpAndFail('Error: Unknown option "$argument".');
+  return m[2];
+}
+
+String extractPath(String argument) {
+  String path = nativeToUriPath(extractParameter(argument));
+  return path.endsWith("/") ? path : "$path/";
+}
+
+void parseCommandLine(List<OptionHandler> handlers, List<String> argv) {
+  // TODO(ahe): Use ../../args/args.dart for parsing options instead.
+  var patterns = <String>[];
+  for (OptionHandler handler in handlers) {
+    patterns.add(handler.pattern);
+  }
+  var pattern = new RegExp('^(${Strings.join(patterns, ")\$|(")})\$');
+  OUTER: for (String argument in argv) {
+    Match match = pattern.firstMatch(argument);
+    assert(match.groupCount == handlers.length);
+    for (int i = 0; i < handlers.length; i++) {
+      if (match[i + 1] != null) {
+        handlers[i].handle(argument);
+        continue OUTER;
+      }
+    }
+    throw 'Internal error: "$argument" did not match';
+  }
+}
+
+void compile(List<String> argv) {
+  bool isWindows = (Platform.operatingSystem == 'windows');
+  Uri cwd = getCurrentDirectory();
+  Uri libraryRoot = cwd;
+  Uri out = cwd.resolve('out.js');
+  Uri sourceMapOut = cwd.resolve('out.js.map');
+  Uri packageRoot = null;
+  List<String> options = new List<String>();
+  bool explicitOut = false;
+  bool wantHelp = false;
+  String outputLanguage = 'JavaScript';
+  bool stripArgumentSet = false;
+  bool analyzeOnly = false;
+  SourceFileProvider inputProvider = new SourceFileProvider();
+  FormattingDiagnosticHandler diagnosticHandler =
+      new FormattingDiagnosticHandler(inputProvider);
+
+  passThrough(String argument) => options.add(argument);
+
+  setLibraryRoot(String argument) {
+    libraryRoot = cwd.resolve(extractPath(argument));
+  }
+
+  setPackageRoot(String argument) {
+    packageRoot = cwd.resolve(extractPath(argument));
+  }
+
+  setOutput(String argument) {
+    explicitOut = true;
+    out = cwd.resolve(nativeToUriPath(extractParameter(argument)));
+    sourceMapOut = Uri.parse('$out.map');
+  }
+
+  setOutputType(String argument) {
+    if (argument == '--output-type=dart') {
+      outputLanguage = OUTPUT_LANGUAGE_DART;
+      if (!explicitOut) {
+        out = cwd.resolve('out.dart');
+        sourceMapOut = cwd.resolve('out.dart.map');
+      }
+    }
+    passThrough(argument);
+  }
+
+  String getDepsOutput(Map<String, SourceFile> sourceFiles) {
+    var filenames = new List.from(sourceFiles.keys);
+    filenames.sort();
+    return Strings.join(filenames, "\n");
+  }
+
+  setStrip(String argument) {
+    stripArgumentSet = true;
+    passThrough(argument);
+  }
+
+  setAnalyzeOnly(String argument) {
+    analyzeOnly = true;
+    passThrough(argument);
+  }
+
+  setCategories(String argument) {
+    List<String> categories = extractParameter(argument).split(',');
+    Set<String> allowedCategories =
+        LIBRARIES.values.map((x) => x.category).toSet();
+    allowedCategories.remove('Shared');
+    allowedCategories.remove('Internal');
+    List<String> allowedCategoriesList =
+        new List<String>.from(allowedCategories);
+    allowedCategoriesList.sort();
+    if (categories.contains('all')) {
+      categories = allowedCategoriesList;
+    } else {
+      String allowedCategoriesString =
+          Strings.join(allowedCategoriesList, ', ');
+      for (String category in categories) {
+        if (!allowedCategories.contains(category)) {
+          fail('Error: unsupported library category "$category", '
+               'supported categories are: $allowedCategoriesString');
+        }
+      }
+    }
+    return passThrough('--categories=${Strings.join(categories, ",")}');
+  }
+
+  handleShortOptions(String argument) {
+    var shortOptions = argument.substring(1).splitChars();
+    for (var shortOption in shortOptions) {
+      switch (shortOption) {
+        case 'v':
+          diagnosticHandler.verbose = true;
+          break;
+        case 'h':
+        case '?':
+          wantHelp = true;
+          break;
+        case 'c':
+          passThrough('--enable-checked-mode');
+          break;
+        default:
+          throw 'Internal error: "$shortOption" did not match';
+      }
+    }
+  }
+
+  List<String> arguments = <String>[];
+  List<OptionHandler> handlers = <OptionHandler>[
+    new OptionHandler('-[chv?]+', handleShortOptions),
+    new OptionHandler('--throw-on-error',
+                      (_) => diagnosticHandler.throwOnError = true),
+    new OptionHandler('--suppress-warnings',
+                      (_) => diagnosticHandler.showWarnings = false),
+    new OptionHandler('--output-type=dart|--output-type=js', setOutputType),
+    new OptionHandler('--verbose', (_) => diagnosticHandler.verbose = true),
+    new OptionHandler('--library-root=.+', setLibraryRoot),
+    new OptionHandler('--out=.+|-o.+', setOutput),
+    new OptionHandler('--allow-mock-compilation', passThrough),
+    new OptionHandler('--minify', passThrough),
+    new OptionHandler('--force-strip=.*', setStrip),
+    // TODO(ahe): Remove the --no-colors option.
+    new OptionHandler('--disable-diagnostic-colors',
+                      (_) => diagnosticHandler.enableColors = false),
+    new OptionHandler('--enable-diagnostic-colors',
+                      (_) => diagnosticHandler.enableColors = true),
+    new OptionHandler('--enable[_-]checked[_-]mode|--checked',
+                      (_) => passThrough('--enable-checked-mode')),
+    new OptionHandler('--enable-concrete-type-inference',
+                      (_) => passThrough('--enable-concrete-type-inference')),
+    new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true),
+    new OptionHandler('--package-root=.+|-p.+', setPackageRoot),
+    new OptionHandler('--disallow-unsafe-eval', passThrough),
+    new OptionHandler('--analyze-all', passThrough),
+    new OptionHandler('--analyze-only', setAnalyzeOnly),
+    new OptionHandler('--disable-native-live-type-analysis', passThrough),
+    new OptionHandler('--reject-deprecated-language-features', passThrough),
+    new OptionHandler('--report-sdk-use-of-deprecated-language-features',
+                      passThrough),
+    new OptionHandler('--categories=.*', setCategories),
+
+    // The following two options must come last.
+    new OptionHandler('-.*', (String argument) {
+      helpAndFail('Error: Unknown option "$argument".');
+    }),
+    new OptionHandler('.*', (String argument) {
+      arguments.add(nativeToUriPath(argument));
+    })
+  ];
+
+  parseCommandLine(handlers, argv);
+  if (wantHelp) helpAndExit(diagnosticHandler.verbose);
+
+  if (outputLanguage != OUTPUT_LANGUAGE_DART && stripArgumentSet) {
+    helpAndFail('Error: --force-strip may only be used with '
+        '--output-type=dart');
+  }
+  if (arguments.isEmpty) {
+    helpAndFail('Error: No Dart file specified.');
+  }
+  if (arguments.length > 1) {
+    var extra = arguments.getRange(1, arguments.length - 1);
+    helpAndFail('Error: Extra arguments: ${Strings.join(extra, " ")}');
+  }
+
+  void handler(Uri uri, int begin, int end, String message,
+               api.Diagnostic kind) {
+    diagnosticHandler.diagnosticHandler(uri, begin, end, message, kind);
+  }
+
+  Uri uri = cwd.resolve(arguments[0]);
+  if (packageRoot == null) {
+    packageRoot = uri.resolve('./packages/');
+  }
+
+  diagnosticHandler.info('package root is $packageRoot');
+
+  int charactersWritten = 0;
+
+  compilationDone(String code) {
+    if (analyzeOnly) return;
+    if (code == null) {
+      fail('Error: Compilation failed.');
+    }
+    writeString(Uri.parse('$out.deps'),
+                getDepsOutput(inputProvider.sourceFiles));
+    diagnosticHandler.info(
+         'compiled ${inputProvider.dartCharactersRead} characters Dart '
+         '-> $charactersWritten characters $outputLanguage '
+         'in ${relativize(cwd, out, isWindows)}');
+    if (!explicitOut) {
+      String input = uriPathToNative(arguments[0]);
+      String output = relativize(cwd, out, isWindows);
+      print('Dart file $input compiled to $outputLanguage: $output');
+    }
+  }
+
+  StreamSink<String> outputProvider(String name, String extension) {
+    Uri uri;
+    String sourceMapFileName;
+    bool isPrimaryOutput = false;
+    if (name == '') {
+      if (extension == 'js' || extension == 'dart') {
+        isPrimaryOutput = true;
+        uri = out;
+        sourceMapFileName =
+            sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1);
+      } else if (extension == 'js.map' || extension == 'dart.map') {
+        uri = sourceMapOut;
+      } else {
+        fail('Error: Unknown extension: $extension');
+      }
+    } else {
+      uri = out.resolve('$name.$extension');
+    }
+
+    if (uri.scheme != 'file') {
+      fail('Error: Unhandled scheme ${uri.scheme} in $uri.');
+    }
+    var outputStream = new File(uriPathToNative(uri.path)).openOutputStream();
+
+    CountingSink sink;
+
+    onDone() {
+      if (sourceMapFileName != null) {
+        String sourceMapTag = '//@ sourceMappingURL=$sourceMapFileName\n';
+        sink.count += sourceMapTag.length;
+        outputStream.writeString(sourceMapTag);
+      }
+      outputStream.close();
+      if (isPrimaryOutput) {
+        charactersWritten += sink.count;
+      }
+    }
+
+    var controller = new StreamController<String>();
+    controller.stream.listen(outputStream.writeString, onDone: onDone);
+    sink = new CountingSink(controller);
+    return sink;
+  }
+
+  api.compile(uri, libraryRoot, packageRoot,
+              inputProvider.readStringFromUri, handler,
+              options, outputProvider)
+      .then(compilationDone);
+}
+
+// TODO(ahe): Get rid of this class if http://dartbug.com/8118 is fixed.
+class CountingSink implements StreamSink<String> {
+  final StreamSink<String> sink;
+  int count = 0;
+
+  CountingSink(this.sink);
+
+  add(String value) {
+    sink.add(value);
+    count += value.length;
+  }
+
+  signalError(AsyncError error) => sink.signalError(error);
+
+  close() => sink.close();
+}
+
+class AbortLeg {
+  final message;
+  AbortLeg(this.message);
+  toString() => 'Aborted due to --throw-on-error: $message';
+}
+
+void writeString(Uri uri, String text) {
+  if (uri.scheme != 'file') {
+    fail('Error: Unhandled scheme ${uri.scheme}.');
+  }
+  var file = new File(uriPathToNative(uri.path)).openSync(FileMode.WRITE);
+  file.writeStringSync(text);
+  file.closeSync();
+}
+
+void fail(String message) {
+  print(message);
+  exit(1);
+}
+
+void compilerMain(Options options) {
+  var root = uriPathToNative("/$LIBRARY_ROOT");
+  List<String> argv = ['--library-root=${options.script}$root'];
+  argv.addAll(options.arguments);
+  compile(argv);
+}
+
+void help() {
+  // This message should be no longer than 20 lines. The default
+  // terminal size normally 80x24. Two lines are used for the prompts
+  // before and after running the compiler. Another two lines may be
+  // used to print an error message.
+  print('''
+Usage: dart2js [options] dartfile
+
+Compiles Dart to JavaScript.
+
+Common options:
+  -o<file> Generate the output into <file>.
+  -c       Insert runtime type checks and enable assertions (checked mode).
+  -h       Display this message (add -v for information about all options).''');
+}
+
+void verboseHelp() {
+  print('''
+Usage: dart2js [options] dartfile
+
+Compiles Dart to JavaScript.
+
+Supported options:
+  -o<file>, --out=<file>
+    Generate the output into <file>.
+
+  -c, --enable-checked-mode, --checked
+    Insert runtime type checks and enable assertions (checked mode).
+
+  -h, /h, /?, --help
+    Display this message (add -v for information about all options).
+
+  -v, --verbose
+    Display verbose information.
+
+  -p<path>, --package-root=<path>
+    Where to find packages, that is, "package:..." imports.
+
+  --analyze-all
+    Analyze all code.  Without this option, the compiler only analyzes
+    code that is reachable from [main].  This option is useful for
+    finding errors in libraries, but using it can result in bigger and
+    slower output.
+
+  --analyze-only
+    Analyze but do not generate code.
+
+  --minify
+    Generate minified output.
+
+  --suppress-warnings
+    Do not display any warnings.
+
+  --enable-diagnostic-colors
+    Add colors to diagnostic messages.
+
+The following options are only used for compiler development and may
+be removed in a future version:
+
+  --output-type=dart
+    Output Dart code instead of JavaScript.
+
+  --throw-on-error
+    Throw an exception if a compile-time error is detected.
+
+  --library-root=<directory>
+    Where to find the Dart platform libraries.
+
+  --allow-mock-compilation
+    Do not generate a call to main if either of the following
+    libraries are used: dart:dom, dart:html dart:io.
+
+  --enable-concrete-type-inference
+    Enable experimental concrete type inference.
+
+  --disable-native-live-type-analysis
+    Disable the optimization that removes unused native types from dart:html
+    and related libraries.
+
+  --disallow-unsafe-eval
+    Disable dynamic generation of code in the generated output. This is
+    necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/).
+    This flag is not continuously tested. Please report breakages and we
+    will fix them as soon as possible.
+
+  --reject-deprecated-language-features
+    Reject deprecated language features.  Without this option, the
+    compiler will accept language features that are no longer valid
+    according to The Dart Programming Language Specification, version
+    0.12, M1.
+
+  --report-sdk-use-of-deprecated-language-features
+    Report use of deprecated features in Dart platform libraries.
+    Without this option, the compiler will silently accept use of
+    deprecated language features from these libraries.  The option
+    --reject-deprecated-language-features controls if these usages are
+    reported as errors or warnings.
+
+  --categories=<categories>
+
+    A comma separated list of allowed library categories.  The default
+    is "Client".  Possible categories can be seen by providing an
+    unsupported category, for example, --categories=help.  To enable
+    all categories, use --categories=all.
+
+'''.trim());
+}
+
+void helpAndExit(bool verbose) {
+  if (verbose) {
+    verboseHelp();
+  } else {
+    help();
+  }
+  exit(0);
+}
+
+void helpAndFail(String message) {
+  help();
+  print('');
+  fail(message);
+}
+
+void main() {
+  try {
+    compilerMain(new Options());
+  } catch (exception, trace) {
+    try {
+      print('Internal error: $exception');
+    } catch (ignored) {
+      print('Internal error: error while printing exception');
+    }
+    try {
+      print(trace);
+    } finally {
+      exit(253); // 253 is recognized as a crash by our test scripts.
+    }
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot
new file mode 100644
index 0000000..758a05a
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot
Binary files differ
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart b/pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart
new file mode 100644
index 0000000..9b683f2
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart
@@ -0,0 +1,61 @@
+// 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.
+
+library dart2js;
+
+import 'dart:async';
+import 'dart:uri';
+import 'dart:collection' show Queue, LinkedHashMap;
+
+import 'closure.dart' as closureMapping;
+import 'dart_backend/dart_backend.dart' as dart_backend;
+import 'dart_types.dart';
+import 'elements/elements.dart';
+import 'elements/modelx.dart'
+    show ErroneousElementX,
+         CompilationUnitElementX,
+         LibraryElementX,
+         PrefixElementX,
+         VoidElementX;
+import 'js_backend/js_backend.dart' as js_backend;
+import 'native_handler.dart' as native;
+import 'scanner/scanner_implementation.dart';
+import 'scanner/scannerlib.dart';
+import 'ssa/ssa.dart';
+import 'string_validator.dart';
+import 'source_file.dart';
+import 'tree/tree.dart';
+import 'universe/universe.dart';
+import 'util/characters.dart';
+import 'util/util.dart';
+import '../compiler.dart' as api;
+import 'patch_parser.dart';
+import 'types/types.dart' as ti;
+import 'resolution/resolution.dart';
+import 'js/js.dart' as js;
+
+export 'resolution/resolution.dart' show TreeElements, TreeElementMapping;
+export 'scanner/scannerlib.dart' show SourceString,
+                                      isUserDefinableOperator,
+                                      isUnaryOperator,
+                                      isBinaryOperator,
+                                      isTernaryOperator,
+                                      isMinusOperator;
+export 'universe/universe.dart' show Selector;
+
+part 'code_buffer.dart';
+part 'compile_time_constants.dart';
+part 'compiler.dart';
+part 'constants.dart';
+part 'constant_system.dart';
+part 'constant_system_dart.dart';
+part 'diagnostic_listener.dart';
+part 'enqueue.dart';
+part 'library_loader.dart';
+part 'resolved_visitor.dart';
+part 'script.dart';
+part 'tree_validator.dart';
+part 'typechecker.dart';
+part 'warnings.dart';
+part 'world.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart
new file mode 100644
index 0000000..6a3b000
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart
@@ -0,0 +1,593 @@
+// 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.
+
+part of dart_backend;
+
+// TODO(ahe): This class is simply wrong.  This backend should use
+// elements when it can, not AST nodes.  Perhaps a [Map<Element,
+// TreeElements>] is what is needed.
+class ElementAst {
+  final Node ast;
+  final TreeElements treeElements;
+
+  ElementAst(this.ast, this.treeElements);
+
+  factory ElementAst.rewrite(compiler, ast, treeElements, stripAsserts) {
+    final rewriter =
+        new FunctionBodyRewriter(compiler, treeElements, stripAsserts);
+    return new ElementAst(rewriter.visit(ast), rewriter.cloneTreeElements);
+  }
+
+  ElementAst.forClassLike(this.ast)
+      : this.treeElements = new TreeElementMapping(null);
+}
+
+// TODO(ahe): This class should not subclass [TreeElementMapping], if
+// anything, it should implement TreeElements.
+class AggregatedTreeElements extends TreeElementMapping {
+  final List<TreeElements> treeElements;
+
+  AggregatedTreeElements() : treeElements = <TreeElements>[], super(null);
+
+  Element operator[](Node node) {
+    final result = super[node];
+    return result != null ? result : getFirstNotNullResult((e) => e[node]);
+  }
+
+  Selector getSelector(Send send) {
+    final result = super.getSelector(send);
+    return result != null ?
+        result : getFirstNotNullResult((e) => e.getSelector(send));
+  }
+
+  DartType getType(Node node) {
+    final result = super.getType(node);
+    return result != null ?
+        result : getFirstNotNullResult((e) => e.getType(node));
+  }
+
+  getFirstNotNullResult(f(TreeElements element)) {
+    for (final element in treeElements) {
+      final result = f(element);
+      if (result != null) return result;
+    }
+
+    return null;
+  }
+}
+
+class VariableListAst extends ElementAst {
+  VariableListAst(ast) : super(ast, new AggregatedTreeElements());
+
+  add(VariableElement element, TreeElements treeElements) {
+    AggregatedTreeElements e = this.treeElements;
+    e[element.cachedNode] = element;
+    e.treeElements.add(treeElements);
+  }
+}
+
+class FunctionBodyRewriter extends CloningVisitor {
+  final Compiler compiler;
+  final bool stripAsserts;
+
+  FunctionBodyRewriter(this.compiler, originalTreeElements, this.stripAsserts)
+      : super(originalTreeElements);
+
+  visitBlock(Block block) {
+    shouldOmit(Statement statement) {
+      if (statement is EmptyStatement) return true;
+      if (statement is ExpressionStatement) {
+        Send send = statement.expression.asSend();
+        if (send != null) {
+          Element element = originalTreeElements[send];
+          if (stripAsserts && identical(element, compiler.assertMethod)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    rewriteStatement(Statement statement) {
+      if (statement is Block) {
+        Link statements = statement.statements.nodes;
+        if (!statements.isEmpty && statements.tail.isEmpty) {
+          Statement single = statements.head;
+          bool isDeclaration =
+              single is VariableDefinitions || single is FunctionDeclaration;
+          if (!isDeclaration) return single;
+        }
+      }
+      return statement;
+    }
+
+    NodeList statements = block.statements;
+    LinkBuilder<Statement> builder = new LinkBuilder<Statement>();
+    for (Statement statement in statements.nodes) {
+      if (!shouldOmit(statement)) {
+        builder.addLast(visit(rewriteStatement(statement)));
+      }
+    }
+    return new Block(rewriteNodeList(statements, builder.toLink()));
+  }
+}
+
+class DartBackend extends Backend {
+  final List<CompilerTask> tasks;
+  final bool forceStripTypes;
+  final bool stripAsserts;
+  // TODO(antonm): make available from command-line options.
+  final bool outputAst = false;
+
+  Map<Element, TreeElements> get resolvedElements =>
+      compiler.enqueuer.resolution.resolvedElements;
+
+  /**
+   * Tells whether it is safe to remove type declarations from variables,
+   * functions parameters. It becomes not safe if:
+   * 1) TypeError is used somewhere in the code,
+   * 2) The code has typedefs in right hand side of IS checks,
+   * 3) The code has classes which extend typedefs, have type arguments typedefs
+   *    or type variable bounds typedefs.
+   * These restrictions can be less strict.
+   */
+  bool isSafeToRemoveTypeDeclarations(
+      Map<ClassElement, Set<Element>> classMembers) {
+    Set<DartType> processedTypes = new Set<DartType>();
+    List<DartType> workQueue = new List<DartType>();
+    workQueue.addAll(
+        classMembers.keys.map((classElement) => classElement.thisType));
+    workQueue.addAll(compiler.resolverWorld.isChecks);
+    Element typeErrorElement =
+        compiler.coreLibrary.find(new SourceString('TypeError'));
+    DartType typeErrorType = typeErrorElement.computeType(compiler);
+    if (workQueue.indexOf(typeErrorType) != -1) {
+      return false;
+    }
+
+    void processTypeArguments(Element classElement, NodeList typeArguments) {
+      if (typeArguments == null) return;
+      for (Node typeArgument in typeArguments.nodes) {
+        if (typeArgument is TypeVariable) {
+          typeArgument = typeArgument.bound;
+        }
+        if (typeArgument == null) continue;
+        assert(typeArgument is TypeAnnotation);
+        DartType argumentType =
+            compiler.resolveTypeAnnotation(classElement, typeArgument);
+        assert(argumentType != null);
+        workQueue.add(argumentType);
+      }
+    }
+
+    void processTypeAnnotationList(Element classElement, NodeList annotations) {
+      for (Link link = annotations.nodes; !link.isEmpty; link = link.tail) {
+        TypeAnnotation typeAnnotation = link.head;
+        NodeList typeArguments = typeAnnotation.typeArguments;
+        processTypeArguments(classElement, typeArguments);
+      }
+    }
+
+    void processSuperclassTypeArguments(Element classElement, Node superclass) {
+      if (superclass == null) return;
+      MixinApplication superMixinApplication = superclass.asMixinApplication();
+      if (superMixinApplication != null) {
+        processTypeAnnotationList(classElement, superMixinApplication.mixins);
+      } else {
+        TypeAnnotation typeAnnotation = superclass;
+        NodeList typeArguments = typeAnnotation.typeArguments;
+        processTypeArguments(classElement, typeArguments);
+      }
+    }
+
+    while (!workQueue.isEmpty) {
+      DartType type = workQueue.removeLast();
+      if (processedTypes.contains(type)) continue;
+      processedTypes.add(type);
+      if (type is TypedefType) return false;
+      if (type is InterfaceType) {
+        ClassElement element = type.element;
+        Node node = element.parseNode(compiler);
+        if (node is ClassNode) {
+          ClassNode classNode = node;
+          processTypeArguments(element, classNode.typeParameters);
+          processSuperclassTypeArguments(element, classNode.superclass);
+          processTypeAnnotationList(element, classNode.interfaces);
+        } else {
+          MixinApplication mixinNode = node;
+          processSuperclassTypeArguments(element, mixinNode.superclass);
+          if (mixinNode is NamedMixinApplication) {
+            NamedMixinApplication namedMixinNode = mixinNode;
+            processTypeArguments(element, namedMixinNode.typeParameters);
+          }
+        }
+        // Check all supertypes.
+        if (element.allSupertypes != null) {
+          workQueue.addAll(element.allSupertypes.toList());
+        }
+      }
+    }
+    return true;
+  }
+
+  DartBackend(Compiler compiler, List<String> strips)
+      : tasks = <CompilerTask>[],
+        forceStripTypes = strips.indexOf('types') != -1,
+        stripAsserts = strips.indexOf('asserts') != -1,
+        super(compiler);
+
+  void enqueueHelpers(ResolutionEnqueuer world) {
+    // Right now resolver doesn't always resolve interfaces needed
+    // for literals, so force them. TODO(antonm): fix in the resolver.
+    final LITERAL_TYPE_NAMES = const [
+      'Map', 'List', 'num', 'int', 'double', 'bool'
+    ];
+    final coreLibrary = compiler.coreLibrary;
+    for (final name in LITERAL_TYPE_NAMES) {
+      ClassElement classElement = coreLibrary.findLocal(new SourceString(name));
+      classElement.ensureResolved(compiler);
+    }
+  }
+  void codegen(CodegenWorkItem work) { }
+  void processNativeClasses(Enqueuer world,
+                            Iterable<LibraryElement> libraries) { }
+
+  bool isUserLibrary(LibraryElement lib) {
+    final INTERNAL_HELPERS = [
+      compiler.jsHelperLibrary,
+      compiler.interceptorsLibrary,
+    ];
+    return INTERNAL_HELPERS.indexOf(lib) == -1 && !lib.isPlatformLibrary;
+  }
+
+  void assembleProgram() {
+    // Conservatively traverse all platform libraries and collect member names.
+    // TODO(antonm): ideally we should only collect names of used members,
+    // however as of today there are problems with names of some core library
+    // interfaces, most probably for interfaces of literals.
+    final fixedMemberNames = new Set<String>();
+    for (final library in compiler.libraries.values) {
+      if (!library.isPlatformLibrary) continue;
+      library.implementation.forEachLocalMember((Element element) {
+        if (element.isClass()) {
+          ClassElement classElement = element;
+          // Make sure we parsed the class to initialize its local members.
+          // TODO(smok): Figure out if there is a better way to fill local
+          // members.
+          element.parseNode(compiler);
+          classElement.forEachLocalMember((member) {
+            final name = member.name.slowToString();
+            // Skip operator names.
+            if (!name.startsWith(r'operator$')) {
+              // Fetch name of named constructors and factories if any,
+              // otherwise store regular name.
+              // TODO(antonm): better way to analyze the name.
+              fixedMemberNames.add(name.split(r'$').last);
+            }
+          });
+        }
+        // Even class names are added due to a delicate problem we have:
+        // if one imports dart:core with a prefix, we cannot tell prefix.name
+        // from dynamic invocation (alas!).  So we'd better err on preserving
+        // those names.
+        fixedMemberNames.add(element.name.slowToString());
+      });
+    }
+    // The VM will automatically invoke the call method of objects
+    // that are invoked as functions. Make sure to not rename that.
+    fixedMemberNames.add('call');
+    // TODO(antonm): TypeError.srcType and TypeError.dstType are defined in
+    // runtime/lib/error.dart. Overall, all DartVM specific libs should be
+    // accounted for.
+    fixedMemberNames.add('srcType');
+    fixedMemberNames.add('dstType');
+
+    /**
+     * Tells whether we should output given element. Corelib classes like
+     * Object should not be in the resulting code.
+     */
+    bool shouldOutput(Element element) {
+      return !identical(element.kind, ElementKind.VOID)
+          && isUserLibrary(element.getLibrary())
+          && !element.isSynthesized
+          && element is !AbstractFieldElement;
+    }
+
+    final elementAsts = new Map<Element, ElementAst>();
+
+    parse(element) => element.parseNode(compiler);
+
+    Set<Element> topLevelElements = new Set<Element>();
+    Map<ClassElement, Set<Element>> classMembers =
+        new Map<ClassElement, Set<Element>>();
+
+    // Build all top level elements to emit and necessary class members.
+    var newTypedefElementCallback, newClassElementCallback;
+
+    processElement(element, elementAst) {
+      new ReferencedElementCollector(
+          compiler,
+          element, elementAst.treeElements,
+          newTypedefElementCallback, newClassElementCallback).collect();
+      elementAsts[element] = elementAst;
+    }
+
+    addTopLevel(element, elementAst) {
+      if (topLevelElements.contains(element)) return;
+      topLevelElements.add(element);
+      processElement(element, elementAst);
+    }
+
+    addClass(classElement) {
+      addTopLevel(classElement,
+                  new ElementAst.forClassLike(parse(classElement)));
+      classMembers.putIfAbsent(classElement, () => new Set());
+    }
+
+    newTypedefElementCallback = (TypedefElement element) {
+      if (!shouldOutput(element)) return;
+      addTopLevel(element,
+                  new ElementAst.forClassLike(parse(element)));
+    };
+    newClassElementCallback = (ClassElement classElement) {
+      if (!shouldOutput(classElement)) return;
+      addClass(classElement);
+    };
+
+    compiler.resolverWorld.instantiatedClasses.forEach(
+        (ClassElement classElement) {
+      if (shouldOutput(classElement)) addClass(classElement);
+    });
+    resolvedElements.forEach((element, treeElements) {
+      if (!shouldOutput(element) || treeElements == null) return;
+      var elementAst = new ElementAst.rewrite(
+          compiler, parse(element), treeElements, stripAsserts);
+      if (element.isField()) {
+        final list = (element as VariableElement).variables;
+        elementAst = elementAsts.putIfAbsent(
+            list, () => new VariableListAst(parse(list)));
+        (elementAst as VariableListAst).add(element, treeElements);
+        element = list;
+      }
+
+      if (element.isMember()) {
+        ClassElement enclosingClass = element.getEnclosingClass();
+        assert(enclosingClass.isClass());
+        assert(enclosingClass.isTopLevel());
+        assert(shouldOutput(enclosingClass));
+        addClass(enclosingClass);
+        classMembers[enclosingClass].add(element);
+        processElement(element, elementAst);
+      } else {
+        if (!element.isTopLevel()) {
+          compiler.cancel('Cannot process $element', element: element);
+        }
+        addTopLevel(element, elementAst);
+      }
+    });
+
+    // Add synthesized constructors to classes with no resolved constructors,
+    // but which originally had any constructor.  That should prevent
+    // those classes from being instantiable with default constructor.
+    Identifier synthesizedIdentifier =
+        new Identifier(new StringToken(IDENTIFIER_INFO, '', -1));
+
+    NextClassElement:
+    for (ClassElement classElement in classMembers.keys) {
+      for (Element member in classMembers[classElement]) {
+        if (member.isConstructor()) continue NextClassElement;
+      }
+      if (classElement.constructors.isEmpty) continue NextClassElement;
+
+      // TODO(antonm): check with AAR team if there is better approach.
+      // As an idea: provide template as a Dart code---class C { C.name(); }---
+      // and then overwrite necessary parts.
+      ClassNode classNode = classElement.parseNode(compiler);
+      SynthesizedConstructorElementX constructor =
+          new SynthesizedConstructorElementX(classElement);
+      constructor.type = new FunctionType(
+          constructor,
+          compiler.types.voidType,
+          const Link<DartType>(),
+          const Link<DartType>(),
+          const Link<SourceString>(),
+          const Link<DartType>()
+          );
+      constructor.cachedNode = new FunctionExpression(
+          new Send(classNode.name, synthesizedIdentifier),
+          new NodeList(new StringToken(OPEN_PAREN_INFO, '(', -1),
+                       const Link<Node>(),
+                       new StringToken(CLOSE_PAREN_INFO, ')', -1)),
+          new EmptyStatement(new StringToken(SEMICOLON_INFO, ';', -1)),
+          null, Modifiers.EMPTY, null, null);
+
+      classMembers[classElement].add(constructor);
+      elementAsts[constructor] =
+          new ElementAst(constructor.cachedNode, new TreeElementMapping(null));
+    }
+
+    // Create all necessary placeholders.
+    PlaceholderCollector collector =
+        new PlaceholderCollector(compiler, fixedMemberNames, elementAsts);
+    // Add synthesizedIdentifier to set of unresolved names to rename it to
+    // some unused identifier.
+    collector.unresolvedNodes.add(synthesizedIdentifier);
+    makePlaceholders(element) {
+      collector.collect(element);
+      if (element.isClass()) {
+        classMembers[element].forEach(makePlaceholders);
+      }
+    }
+    topLevelElements.forEach(makePlaceholders);
+    // Create renames.
+    Map<Node, String> renames = new Map<Node, String>();
+    Map<LibraryElement, String> imports = new Map<LibraryElement, String>();
+    bool shouldCutDeclarationTypes = forceStripTypes
+        || (compiler.enableMinification
+            && isSafeToRemoveTypeDeclarations(classMembers));
+    renamePlaceholders(
+        compiler, collector, renames, imports,
+        fixedMemberNames, shouldCutDeclarationTypes);
+
+    // Sort elements.
+    final sortedTopLevels = sortElements(topLevelElements);
+    final sortedClassMembers = new Map<ClassElement, List<Element>>();
+    classMembers.forEach((classElement, members) {
+      sortedClassMembers[classElement] = sortElements(members);
+    });
+
+    if (outputAst) {
+      // TODO(antonm): Ideally XML should be a separate backend.
+      // TODO(antonm): obey renames and minification, at least as an option.
+      StringBuffer sb = new StringBuffer();
+      outputElement(element) { sb.add(parse(element).toDebugString()); }
+
+      // Emit XML for AST instead of the program.
+      for (final topLevel in sortedTopLevels) {
+        if (topLevel.isClass()) {
+          // TODO(antonm): add some class info.
+          sortedClassMembers[topLevel].forEach(outputElement);
+        } else {
+          outputElement(topLevel);
+        }
+      }
+      compiler.assembledCode = '<Program>\n$sb</Program>\n';
+      return;
+    }
+
+    final topLevelNodes = <Node>[];
+    final memberNodes = new Map<ClassNode, List<Node>>();
+    for (final element in sortedTopLevels) {
+      topLevelNodes.add(elementAsts[element].ast);
+      if (element.isClass() && !element.isMixinApplication) {
+        final members = <Node>[];
+        for (final member in sortedClassMembers[element]) {
+          members.add(elementAsts[member].ast);
+        }
+        memberNodes[elementAsts[element].ast] = members;
+      }
+    }
+
+    final unparser = new EmitterUnparser(renames);
+    emitCode(unparser, imports, topLevelNodes, memberNodes);
+    compiler.assembledCode = unparser.result;
+
+    // Output verbose info about size ratio of resulting bundle to all
+    // referenced non-platform sources.
+    logResultBundleSizeInfo(topLevelElements);
+  }
+
+  void logResultBundleSizeInfo(Set<Element> topLevelElements) {
+    Iterable<LibraryElement> referencedLibraries =
+        compiler.libraries.values.where(isUserLibrary);
+    // Sum total size of scripts in each referenced library.
+    int nonPlatformSize = 0;
+    for (LibraryElement lib in referencedLibraries) {
+      for (CompilationUnitElement compilationUnit in lib.compilationUnits) {
+        nonPlatformSize += compilationUnit.script.text.length;
+      }
+    }
+    int percentage = compiler.assembledCode.length * 100 ~/ nonPlatformSize;
+    log('Total used non-platform files size: ${nonPlatformSize} bytes, '
+        'bundle size: ${compiler.assembledCode.length} bytes (${percentage}%)');
+  }
+
+  log(String message) => compiler.log('[DartBackend] $message');
+}
+
+class EmitterUnparser extends Unparser {
+  final Map<Node, String> renames;
+
+  EmitterUnparser(this.renames);
+
+  visit(Node node) {
+    if (node != null && renames.containsKey(node)) {
+      sb.add(renames[node]);
+    } else {
+      super.visit(node);
+    }
+  }
+
+  unparseSendReceiver(Send node, {bool spacesNeeded: false}) {
+    // TODO(smok): Remove ugly hack for library prefices.
+    if (node.receiver != null && renames[node.receiver] == '') return;
+    super.unparseSendReceiver(node, spacesNeeded: spacesNeeded);
+  }
+
+  unparseFunctionName(Node name) {
+    if (name != null && renames.containsKey(name)) {
+      sb.add(renames[name]);
+    } else {
+      super.unparseFunctionName(name);
+    }
+  }
+}
+
+
+/**
+ * Some elements are not recorded by resolver now,
+ * for example, typedefs or classes which are only
+ * used in signatures, as/is operators or in super clauses
+ * (just to name a few).  Retraverse AST to pick those up.
+ */
+class ReferencedElementCollector extends Visitor {
+  final Compiler compiler;
+  final Element rootElement;
+  final TreeElements treeElements;
+  final newTypedefElementCallback;
+  final newClassElementCallback;
+
+  ReferencedElementCollector(
+      this.compiler,
+      Element rootElement, this.treeElements,
+      this.newTypedefElementCallback, this.newClassElementCallback)
+      : this.rootElement = (rootElement is VariableElement)
+          ? (rootElement as VariableElement).variables : rootElement;
+
+  visitClassNode(ClassNode node) {
+    super.visitClassNode(node);
+    // Temporary hack which should go away once interfaces
+    // and default clauses are out.
+    if (node.defaultClause != null) {
+      // Resolver cannot resolve parameterized default clauses.
+      TypeAnnotation evilCousine = new TypeAnnotation(
+          node.defaultClause.typeName, null);
+      evilCousine.accept(this);
+    }
+  }
+
+  visitNode(Node node) { node.visitChildren(this); }
+
+  visitTypeAnnotation(TypeAnnotation typeAnnotation) {
+    // We call [resolveReturnType] to allow having 'void'.
+    final type = compiler.resolveReturnType(rootElement, typeAnnotation);
+    Element typeElement = type.element;
+    if (typeElement.isTypedef()) newTypedefElementCallback(typeElement);
+    if (typeElement.isClass()) newClassElementCallback(typeElement);
+    typeAnnotation.visitChildren(this);
+  }
+
+  void collect() {
+    compiler.withCurrentElement(rootElement, () {
+      rootElement.parseNode(compiler).accept(this);
+    });
+  }
+}
+
+compareBy(f) => (x, y) => f(x).compareTo(f(y));
+
+List sorted(Iterable l, comparison) {
+  final result = new List.from(l);
+  result.sort(comparison);
+  return result;
+}
+
+compareElements(e0, e1) {
+  int result = compareBy((e) => e.getLibrary().canonicalUri.toString())(e0, e1);
+  if (result != 0) return result;
+  return compareBy((e) => e.position().charOffset)(e0, e1);
+}
+
+List<Element> sortElements(Iterable<Element> elements) =>
+    sorted(elements, compareElements);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart
new file mode 100644
index 0000000..8a8abe4
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart
@@ -0,0 +1,25 @@
+// 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.
+
+library dart_backend;
+
+import '../elements/elements.dart';
+import '../elements/modelx.dart' show SynthesizedConstructorElementX;
+import '../dart2jslib.dart';
+import '../dart_types.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+
+import '../scanner/scannerlib.dart' show StringToken,
+                                         Keyword,
+                                         OPEN_PAREN_INFO,
+                                         CLOSE_PAREN_INFO,
+                                         SEMICOLON_INFO,
+                                         IDENTIFIER_INFO;
+
+part 'backend.dart';
+part 'emitter.dart';
+part 'renamer.dart';
+part 'placeholder_collector.dart';
+part 'utils.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart
new file mode 100644
index 0000000..c074af4
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart
@@ -0,0 +1,24 @@
+// 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.
+
+part of dart_backend;
+
+String emitCode(
+      Unparser unparser,
+      Map<LibraryElement, String> imports,
+      Collection<Node> topLevelNodes,
+      Map<ClassNode, Collection<Node>> classMembers) {
+  imports.forEach((libraryElement, prefix) {
+    unparser.unparseImportTag('${libraryElement.canonicalUri}', prefix);
+  });
+
+  for (final node in topLevelNodes) {
+    if (node is ClassNode) {
+      // TODO(smok): Filter out default constructors here.
+      unparser.unparseClassWithBody(node, classMembers[node]);
+    } else {
+      unparser.unparse(node);
+    }
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart
new file mode 100644
index 0000000..d184663
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart
@@ -0,0 +1,626 @@
+// 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.
+
+part of dart_backend;
+
+class LocalPlaceholder {
+  final String identifier;
+  final Set<Node> nodes;
+  LocalPlaceholder(this.identifier) : nodes = new Set<Node>();
+  int get hashCode => identifier.hashCode;
+  String toString() =>
+      'local_placeholder[id($identifier), nodes($nodes)]';
+}
+
+class FunctionScope {
+  final Set<String> parameterIdentifiers;
+  final Set<LocalPlaceholder> localPlaceholders;
+  FunctionScope()
+      : parameterIdentifiers = new Set<String>(),
+      localPlaceholders = new Set<LocalPlaceholder>();
+  void registerParameter(Identifier node) {
+    parameterIdentifiers.add(node.source.slowToString());
+  }
+}
+
+class ConstructorPlaceholder {
+  final Node node;
+  final DartType type;
+  final bool isRedirectingCall;
+  ConstructorPlaceholder(this.node, this.type)
+      : this.isRedirectingCall = false;
+  // Note: factory redirection is not redirecting call!
+  ConstructorPlaceholder.redirectingCall(this.node)
+      : this.type = null, this.isRedirectingCall = true;
+}
+
+class DeclarationTypePlaceholder {
+  final TypeAnnotation typeNode;
+  final bool requiresVar;
+  DeclarationTypePlaceholder(this.typeNode, this.requiresVar);
+}
+
+class SendVisitor extends ResolvedVisitor {
+  final PlaceholderCollector collector;
+
+  get compiler => collector.compiler;
+
+  SendVisitor(this.collector, TreeElements elements) : super(elements);
+
+  visitOperatorSend(Send node) {}
+  visitForeignSend(Send node) {}
+
+  visitSuperSend(Send node) {
+    Element element = elements[node];
+    if (element != null && element.isConstructor()) {
+      collector.makeRedirectingConstructorPlaceholder(node.selector, element);
+    } else {
+      collector.tryMakeMemberPlaceholder(node.selector);
+    }
+  }
+
+  visitDynamicSend(Send node) {
+    final element = elements[node];
+    if (element == null || !element.isErroneous()) {
+      collector.tryMakeMemberPlaceholder(node.selector);
+    }
+  }
+
+  visitClosureSend(Send node) {
+    final element = elements[node];
+    if (element != null) {
+      collector.tryMakeLocalPlaceholder(element, node.selector);
+    }
+  }
+
+  visitGetterSend(Send node) {
+    final element = elements[node];
+    // element == null means dynamic property access.
+    if (element == null) {
+      collector.tryMakeMemberPlaceholder(node.selector);
+    } else if (element.isErroneous()) {
+      return;
+    } else if (element.isPrefix()) {
+      // Node is prefix part in case of source 'lib.somesetter = 5;'
+      collector.makeNullPlaceholder(node);
+    } else if (Elements.isStaticOrTopLevel(element)) {
+      // Unqualified or prefixed top level or static.
+      collector.makeElementPlaceholder(node.selector, element);
+    } else if (!element.isTopLevel()) {
+      if (element.isInstanceMember()) {
+        collector.tryMakeMemberPlaceholder(node.selector);
+      } else {
+        // May get FunctionExpression here in selector
+        // in case of A(int this.f());
+        if (node.selector is Identifier) {
+          collector.tryMakeLocalPlaceholder(element, node.selector);
+        } else {
+          assert(node.selector is FunctionExpression);
+        }
+      }
+    }
+  }
+
+  visitStaticSend(Send node) {
+    final element = elements[node];
+    if (Elements.isUnresolved(element)
+        || identical(element, compiler.assertMethod)) {
+      return;
+    }
+    if (element.isConstructor() || element.isFactoryConstructor()) {
+      // Rename named constructor in redirection position:
+      // class C { C.named(); C.redirecting() : this.named(); }
+      if (node.receiver is Identifier
+          && node.receiver.asIdentifier().isThis()) {
+        assert(node.selector is Identifier);
+        collector.makeRedirectingConstructorPlaceholder(node.selector, element);
+      }
+      return;
+    }
+    collector.makeElementPlaceholder(node.selector, element);
+    // Another ugly case: <lib prefix>.<top level> is represented as
+    // receiver: lib prefix, selector: top level.
+    if (element.isTopLevel() && node.receiver != null) {
+      assert(elements[node.receiver].isPrefix());
+      // Hack: putting null into map overrides receiver of original node.
+      collector.makeNullPlaceholder(node.receiver);
+    }
+  }
+
+  internalError(String reason, {Node node}) {
+    collector.internalError(reason, node: node);
+  }
+
+  visitTypeReferenceSend(Send node) {
+    collector.makeElementPlaceholder(node.selector, elements[node]);
+  }
+}
+
+class PlaceholderCollector extends Visitor {
+  final Compiler compiler;
+  final Set<String> fixedMemberNames; // member names which cannot be renamed.
+  final Map<Element, ElementAst> elementAsts;
+  final Set<Node> nullNodes;  // Nodes that should not be in output.
+  final Set<Node> unresolvedNodes;
+  final Map<Element, Set<Node>> elementNodes;
+  final Map<FunctionElement, FunctionScope> functionScopes;
+  final Map<LibraryElement, Set<Identifier>> privateNodes;
+  final List<DeclarationTypePlaceholder> declarationTypePlaceholders;
+  final Map<String, Set<Identifier>> memberPlaceholders;
+  final Map<Element, List<ConstructorPlaceholder>> constructorPlaceholders;
+  Map<String, LocalPlaceholder> currentLocalPlaceholders;
+  Element currentElement;
+  FunctionElement topmostEnclosingFunction;
+  TreeElements treeElements;
+
+  LibraryElement get coreLibrary => compiler.coreLibrary;
+  FunctionElement get entryFunction => compiler.mainApp.find(Compiler.MAIN);
+
+  get currentFunctionScope => functionScopes.putIfAbsent(
+      topmostEnclosingFunction, () => new FunctionScope());
+
+  PlaceholderCollector(this.compiler, this.fixedMemberNames, this.elementAsts) :
+      nullNodes = new Set<Node>(),
+      unresolvedNodes = new Set<Node>(),
+      elementNodes = new Map<Element, Set<Node>>(),
+      functionScopes = new Map<FunctionElement, FunctionScope>(),
+      privateNodes = new Map<LibraryElement, Set<Identifier>>(),
+      declarationTypePlaceholders = new List<DeclarationTypePlaceholder>(),
+      memberPlaceholders = new Map<String, Set<Identifier>>(),
+      constructorPlaceholders =
+          new Map<Element, List<ConstructorPlaceholder>>();
+
+  void collectFunctionDeclarationPlaceholders(
+      FunctionElement element, FunctionExpression node) {
+    if (element.isGenerativeConstructor() || element.isFactoryConstructor()) {
+      DartType type = element.getEnclosingClass().thisType.asRaw();
+      makeConstructorPlaceholder(node.name, element, type);
+      Return bodyAsReturn = node.body.asReturn();
+      if (bodyAsReturn != null && bodyAsReturn.isRedirectingFactoryBody) {
+        // Factory redirection.
+        FunctionElement redirectTarget = element.defaultImplementation;
+        assert(redirectTarget != null && redirectTarget != element);
+        type = redirectTarget.getEnclosingClass().thisType.asRaw();
+        makeConstructorPlaceholder(
+            bodyAsReturn.expression, redirectTarget, type);
+      }
+    } else if (Elements.isStaticOrTopLevel(element)) {
+      // Note: this code should only rename private identifiers for class'
+      // fields/getters/setters/methods.  Top-level identifiers are renamed
+      // just to escape conflicts and that should be enough as we shouldn't
+      // be able to resolve private identifiers for other libraries.
+      makeElementPlaceholder(node.name, element);
+    } else if (element.isMember()) {
+      if (node.name is Identifier) {
+        tryMakeMemberPlaceholder(node.name);
+      } else {
+        assert(node.name.asSend().isOperator);
+      }
+    }
+  }
+
+  void collectFieldDeclarationPlaceholders(Element element, Node node) {
+    Identifier name = node is Identifier ? node : node.asSend().selector;
+    if (Elements.isStaticOrTopLevel(element)) {
+      makeElementPlaceholder(name, element);
+    } else if (Elements.isInstanceField(element)) {
+      tryMakeMemberPlaceholder(name);
+    }
+  }
+
+  void collect(Element element) {
+    this.currentElement = element;
+    this.topmostEnclosingFunction = null;
+    final ElementAst elementAst = elementAsts[element];
+    this.treeElements = elementAst.treeElements;
+    Node elementNode = elementAst.ast;
+    if (element is FunctionElement) {
+      collectFunctionDeclarationPlaceholders(element, elementNode);
+    } else if (element is VariableListElement) {
+      VariableDefinitions definitions = elementNode;
+      for (Node definition in definitions.definitions) {
+        final definitionElement = treeElements[definition];
+        // definitionElement == null if variable is actually unused.
+        if (definitionElement == null) continue;
+        collectFieldDeclarationPlaceholders(definitionElement, definition);
+      }
+      makeVarDeclarationTypePlaceholder(definitions);
+    } else {
+      assert(element is ClassElement || element is TypedefElement);
+    }
+    currentLocalPlaceholders = new Map<String, LocalPlaceholder>();
+    compiler.withCurrentElement(element, () {
+      elementNode.accept(this);
+    });
+  }
+
+  void tryMakeLocalPlaceholder(Element element, Identifier node) {
+    bool isOptionalParameter() {
+      FunctionElement function = element.enclosingElement;
+      for (Element parameter in function.functionSignature.optionalParameters) {
+        if (identical(parameter, element)) return true;
+      }
+      return false;
+    }
+
+    // TODO(smok): Maybe we should rename privates as well, their privacy
+    // should not matter if they are local vars.
+    if (node.source.isPrivate()) return;
+    if (element.isParameter() && isOptionalParameter()) {
+      currentFunctionScope.registerParameter(node);
+    } else if (Elements.isLocal(element)) {
+      makeLocalPlaceholder(node);
+    }
+  }
+
+  void tryMakeMemberPlaceholder(Identifier node) {
+    assert(node != null);
+    if (node.source.isPrivate()) return;
+    if (node is Operator) return;
+    final identifier = node.source.slowToString();
+    if (fixedMemberNames.contains(identifier)) return;
+    memberPlaceholders.putIfAbsent(
+        identifier, () => new Set<Identifier>()).add(node);
+  }
+
+  void makeTypePlaceholder(Node node, DartType type) {
+    if (node is Send) {
+      // Prefix.
+      assert(node.receiver is Identifier);
+      assert(node.selector is Identifier);
+      makeNullPlaceholder(node.receiver);
+      node = node.selector;
+    }
+    makeElementPlaceholder(node, type.element);
+  }
+
+  void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) {
+    if (type == null) return;
+    declarationTypePlaceholders.add(
+        new DeclarationTypePlaceholder(type, false));
+  }
+
+  void makeVarDeclarationTypePlaceholder(VariableDefinitions node) {
+    // TODO(smok): Maybe instead of calling this method and
+    // makeDeclaratioTypePlaceholder have type declaration placeholder
+    // collector logic in visitVariableDefinitions when resolver becomes better
+    // and/or catch syntax changes.
+    if (node.type == null) return;
+    Element definitionElement = treeElements[node.definitions.nodes.head];
+    bool requiresVar = !node.modifiers.isFinalOrConst();
+    declarationTypePlaceholders.add(
+        new DeclarationTypePlaceholder(node.type, requiresVar));
+  }
+
+  void makeNullPlaceholder(Node node) {
+    assert(node is Identifier || node is Send);
+    nullNodes.add(node);
+  }
+
+  void makeElementPlaceholder(Node node, Element element) {
+    assert(element != null);
+    if (identical(element, entryFunction)) return;
+    if (identical(element.getLibrary(), coreLibrary)) return;
+    if (element.getLibrary().isPlatformLibrary && !element.isTopLevel()) {
+      return;
+    }
+    if (element == compiler.types.dynamicType.element) {
+      internalError(
+          'Should never make element placeholder for dynamic type element',
+          node: node);
+    }
+    elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node);
+  }
+
+  void makePrivateIdentifier(Identifier node) {
+    assert(node != null);
+    privateNodes.putIfAbsent(
+        currentElement.getLibrary(), () => new Set<Identifier>()).add(node);
+  }
+
+  void makeUnresolvedPlaceholder(Node node) {
+    unresolvedNodes.add(node);
+  }
+
+  void makeLocalPlaceholder(Identifier identifier) {
+    LocalPlaceholder getLocalPlaceholder() {
+      String name = identifier.source.slowToString();
+      return currentLocalPlaceholders.putIfAbsent(name, () {
+        LocalPlaceholder localPlaceholder = new LocalPlaceholder(name);
+        currentFunctionScope.localPlaceholders.add(localPlaceholder);
+        return localPlaceholder;
+      });
+    }
+
+    getLocalPlaceholder().nodes.add(identifier);
+  }
+
+  void makeConstructorPlaceholder(Node node, Element element, DartType type) {
+    assert(type != null);
+    constructorPlaceholders
+        .putIfAbsent(element, () => <ConstructorPlaceholder>[])
+            .add(new ConstructorPlaceholder(node, type));
+  }
+  void makeRedirectingConstructorPlaceholder(Node node, Element element) {
+    constructorPlaceholders
+        .putIfAbsent(element, () => <ConstructorPlaceholder>[])
+            .add(new ConstructorPlaceholder.redirectingCall(node));
+  }
+
+  void internalError(String reason, {Node node}) {
+    compiler.cancel(reason, node: node);
+  }
+
+  void unreachable() { internalError('Unreachable case'); }
+
+  visit(Node node) => (node == null) ? null : node.accept(this);
+
+  visitNode(Node node) { node.visitChildren(this); }  // We must go deeper.
+
+  visitNewExpression(NewExpression node) {
+    Send send = node.send;
+    InterfaceType type = treeElements.getType(node);
+    assert(type != null);
+    Element constructor = treeElements[send];
+    assert(constructor != null);
+    assert(send.receiver == null);
+    if (!Elements.isErroneousElement(constructor)) {
+      makeConstructorPlaceholder(node.send.selector, constructor, type);
+      // TODO(smok): Should this be in visitNamedArgument?
+      // Field names can be exposed as names of optional arguments, e.g.
+      // class C {
+      //   final field;
+      //   C([this.field]);
+      // }
+      // Do not forget to rename them as well.
+      FunctionElement constructorFunction = constructor;
+      Link<Element> optionalParameters =
+          constructorFunction.functionSignature.optionalParameters;
+      for (final argument in send.argumentsNode) {
+        NamedArgument named = argument.asNamedArgument();
+        if (named == null) continue;
+        Identifier name = named.name;
+        String nameAsString = name.source.slowToString();
+        for (final parameter in optionalParameters) {
+          if (identical(parameter.kind, ElementKind.FIELD_PARAMETER)) {
+            if (parameter.name.slowToString() == nameAsString) {
+              tryMakeMemberPlaceholder(name);
+              break;
+            }
+          }
+        }
+      }
+    } else {
+      makeUnresolvedPlaceholder(node.send.selector);
+    }
+    visit(node.send.argumentsNode);
+  }
+
+  visitSend(Send send) {
+    new SendVisitor(this, treeElements).visitSend(send);
+    send.visitChildren(this);
+  }
+
+  visitSendSet(SendSet send) {
+    Element element = treeElements[send];
+    if (Elements.isErroneousElement(element)) {
+      // Complicated case: constructs like receiver.selector++ can resolve
+      // to ErroneousElement.  Fortunately, receiver.selector still
+      // can be resoved via treeElements[send.selector], that's all
+      // that is needed to rename the construct properly.
+      element = treeElements[send.selector];
+    }
+    if (element == null) {
+      if (send.receiver != null) tryMakeMemberPlaceholder(send.selector);
+    } else if (!element.isErroneous()) {
+      if (Elements.isStaticOrTopLevel(element)) {
+        // TODO(smok): Worth investigating why sometimes we get getter/setter
+        // here and sometimes abstract field.
+        assert(element.isClass() || element is VariableElement ||
+               element.isAccessor() || element.isAbstractField() ||
+               element.isFunction() || element.isTypedef() ||
+               element is TypeVariableElement);
+        makeElementPlaceholder(send.selector, element);
+      } else {
+        assert(send.selector is Identifier);
+        if (Elements.isInstanceField(element)) {
+          tryMakeMemberPlaceholder(send.selector);
+        } else {
+          tryMakeLocalPlaceholder(element, send.selector);
+        }
+      }
+    }
+    send.visitChildren(this);
+  }
+
+  visitIdentifier(Identifier identifier) {
+    if (identifier.source.isPrivate()) makePrivateIdentifier(identifier);
+  }
+
+  static bool isPlainTypeName(TypeAnnotation typeAnnotation) {
+    if (typeAnnotation.typeName is !Identifier) return false;
+    if (typeAnnotation.typeArguments == null) return true;
+    if (typeAnnotation.typeArguments.isEmpty) return true;
+    return false;
+  }
+
+  static bool isDynamicType(TypeAnnotation typeAnnotation) {
+    if (!isPlainTypeName(typeAnnotation)) return false;
+    String name = typeAnnotation.typeName.asIdentifier().source.slowToString();
+    // TODO(aprelev@gmail.com): Removed deprecated Dynamic keyword support.
+    return name == 'Dynamic' || name == 'dynamic';
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    // Poor man generic variables resolution.
+    // TODO(antonm): get rid of it once resolver can deal with it.
+    TypeDeclarationElement typeDeclarationElement;
+    if (currentElement is TypeDeclarationElement) {
+      typeDeclarationElement = currentElement;
+    } else {
+      typeDeclarationElement = currentElement.getEnclosingClass();
+    }
+    if (typeDeclarationElement != null && isPlainTypeName(node)
+        && tryResolveAndCollectTypeVariable(
+               typeDeclarationElement, node.typeName)) {
+      return;
+    }
+    // We call [resolveReturnType] to allow having 'void'.
+    final type = compiler.resolveReturnType(currentElement, node);
+    if (type is InterfaceType || type is TypedefType) {
+      // TODO(antonm): is there a better way to detect unresolved types?
+      // Corner case: dart:core type with a prefix.
+      // Most probably there are some additional problems with
+      // coreLibPrefix.topLevels.
+      if (!identical(type.element, compiler.types.dynamicType.element)) {
+        makeTypePlaceholder(node.typeName, type);
+      } else {
+        if (!isDynamicType(node)) makeUnresolvedPlaceholder(node.typeName);
+      }
+    }
+    // Visit only type arguments, otherwise in case of lib.Class type
+    // annotation typeName is Send and we go to visitGetterSend, as a result
+    // "Class" is added to member placeholders.
+    visit(node.typeArguments);
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    // Collect only local placeholders.
+    for (Node definition in node.definitions.nodes) {
+      Element definitionElement = treeElements[definition];
+      // definitionElement may be null if we're inside variable definitions
+      // of a function that is a parameter of another function.
+      // TODO(smok): Fix this when resolver correctly deals with
+      // such cases.
+      if (definitionElement == null) continue;
+      if (definition is Send) {
+        // May get FunctionExpression here in definition.selector
+        // in case of A(int this.f());
+        if (definition.selector is Identifier) {
+          if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) {
+            tryMakeMemberPlaceholder(definition.selector);
+          } else {
+            tryMakeLocalPlaceholder(definitionElement, definition.selector);
+          }
+        } else {
+          assert(definition.selector is FunctionExpression);
+          if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) {
+            tryMakeMemberPlaceholder(
+                definition.selector.asFunctionExpression().name);
+          }
+        }
+      } else if (definition is Identifier) {
+        tryMakeLocalPlaceholder(definitionElement, definition);
+      } else if (definition is FunctionExpression) {
+        // Skip, it will be processed in visitFunctionExpression.
+      } else {
+        internalError('Unexpected definition structure $definition');
+      }
+    }
+    node.visitChildren(this);
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    bool isKeyword(Identifier id) =>
+        id != null && Keyword.keywords[id.source.slowToString()] != null;
+
+    Element element = treeElements[node];
+    // May get null here in case of A(int this.f());
+    if (element != null) {
+      // Rename only local functions.
+      if (topmostEnclosingFunction == null) {
+        topmostEnclosingFunction = element;
+      }
+      if (!identical(element, currentElement)) {
+        if (node.name != null) {
+          assert(node.name is Identifier);
+          tryMakeLocalPlaceholder(element, node.name);
+        }
+      }
+    }
+    node.visitChildren(this);
+    // Make sure we don't omit return type of methods which names are
+    // identifiers, because the following works fine:
+    // int interface() => 1;
+    // But omitting 'int' makes VM unhappy.
+    // TODO(smok): Remove it when http://dartbug.com/5278 is fixed.
+    if (node.name == null || !isKeyword(node.name.asIdentifier())) {
+      makeOmitDeclarationTypePlaceholder(node.returnType);
+    }
+    collectFunctionParameters(node.parameters);
+  }
+
+  void collectFunctionParameters(NodeList parameters) {
+    if (parameters == null) return;
+    for (Node parameter in parameters.nodes) {
+      if (parameter is NodeList) {
+        // Optional parameter list.
+        collectFunctionParameters(parameter);
+      } else {
+        assert(parameter is VariableDefinitions);
+        makeOmitDeclarationTypePlaceholder(
+            parameter.asVariableDefinitions().type);
+      }
+    }
+  }
+
+  visitClassNode(ClassNode node) {
+    ClassElement classElement = currentElement;
+    makeElementPlaceholder(node.name, classElement);
+    node.visitChildren(this);
+    if (node.defaultClause != null) {
+      // Can't just visit class node's default clause because of the bug in the
+      // resolver, it just crashes when it meets type variable.
+      DartType defaultType = classElement.defaultClass;
+      assert(defaultType != null);
+      makeTypePlaceholder(node.defaultClause.typeName, defaultType);
+      visit(node.defaultClause.typeArguments);
+    }
+  }
+
+  bool tryResolveAndCollectTypeVariable(
+      TypeDeclarationElement typeDeclaration, Identifier name) {
+    // Hack for case when interface and default class are in different
+    // libraries, try to resolve type variable to default class type arg.
+    // Example:
+    // lib1: interface I<K> default C<K> {...}
+    // lib2: class C<K> {...}
+    if (typeDeclaration is ClassElement
+        && (typeDeclaration as ClassElement).defaultClass != null) {
+      typeDeclaration = (typeDeclaration as ClassElement).defaultClass.element;
+    }
+    // Another poor man type resolution.
+    // Find this variable in enclosing type declaration parameters.
+    for (DartType type in typeDeclaration.typeVariables) {
+      if (type.name.slowToString() == name.source.slowToString()) {
+        makeTypePlaceholder(name, type);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    assert(currentElement is TypedefElement || currentElement is ClassElement);
+    tryResolveAndCollectTypeVariable(currentElement, node.name);
+    node.visitChildren(this);
+  }
+
+  visitTypedef(Typedef node) {
+    assert(currentElement is TypedefElement);
+    makeElementPlaceholder(node.name, currentElement);
+    node.visitChildren(this);
+    makeOmitDeclarationTypePlaceholder(node.returnType);
+    collectFunctionParameters(node.formals);
+  }
+
+  visitBlock(Block node) {
+    for (Node statement in node.statements.nodes) {
+      if (statement is VariableDefinitions) {
+        makeVarDeclarationTypePlaceholder(statement);
+      }
+    }
+    node.visitChildren(this);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart
new file mode 100644
index 0000000..886d39e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart
@@ -0,0 +1,359 @@
+// 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.
+
+part of dart_backend;
+
+Function get _compareNodes =>
+    compareBy((n) => n.getBeginToken().charOffset);
+
+typedef String _Renamer(Renamable renamable);
+abstract class Renamable {
+  const int RENAMABLE_TYPE_ELEMENT = 1;
+  const int RENAMABLE_TYPE_MEMBER = 2;
+  const int RENAMABLE_TYPE_LOCAL = 3;
+
+  final Set<Node> nodes;
+  final _Renamer renamer;
+
+  Renamable(this.nodes, this.renamer);
+  int compareTo(Renamable other) {
+    int nodesDiff = other.nodes.length.compareTo(this.nodes.length);
+    if (nodesDiff != 0) return nodesDiff;
+    int typeDiff = this.getTypeId().compareTo(other.getTypeId());
+    return typeDiff != 0 ? typeDiff : compareInternals(other);
+  }
+
+  int compareInternals(Renamable other);
+  int getTypeId();
+
+  String rename() => renamer(this);
+}
+
+class ElementRenamable extends Renamable {
+  final Element element;
+
+  ElementRenamable(this.element, Set<Node> nodes, _Renamer renamer)
+      : super(nodes, renamer);
+
+  int compareInternals(ElementRenamable other) =>
+      compareElements(this.element, other.element);
+  int getTypeId() => RENAMABLE_TYPE_ELEMENT;
+}
+
+class MemberRenamable extends Renamable {
+  final String identifier;
+  MemberRenamable(this.identifier, Set<Node> nodes, _Renamer renamer)
+      : super(nodes, renamer);
+  int compareInternals(MemberRenamable other) =>
+      this.identifier.compareTo(other.identifier);
+  int getTypeId() => RENAMABLE_TYPE_MEMBER;
+}
+
+class LocalRenamable extends Renamable {
+  LocalRenamable(Set<Node> nodes, _Renamer renamer) : super(nodes, renamer);
+  int compareInternals(LocalRenamable other) =>
+      _compareNodes(sorted(this.nodes, _compareNodes)[0],
+          sorted(other.nodes, _compareNodes)[0]);
+  int getTypeId() => RENAMABLE_TYPE_LOCAL;
+}
+
+/**
+ * Renames only top-level elements that would let to ambiguity if not renamed.
+ */
+void renamePlaceholders(
+    Compiler compiler,
+    PlaceholderCollector placeholderCollector,
+    Map<Node, String> renames,
+    Map<LibraryElement, String> imports,
+    Set<String> fixedMemberNames,
+    bool cutDeclarationTypes) {
+  final Map<LibraryElement, Map<String, String>> renamed
+      = new Map<LibraryElement, Map<String, String>>();
+
+  renameNodes(Collection<Node> nodes, renamer) {
+    for (Node node in sorted(nodes, _compareNodes)) {
+      renames[node] = renamer(node);
+    }
+  }
+
+  sortedForEach(Map<Element, dynamic> map, f) {
+    for (Element element in sortElements(map.keys)) {
+      f(element, map[element]);
+    }
+  }
+
+  String renameType(DartType type, Function renameElement) {
+    // TODO(smok): Do not rename type if it is in platform library or
+    // js-helpers.
+    StringBuffer result = new StringBuffer(renameElement(type.element));
+    if (type is InterfaceType) {
+      if (!type.isRaw) {
+        result.add('<');
+        Link<DartType> argumentsLink = type.typeArguments;
+        result.add(renameType(argumentsLink.head, renameElement));
+        for (Link<DartType> link = argumentsLink.tail; !link.isEmpty;
+             link = link.tail) {
+          result.add(',');
+          result.add(renameType(link.head, renameElement));
+        }
+        result.add('>');
+      }
+    }
+    return result.toString();
+  }
+
+  String renameConstructor(Element element, ConstructorPlaceholder placeholder,
+      Function renameString, Function renameElement) {
+    assert(element.isConstructor());
+    StringBuffer result = new StringBuffer();
+    String name = element.name.slowToString();
+    if (element.name != element.getEnclosingClass().name) {
+      // Named constructor or factory. Is there a more reliable way to check
+      // this case?
+      if (!placeholder.isRedirectingCall) {
+        result.add(renameType(placeholder.type, renameElement));
+        result.add('.');
+      }
+      String prefix = '${element.getEnclosingClass().name.slowToString()}\$';
+      if (!name.startsWith(prefix)) {
+        // Factory for another interface (that is going away soon).
+        compiler.internalErrorOnElement(element,
+            "Factory constructors for external interfaces are not supported.");
+      }
+      name = name.substring(prefix.length);
+      if (!element.getLibrary().isPlatformLibrary) {
+        name = renameString(element.getLibrary(), name);
+      }
+      result.add(name);
+    } else {
+      assert(!placeholder.isRedirectingCall);
+      result.add(renameType(placeholder.type, renameElement));
+    }
+    return result.toString();
+  }
+
+  Function makeElementRenamer(rename, generateUniqueName) => (element) {
+    assert(Elements.isErroneousElement(element) ||
+           Elements.isStaticOrTopLevel(element) ||
+           element is TypeVariableElement);
+    // TODO(smok): We may want to reuse class static field and method names.
+    String originalName = element.name.slowToString();
+    LibraryElement library = element.getLibrary();
+    if (identical(element.getLibrary(), compiler.coreLibrary)) {
+      return originalName;
+    }
+    if (library.isPlatformLibrary && !library.isInternalLibrary) {
+      assert(element.isTopLevel());
+      final prefix =
+          imports.putIfAbsent(library, () => generateUniqueName('p'));
+      return '$prefix.$originalName';
+    }
+
+    return rename(library, originalName);
+  };
+
+  Function makeRenamer(generateUniqueName) =>
+      (library, originalName) =>
+          renamed.putIfAbsent(library, () => {})
+              .putIfAbsent(originalName,
+                  () => generateUniqueName(originalName));
+
+  // Renamer function that takes library and original name and returns a new
+  // name for given identifier.
+  Function rename;
+  Function renameElement;
+  // A function that takes original identifier name and generates a new unique
+  // identifier.
+  Function generateUniqueName;
+  if (compiler.enableMinification) {
+    MinifyingGenerator generator = new MinifyingGenerator();
+    Set<String> forbiddenIdentifiers = new Set<String>.from(['main']);
+    forbiddenIdentifiers.addAll(Keyword.keywords.keys);
+    forbiddenIdentifiers.addAll(fixedMemberNames);
+    generateUniqueName = (_) =>
+        generator.generate(forbiddenIdentifiers.contains);
+    rename = makeRenamer(generateUniqueName);
+    renameElement = makeElementRenamer(rename, generateUniqueName);
+
+    Set<String> allParameterIdentifiers = new Set<String>();
+    for (var functionScope in placeholderCollector.functionScopes.values) {
+      allParameterIdentifiers.addAll(functionScope.parameterIdentifiers);
+    }
+    // Build a sorted (by usage) list of local nodes that will be renamed to
+    // the same identifier. So the top-used local variables in all functions
+    // will be renamed first and will all share the same new identifier.
+    List<Set<Node>> allSortedLocals = new List<Set<Node>>();
+    for (var functionScope in placeholderCollector.functionScopes.values) {
+      // Add current sorted local identifiers to the whole sorted list
+      // of all local identifiers for all functions.
+      List<LocalPlaceholder> currentSortedPlaceholders =
+          sorted(functionScope.localPlaceholders,
+              compareBy((LocalPlaceholder ph) => -ph.nodes.length));
+      List<Set<Node>> currentSortedNodes =
+          currentSortedPlaceholders.map((ph) => ph.nodes).toList();
+      // Make room in all sorted locals list for new stuff.
+      while (currentSortedNodes.length > allSortedLocals.length) {
+        allSortedLocals.add(new Set<Node>());
+      }
+      for (int i = 0; i < currentSortedNodes.length; i++) {
+        allSortedLocals[i].addAll(currentSortedNodes[i]);
+      }
+    }
+
+    // Rename elements, members and locals together based on their usage count,
+    // otherwise when we rename elements first there will be no good identifiers
+    // left for members even if they are used often.
+    String elementRenamer(ElementRenamable elementRenamable) =>
+        renameElement(elementRenamable.element);
+    String memberRenamer(MemberRenamable memberRenamable) =>
+        generator.generate(forbiddenIdentifiers.contains);
+    String localRenamer(LocalRenamable localRenamable) =>
+        generator.generate((name) =>
+            allParameterIdentifiers.contains(name)
+            || forbiddenIdentifiers.contains(name));
+    List<Renamable> renamables = [];
+    placeholderCollector.elementNodes.forEach(
+        (Element element, Set<Node> nodes) {
+      renamables.add(new ElementRenamable(element, nodes, elementRenamer));
+    });
+    placeholderCollector.memberPlaceholders.forEach(
+        (String memberName, Set<Identifier> identifiers) {
+      renamables.add(
+          new MemberRenamable(memberName, identifiers, memberRenamer));
+    });
+    for (Set<Node> localIdentifiers in allSortedLocals) {
+      renamables.add(new LocalRenamable(localIdentifiers, localRenamer));
+    }
+    renamables.sort((Renamable renamable1, Renamable renamable2) =>
+        renamable1.compareTo(renamable2));
+    for (Renamable renamable in renamables) {
+      String newName = renamable.rename();
+      renameNodes(renamable.nodes, (_) => newName);
+    }
+  } else {
+    // Never rename anything to 'main'.
+    final usedTopLevelOrMemberIdentifiers = new Set<String>();
+    usedTopLevelOrMemberIdentifiers.add('main');
+    usedTopLevelOrMemberIdentifiers.addAll(fixedMemberNames);
+    generateUniqueName = (originalName) {
+      String newName = conservativeGenerator(
+          originalName, usedTopLevelOrMemberIdentifiers.contains);
+      usedTopLevelOrMemberIdentifiers.add(newName);
+      return newName;
+    };
+    rename = makeRenamer(generateUniqueName);
+    renameElement = makeElementRenamer(rename, generateUniqueName);
+    // Rename elements.
+    sortedForEach(placeholderCollector.elementNodes,
+        (Element element, Set<Node> nodes) {
+      renameNodes(nodes, (_) => renameElement(element));
+    });
+
+    // Rename locals.
+    sortedForEach(placeholderCollector.functionScopes,
+        (functionElement, functionScope) {
+      Set<LocalPlaceholder> placeholders = functionScope.localPlaceholders;
+      Set<String> memberIdentifiers = new Set<String>();
+      if (functionElement.getEnclosingClass() != null) {
+        functionElement.getEnclosingClass().forEachMember(
+            (enclosingClass, member) {
+              memberIdentifiers.add(member.name.slowToString());
+            });
+      }
+      Set<String> usedLocalIdentifiers = new Set<String>();
+      for (LocalPlaceholder placeholder in placeholders) {
+        String nextId =
+            conservativeGenerator(placeholder.identifier, (name) =>
+                functionScope.parameterIdentifiers.contains(name)
+                || usedTopLevelOrMemberIdentifiers.contains(name)
+                || usedLocalIdentifiers.contains(name)
+                || memberIdentifiers.contains(name));
+        usedLocalIdentifiers.add(nextId);
+        renameNodes(placeholder.nodes, (_) => nextId);
+      }
+    });
+
+    final usedMemberIdentifiers = new Set<String>.from(fixedMemberNames);
+    // Do not rename members to top-levels, that allows to avoid renaming
+    // members to constructors.
+    usedMemberIdentifiers.addAll(usedTopLevelOrMemberIdentifiers);
+    placeholderCollector.memberPlaceholders.forEach((identifier, nodes) {
+      String newIdentifier = conservativeGenerator(
+          identifier, usedMemberIdentifiers.contains);
+      renameNodes(nodes, (_) => newIdentifier);
+    });
+  }
+
+  // Rename constructors.
+  sortedForEach(placeholderCollector.constructorPlaceholders,
+      (Element constructor, List<ConstructorPlaceholder> placeholders) {
+        for (ConstructorPlaceholder ph in placeholders) {
+          renames[ph.node] =
+              renameConstructor(constructor, ph, rename, renameElement);
+        }
+  });
+  sortedForEach(placeholderCollector.privateNodes, (library, nodes) {
+    renameNodes(nodes, (node) => rename(library, node.source.slowToString()));
+  });
+  renameNodes(placeholderCollector.unresolvedNodes,
+      (_) => generateUniqueName('Unresolved'));
+  renameNodes(placeholderCollector.nullNodes, (_) => '');
+  if (cutDeclarationTypes) {
+    for (DeclarationTypePlaceholder placeholder in
+         placeholderCollector.declarationTypePlaceholders) {
+      renames[placeholder.typeNode] = placeholder.requiresVar ? 'var' : '';
+    }
+  }
+}
+
+/** Always tries to return original identifier name unless it is forbidden. */
+String conservativeGenerator(
+    String originalName, bool isForbidden(String name)) {
+  String newName = originalName;
+  while (isForbidden(newName)) {
+    newName = 'p_$newName';
+  }
+  return newName;
+}
+
+/** Always tries to generate the most compact identifier. */
+class MinifyingGenerator {
+  static const String firstCharAlphabet =
+      r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+  static const String otherCharsAlphabet =
+      r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$';
+  int nextIdIndex;
+
+  MinifyingGenerator() : nextIdIndex = 0;
+
+  String generate(bool isForbidden(String name)) {
+    String newName;
+    do {
+      newName = getNextId();
+    } while(isForbidden(newName));
+    return newName;
+  }
+
+  /**
+   * Generates next mini ID with current index and alphabet.
+   * Advances current index.
+   * In other words, it converts index to visual representation
+   * as if digits are given characters.
+   */
+  String getNextId() {
+    // It's like converting index in decimal to [chars] radix.
+    int index = nextIdIndex++;
+    StringBuffer resultBuilder = new StringBuffer();
+    if (index < firstCharAlphabet.length) return firstCharAlphabet[index];
+    resultBuilder.add(firstCharAlphabet[index % firstCharAlphabet.length]);
+    index ~/= firstCharAlphabet.length;
+    int length = otherCharsAlphabet.length;
+    while (index >= length) {
+      resultBuilder.add(otherCharsAlphabet[index % length]);
+      index ~/= length;
+    }
+    resultBuilder.add(otherCharsAlphabet[index]);
+    return resultBuilder.toString();
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart
new file mode 100644
index 0000000..c30a2cf
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart
@@ -0,0 +1,292 @@
+// 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.
+
+part of dart_backend;
+
+class CloningVisitor implements Visitor<Node> {
+  final TreeElements originalTreeElements;
+  final TreeElementMapping cloneTreeElements;
+
+  CloningVisitor(originalTreeElements)
+      : cloneTreeElements =
+            new TreeElementMapping(originalTreeElements.currentElement),
+        this.originalTreeElements = originalTreeElements;
+
+  visit(Node node) {
+    if (node == null) return null;
+    final clone = node.accept(this);
+
+    final originalElement = originalTreeElements[node];
+    if (originalElement != null) cloneTreeElements[clone] = originalElement;
+
+    final originalType = originalTreeElements.getType(node);
+    if (originalType != null) cloneTreeElements.setType(clone, originalType);
+    return clone;
+  }
+
+  visitBlock(Block node) => new Block(visit(node.statements));
+
+  visitBreakStatement(BreakStatement node) => new BreakStatement(
+      visit(node.target), node.keywordToken, node.semicolonToken);
+
+  visitCascade(Cascade node) => new Cascade(visit(node.expression));
+
+  visitCascadeReceiver(CascadeReceiver node) => new CascadeReceiver(
+      visit(node.expression), node.cascadeOperator);
+
+  visitCaseMatch(CaseMatch node) => new CaseMatch(
+      node.caseKeyword, visit(node.expression), node.colonToken);
+
+  visitCatchBlock(CatchBlock node) => new CatchBlock(
+      visit(node.type), visit(node.formals), visit(node.block),
+      node.onKeyword, node.catchKeyword);
+
+  visitClassNode(ClassNode node) => new ClassNode(
+      visit(node.modifiers), visit(node.name), visit(node.typeParameters),
+      visit(node.superclass), visit(node.interfaces), visit(node.defaultClause),
+      node.beginToken, node.extendsKeyword, visit(node.body), node.endToken);
+
+  visitConditional(Conditional node) => new Conditional(
+      visit(node.condition), visit(node.thenExpression),
+      visit(node.elseExpression), node.questionToken, node.colonToken);
+
+  visitContinueStatement(ContinueStatement node) => new ContinueStatement(
+      visit(node.target), node.keywordToken, node.semicolonToken);
+
+  visitDoWhile(DoWhile node) => new DoWhile(
+      visit(node.body), visit(node.condition),
+      node.doKeyword, node.whileKeyword, node.endToken);
+
+  visitEmptyStatement(EmptyStatement node) => new EmptyStatement(
+      node.semicolonToken);
+
+  visitExpressionStatement(ExpressionStatement node) => new ExpressionStatement(
+      visit(node.expression), node.endToken);
+
+  visitFor(For node) => new For(
+      visit(node.initializer), visit(node.conditionStatement),
+      visit(node.update), visit(node.body), node.forToken);
+
+  visitForIn(ForIn node) => new ForIn(
+      visit(node.declaredIdentifier), visit(node.expression), visit(node.body),
+      node.forToken, node.inToken);
+
+  visitFunctionDeclaration(FunctionDeclaration node) => new FunctionDeclaration(
+      visit(node.function));
+
+  rewriteFunctionExpression(FunctionExpression node, Statement body) =>
+      new FunctionExpression(
+          visit(node.name), visit(node.parameters), body,
+          visit(node.returnType), visit(node.modifiers),
+          visit(node.initializers), node.getOrSet);
+
+  visitFunctionExpression(FunctionExpression node) =>
+      rewriteFunctionExpression(node, visit(node.body));
+
+  visitIdentifier(Identifier node) => new Identifier(node.token);
+
+  visitIf(If node) => new If(
+      visit(node.condition), visit(node.thenPart), visit(node.elsePart),
+      node.ifToken, node.elseToken);
+
+  visitLabel(Label node) => new Label(visit(node.identifier), node.colonToken);
+
+  visitLabeledStatement(LabeledStatement node) => new LabeledStatement(
+      visit(node.labels), visit(node.statement));
+
+  visitLiteralBool(LiteralBool node) => new LiteralBool(
+      node.token, node.handler);
+
+  visitLiteralDouble(LiteralDouble node) => new LiteralDouble(
+      node.token, node.handler);
+
+  visitLiteralInt(LiteralInt node) => new LiteralInt(node.token, node.handler);
+
+  visitLiteralList(LiteralList node) => new LiteralList(
+      visit(node.typeArguments), visit(node.elements), node.constKeyword);
+
+  visitLiteralMap(LiteralMap node) => new LiteralMap(
+      visit(node.typeArguments), visit(node.entries), node.constKeyword);
+
+  visitLiteralMapEntry(LiteralMapEntry node) => new LiteralMapEntry(
+      visit(node.key), node.colonToken, visit(node.value));
+
+  visitLiteralNull(LiteralNull node) => new LiteralNull(node.token);
+
+  visitLiteralString(LiteralString node) => new LiteralString(
+      node.token, node.dartString);
+
+  visitMixinApplication(MixinApplication node) => new MixinApplication(
+      visit(node.superclass), visit(node.mixins));
+
+  visitNamedMixinApplication(NamedMixinApplication node) =>
+      new NamedMixinApplication(visit(node.name),
+                                visit(node.typeParameters),
+                                visit(node.modifiers),
+                                visit(node.mixinApplication),
+                                visit(node.interfaces),
+                                node.typedefKeyword,
+                                node.endToken);
+
+  visitModifiers(Modifiers node) => new Modifiers(visit(node.nodes));
+
+  visitNamedArgument(NamedArgument node) => new NamedArgument(
+      visit(node.name), node.colonToken, visit(node.expression));
+
+  visitNewExpression(NewExpression node) => new NewExpression(
+      node.newToken, visit(node.send));
+
+  rewriteNodeList(NodeList node, Link link) =>
+      new NodeList(node.beginToken, link, node.endToken, node.delimiter);
+
+  visitNodeList(NodeList node) {
+    // Special case for classes which exist in hierarchy, but not
+    // in the visitor.
+    if (node is Prefix) {
+      return node.nodes.isEmpty ?
+          new Prefix() : new Prefix.singleton(visit(node.nodes.head));
+    }
+    if (node is Postfix) {
+      return node.nodes.isEmpty ?
+          new Postfix() : new Postfix.singleton(visit(node.nodes.head));
+    }
+    LinkBuilder<Node> builder = new LinkBuilder<Node>();
+    for (Node n in node.nodes) {
+      builder.addLast(visit(n));
+    }
+    return rewriteNodeList(node, builder.toLink());
+  }
+
+  visitOperator(Operator node) => new Operator(node.token);
+
+  visitParenthesizedExpression(ParenthesizedExpression node) =>
+      new ParenthesizedExpression(visit(node.expression), node.beginToken);
+
+  visitReturn(Return node) => new Return(
+      node.beginToken, node.endToken, visit(node.expression));
+
+  visitScriptTag(ScriptTag node) => new ScriptTag(
+      visit(node.tag), visit(node.argument),
+      visit(node.prefixIdentifier), visit(node.prefix),
+      node.beginToken, node.endToken);
+
+  visitSend(Send node) => new Send(
+      visit(node.receiver), visit(node.selector), visit(node.argumentsNode));
+
+  visitSendSet(SendSet node) => new SendSet(
+      visit(node.receiver), visit(node.selector),
+      visit(node.assignmentOperator), visit(node.argumentsNode));
+
+  visitStringInterpolation(StringInterpolation node) =>
+      new StringInterpolation(visit(node.string), visit(node.parts));
+
+  visitStringInterpolationPart(StringInterpolationPart node) =>
+      new StringInterpolationPart(visit(node.expression), visit(node.string));
+
+  visitStringJuxtaposition(StringJuxtaposition node) =>
+      new StringJuxtaposition(visit(node.first), visit(node.second));
+
+  visitSwitchCase(SwitchCase node) => new SwitchCase(
+      visit(node.labelsAndCases), node.defaultKeyword, visit(node.statements),
+      node.startToken);
+
+  visitSwitchStatement(SwitchStatement node) => new SwitchStatement(
+      visit(node.parenthesizedExpression), visit(node.cases),
+      node.switchKeyword);
+
+  visitThrow(Throw node) => new Throw(
+      visit(node.expression), node.throwToken, node.endToken);
+
+  visitTryStatement(TryStatement node) => new TryStatement(
+      visit(node.tryBlock), visit(node.catchBlocks), visit(node.finallyBlock),
+      node.tryKeyword, node.finallyKeyword);
+
+  visitTypeAnnotation(TypeAnnotation node) => new TypeAnnotation(
+      visit(node.typeName), visit(node.typeArguments));
+
+  visitTypedef(Typedef node) => new Typedef(
+      visit(node.returnType), visit(node.name), visit(node.typeParameters),
+      visit(node.formals), node.typedefKeyword, node.endToken);
+
+  visitTypeVariable(TypeVariable node) => new TypeVariable(
+      visit(node.name), visit(node.bound));
+
+  visitVariableDefinitions(VariableDefinitions node) => new VariableDefinitions(
+      visit(node.type), visit(node.modifiers), visit(node.definitions));
+
+  visitWhile(While node) => new While(
+      visit(node.condition), visit(node.body), node.whileKeyword);
+
+  Node visitNode(Node node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitCombinator(Combinator node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitExport(Export node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitExpression(Expression node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitGotoStatement(GotoStatement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitImport(Import node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLibraryDependency(LibraryTag node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLibraryName(LibraryName node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLibraryTag(LibraryTag node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLiteral(Literal node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLoop(Loop node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPart(Part node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPartOf(PartOf node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPostfix(Postfix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPrefix(Prefix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitStatement(Statement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitStringNode(StringNode node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  unimplemented(String message, {Node node}) {
+    throw message;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_types.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_types.dart
new file mode 100644
index 0000000..7c78bd5
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/dart_types.dart
@@ -0,0 +1,806 @@
+// 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.
+
+library dart_types;
+
+import 'dart2jslib.dart' show Compiler, invariant, Script, Message;
+import 'elements/modelx.dart' show VoidElementX, LibraryElementX;
+import 'elements/elements.dart';
+import 'scanner/scannerlib.dart' show SourceString;
+import 'util/util.dart' show Link, LinkBuilder;
+
+class TypeKind {
+  final String id;
+
+  const TypeKind(String this.id);
+
+  static const TypeKind FUNCTION = const TypeKind('function');
+  static const TypeKind INTERFACE = const TypeKind('interface');
+  static const TypeKind STATEMENT = const TypeKind('statement');
+  static const TypeKind TYPEDEF = const TypeKind('typedef');
+  static const TypeKind TYPE_VARIABLE = const TypeKind('type variable');
+  static const TypeKind MALFORMED_TYPE = const TypeKind('malformed');
+  static const TypeKind VOID = const TypeKind('void');
+
+  String toString() => id;
+}
+
+abstract class DartType {
+  SourceString get name;
+
+  TypeKind get kind;
+
+  const DartType();
+
+  /**
+   * Returns the [Element] which declared this type.
+   *
+   * This can be [ClassElement] for classes, [TypedefElement] for typedefs,
+   * [TypeVariableElement] for type variables and [FunctionElement] for
+   * function types.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  Element get element;
+
+  /**
+   * Performs the substitution [: [arguments[i]/parameters[i]]this :].
+   *
+   * The notation is known from this lambda calculus rule:
+   *
+   *     (lambda x.e0)e1 -> [e1/x]e0.
+   *
+   * See [TypeVariableType] for a motivation for this method.
+   *
+   * Invariant: There must be the same number of [arguments] and [parameters].
+   */
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters);
+
+  /**
+   * Returns the unaliased type of this type.
+   *
+   * The unaliased type of a typedef'd type is the unaliased type to which its
+   * name is bound. The unaliased version of any other type is the type itself.
+   *
+   * For example, the unaliased type of [: typedef A Func<A,B>(B b) :] is the
+   * function type [: (B) -> A :] and the unaliased type of
+   * [: Func<int,String> :] is the function type [: (String) -> int :].
+   */
+  DartType unalias(Compiler compiler);
+
+  /**
+   * A type is malformed if it is itself a malformed type or contains a
+   * malformed type.
+   */
+  bool get isMalformed => false;
+
+  /**
+   * Calls [f] with each [MalformedType] within this type.
+   *
+   * If [f] returns [: false :], the traversal stops prematurely.
+   *
+   * [forEachMalformedType] returns [: false :] if the traversal was stopped
+   * prematurely.
+   */
+  bool forEachMalformedType(bool f(MalformedType type)) => true;
+
+  bool operator ==(other);
+
+  /**
+   * Is [: true :] if this type has no explict type arguments.
+   */
+  bool get isRaw => true;
+
+  DartType asRaw() => this;
+}
+
+/**
+ * Represents a type variable, that is the type parameters of a class type.
+ *
+ * For example, in [: class Array<E> { ... } :], E is a type variable.
+ *
+ * Each class should have its own unique type variables, one for each type
+ * parameter. A class with type parameters is said to be parameterized or
+ * generic.
+ *
+ * Non-static members, constructors, and factories of generic
+ * class/interface can refer to type variables of the current class
+ * (not of supertypes).
+ *
+ * When using a generic type, also known as an application or
+ * instantiation of the type, the actual type arguments should be
+ * substituted for the type variables in the class declaration.
+ *
+ * For example, given a box, [: class Box<T> { T value; } :], the
+ * type of the expression [: new Box<String>().value :] is
+ * [: String :] because we must substitute [: String :] for the
+ * the type variable [: T :].
+ */
+class TypeVariableType extends DartType {
+  final TypeVariableElement element;
+
+  TypeVariableType(this.element);
+
+  TypeKind get kind => TypeKind.TYPE_VARIABLE;
+
+  SourceString get name => element.name;
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    if (parameters.isEmpty) {
+      assert(arguments.isEmpty);
+      // Return fast on empty substitutions.
+      return this;
+    }
+    Link<DartType> parameterLink = parameters;
+    Link<DartType> argumentLink = arguments;
+    while (!argumentLink.isEmpty && !parameterLink.isEmpty) {
+      TypeVariableType parameter = parameterLink.head;
+      DartType argument = argumentLink.head;
+      if (parameter == this) {
+        assert(argumentLink.tail.isEmpty == parameterLink.tail.isEmpty);
+        return argument;
+      }
+      parameterLink = parameterLink.tail;
+      argumentLink = argumentLink.tail;
+    }
+    assert(argumentLink.isEmpty && parameterLink.isEmpty);
+    // The type variable was not substituted.
+    return this;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  int get hashCode => 17 * element.hashCode;
+
+  bool operator ==(other) {
+    if (other is !TypeVariableType) return false;
+    return identical(other.element, element);
+  }
+
+  String toString() => name.slowToString();
+}
+
+/**
+ * A statement type tracks whether a statement returns or may return.
+ */
+class StatementType extends DartType {
+  final String stringName;
+
+  Element get element => null;
+
+  TypeKind get kind => TypeKind.STATEMENT;
+
+  SourceString get name => new SourceString(stringName);
+
+  const StatementType(this.stringName);
+
+  static const RETURNING = const StatementType('<returning>');
+  static const NOT_RETURNING = const StatementType('<not returning>');
+  static const MAYBE_RETURNING = const StatementType('<maybe returning>');
+
+  /** Combine the information about two control-flow edges that are joined. */
+  StatementType join(StatementType other) {
+    return (identical(this, other)) ? this : MAYBE_RETURNING;
+  }
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    // Statement types are not substitutable.
+    return this;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  int get hashCode => 17 * stringName.hashCode;
+
+  bool operator ==(other) {
+    if (other is !StatementType) return false;
+    return other.stringName == stringName;
+  }
+
+  String toString() => stringName;
+}
+
+class VoidType extends DartType {
+  const VoidType(this.element);
+
+  TypeKind get kind => TypeKind.VOID;
+
+  SourceString get name => element.name;
+
+  final Element element;
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    // Void cannot be substituted.
+    return this;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  int get hashCode => 1729;
+
+  bool operator ==(other) => other is VoidType;
+
+  String toString() => name.slowToString();
+}
+
+class MalformedType extends DartType {
+  final ErroneousElement element;
+
+  /**
+   * [declaredType] holds the type which the user wrote in code.
+   *
+   * For instance, for a resolved but malformed type like [: Map<String> :] the
+   * [declaredType] is [: Map<String> :] whereas for an unresolved type
+   */
+  final DartType userProvidedBadType;
+
+  /**
+   * Type arguments for the malformed typed, if these cannot fit in the
+   * [declaredType].
+   *
+   * This field is for instance used for [: dynamic<int> :] and [: T<int> :]
+   * where [: T :] is a type variable, in which case [declaredType] holds
+   * [: dynamic :] and [: T :], respectively, or for [: X<int> :] where [: X :]
+   * is not resolved or does not imply a type.
+   */
+  final Link<DartType> typeArguments;
+
+  MalformedType(this.element, this.userProvidedBadType,
+                [this.typeArguments = null]);
+
+  TypeKind get kind => TypeKind.MALFORMED_TYPE;
+
+  SourceString get name => element.name;
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    // Malformed types are not substitutable.
+    return this;
+  }
+
+  bool get isMalformed => true;
+
+  bool forEachMalformedType(bool f(MalformedType type)) => f(this);
+
+  DartType unalias(Compiler compiler) => this;
+
+  String toString() {
+    var sb = new StringBuffer();
+    if (typeArguments != null) {
+      if (userProvidedBadType != null) {
+        sb.add(userProvidedBadType.name.slowToString());
+      } else {
+        sb.add(element.name.slowToString());
+      }
+      if (!typeArguments.isEmpty) {
+        sb.add('<');
+        typeArguments.printOn(sb, ', ');
+        sb.add('>');
+      }
+    } else {
+      sb.add(userProvidedBadType.toString());
+    }
+    return sb.toString();
+  }
+}
+
+bool hasMalformed(Link<DartType> types) {
+  for (DartType typeArgument in types) {
+    if (typeArgument.isMalformed) {
+      return true;
+    }
+  }
+  return false;
+}
+
+abstract class GenericType extends DartType {
+  final Link<DartType> typeArguments;
+  final bool isMalformed;
+
+  GenericType(Link<DartType> this.typeArguments, bool this.isMalformed);
+
+  TypeDeclarationElement get element;
+
+  /// Creates a new instance of this type using the provided type arguments.
+  GenericType _createType(Link<DartType> newTypeArguments);
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    if (typeArguments.isEmpty) {
+      // Return fast on non-generic types.
+      return this;
+    }
+    if (parameters.isEmpty) {
+      assert(arguments.isEmpty);
+      // Return fast on empty substitutions.
+      return this;
+    }
+    Link<DartType> newTypeArguments =
+        Types.substTypes(typeArguments, arguments, parameters);
+    if (!identical(typeArguments, newTypeArguments)) {
+      // Create a new type only if necessary.
+      return _createType(newTypeArguments);
+    }
+    return this;
+  }
+
+  bool forEachMalformedType(bool f(MalformedType type)) {
+    for (DartType typeArgument in typeArguments) {
+      if (!typeArgument.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.add(name.slowToString());
+    if (!isRaw) {
+      sb.add('<');
+      typeArguments.printOn(sb, ', ');
+      sb.add('>');
+    }
+    return sb.toString();
+  }
+
+  int get hashCode {
+    int hash = element.hashCode;
+    for (Link<DartType> arguments = this.typeArguments;
+         !arguments.isEmpty;
+         arguments = arguments.tail) {
+      int argumentHash = arguments.head != null ? arguments.head.hashCode : 0;
+      hash = 17 * hash + 3 * argumentHash;
+    }
+    return hash;
+  }
+
+  bool operator ==(other) {
+    if (!identical(element, other.element)) return false;
+    return typeArguments == other.typeArguments;
+  }
+
+  bool get isRaw => typeArguments.isEmpty || identical(this, element.rawType);
+
+  GenericType asRaw() => element.rawType;
+}
+
+// TODO(johnniwinther): Add common supertype for InterfaceType and TypedefType.
+class InterfaceType extends GenericType {
+  final ClassElement element;
+
+  InterfaceType(this.element,
+                [Link<DartType> typeArguments = const Link<DartType>()])
+      : super(typeArguments, hasMalformed(typeArguments)) {
+    assert(invariant(element, element.isDeclaration));
+  }
+
+  TypeKind get kind => TypeKind.INTERFACE;
+
+  SourceString get name => element.name;
+
+  InterfaceType _createType(Link<DartType> newTypeArguments) {
+    return new InterfaceType(element, newTypeArguments);
+  }
+
+  /**
+   * Returns the type as an instance of class [other], if possible, null
+   * otherwise.
+   */
+  DartType asInstanceOf(ClassElement other) {
+    if (element == other) return this;
+    for (InterfaceType supertype in element.allSupertypes) {
+      ClassElement superclass = supertype.element;
+      if (superclass == other) {
+        Link<DartType> arguments = Types.substTypes(supertype.typeArguments,
+                                                    typeArguments,
+                                                    element.typeVariables);
+        return new InterfaceType(superclass, arguments);
+      }
+    }
+    return null;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  bool operator ==(other) {
+    if (other is !InterfaceType) return false;
+    return super == other;
+  }
+
+  InterfaceType asRaw() => super.asRaw();
+}
+
+class FunctionType extends DartType {
+  final Element element;
+  final DartType returnType;
+  final Link<DartType> parameterTypes;
+  final Link<DartType> optionalParameterTypes;
+
+  /**
+   * The names of the named parameters ordered lexicographically.
+   */
+  final Link<SourceString> namedParameters;
+
+  /**
+   * The types of the named parameters in the order corresponding to the
+   * [namedParameters].
+   */
+  final Link<DartType> namedParameterTypes;
+  final bool isMalformed;
+
+  factory FunctionType(Element element,
+                       DartType returnType,
+                       Link<DartType> parameterTypes,
+                       Link<DartType> optionalParameterTypes,
+                       Link<SourceString> namedParameters,
+                       Link<DartType> namedParameterTypes) {
+    // Compute [isMalformed] eagerly since it is faster than a lazy computation
+    // and since [isMalformed] most likely will be accessed in [Types.isSubtype]
+    // anyway.
+    bool isMalformed = returnType != null &&
+                       returnType.isMalformed ||
+                       hasMalformed(parameterTypes) ||
+                       hasMalformed(optionalParameterTypes) ||
+                       hasMalformed(namedParameterTypes);
+    return new FunctionType.internal(element,
+                                     returnType,
+                                     parameterTypes,
+                                     optionalParameterTypes,
+                                     namedParameters,
+                                     namedParameterTypes,
+                                     isMalformed);
+  }
+
+  FunctionType.internal(Element this.element,
+                        DartType this.returnType,
+                        Link<DartType> this.parameterTypes,
+                        Link<DartType> this.optionalParameterTypes,
+                        Link<SourceString> this.namedParameters,
+                        Link<DartType> this.namedParameterTypes,
+                        bool this.isMalformed) {
+    assert(element == null || invariant(element, element.isDeclaration));
+    // Assert that optional and named parameters are not used at the same time.
+    assert(optionalParameterTypes.isEmpty || namedParameterTypes.isEmpty);
+    assert(namedParameters.slowLength() == namedParameterTypes.slowLength());
+  }
+
+  TypeKind get kind => TypeKind.FUNCTION;
+
+  DartType getNamedParameterType(SourceString name) {
+    Link<SourceString> namedParameter = namedParameters;
+    Link<DartType> namedParameterType = namedParameterTypes;
+    while (!namedParameter.isEmpty && !namedParameterType.isEmpty) {
+      if (namedParameter.head == name) {
+        return namedParameterType.head;
+      }
+      namedParameter = namedParameter.tail;
+      namedParameterType = namedParameterType.tail;
+    }
+    return null;
+  }
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    if (parameters.isEmpty) {
+      assert(arguments.isEmpty);
+      // Return fast on empty substitutions.
+      return this;
+    }
+    var newReturnType = returnType.subst(arguments, parameters);
+    bool changed = !identical(newReturnType, returnType);
+    var newParameterTypes =
+        Types.substTypes(parameterTypes, arguments, parameters);
+    var newOptionalParameterTypes =
+        Types.substTypes(optionalParameterTypes, arguments, parameters);
+    var newNamedParameterTypes =
+        Types.substTypes(namedParameterTypes, arguments, parameters);
+    if (!changed &&
+        (!identical(parameterTypes, newParameterTypes) ||
+         !identical(optionalParameterTypes, newOptionalParameterTypes) ||
+         !identical(namedParameterTypes, newNamedParameterTypes))) {
+      changed = true;
+    }
+    if (changed) {
+      // Create a new type only if necessary.
+      return new FunctionType(element,
+                              newReturnType,
+                              newParameterTypes,
+                              newOptionalParameterTypes,
+                              namedParameters,
+                              newNamedParameterTypes);
+    }
+    return this;
+  }
+
+  bool forEachMalformedType(bool f(MalformedType type)) {
+    if (!returnType.forEachMalformedType(f)) {
+      return false;
+    }
+    for (DartType parameterType in parameterTypes) {
+      if (!parameterType.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    for (DartType parameterType in optionalParameterTypes) {
+      if (!parameterType.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    for (DartType parameterType in namedParameterTypes) {
+      if (!parameterType.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.add('(');
+    parameterTypes.printOn(sb, ', ');
+    bool first = parameterTypes.isEmpty;
+    if (!optionalParameterTypes.isEmpty) {
+      if (!first) {
+        sb.add(', ');
+      }
+      sb.add('[');
+      optionalParameterTypes.printOn(sb, ', ');
+      sb.add(']');
+      first = false;
+    }
+    if (!namedParameterTypes.isEmpty) {
+      if (!first) {
+        sb.add(', ');
+      }
+      sb.add('{');
+      Link<SourceString> namedParameter = namedParameters;
+      Link<DartType> namedParameterType = namedParameterTypes;
+      first = true;
+      while (!namedParameter.isEmpty && !namedParameterType.isEmpty) {
+        if (!first) {
+          sb.add(', ');
+        }
+        sb.add(namedParameterType.head);
+        sb.add(' ');
+          sb.add(namedParameter.head.slowToString());
+        namedParameter = namedParameter.tail;
+        namedParameterType = namedParameterType.tail;
+        first = false;
+      }
+      sb.add('}');
+    }
+    sb.add(') -> ${returnType}');
+    return sb.toString();
+  }
+
+  SourceString get name => const SourceString('Function');
+
+  int computeArity() {
+    int arity = 0;
+    parameterTypes.forEach((_) { arity++; });
+    return arity;
+  }
+
+  int get hashCode {
+    int hash = 17 * element.hashCode + 3 * returnType.hashCode;
+    for (DartType parameter  in parameterTypes) {
+      hash = 17 * hash + 3 * parameter.hashCode;
+    }
+    for (DartType parameter  in optionalParameterTypes) {
+      hash = 17 * hash + 3 * parameter.hashCode;
+    }
+    for (SourceString name  in namedParameters) {
+      hash = 17 * hash + 3 * name.hashCode;
+    }
+    for (DartType parameter  in namedParameterTypes) {
+      hash = 17 * hash + 3 * parameter.hashCode;
+    }
+    return hash;
+  }
+
+  bool operator ==(other) {
+    if (other is !FunctionType) return false;
+    return returnType == other.returnType
+           && parameterTypes == other.parameterTypes
+           && optionalParameterTypes == other.optionalParameterTypes
+           && namedParameters == other.namedParameters
+           && namedParameterTypes == other.namedParameterTypes;
+  }
+}
+
+class TypedefType extends GenericType {
+  final TypedefElement element;
+
+  TypedefType(this.element,
+              [Link<DartType> typeArguments = const Link<DartType>()])
+      : super(typeArguments, hasMalformed(typeArguments));
+
+  TypedefType _createType(Link<DartType> newTypeArguments) {
+    return new TypedefType(element, newTypeArguments);
+  }
+
+  TypeKind get kind => TypeKind.TYPEDEF;
+
+  SourceString get name => element.name;
+
+  DartType unalias(Compiler compiler) {
+    // TODO(ahe): This should be [ensureResolved].
+    compiler.resolveTypedef(element);
+    DartType definition = element.alias.unalias(compiler);
+    TypedefType declaration = element.computeType(compiler);
+    return definition.subst(typeArguments, declaration.typeArguments);
+  }
+
+  bool operator ==(other) {
+    if (other is !TypedefType) return false;
+    return super == other;
+  }
+
+  TypedefType asRaw() => super.asRaw();
+}
+
+/**
+ * Special type to hold the [dynamic] type. Used for correctly returning
+ * 'dynamic' on [toString].
+ */
+class DynamicType extends InterfaceType {
+  DynamicType(ClassElement element) : super(element);
+
+  SourceString get name => const SourceString('dynamic');
+}
+
+class Types {
+  final Compiler compiler;
+  // TODO(karlklose): should we have a class Void?
+  final VoidType voidType;
+  final DynamicType dynamicType;
+
+  factory Types(Compiler compiler, ClassElement dynamicElement) {
+    LibraryElement library = new LibraryElementX(new Script(null, null));
+    VoidType voidType = new VoidType(new VoidElementX(library));
+    DynamicType dynamicType = new DynamicType(dynamicElement);
+    dynamicElement.rawType = dynamicElement.thisType = dynamicType;
+    return new Types.internal(compiler, voidType, dynamicType);
+  }
+
+  Types.internal(this.compiler, this.voidType, this.dynamicType);
+
+  /** Returns true if t is a subtype of s */
+  bool isSubtype(DartType t, DartType s) {
+    if (identical(t, s) ||
+        identical(t, dynamicType) ||
+        identical(s, dynamicType) ||
+        t.isMalformed ||
+        s.isMalformed ||
+        identical(s.element, compiler.objectClass) ||
+        identical(t.element, compiler.nullClass)) {
+      return true;
+    }
+    t = t.unalias(compiler);
+    s = s.unalias(compiler);
+
+    if (t is VoidType) {
+      return false;
+    } else if (t is InterfaceType) {
+      if (s is !InterfaceType) return false;
+      ClassElement tc = t.element;
+      if (identical(tc, s.element)) return true;
+      for (Link<DartType> supertypes = tc.allSupertypes;
+           supertypes != null && !supertypes.isEmpty;
+           supertypes = supertypes.tail) {
+        DartType supertype = supertypes.head;
+        if (identical(supertype.element, s.element)) return true;
+      }
+      return false;
+    } else if (t is FunctionType) {
+      if (identical(s.element, compiler.functionClass)) return true;
+      if (s is !FunctionType) return false;
+      FunctionType tf = t;
+      FunctionType sf = s;
+      Link<DartType> tps = tf.parameterTypes;
+      Link<DartType> sps = sf.parameterTypes;
+      while (!tps.isEmpty && !sps.isEmpty) {
+        if (!isAssignable(tps.head, sps.head)) return false;
+        tps = tps.tail;
+        sps = sps.tail;
+      }
+      if (!tps.isEmpty || !sps.isEmpty) return false;
+      if (!isAssignable(sf.returnType, tf.returnType)) return false;
+      if (!sf.namedParameters.isEmpty) {
+        // Since named parameters are globally ordered we can determine the
+        // subset relation with a linear search for [:sf.NamedParameters:]
+        // within [:tf.NamedParameters:].
+        Link<SourceString> tNames = tf.namedParameters;
+        Link<DartType> tTypes = tf.namedParameterTypes;
+        Link<SourceString> sNames = sf.namedParameters;
+        Link<DartType> sTypes = sf.namedParameterTypes;
+        while (!tNames.isEmpty && !sNames.isEmpty) {
+          if (sNames.head == tNames.head) {
+            if (!isAssignable(tTypes.head, sTypes.head)) return false;
+
+            sNames = sNames.tail;
+            sTypes = sTypes.tail;
+          }
+          tNames = tNames.tail;
+          tTypes = tTypes.tail;
+        }
+        if (!sNames.isEmpty) {
+          // We didn't find all names.
+          return false;
+        }
+      }
+      if (!sf.optionalParameterTypes.isEmpty) {
+        Link<DartType> tOptionalParameterType = tf.optionalParameterTypes;
+        Link<DartType> sOptionalParameterType = sf.optionalParameterTypes;
+        while (!tOptionalParameterType.isEmpty &&
+               !sOptionalParameterType.isEmpty) {
+          if (!isAssignable(tOptionalParameterType.head,
+                            sOptionalParameterType.head)) {
+            return false;
+          }
+          sOptionalParameterType = sOptionalParameterType.tail;
+          tOptionalParameterType = tOptionalParameterType.tail;
+        }
+        if (!sOptionalParameterType.isEmpty) {
+          // We didn't find enough optional parameters.
+          return false;
+        }
+      }
+      return true;
+    } else if (t is TypeVariableType) {
+      if (s is !TypeVariableType) return false;
+      return (identical(t.element, s.element));
+    } else {
+      throw 'internal error: unknown type kind';
+    }
+  }
+
+  bool isAssignable(DartType r, DartType s) {
+    return isSubtype(r, s) || isSubtype(s, r);
+  }
+
+
+  /**
+   * Helper method for performing substitution of a linked list of types.
+   *
+   * If no types are changed by the substitution, the [types] is returned
+   * instead of a newly created linked list.
+   */
+  static Link<DartType> substTypes(Link<DartType> types,
+                                   Link<DartType> arguments,
+                                   Link<DartType> parameters) {
+    bool changed = false;
+    var builder = new LinkBuilder<DartType>();
+    Link<DartType> typeLink = types;
+    while (!typeLink.isEmpty) {
+      var argument = typeLink.head.subst(arguments, parameters);
+      if (!changed && !identical(argument, typeLink.head)) {
+        changed = true;
+      }
+      builder.addLast(argument);
+      typeLink = typeLink.tail;
+    }
+    if (changed) {
+      // Create a new link only if necessary.
+      return builder.toLink();
+    }
+    return types;
+  }
+
+  /**
+   * Combine error messages in a malformed type to a single message string.
+   */
+  static String fetchReasonsFromMalformedType(DartType type) {
+    // TODO(johnniwinther): Figure out how to produce good error message in face
+    // of multiple errors, and how to ensure non-localized error messages.
+    var reasons = new List<String>();
+    type.forEachMalformedType((MalformedType malformedType) {
+      ErroneousElement error = malformedType.element;
+      Message message = error.messageKind.message(error.messageArguments);
+      reasons.add(message.toString());
+      return true;
+    });
+    return Strings.join(reasons, ', ');
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart b/pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart
new file mode 100644
index 0000000..1d0c2b9
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart
@@ -0,0 +1,28 @@
+// 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.
+
+part of dart2js;
+
+abstract class DiagnosticListener {
+  // TODO(karlklose): replace cancel with better error reporting mechanism.
+  void cancel(String reason, {node, token, instruction, element});
+  // TODO(karlklose): rename log to something like reportInfo.
+  void log(message);
+  // TODO(karlklose): add reportWarning and reportError to this interface.
+
+  void internalErrorOnElement(Element element, String message);
+  void internalError(String message,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element});
+
+  SourceSpan spanFromSpannable(Spannable node, [Uri uri]);
+
+  void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind);
+
+  // TODO(ahe): Rename to reportError when that method has been removed.
+  void reportErrorCode(Spannable node, MessageKind errorCode, [Map arguments]);
+
+  /// Returns true if a diagnostic was emitted.
+  bool onDeprecatedFeature(Spannable span, String feature);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart b/pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart
new file mode 100644
index 0000000..2798f95
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart
@@ -0,0 +1,792 @@
+// 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.
+
+library elements;
+
+import 'dart:uri';
+
+import 'modelx.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../resolution/resolution.dart';
+
+import '../dart2jslib.dart' show InterfaceType,
+                                 DartType,
+                                 TypeVariableType,
+                                 TypedefType,
+                                 MessageKind,
+                                 DiagnosticListener,
+                                 Script,
+                                 FunctionType,
+                                 SourceString,
+                                 Selector,
+                                 Constant,
+                                 Compiler;
+
+import '../dart_types.dart';
+
+import '../scanner/scannerlib.dart' show Token,
+                                         isUserDefinableOperator,
+                                         isMinusOperator;
+
+const int STATE_NOT_STARTED = 0;
+const int STATE_STARTED = 1;
+const int STATE_DONE = 2;
+
+class ElementCategory {
+  /**
+   * Represents things that we don't expect to find when looking in a
+   * scope.
+   */
+  static const int NONE = 0;
+
+  /** Field, parameter, or variable. */
+  static const int VARIABLE = 1;
+
+  /** Function, method, or foreign function. */
+  static const int FUNCTION = 2;
+
+  static const int CLASS = 4;
+
+  static const int PREFIX = 8;
+
+  /** Constructor or factory. */
+  static const int FACTORY = 16;
+
+  static const int ALIAS = 32;
+
+  static const int SUPER = 64;
+
+  /** Type variable */
+  static const int TYPE_VARIABLE = 128;
+
+  static const int IMPLIES_TYPE = CLASS | ALIAS | TYPE_VARIABLE;
+}
+
+class ElementKind {
+  final String id;
+  final int category;
+
+  const ElementKind(String this.id, this.category);
+
+  static const ElementKind VARIABLE =
+      const ElementKind('variable', ElementCategory.VARIABLE);
+  static const ElementKind PARAMETER =
+      const ElementKind('parameter', ElementCategory.VARIABLE);
+  // Parameters in constructors that directly initialize fields. For example:
+  // [:A(this.field):].
+  static const ElementKind FIELD_PARAMETER =
+      const ElementKind('field_parameter', ElementCategory.VARIABLE);
+  static const ElementKind FUNCTION =
+      const ElementKind('function', ElementCategory.FUNCTION);
+  static const ElementKind CLASS =
+      const ElementKind('class', ElementCategory.CLASS);
+  static const ElementKind GENERATIVE_CONSTRUCTOR =
+      const ElementKind('generative_constructor', ElementCategory.FACTORY);
+  static const ElementKind FIELD =
+      const ElementKind('field', ElementCategory.VARIABLE);
+  static const ElementKind VARIABLE_LIST =
+      const ElementKind('variable_list', ElementCategory.NONE);
+  static const ElementKind FIELD_LIST =
+      const ElementKind('field_list', ElementCategory.NONE);
+  static const ElementKind GENERATIVE_CONSTRUCTOR_BODY =
+      const ElementKind('generative_constructor_body', ElementCategory.NONE);
+  static const ElementKind COMPILATION_UNIT =
+      const ElementKind('compilation_unit', ElementCategory.NONE);
+  static const ElementKind GETTER =
+      const ElementKind('getter', ElementCategory.NONE);
+  static const ElementKind SETTER =
+      const ElementKind('setter', ElementCategory.NONE);
+  static const ElementKind TYPE_VARIABLE =
+      const ElementKind('type_variable', ElementCategory.TYPE_VARIABLE);
+  static const ElementKind ABSTRACT_FIELD =
+      const ElementKind('abstract_field', ElementCategory.VARIABLE);
+  static const ElementKind LIBRARY =
+      const ElementKind('library', ElementCategory.NONE);
+  static const ElementKind PREFIX =
+      const ElementKind('prefix', ElementCategory.PREFIX);
+  static const ElementKind TYPEDEF =
+      const ElementKind('typedef', ElementCategory.ALIAS);
+
+  static const ElementKind STATEMENT =
+      const ElementKind('statement', ElementCategory.NONE);
+  static const ElementKind LABEL =
+      const ElementKind('label', ElementCategory.NONE);
+  static const ElementKind VOID =
+      const ElementKind('void', ElementCategory.NONE);
+
+  static const ElementKind AMBIGUOUS =
+      const ElementKind('ambiguous', ElementCategory.NONE);
+  static const ElementKind ERROR =
+      const ElementKind('error', ElementCategory.NONE);
+  static const ElementKind MALFORMED_TYPE =
+      const ElementKind('malformed', ElementCategory.NONE);
+
+  toString() => id;
+}
+
+abstract class Element implements Spannable {
+  SourceString get name;
+  ElementKind get kind;
+  Modifiers get modifiers;
+  Element get enclosingElement;
+  Link<MetadataAnnotation> get metadata;
+
+  Node parseNode(DiagnosticListener listener);
+  DartType computeType(Compiler compiler);
+
+  bool isFunction();
+  bool isConstructor();
+  bool isClosure();
+  bool isMember();
+  bool isInstanceMember();
+  bool isInStaticMember();
+
+  bool isFactoryConstructor();
+  bool isGenerativeConstructor();
+  bool isGenerativeConstructorBody();
+  bool isCompilationUnit();
+  bool isClass();
+  bool isPrefix();
+  bool isVariable();
+  bool isParameter();
+  bool isStatement();
+  bool isTypedef();
+  bool isTypeVariable();
+  bool isField();
+  bool isAbstractField();
+  bool isGetter();
+  bool isSetter();
+  bool isAccessor();
+  bool isLibrary();
+  bool isErroneous();
+  bool isAmbiguous();
+
+  bool isTopLevel();
+  bool isAssignable();
+  bool isNative();
+
+  bool impliesType();
+
+  Token position();
+
+  CompilationUnitElement getCompilationUnit();
+  LibraryElement getLibrary();
+  LibraryElement getImplementationLibrary();
+  ClassElement getEnclosingClass();
+  Element getEnclosingClassOrCompilationUnit();
+  Element getEnclosingMember();
+  Element getOutermostEnclosingMemberOrTopLevel();
+
+  FunctionElement asFunctionElement();
+
+  bool get isPatched;
+  bool get isPatch;
+  bool get isImplementation;
+  bool get isDeclaration;
+  bool get isSynthesized;
+
+  Element get implementation;
+  Element get declaration;
+  Element get patch;
+  Element get origin;
+
+  bool hasFixedBackendName();
+  String fixedBackendName();
+
+  bool isAbstract(Compiler compiler);
+  bool isForeign(Compiler compiler);
+
+  void addMetadata(MetadataAnnotation annotation);
+  void setNative(String name);
+  void setFixedBackendName(String name);
+
+  Scope buildScope();
+}
+
+class Elements {
+  static bool isUnresolved(Element e) {
+    return e == null || e.isErroneous();
+  }
+  static bool isErroneousElement(Element e) => e != null && e.isErroneous();
+
+  static bool isClass(Element e) => e != null && e.kind == ElementKind.CLASS;
+  static bool isTypedef(Element e) {
+    return e != null && e.kind == ElementKind.TYPEDEF;
+  }
+
+  static bool isLocal(Element element) {
+    return !Elements.isUnresolved(element)
+            && !element.isInstanceMember()
+            && !isStaticOrTopLevelField(element)
+            && !isStaticOrTopLevelFunction(element)
+            && (identical(element.kind, ElementKind.VARIABLE) ||
+                identical(element.kind, ElementKind.PARAMETER) ||
+                identical(element.kind, ElementKind.FUNCTION));
+  }
+
+  static bool isInstanceField(Element element) {
+    return !Elements.isUnresolved(element)
+           && element.isInstanceMember()
+           && (identical(element.kind, ElementKind.FIELD)
+               || identical(element.kind, ElementKind.GETTER)
+               || identical(element.kind, ElementKind.SETTER));
+  }
+
+  static bool isStaticOrTopLevel(Element element) {
+    // TODO(ager): This should not be necessary when patch support has
+    // been reworked.
+    if (!Elements.isUnresolved(element)
+        && element.modifiers.isStatic()) {
+      return true;
+    }
+    return !Elements.isUnresolved(element)
+           && !element.isInstanceMember()
+           && !element.isPrefix()
+           && element.enclosingElement != null
+           && (element.enclosingElement.kind == ElementKind.CLASS ||
+               element.enclosingElement.kind == ElementKind.COMPILATION_UNIT ||
+               element.enclosingElement.kind == ElementKind.LIBRARY);
+  }
+
+  static bool isStaticOrTopLevelField(Element element) {
+    return isStaticOrTopLevel(element)
+           && (identical(element.kind, ElementKind.FIELD)
+               || identical(element.kind, ElementKind.GETTER)
+               || identical(element.kind, ElementKind.SETTER));
+  }
+
+  static bool isStaticOrTopLevelFunction(Element element) {
+    return isStaticOrTopLevel(element)
+           && (identical(element.kind, ElementKind.FUNCTION));
+  }
+
+  static bool isInstanceMethod(Element element) {
+    return !Elements.isUnresolved(element)
+           && element.isInstanceMember()
+           && (identical(element.kind, ElementKind.FUNCTION));
+  }
+
+  static bool isInstanceSend(Send send, TreeElements elements) {
+    Element element = elements[send];
+    if (element == null) return !isClosureSend(send, element);
+    return isInstanceMethod(element) || isInstanceField(element);
+  }
+
+  static bool isClosureSend(Send send, Element element) {
+    if (send.isPropertyAccess) return false;
+    if (send.receiver != null) return false;
+    // (o)() or foo()().
+    if (element == null && send.selector.asIdentifier() == null) return true;
+    if (element == null) return false;
+    // foo() with foo a local or a parameter.
+    return isLocal(element);
+  }
+
+  static SourceString constructConstructorName(SourceString receiver,
+                                               SourceString selector) {
+    String r = receiver.slowToString();
+    String s = selector.slowToString();
+    return new SourceString('$r\$$s');
+  }
+
+  static SourceString deconstructConstructorName(SourceString name,
+                                                 ClassElement holder) {
+    String r = '${holder.name.slowToString()}\$';
+    String s = name.slowToString();
+    if (s.startsWith(r)) {
+      return new SourceString(s.substring(r.length));
+    }
+    return null;
+  }
+
+  /**
+   * Map an operator-name to a valid Dart identifier.
+   *
+   * For non-operator names, this metod just returns its input.
+   *
+   * The results returned from this method are guaranteed to be valid
+   * JavaScript identifers, except it may include reserved words for
+   * non-operator names.
+   */
+  static SourceString operatorNameToIdentifier(SourceString name) {
+    if (name == null) return null;
+    String value = name.stringValue;
+    if (value == null) {
+      return name;
+    } else if (identical(value, '==')) {
+      return const SourceString(r'operator$eq');
+    } else if (identical(value, '~')) {
+      return const SourceString(r'operator$not');
+    } else if (identical(value, '[]')) {
+      return const SourceString(r'operator$index');
+    } else if (identical(value, '[]=')) {
+      return const SourceString(r'operator$indexSet');
+    } else if (identical(value, '*')) {
+      return const SourceString(r'operator$mul');
+    } else if (identical(value, '/')) {
+      return const SourceString(r'operator$div');
+    } else if (identical(value, '%')) {
+      return const SourceString(r'operator$mod');
+    } else if (identical(value, '~/')) {
+      return const SourceString(r'operator$tdiv');
+    } else if (identical(value, '+')) {
+      return const SourceString(r'operator$add');
+    } else if (identical(value, '<<')) {
+      return const SourceString(r'operator$shl');
+    } else if (identical(value, '>>')) {
+      return const SourceString(r'operator$shr');
+    } else if (identical(value, '>=')) {
+      return const SourceString(r'operator$ge');
+    } else if (identical(value, '>')) {
+      return const SourceString(r'operator$gt');
+    } else if (identical(value, '<=')) {
+      return const SourceString(r'operator$le');
+    } else if (identical(value, '<')) {
+      return const SourceString(r'operator$lt');
+    } else if (identical(value, '&')) {
+      return const SourceString(r'operator$and');
+    } else if (identical(value, '^')) {
+      return const SourceString(r'operator$xor');
+    } else if (identical(value, '|')) {
+      return const SourceString(r'operator$or');
+    } else if (identical(value, '-')) {
+      return const SourceString(r'operator$sub');
+    } else if (identical(value, 'unary-')) {
+      return const SourceString(r'operator$negate');
+    } else {
+      return name;
+    }
+  }
+
+  static SourceString constructOperatorNameOrNull(SourceString op,
+                                                  bool isUnary) {
+    String value = op.stringValue;
+    if (isMinusOperator(value)) {
+      return isUnary ? const SourceString('unary-') : op;
+    } else if (isUserDefinableOperator(value)) {
+      return op;
+    } else {
+      return null;
+    }
+  }
+
+  static SourceString constructOperatorName(SourceString op, bool isUnary) {
+    SourceString operatorName = constructOperatorNameOrNull(op, isUnary);
+    if (operatorName == null) throw 'Unhandled operator: ${op.slowToString()}';
+    else return operatorName;
+  }
+
+  static SourceString mapToUserOperatorOrNull(SourceString op) {
+    String value = op.stringValue;
+
+    if (identical(value, '!=')) return const SourceString('==');
+    if (identical(value, '*=')) return const SourceString('*');
+    if (identical(value, '/=')) return const SourceString('/');
+    if (identical(value, '%=')) return const SourceString('%');
+    if (identical(value, '~/=')) return const SourceString('~/');
+    if (identical(value, '+=')) return const SourceString('+');
+    if (identical(value, '-=')) return const SourceString('-');
+    if (identical(value, '<<=')) return const SourceString('<<');
+    if (identical(value, '>>=')) return const SourceString('>>');
+    if (identical(value, '&=')) return const SourceString('&');
+    if (identical(value, '^=')) return const SourceString('^');
+    if (identical(value, '|=')) return const SourceString('|');
+
+    return null;
+  }
+
+  static SourceString mapToUserOperator(SourceString op) {
+    SourceString userOperator = mapToUserOperatorOrNull(op);
+    if (userOperator == null) throw 'Unhandled operator: ${op.slowToString()}';
+    else return userOperator;
+  }
+
+  static bool isNumberOrStringSupertype(Element element, Compiler compiler) {
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    return (element == coreLibrary.find(const SourceString('Comparable')));
+  }
+
+  static bool isStringOnlySupertype(Element element, Compiler compiler) {
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    return element == coreLibrary.find(const SourceString('Pattern'));
+  }
+
+  static bool isListSupertype(Element element, Compiler compiler) {
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    return (element == coreLibrary.find(const SourceString('Collection')))
+        || (element == coreLibrary.find(const SourceString('Iterable')));
+  }
+
+  /// A `compareTo` function that places [Element]s in a consistent order based
+  /// on the source code order.
+  static int compareByPosition(Element a, Element b) {
+    CompilationUnitElement unitA = a.getCompilationUnit();
+    CompilationUnitElement unitB = b.getCompilationUnit();
+    if (!identical(unitA, unitB)) {
+      int r = unitA.script.uri.path.compareTo(unitB.script.uri.path);
+      if (r != 0) return r;
+    }
+    Token positionA = a.position();
+    Token positionB = b.position();
+    int r = positionA.charOffset.compareTo(positionB.charOffset);
+    if (r != 0) return r;
+    r = a.name.slowToString().compareTo(b.name.slowToString());
+    if (r != 0) return r;
+    // Same file, position and name.  If this happens, we should find out why
+    // and make the order total and independent of hashCode.
+    return a.hashCode.compareTo(b.hashCode);
+  }
+
+  static List<Element> sortedByPosition(Iterable<Element> elements) {
+    return elements.toList()..sort(compareByPosition);
+  }
+}
+
+abstract class ErroneousElement extends Element implements FunctionElement {
+  MessageKind get messageKind;
+  Map get messageArguments;
+}
+
+abstract class AmbiguousElement extends Element {
+  MessageKind get messageKind;
+  Map get messageArguments;
+  Element get existingElement;
+  Element get newElement;
+}
+
+// TODO(kasperl): This probably shouldn't be called an element. It's
+// just an interface shared by classes and libraries.
+abstract class ScopeContainerElement {
+  Element localLookup(SourceString elementName);
+}
+
+abstract class CompilationUnitElement extends Element {
+  Script get script;
+  PartOf get partTag;
+
+  void addMember(Element element, DiagnosticListener listener);
+  void setPartOf(PartOf tag, DiagnosticListener listener);
+  bool get hasMembers;
+}
+
+abstract class LibraryElement extends Element implements ScopeContainerElement {
+  /**
+   * The canonical uri for this library.
+   *
+   * For user libraries the canonical uri is the script uri. For platform
+   * libraries the canonical uri is of the form [:dart:x:].
+   */
+  Uri get canonicalUri;
+  CompilationUnitElement get entryCompilationUnit;
+  Link<CompilationUnitElement> get compilationUnits;
+  Link<LibraryTag> get tags;
+  LibraryName get libraryTag;
+  Link<Element> get exports;
+
+  /**
+   * [:true:] if this library is part of the platform, that is its canonical
+   * uri has the scheme 'dart'.
+   */
+  bool get isPlatformLibrary;
+
+  /**
+   * [:true:] if this library is a platform library whose path starts with
+   * an underscore.
+   */
+  bool get isInternalLibrary;
+  bool get canUseNative;
+  bool get exportsHandled;
+
+  // TODO(kasperl): We should try to get rid of these.
+  void set canUseNative(bool value);
+  void set libraryTag(LibraryName value);
+
+  LibraryElement get implementation;
+
+  void addCompilationUnit(CompilationUnitElement element);
+  void addTag(LibraryTag tag, DiagnosticListener listener);
+  void addImport(Element element, DiagnosticListener listener);
+
+  void addMember(Element element, DiagnosticListener listener);
+  void addToScope(Element element, DiagnosticListener listener);
+
+  // TODO(kasperl): Get rid of this method.
+  Iterable<Element> getNonPrivateElementsInScope();
+
+  void setExports(Iterable<Element> exportedElements);
+
+  Element find(SourceString elementName);
+  Element findLocal(SourceString elementName);
+  void forEachExport(f(Element element));
+
+  void forEachLocalMember(f(Element element));
+
+  bool hasLibraryName();
+  String getLibraryOrScriptName();
+}
+
+abstract class PrefixElement extends Element {
+  Map<SourceString, Element> get imported;
+  Element lookupLocalMember(SourceString memberName);
+}
+
+abstract class TypedefElement extends Element
+    implements TypeDeclarationElement {
+  TypedefType get rawType;
+  DartType get alias;
+  FunctionSignature get functionSignature;
+  Link<DartType> get typeVariables;
+
+  bool get isResolved;
+  bool get isBeingResolved;
+
+  // TODO(kasperl): Try to get rid of these setters.
+  void set alias(DartType value);
+  void set isResolved(bool value);
+  void set isBeingResolved(bool value);
+  void set functionSignature(FunctionSignature value);
+}
+
+abstract class VariableElement extends Element {
+  VariableListElement get variables;
+
+  // TODO(kasperl): Try to get rid of this.
+  Expression get cachedNode;
+}
+
+abstract class FieldParameterElement extends VariableElement {
+  VariableElement get fieldElement;
+}
+
+abstract class VariableListElement extends Element {
+  DartType get type;
+  FunctionSignature get functionSignature;
+
+  // TODO(kasperl): Try to get rid of this.
+  void set type(DartType value);
+}
+
+abstract class AbstractFieldElement extends Element {
+  FunctionElement get getter;
+  FunctionElement get setter;
+}
+
+abstract class FunctionSignature {
+  DartType get returnType;
+  Link<Element> get requiredParameters;
+  Link<Element> get optionalParameters;
+
+  int get requiredParameterCount;
+  int get optionalParameterCount;
+  bool get optionalParametersAreNamed;
+
+  int get parameterCount;
+  List<Element> get orderedOptionalParameters;
+
+  void forEachParameter(void function(Element parameter));
+  void forEachRequiredParameter(void function(Element parameter));
+  void forEachOptionalParameter(void function(Element parameter));
+
+  void orderedForEachParameter(void function(Element parameter));
+}
+
+abstract class FunctionElement extends Element {
+  FunctionExpression get cachedNode;
+  DartType get type;
+  FunctionSignature get functionSignature;
+  FunctionElement get redirectionTarget;
+  FunctionElement get defaultImplementation;
+
+  FunctionElement get patch;
+  FunctionElement get origin;
+
+  // TODO(kasperl): These are bit fishy. Do we really need them?
+  void set patch(FunctionElement value);
+  void set origin(FunctionElement value);
+  void set defaultImplementation(FunctionElement value);
+
+  void setPatch(FunctionElement patchElement);
+  FunctionSignature computeSignature(Compiler compiler);
+  int requiredParameterCount(Compiler compiler);
+  int optionalParameterCount(Compiler compiler);
+  int parameterCount(Compiler compiler);
+
+  FunctionExpression parseNode(DiagnosticListener listener);
+}
+
+abstract class ConstructorBodyElement extends FunctionElement {
+  FunctionElement get constructor;
+}
+
+/**
+ * [TypeDeclarationElement] defines the common interface for class/interface
+ * declarations and typedefs.
+ */
+abstract class TypeDeclarationElement extends Element {
+  GenericType get rawType;
+
+  /**
+   * The type variables declared on this declaration. The type variables are not
+   * available until the type of the element has been computed through
+   * [computeType].
+   */
+  Link<DartType> get typeVariables;
+}
+
+abstract class ClassElement extends TypeDeclarationElement
+    implements ScopeContainerElement {
+  int get id;
+
+  InterfaceType get rawType;
+  InterfaceType get thisType;
+
+  ClassElement get superclass;
+
+  DartType get supertype;
+  Link<DartType> get allSupertypes;
+  Link<DartType> get interfaces;
+
+  bool get hasConstructor;
+  Link<Element> get constructors;
+
+  ClassElement get patch;
+  ClassElement get origin;
+  ClassElement get declaration;
+  ClassElement get implementation;
+
+  int get supertypeLoadState;
+  int get resolutionState;
+  SourceString get nativeTagInfo;
+
+  bool get isMixinApplication;
+  bool get hasBackendMembers;
+  bool get hasLocalScopeMembers;
+
+  // TODO(kasperl): These are bit fishy. Do we really need them?
+  void set rawType(InterfaceType value);
+  void set thisType(InterfaceType value);
+  void set supertype(DartType value);
+  void set allSupertypes(Link<DartType> value);
+  void set interfaces(Link<DartType> value);
+  void set patch(ClassElement value);
+  void set origin(ClassElement value);
+  void set supertypeLoadState(int value);
+  void set resolutionState(int value);
+  void set nativeTagInfo(SourceString value);
+
+  // TODO(kasperl): These seem outdated.
+  bool isInterface();
+  DartType get defaultClass;
+  void set defaultClass(DartType value);
+
+  bool isObject(Compiler compiler);
+  bool isSubclassOf(ClassElement cls);
+  bool implementsInterface(ClassElement intrface);
+  bool isShadowedByField(Element fieldMember);
+
+  ClassElement ensureResolved(Compiler compiler);
+
+  void addMember(Element element, DiagnosticListener listener);
+  void addToScope(Element element, DiagnosticListener listener);
+
+  /**
+   * Add a synthetic nullary constructor if there are no other
+   * constructors.
+   */
+  void addDefaultConstructorIfNeeded(Compiler compiler);
+
+  void addBackendMember(Element element);
+  void reverseBackendMembers();
+
+  Element lookupMember(SourceString memberName);
+  Element lookupSelector(Selector selector);
+
+  Element lookupLocalMember(SourceString memberName);
+  Element lookupBackendMember(SourceString memberName);
+  Element lookupSuperMember(SourceString memberName);
+
+  Element lookupSuperMemberInLibrary(SourceString memberName,
+                                     LibraryElement library);
+
+  Element lookupSuperInterfaceMember(SourceString memberName,
+                                     LibraryElement fromLibrary);
+
+  Element validateConstructorLookupResults(Selector selector,
+                                           Element result,
+                                           Element noMatch(Element));
+
+  Element lookupConstructor(Selector selector, [Element noMatch(Element)]);
+  Element lookupFactoryConstructor(Selector selector,
+                                   [Element noMatch(Element)]);
+
+  void forEachMember(void f(ClassElement enclosingClass, Element member),
+                     {includeBackendMembers: false,
+                      includeSuperMembers: false});
+
+  void forEachInstanceField(void f(ClassElement enclosingClass, Element field),
+                            {includeBackendMembers: false,
+                             includeSuperMembers: false});
+
+  void forEachLocalMember(void f(Element member));
+  void forEachBackendMember(void f(Element member));
+}
+
+abstract class MixinApplicationElement extends ClassElement {
+  ClassElement get mixin;
+  void set mixin(ClassElement value);
+}
+
+abstract class LabelElement extends Element {
+  Label get label;
+  String get labelName;
+  TargetElement get target;
+
+  bool get isTarget;
+  bool get isBreakTarget;
+  bool get isContinueTarget;
+
+  void setBreakTarget();
+  void setContinueTarget();
+}
+
+abstract class TargetElement extends Element {
+  Node get statement;
+  int get nestingLevel;
+  Link<LabelElement> get labels;
+
+  bool get isTarget;
+  bool get isBreakTarget;
+  bool get isContinueTarget;
+  bool get isSwitch;
+
+  // TODO(kasperl): Try to get rid of these.
+  void set isBreakTarget(bool value);
+  void set isContinueTarget(bool value);
+
+  LabelElement addLabel(Label label, String labelName);
+}
+
+abstract class TypeVariableElement extends Element {
+  TypeVariableType get type;
+  DartType get bound;
+
+  // TODO(kasperl): Try to get rid of these.
+  void set type(TypeVariableType value);
+  void set bound(DartType value);
+}
+
+abstract class MetadataAnnotation implements Spannable {
+  Constant get value;
+  Element get annotatedElement;
+  int get resolutionState;
+  Token get beginToken;
+  Token get endToken;
+
+  // TODO(kasperl): Try to get rid of these.
+  void set annotatedElement(Element value);
+  void set resolutionState(int value);
+
+  MetadataAnnotation ensureResolved(Compiler compiler);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart b/pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart
new file mode 100644
index 0000000..f007946
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart
@@ -0,0 +1,1981 @@
+// Copyright (c) 2013, 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.
+
+library elements.modelx;
+
+import 'dart:uri';
+
+import 'elements.dart';
+import '../../compiler.dart' as api;
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../resolution/resolution.dart';
+
+import '../dart2jslib.dart' show invariant,
+                                 InterfaceType,
+                                 DartType,
+                                 TypeVariableType,
+                                 TypedefType,
+                                 MessageKind,
+                                 DiagnosticListener,
+                                 Script,
+                                 FunctionType,
+                                 SourceString,
+                                 Selector,
+                                 Constant,
+                                 Compiler;
+
+import '../dart_types.dart';
+
+import '../scanner/scannerlib.dart' show Token, EOF_TOKEN;
+
+
+class ElementX implements Element {
+  static int elementHashCode = 0;
+
+  final SourceString name;
+  final ElementKind kind;
+  final Element enclosingElement;
+  final int hashCode = ++elementHashCode;
+  Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>();
+
+  ElementX(this.name, this.kind, this.enclosingElement) {
+    assert(isErroneous() || getImplementationLibrary() != null);
+  }
+
+  Modifiers get modifiers => Modifiers.EMPTY;
+
+  Node parseNode(DiagnosticListener listener) {
+    listener.internalErrorOnElement(this, 'not implemented');
+  }
+
+  DartType computeType(Compiler compiler) {
+    compiler.internalError("$this.computeType.", token: position());
+  }
+
+  void addMetadata(MetadataAnnotation annotation) {
+    assert(annotation.annotatedElement == null);
+    annotation.annotatedElement = this;
+    metadata = metadata.prepend(annotation);
+  }
+
+  bool isFunction() => identical(kind, ElementKind.FUNCTION);
+  bool isConstructor() => isFactoryConstructor() || isGenerativeConstructor();
+  bool isClosure() => false;
+  bool isMember() {
+    // Check that this element is defined in the scope of a Class.
+    return enclosingElement != null && enclosingElement.isClass();
+  }
+  bool isInstanceMember() => false;
+
+  /**
+   * Returns [:true:] if this element is enclosed in a static member or is
+   * itself a static member.
+   */
+  bool isInStaticMember() {
+    Element member = getEnclosingMember();
+    return member != null && member.modifiers.isStatic();
+  }
+
+  bool isFactoryConstructor() => modifiers.isFactory();
+  bool isGenerativeConstructor() =>
+      identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR);
+  bool isGenerativeConstructorBody() =>
+      identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY);
+  bool isCompilationUnit() => identical(kind, ElementKind.COMPILATION_UNIT);
+  bool isClass() => identical(kind, ElementKind.CLASS);
+  bool isPrefix() => identical(kind, ElementKind.PREFIX);
+  bool isVariable() => identical(kind, ElementKind.VARIABLE);
+  bool isParameter() => identical(kind, ElementKind.PARAMETER);
+  bool isStatement() => identical(kind, ElementKind.STATEMENT);
+  bool isTypedef() => identical(kind, ElementKind.TYPEDEF);
+  bool isTypeVariable() => identical(kind, ElementKind.TYPE_VARIABLE);
+  bool isField() => identical(kind, ElementKind.FIELD);
+  bool isAbstractField() => identical(kind, ElementKind.ABSTRACT_FIELD);
+  bool isGetter() => identical(kind, ElementKind.GETTER);
+  bool isSetter() => identical(kind, ElementKind.SETTER);
+  bool isAccessor() => isGetter() || isSetter();
+  bool isLibrary() => identical(kind, ElementKind.LIBRARY);
+  bool impliesType() => (kind.category & ElementCategory.IMPLIES_TYPE) != 0;
+
+  /** See [ErroneousElement] for documentation. */
+  bool isErroneous() => false;
+
+  /** See [AmbiguousElement] for documentation. */
+  bool isAmbiguous() => false;
+
+  /**
+   * Is [:true:] if this element has a corresponding patch.
+   *
+   * If [:true:] this element has a non-null [patch] field.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isPatched => false;
+
+  /**
+   * Is [:true:] if this element is a patch.
+   *
+   * If [:true:] this element has a non-null [origin] field.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isPatch => false;
+
+  /**
+   * Is [:true:] if this element defines the implementation for the entity of
+   * this element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isImplementation => !isPatched;
+
+  /**
+   * Is [:true:] if this element introduces the entity of this element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isDeclaration => !isPatch;
+
+  bool get isSynthesized => false;
+
+  /**
+   * Returns the element which defines the implementation for the entity of this
+   * element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  Element get implementation => isPatched ? patch : this;
+
+  /**
+   * Returns the element which introduces the entity of this element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  Element get declaration => isPatch ? origin : this;
+
+  Element get patch {
+    throw new UnsupportedError('patch is not supported on $this');
+  }
+
+  Element get origin {
+    throw new UnsupportedError('origin is not supported on $this');
+  }
+
+  // TODO(johnniwinther): This breaks for libraries (for which enclosing
+  // elements are null) and is invalid for top level variable declarations for
+  // which the enclosing element is a VariableDeclarations and not a compilation
+  // unit.
+  bool isTopLevel() {
+    return enclosingElement != null && enclosingElement.isCompilationUnit();
+  }
+
+  bool isAssignable() {
+    if (modifiers.isFinalOrConst()) return false;
+    if (isFunction() || isGenerativeConstructor()) return false;
+    return true;
+  }
+
+  Token position() => null;
+
+  Token findMyName(Token token) {
+    for (Token t = token; !identical(t.kind, EOF_TOKEN); t = t.next) {
+      if (t.value == name) return t;
+    }
+    return token;
+  }
+
+  CompilationUnitElement getCompilationUnit() {
+    Element element = this;
+    while (!element.isCompilationUnit()) {
+      element = element.enclosingElement;
+    }
+    return element;
+  }
+
+  LibraryElement getLibrary() => enclosingElement.getLibrary();
+
+  LibraryElement getImplementationLibrary() {
+    Element element = this;
+    while (!identical(element.kind, ElementKind.LIBRARY)) {
+      element = element.enclosingElement;
+    }
+    return element;
+  }
+
+  ClassElement getEnclosingClass() {
+    for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isClass()) return e;
+    }
+    return null;
+  }
+
+  Element getEnclosingClassOrCompilationUnit() {
+   for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isClass() || e.isCompilationUnit()) return e;
+    }
+    return null;
+  }
+
+  /**
+   * Returns the member enclosing this element or the element itself if it is a
+   * member. If no enclosing element is found, [:null:] is returned.
+   */
+  Element getEnclosingMember() {
+    for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isMember()) return e;
+    }
+    return null;
+  }
+
+  Element getOutermostEnclosingMemberOrTopLevel() {
+    // TODO(lrn): Why is this called "Outermost"?
+    for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isMember() || e.isTopLevel()) {
+        return e;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Creates the scope for this element.
+   */
+  Scope buildScope() => enclosingElement.buildScope();
+
+  String toString() {
+    // TODO(johnniwinther): Test for nullness of name, or make non-nullness an
+    // invariant for all element types?
+    var nameText = name != null ? name.slowToString() : '?';
+    if (enclosingElement != null && !isTopLevel()) {
+      String holderName = enclosingElement.name != null
+          ? enclosingElement.name.slowToString()
+          : '${enclosingElement.kind}?';
+      return '$kind($holderName#${nameText})';
+    } else {
+      return '$kind(${nameText})';
+    }
+  }
+
+  String _fixedBackendName = null;
+  bool _isNative = false;
+  bool isNative() => _isNative;
+  bool hasFixedBackendName() => _fixedBackendName != null;
+  String fixedBackendName() => _fixedBackendName;
+  // Marks this element as a native element.
+  void setNative(String name) {
+    _isNative = true;
+    _fixedBackendName = name;
+  }
+  void setFixedBackendName(String name) {
+    _fixedBackendName = name;
+  }
+
+  FunctionElement asFunctionElement() => null;
+
+  static bool isInvalid(Element e) => e == null || e.isErroneous();
+
+  bool isAbstract(Compiler compiler) => modifiers.isAbstract();
+  bool isForeign(Compiler compiler) => getLibrary() == compiler.foreignLibrary;
+}
+
+/**
+ * Represents an unresolvable or duplicated element.
+ *
+ * An [ErroneousElement] is used instead of [null] to provide additional
+ * information about the error that caused the element to be unresolvable
+ * or otherwise invalid.
+ *
+ * Accessing any field or calling any method defined on [ErroneousElement]
+ * except [isErroneous] will currently throw an exception. (This might
+ * change when we actually want more information on the erroneous element,
+ * e.g., the name of the element we were trying to resolve.)
+ *
+ * Code that cannot not handle an [ErroneousElement] should use
+ *   [: Element.isInvalid(element) :]
+ * to check for unresolvable elements instead of
+ *   [: element == null :].
+ */
+class ErroneousElementX extends ElementX implements ErroneousElement {
+  final MessageKind messageKind;
+  final Map messageArguments;
+
+  ErroneousElementX(this.messageKind, this.messageArguments,
+                    SourceString name, Element enclosing)
+      : super(name, ElementKind.ERROR, enclosing);
+
+  isErroneous() => true;
+
+  unsupported() {
+    throw 'unsupported operation on erroneous element';
+  }
+
+  Link<MetadataAnnotation> get metadata => unsupported();
+  get type => unsupported();
+  get cachedNode => unsupported();
+  get functionSignature => unsupported();
+  get patch => unsupported();
+  get origin => unsupported();
+  get defaultImplementation => unsupported();
+
+  bool get isPatched => unsupported();
+  bool get isPatch => unsupported();
+
+  setPatch(patch) => unsupported();
+  computeSignature(compiler) => unsupported();
+  requiredParameterCount(compiler) => unsupported();
+  optionalParameterCount(compiler) => unsupported();
+  parameterCount(compiler) => unsupported();
+
+  // TODO(kasperl): These seem unnecessary.
+  set patch(value) => unsupported();
+  set origin(value) => unsupported();
+  set defaultImplementation(value) => unsupported();
+
+  get redirectionTarget => this;
+
+  getLibrary() => enclosingElement.getLibrary();
+
+  String toString() {
+    String n = name.slowToString();
+    return '<$n: ${messageKind.message(messageArguments)}>';
+  }
+}
+
+/**
+ * An ambiguous element represents multiple elements accessible by the same name.
+ *
+ * Ambiguous elements are created during handling of import/export scopes. If an
+ * ambiguous element is encountered during resolution a warning/error should be
+ * reported.
+ */
+class AmbiguousElementX extends ElementX implements AmbiguousElement {
+  /**
+   * The message to report on resolving this element.
+   */
+  final MessageKind messageKind;
+
+  /**
+   * The message arguments to report on resolving this element.
+   */
+  final Map messageArguments;
+
+  /**
+   * The first element that this ambiguous element might refer to.
+   */
+  final Element existingElement;
+
+  /**
+   * The second element that this ambiguous element might refer to.
+   */
+  final Element newElement;
+
+  AmbiguousElementX(this.messageKind, this.messageArguments,
+      Element enclosingElement, Element existingElement, Element newElement)
+      : this.existingElement = existingElement,
+        this.newElement = newElement,
+        super(existingElement.name, ElementKind.AMBIGUOUS, enclosingElement);
+
+  bool isAmbiguous() => true;
+}
+
+class ScopeX {
+  final Map<SourceString, Element> contents = new Map<SourceString, Element>();
+
+  bool get isEmpty => contents.isEmpty;
+  Iterable<Element> get values => contents.values;
+
+  Element lookup(SourceString name) {
+    return contents[name];
+  }
+
+  void add(Element element, DiagnosticListener listener) {
+    if (element.isAccessor()) {
+      addAccessor(element, contents[element.name], listener);
+    } else {
+      Element existing = contents.putIfAbsent(element.name, () => element);
+      if (!identical(existing, element)) {
+        // TODO(ahe): Do something similar to Resolver.reportErrorWithContext.
+        listener.cancel('duplicate definition', token: element.position());
+        listener.cancel('existing definition', token: existing.position());
+      }
+    }
+  }
+
+  /**
+   * Adds a definition for an [accessor] (getter or setter) to a scope.
+   * The definition binds to an abstract field that can hold both a getter
+   * and a setter.
+   *
+   * The abstract field is added once, for the first getter or setter, and
+   * reused if the other one is also added.
+   * The abstract field should not be treated as a proper member of the
+   * container, it's simply a way to return two results for one lookup.
+   * That is, the getter or setter does not have the abstract field as enclosing
+   * element, they are enclosed by the class or compilation unit, as is the
+   * abstract field.
+   */
+  void addAccessor(Element accessor,
+                   Element existing,
+                   DiagnosticListener listener) {
+    void reportError(Element other) {
+      // TODO(ahe): Do something similar to Resolver.reportErrorWithContext.
+      listener.cancel('duplicate definition of ${accessor.name.slowToString()}',
+                      element: accessor);
+      listener.cancel('existing definition', element: other);
+    }
+
+    if (existing != null) {
+      if (!identical(existing.kind, ElementKind.ABSTRACT_FIELD)) {
+        reportError(existing);
+      } else {
+        AbstractFieldElementX field = existing;
+        if (accessor.isGetter()) {
+          if (field.getter != null && field.getter != accessor) {
+            reportError(field.getter);
+          }
+          field.getter = accessor;
+        } else {
+          assert(accessor.isSetter());
+          if (field.setter != null && field.setter != accessor) {
+            reportError(field.setter);
+          }
+          field.setter = accessor;
+        }
+      }
+    } else {
+      Element container = accessor.getEnclosingClassOrCompilationUnit();
+      AbstractFieldElementX field =
+          new AbstractFieldElementX(accessor.name, container);
+      if (accessor.isGetter()) {
+        field.getter = accessor;
+      } else {
+        field.setter = accessor;
+      }
+      add(field, listener);
+    }
+  }
+}
+
+class CompilationUnitElementX extends ElementX
+    implements CompilationUnitElement {
+  final Script script;
+  PartOf partTag;
+  Link<Element> localMembers = const Link<Element>();
+
+  CompilationUnitElementX(Script script, LibraryElement library)
+    : this.script = script,
+      super(new SourceString(script.name),
+            ElementKind.COMPILATION_UNIT,
+            library) {
+    library.addCompilationUnit(this);
+  }
+
+  void addMember(Element element, DiagnosticListener listener) {
+    // Keep a list of top level members.
+    localMembers = localMembers.prepend(element);
+    // Provide the member to the library to build scope.
+    if (enclosingElement.isPatch) {
+      getImplementationLibrary().addMember(element, listener);
+    } else {
+      getLibrary().addMember(element, listener);
+    }
+  }
+
+  void setPartOf(PartOf tag, DiagnosticListener listener) {
+    LibraryElementX library = enclosingElement;
+    if (library.entryCompilationUnit == this) {
+      listener.reportMessage(
+          listener.spanFromSpannable(tag),
+          MessageKind.ILLEGAL_DIRECTIVE.error(),
+          api.Diagnostic.WARNING);
+      return;
+    }
+    if (!localMembers.isEmpty) {
+      listener.reportErrorCode(tag, MessageKind.BEFORE_TOP_LEVEL);
+      return;
+    }
+    if (partTag != null) {
+      listener.reportMessage(
+          listener.spanFromSpannable(tag),
+          MessageKind.DUPLICATED_PART_OF.error(),
+          api.Diagnostic.WARNING);
+      return;
+    }
+    partTag = tag;
+    LibraryName libraryTag = getLibrary().libraryTag;
+    if (libraryTag != null) {
+      String actualName = tag.name.toString();
+      String expectedName = libraryTag.name.toString();
+      if (expectedName != actualName) {
+        listener.reportMessage(
+            listener.spanFromSpannable(tag.name),
+            MessageKind.LIBRARY_NAME_MISMATCH.error(
+                {'libraryName': expectedName}),
+            api.Diagnostic.WARNING);
+      }
+    }
+  }
+
+  bool get hasMembers => !localMembers.isEmpty;
+}
+
+class LibraryElementX extends ElementX implements LibraryElement {
+  final Uri canonicalUri;
+  CompilationUnitElement entryCompilationUnit;
+  Link<CompilationUnitElement> compilationUnits =
+      const Link<CompilationUnitElement>();
+  Link<LibraryTag> tags = const Link<LibraryTag>();
+  LibraryName libraryTag;
+  bool canUseNative = false;
+  Link<Element> localMembers = const Link<Element>();
+  final ScopeX localScope = new ScopeX();
+
+  /**
+   * If this library is patched, [patch] points to the patch library.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  LibraryElementX patch = null;
+
+  /**
+   * If this is a patch library, [origin] points to the origin library.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  final LibraryElementX origin;
+
+  /**
+   * Map for elements imported through import declarations.
+   *
+   * Addition to the map is performed by [addImport]. Lookup is done trough
+   * [find].
+   */
+  final Map<SourceString, Element> importScope;
+
+  /**
+   * Link for elements exported either through export declarations or through
+   * declaration. This field should not be accessed directly but instead through
+   * the [exports] getter.
+   *
+   * [LibraryDependencyHandler] sets this field through [setExports] when the
+   * library is loaded.
+   */
+  Link<Element> slotForExports;
+
+  LibraryElementX(Script script, [Uri canonicalUri, LibraryElement this.origin])
+    : this.canonicalUri = ((canonicalUri == null) ? script.uri : canonicalUri),
+      importScope = new Map<SourceString, Element>(),
+      super(new SourceString(script.name), ElementKind.LIBRARY, null) {
+    entryCompilationUnit = new CompilationUnitElementX(script, this);
+    if (isPatch) {
+      origin.patch = this;
+    }
+  }
+
+  bool get isPatched => patch != null;
+  bool get isPatch => origin != null;
+
+  LibraryElement get declaration => super.declaration;
+  LibraryElement get implementation => super.implementation;
+
+  CompilationUnitElement getCompilationUnit() => entryCompilationUnit;
+
+  void addCompilationUnit(CompilationUnitElement element) {
+    compilationUnits = compilationUnits.prepend(element);
+  }
+
+  void addTag(LibraryTag tag, DiagnosticListener listener) {
+    tags = tags.prepend(tag);
+  }
+
+  /**
+   * Adds [element] to the import scope of this library.
+   *
+   * If an element by the same name is already in the imported scope, an
+   * [ErroneousElement] will be put in the imported scope, allowing for the
+   * detection of ambiguous uses of imported names.
+   */
+  void addImport(Element element, DiagnosticListener listener) {
+    Element existing = importScope[element.name];
+    if (existing != null) {
+      // TODO(johnniwinther): Provide access to the import tags from which
+      // the elements came.
+      importScope[element.name] = new AmbiguousElementX(
+          MessageKind.DUPLICATE_IMPORT, {'name': element.name},
+          this, existing, element);
+    } else {
+      importScope[element.name] = element;
+    }
+  }
+
+  void addMember(Element element, DiagnosticListener listener) {
+    localMembers = localMembers.prepend(element);
+    addToScope(element, listener);
+  }
+
+  void addToScope(Element element, DiagnosticListener listener) {
+    localScope.add(element, listener);
+  }
+
+  Element localLookup(SourceString elementName) {
+    Element result = localScope.lookup(elementName);
+    if (result == null && isPatch) {
+      result = origin.localLookup(elementName);
+    }
+    return result;
+  }
+
+  /**
+   * Returns [:true:] if the export scope has already been computed for this
+   * library.
+   */
+  bool get exportsHandled => slotForExports != null;
+
+  Link<Element> get exports {
+    assert(invariant(this, exportsHandled,
+                     message: 'Exports not handled on $this'));
+    return slotForExports;
+  }
+
+  /**
+   * Sets the export scope of this library. This method can only be called once.
+   */
+  void setExports(Iterable<Element> exportedElements) {
+    assert(invariant(this, !exportsHandled,
+        message: 'Exports already set to $slotForExports on $this'));
+    assert(invariant(this, exportedElements != null));
+    var builder = new LinkBuilder<Element>();
+    for (Element export in exportedElements) {
+      builder.addLast(export);
+    }
+    slotForExports = builder.toLink();
+  }
+
+  LibraryElement getLibrary() => isPatch ? origin : this;
+
+  /**
+   * Look up a top-level element in this library. The element could
+   * potentially have been imported from another library. Returns
+   * null if no such element exist and an [ErroneousElement] if multiple
+   * elements have been imported.
+   */
+  Element find(SourceString elementName) {
+    Element result = localScope.lookup(elementName);
+    if (result != null) return result;
+    if (origin != null) {
+      result = origin.localScope.lookup(elementName);
+      if (result != null) return result;
+    }
+    result = importScope[elementName];
+    if (result != null) return result;
+    if (origin != null) {
+      result = origin.importScope[elementName];
+      if (result != null) return result;
+    }
+    return null;
+  }
+
+  /** Look up a top-level element in this library, but only look for
+    * non-imported elements. Returns null if no such element exist. */
+  Element findLocal(SourceString elementName) {
+    // TODO(johnniwinther): How to handle injected elements in the patch
+    // library?
+    Element result = localScope.lookup(elementName);
+    if (result == null || result.getLibrary() != this) return null;
+    return result;
+  }
+
+  void forEachExport(f(Element element)) {
+    exports.forEach((Element e) => f(e));
+  }
+
+  void forEachLocalMember(f(Element element)) {
+    if (isPatch) {
+      // Patch libraries traverse both origin and injected members.
+      origin.localMembers.forEach(f);
+
+      void filterPatch(Element element) {
+        if (!element.isPatch) {
+          // Do not traverse the patch members.
+          f(element);
+        }
+      }
+      localMembers.forEach(filterPatch);
+    } else {
+      localMembers.forEach(f);
+    }
+  }
+
+  Iterable<Element> getNonPrivateElementsInScope() {
+    return localScope.values.where((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();
+    });
+  }
+
+  bool hasLibraryName() => libraryTag != null;
+
+  /**
+   * Returns the library name (as defined by the #library tag) or for script
+   * (which have no #library tag) the script file name. The latter case is used
+   * to private 'library name' for scripts to use for instance in dartdoc.
+   */
+  String getLibraryOrScriptName() {
+    if (libraryTag != null) {
+      return libraryTag.name.toString();
+    } else {
+      // Use the file name as script name.
+      String path = canonicalUri.path;
+      return path.substring(path.lastIndexOf('/') + 1);
+    }
+  }
+
+  Scope buildScope() => new LibraryScope(this);
+
+  bool get isPlatformLibrary => canonicalUri.scheme == "dart";
+
+  bool get isInternalLibrary =>
+      isPlatformLibrary && canonicalUri.path.startsWith('_');
+
+  String toString() {
+    if (origin != null) {
+      return 'patch library(${getLibraryOrScriptName()})';
+    } else if (patch != null) {
+      return 'origin library(${getLibraryOrScriptName()})';
+    } else {
+      return 'library(${getLibraryOrScriptName()})';
+    }
+  }
+}
+
+class PrefixElementX extends ElementX implements PrefixElement {
+  Map<SourceString, Element> imported;
+  Token firstPosition;
+
+  PrefixElementX(SourceString prefix, Element enclosing, this.firstPosition)
+      : imported = new Map<SourceString, Element>(),
+        super(prefix, ElementKind.PREFIX, enclosing);
+
+  Element lookupLocalMember(SourceString memberName) => imported[memberName];
+
+  DartType computeType(Compiler compiler) => compiler.types.dynamicType;
+
+  Token position() => firstPosition;
+}
+
+class TypedefElementX extends ElementX implements TypedefElement {
+  Typedef cachedNode;
+  TypedefType cachedType;
+
+  /**
+   * Canonicalize raw version of [cachedType].
+   *
+   * See [ClassElement.rawType] for motivation.
+   *
+   * The [rawType] is computed together with [cachedType] in [computeType].
+   */
+  TypedefType rawType;
+
+  /**
+   * The type annotation which defines this typedef.
+   */
+  DartType alias;
+
+  bool isResolved = false;
+  bool isBeingResolved = false;
+
+  TypedefElementX(SourceString name, Element enclosing)
+      : super(name, ElementKind.TYPEDEF, enclosing);
+
+  /**
+   * Function signature for a typedef of a function type. The signature is
+   * kept to provide full information about parameter names through the mirror
+   * system.
+   *
+   * The [functionSignature] is not available until the typedef element has been
+   * resolved.
+   */
+  FunctionSignature functionSignature;
+
+  TypedefType computeType(Compiler compiler) {
+    if (cachedType != null) return cachedType;
+    Typedef node = parseNode(compiler);
+    Link<DartType> parameters =
+        TypeDeclarationElementX.createTypeVariables(this, node.typeParameters);
+    cachedType = new TypedefType(this, parameters);
+    if (parameters.isEmpty) {
+      rawType = cachedType;
+    } else {
+      var dynamicParameters = const Link<DartType>();
+      parameters.forEach((_) {
+        dynamicParameters =
+            dynamicParameters.prepend(compiler.types.dynamicType);
+      });
+      rawType = new TypedefType(this, dynamicParameters);
+    }
+    compiler.resolveTypedef(this);
+    return cachedType;
+  }
+
+  Link<DartType> get typeVariables => cachedType.typeArguments;
+
+  Scope buildScope() {
+    return new TypeDeclarationScope(enclosingElement.buildScope(), this);
+  }
+}
+
+class VariableElementX extends ElementX implements VariableElement {
+  final VariableListElement variables;
+  Expression cachedNode; // The send or the identifier in the variables list.
+
+  Modifiers get modifiers => variables.modifiers;
+
+  VariableElementX(SourceString name,
+                   VariableListElement variables,
+                   ElementKind kind,
+                   this.cachedNode)
+    : this.variables = variables,
+      super(name, kind, variables.enclosingElement);
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    VariableDefinitions definitions = variables.parseNode(listener);
+    for (Link<Node> link = definitions.definitions.nodes;
+         !link.isEmpty; link = link.tail) {
+      Expression initializedIdentifier = link.head;
+      Identifier identifier = initializedIdentifier.asIdentifier();
+      if (identifier == null) {
+        identifier = initializedIdentifier.asSendSet().selector.asIdentifier();
+      }
+      if (identical(name, identifier.source)) {
+        cachedNode = initializedIdentifier;
+        return cachedNode;
+      }
+    }
+    listener.cancel('internal error: could not find $name', node: variables);
+  }
+
+  DartType computeType(Compiler compiler) {
+    return variables.computeType(compiler);
+  }
+
+  DartType get type => variables.type;
+
+  bool isInstanceMember() => variables.isInstanceMember();
+
+  // Note: cachedNode.getBeginToken() will not be correct in all
+  // cases, for example, for function typed parameters.
+  Token position() => findMyName(variables.position());
+}
+
+/**
+ * Parameters in constructors that directly initialize fields. For example:
+ * [:A(this.field):].
+ */
+class FieldParameterElementX extends VariableElementX
+    implements FieldParameterElement {
+  VariableElement fieldElement;
+
+  FieldParameterElementX(SourceString name,
+                         this.fieldElement,
+                         VariableListElement variables,
+                         Node node)
+      : super(name, variables, ElementKind.FIELD_PARAMETER, node);
+}
+
+// This element represents a list of variable or field declaration.
+// It contains the node, and the type. A [VariableElement] always
+// references its [VariableListElement]. It forwards its
+// [computeType] and [parseNode] methods to this element.
+class VariableListElementX extends ElementX implements VariableListElement {
+  VariableDefinitions cachedNode;
+  DartType type;
+  final Modifiers modifiers;
+
+  /**
+   * Function signature for a variable with a function type. The signature is
+   * kept to provide full information about parameter names through the mirror
+   * system.
+   */
+  FunctionSignature functionSignature;
+
+  VariableListElementX(ElementKind kind,
+                       Modifiers this.modifiers,
+                       Element enclosing)
+    : super(null, kind, enclosing);
+
+  VariableListElementX.node(VariableDefinitions node,
+                            ElementKind kind,
+                            Element enclosing)
+      : super(null, kind, enclosing),
+        this.cachedNode = node,
+        this.modifiers = node.modifiers {
+    assert(modifiers != null);
+  }
+
+  VariableDefinitions parseNode(DiagnosticListener listener) {
+    return cachedNode;
+  }
+
+  DartType computeType(Compiler compiler) {
+    if (type != null) return type;
+    compiler.withCurrentElement(this, () {
+      VariableDefinitions node = parseNode(compiler);
+      if (node.type != null) {
+        type = compiler.resolveTypeAnnotation(this, node.type);
+      } else {
+        // Is node.definitions exactly one FunctionExpression?
+        Link<Node> link = node.definitions.nodes;
+        if (!link.isEmpty &&
+            link.head.asFunctionExpression() != null &&
+            link.tail.isEmpty) {
+          FunctionExpression functionExpression = link.head;
+          // We found exactly one FunctionExpression
+          functionSignature =
+              compiler.resolveFunctionExpression(this, functionExpression);
+          type = compiler.computeFunctionType(compiler.functionClass,
+                                              functionSignature);
+        } else {
+          type = compiler.types.dynamicType;
+        }
+      }
+    });
+    assert(type != null);
+    return type;
+  }
+
+  Token position() => cachedNode.getBeginToken();
+
+  bool isInstanceMember() {
+    return isMember() && !modifiers.isStatic();
+  }
+}
+
+class AbstractFieldElementX extends ElementX implements AbstractFieldElement {
+  FunctionElement getter;
+  FunctionElement setter;
+
+  AbstractFieldElementX(SourceString name, Element enclosing)
+      : super(name, ElementKind.ABSTRACT_FIELD, enclosing);
+
+  DartType computeType(Compiler compiler) {
+    throw "internal error: AbstractFieldElement has no type";
+  }
+
+  Node parseNode(DiagnosticListener listener) {
+    throw "internal error: AbstractFieldElement has no node";
+  }
+
+  position() {
+    // The getter and setter may be defined in two different
+    // compilation units.  However, we know that one of them is
+    // non-null and defined in the same compilation unit as the
+    // abstract element.
+    // TODO(lrn): No we don't know that if the element from the same
+    // compilation unit is patched.
+    //
+    // We need to make sure that the position returned is relative to
+    // the compilation unit of the abstract element.
+    if (getter != null
+        && identical(getter.getCompilationUnit(), getCompilationUnit())) {
+      return getter.position();
+    } else {
+      return setter.position();
+    }
+  }
+
+  Modifiers get modifiers {
+    // The resolver ensures that the flags match (ignoring abstract).
+    if (getter != null) {
+      return new Modifiers.withFlags(
+          getter.modifiers.nodes,
+          getter.modifiers.flags | Modifiers.FLAG_ABSTRACT);
+    } else {
+      return new Modifiers.withFlags(
+          setter.modifiers.nodes,
+          setter.modifiers.flags | Modifiers.FLAG_ABSTRACT);
+    }
+  }
+}
+
+// TODO(johnniwinther): [FunctionSignature] should be merged with
+// [FunctionType].
+class FunctionSignatureX implements FunctionSignature {
+  final Link<Element> requiredParameters;
+  final Link<Element> optionalParameters;
+  final DartType returnType;
+  final int requiredParameterCount;
+  final int optionalParameterCount;
+  final bool optionalParametersAreNamed;
+
+  List<Element> _orderedOptionalParameters;
+
+  FunctionSignatureX(this.requiredParameters,
+                     this.optionalParameters,
+                     this.requiredParameterCount,
+                     this.optionalParameterCount,
+                     this.optionalParametersAreNamed,
+                     this.returnType);
+
+  void forEachRequiredParameter(void function(Element parameter)) {
+    for (Link<Element> link = requiredParameters;
+         !link.isEmpty;
+         link = link.tail) {
+      function(link.head);
+    }
+  }
+
+  void forEachOptionalParameter(void function(Element parameter)) {
+    for (Link<Element> link = optionalParameters;
+         !link.isEmpty;
+         link = link.tail) {
+      function(link.head);
+    }
+  }
+
+  List<Element> get orderedOptionalParameters {
+    if (_orderedOptionalParameters != null) return _orderedOptionalParameters;
+    List<Element> list = new List<Element>.from(optionalParameters);
+    if (optionalParametersAreNamed) {
+      list.sort((Element a, Element b) {
+        return a.name.slowToString().compareTo(b.name.slowToString());
+      });
+    }
+    _orderedOptionalParameters = list;
+    return list;
+  }
+
+  void forEachParameter(void function(Element parameter)) {
+    forEachRequiredParameter(function);
+    forEachOptionalParameter(function);
+  }
+
+  void orderedForEachParameter(void function(Element parameter)) {
+    forEachRequiredParameter(function);
+    orderedOptionalParameters.forEach(function);
+  }
+
+  int get parameterCount => requiredParameterCount + optionalParameterCount;
+}
+
+class FunctionElementX extends ElementX implements FunctionElement {
+  FunctionExpression cachedNode;
+  DartType type;
+  final Modifiers modifiers;
+
+  FunctionSignature functionSignature;
+
+  /**
+   * A function declaration that should be parsed instead of the current one.
+   * The patch should be parsed as if it was in the current scope. Its
+   * signature must match this function's signature.
+   */
+  // TODO(lrn): Consider using [defaultImplementation] to store the patch.
+  FunctionElement patch = null;
+  FunctionElement origin = null;
+
+  /**
+   * If this is a redirecting factory, [defaultImplementation] will be
+   * changed by the resolver to point to the redirection target.  If
+   * this is an interface constructor, [defaultImplementation] will be
+   * changed by the resolver to point to the default implementation.
+   * Otherwise, [:identical(defaultImplementation, this):].
+   */
+  // TODO(ahe): Rename this field to redirectionTarget and remove
+  // mention of interface constructors above.
+  FunctionElement defaultImplementation;
+
+  FunctionElementX(SourceString name,
+                   ElementKind kind,
+                   Modifiers modifiers,
+                   Element enclosing)
+      : this.tooMuchOverloading(name, null, kind, modifiers, enclosing, null);
+
+  FunctionElementX.node(SourceString name,
+                        FunctionExpression node,
+                        ElementKind kind,
+                        Modifiers modifiers,
+                        Element enclosing)
+      : this.tooMuchOverloading(name, node, kind, modifiers, enclosing, null);
+
+  FunctionElementX.from(SourceString name,
+                        FunctionElement other,
+                        Element enclosing)
+      : this.tooMuchOverloading(name, other.cachedNode, other.kind,
+                                other.modifiers, enclosing,
+                                other.functionSignature);
+
+  FunctionElementX.tooMuchOverloading(SourceString name,
+                                      FunctionExpression this.cachedNode,
+                                      ElementKind kind,
+                                      Modifiers this.modifiers,
+                                      Element enclosing,
+                                      FunctionSignature this.functionSignature)
+      : super(name, kind, enclosing) {
+    assert(modifiers != null);
+    defaultImplementation = this;
+  }
+
+  bool get isPatched => patch != null;
+  bool get isPatch => origin != null;
+
+  FunctionElement get redirectionTarget {
+    if (this == defaultImplementation) return this;
+    var target = defaultImplementation;
+    Set<Element> seen = new Set<Element>();
+    seen.add(target);
+    while (!target.isErroneous() && target != target.defaultImplementation) {
+      target = target.defaultImplementation;
+      if (seen.contains(target)) {
+        // TODO(ahe): This is expedient for now, but it should be
+        // checked by the resolver.  Keeping http://dartbug.com/3970
+        // open to track this.
+        throw new SpannableAssertionFailure(
+            target, 'redirecting factory leads to cycle');
+      }
+    }
+    return target;
+  }
+
+  /**
+   * Applies a patch function to this function. The patch function's body
+   * is used as replacement when parsing this function's body.
+   * This method must not be called after the function has been parsed,
+   * and it must be called at most once.
+   */
+  void setPatch(FunctionElement patchElement) {
+    // Sanity checks. The caller must check these things before calling.
+    assert(patch == null);
+    this.patch = patchElement;
+  }
+
+  bool isInstanceMember() {
+    return isMember()
+           && !isConstructor()
+           && !modifiers.isStatic();
+  }
+
+  FunctionSignature computeSignature(Compiler compiler) {
+    if (functionSignature != null) return functionSignature;
+    compiler.withCurrentElement(this, () {
+      functionSignature = compiler.resolveSignature(this);
+    });
+    return functionSignature;
+  }
+
+  int requiredParameterCount(Compiler compiler) {
+    return computeSignature(compiler).requiredParameterCount;
+  }
+
+  int optionalParameterCount(Compiler compiler) {
+    return computeSignature(compiler).optionalParameterCount;
+  }
+
+  int parameterCount(Compiler compiler) {
+    return computeSignature(compiler).parameterCount;
+  }
+
+  FunctionType computeType(Compiler compiler) {
+    if (type != null) return type;
+    type = compiler.computeFunctionType(declaration,
+                                        computeSignature(compiler));
+    return type;
+  }
+
+  FunctionExpression parseNode(DiagnosticListener listener) {
+    if (patch == null) {
+      if (modifiers.isExternal()) {
+        listener.cancel("Compiling external function with no implementation.",
+                        element: this);
+      }
+    }
+    return cachedNode;
+  }
+
+  Token position() => cachedNode.getBeginToken();
+
+  FunctionElement asFunctionElement() => this;
+
+  String toString() {
+    if (isPatch) {
+      return 'patch ${super.toString()}';
+    } else if (isPatched) {
+      return 'origin ${super.toString()}';
+    } else {
+      return super.toString();
+    }
+  }
+
+  bool isAbstract(Compiler compiler) {
+    if (super.isAbstract(compiler)) return true;
+    if (modifiers.isExternal()) return false;
+    if (isFunction() || isAccessor()) {
+      return !parseNode(compiler).hasBody();
+    }
+    return false;
+  }
+}
+
+class ConstructorBodyElementX extends FunctionElementX
+    implements ConstructorBodyElement {
+  FunctionElement constructor;
+
+  ConstructorBodyElementX(FunctionElement constructor)
+      : this.constructor = constructor,
+        super(constructor.name,
+              ElementKind.GENERATIVE_CONSTRUCTOR_BODY,
+              Modifiers.EMPTY,
+              constructor.enclosingElement) {
+    functionSignature = constructor.functionSignature;
+  }
+
+  bool isInstanceMember() => true;
+
+  FunctionType computeType(Compiler compiler) {
+    compiler.reportFatalError('Internal error: $this.computeType', this);
+  }
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = constructor.parseNode(listener);
+    assert(cachedNode != null);
+    return cachedNode;
+  }
+
+  Token position() => constructor.position();
+}
+
+class SynthesizedConstructorElementX extends FunctionElementX {
+  SynthesizedConstructorElementX(Element enclosing)
+    : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR,
+            Modifiers.EMPTY, enclosing);
+
+  SynthesizedConstructorElementX.forDefault(Element enclosing,
+                                            Compiler compiler)
+    : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR,
+            Modifiers.EMPTY, enclosing) {
+    type = new FunctionType(this,
+        compiler.types.voidType,
+        const Link<DartType>(),
+        const Link<DartType>(),
+        const Link<SourceString>(),
+        const Link<DartType>());
+    cachedNode = new FunctionExpression(
+        new Identifier(enclosing.position()),
+        new NodeList.empty(),
+        new Block(new NodeList.empty()),
+        null, Modifiers.EMPTY, null, null);
+  }
+
+  bool get isSynthesized => true;
+
+  Token position() => enclosingElement.position();
+}
+
+class VoidElementX extends ElementX {
+  VoidElementX(Element enclosing)
+      : super(const SourceString('void'), ElementKind.VOID, enclosing);
+  DartType computeType(compiler) => compiler.types.voidType;
+  Node parseNode(_) {
+    throw 'internal error: parseNode on void';
+  }
+  bool impliesType() => true;
+}
+
+class TypeDeclarationElementX {
+  /**
+   * Creates the type variables, their type and corresponding element, for the
+   * type variables declared in [parameter] on [element]. The bounds of the type
+   * variables are not set until [element] has been resolved.
+   */
+  static Link<DartType> createTypeVariables(TypeDeclarationElement element,
+                                            NodeList parameters) {
+    if (parameters == null) return const Link<DartType>();
+
+    // Create types and elements for type variable.
+    var arguments = new LinkBuilder<DartType>();
+    for (Link link = parameters.nodes; !link.isEmpty; link = link.tail) {
+      TypeVariable node = link.head;
+      SourceString variableName = node.name.source;
+      TypeVariableElement variableElement =
+          new TypeVariableElementX(variableName, element, node);
+      TypeVariableType variableType = new TypeVariableType(variableElement);
+      variableElement.type = variableType;
+      arguments.addLast(variableType);
+    }
+    return arguments.toLink();
+  }
+}
+
+abstract class BaseClassElementX extends ElementX implements ClassElement {
+  final int id;
+
+  /**
+   * The type of [:this:] for this class declaration.
+   *
+   * The type of [:this:] is the interface type based on this element in which
+   * the type arguments are the declared type variables. For instance,
+   * [:List<E>:] for [:List:] and [:Map<K,V>:] for [:Map:].
+   *
+   * This type is computed in [computeType].
+   */
+  InterfaceType thisType;
+
+  /**
+   * The raw type for this class declaration.
+   *
+   * The raw type is the interface type base on this element in which the type
+   * arguments are all [dynamic]. For instance [:List<dynamic>:] for [:List:]
+   * and [:Map<dynamic,dynamic>:] for [:Map:]. For non-generic classes [rawType]
+   * is the same as [thisType].
+   *
+   * The [rawType] field is a canonicalization of the raw type and should be
+   * used to distinguish explicit and implicit uses of the [dynamic]
+   * type arguments. For instance should [:List:] be the [rawType] of the
+   * [:List:] class element whereas [:List<dynamic>:] should be its own
+   * instantiation of [InterfaceType] with [:dynamic:] as type argument. Using
+   * this distinction, we can print the raw type with type arguments only when
+   * the input source has used explicit type arguments.
+   *
+   * This type is computed together with [thisType] in [computeType].
+   */
+  InterfaceType rawType;
+  DartType supertype;
+  DartType defaultClass;
+  Link<DartType> interfaces;
+  SourceString nativeTagInfo;
+  int supertypeLoadState;
+  int resolutionState;
+
+  // backendMembers are members that have been added by the backend to simplify
+  // compilation. They don't have any user-side counter-part.
+  Link<Element> backendMembers = const Link<Element>();
+
+  Link<DartType> allSupertypes;
+
+  BaseClassElementX(SourceString name,
+                    Element enclosing,
+                    this.id,
+                    int initialState)
+      : supertypeLoadState = initialState,
+        resolutionState = initialState,
+        super(name, ElementKind.CLASS, enclosing);
+
+  int get hashCode => id;
+  ClassElement get patch => super.patch;
+  ClassElement get origin => super.origin;
+  ClassElement get declaration => super.declaration;
+  ClassElement get implementation => super.implementation;
+
+  bool get hasBackendMembers => !backendMembers.isEmpty;
+
+  InterfaceType computeType(Compiler compiler) {
+    if (thisType == null) {
+      if (origin == null) {
+        Link<DartType> parameters = computeTypeParameters(compiler);
+        thisType = new InterfaceType(this, parameters);
+        if (parameters.isEmpty) {
+          rawType = thisType;
+        } else {
+          var dynamicParameters = const Link<DartType>();
+          parameters.forEach((_) {
+            dynamicParameters =
+                dynamicParameters.prepend(compiler.types.dynamicType);
+          });
+          rawType = new InterfaceType(this, dynamicParameters);
+        }
+      } else {
+        thisType = origin.computeType(compiler);
+        rawType = origin.rawType;
+      }
+    }
+    return thisType;
+  }
+
+  Link<DartType> computeTypeParameters(Compiler compiler);
+
+  /**
+   * Return [:true:] if this element is the [:Object:] class for the [compiler].
+   */
+  bool isObject(Compiler compiler) =>
+      identical(declaration, compiler.objectClass);
+
+  Link<DartType> get typeVariables => thisType.typeArguments;
+
+  ClassElement ensureResolved(Compiler compiler) {
+    if (resolutionState == STATE_NOT_STARTED) {
+      compiler.resolver.resolveClass(this);
+    }
+    return this;
+  }
+
+  void addDefaultConstructorIfNeeded(Compiler compiler) {
+    if (hasConstructor) return;
+    FunctionElement constructor =
+        new SynthesizedConstructorElementX.forDefault(this, compiler);
+    setDefaultConstructor(constructor, compiler);
+  }
+
+  void setDefaultConstructor(FunctionElement constructor, Compiler compiler);
+
+  void addBackendMember(Element member) {
+    backendMembers = backendMembers.prepend(member);
+  }
+
+  void reverseBackendMembers() {
+    backendMembers = backendMembers.reverse();
+  }
+
+  /**
+   * Lookup local members in the class. This will ignore constructors.
+   */
+  Element lookupLocalMember(SourceString memberName) {
+    var result = localLookup(memberName);
+    if (result != null && result.isConstructor()) return null;
+    return result;
+  }
+
+  /// Lookup a synthetic element created by the backend.
+  Element lookupBackendMember(SourceString memberName) {
+    for (Element element in backendMembers) {
+      if (element.name == memberName) {
+        return element;
+      }
+    }
+  }
+  /**
+   * Lookup super members for the class. This will ignore constructors.
+   */
+  Element lookupSuperMember(SourceString memberName) {
+    return lookupSuperMemberInLibrary(memberName, getLibrary());
+  }
+
+  /**
+   * Lookup super members for the class that is accessible in [library].
+   * This will ignore constructors.
+   */
+  Element lookupSuperMemberInLibrary(SourceString memberName,
+                                     LibraryElement library) {
+    bool includeInjectedMembers = isPatch;
+    bool isPrivate = memberName.isPrivate();
+    for (ClassElement s = superclass; s != null; s = s.superclass) {
+      // Private members from a different library are not visible.
+      if (isPrivate && !identical(library, s.getLibrary())) continue;
+      s = includeInjectedMembers ? s.implementation : s;
+      Element e = s.lookupLocalMember(memberName);
+      if (e == null) continue;
+      // Static members are not inherited.
+      if (e.modifiers.isStatic()) continue;
+      return e;
+    }
+    if (isInterface()) {
+      return lookupSuperInterfaceMember(memberName, getLibrary());
+    }
+    return null;
+  }
+
+  Element lookupSuperInterfaceMember(SourceString memberName,
+                                     LibraryElement fromLibrary) {
+    bool includeInjectedMembers = isPatch;
+    bool isPrivate = memberName.isPrivate();
+    for (InterfaceType t in interfaces) {
+      ClassElement cls = t.element;
+      cls = includeInjectedMembers ? cls.implementation : cls;
+      Element e = cls.lookupLocalMember(memberName);
+      if (e == null) continue;
+      // Private members from a different library are not visible.
+      if (isPrivate && !identical(fromLibrary, e.getLibrary())) continue;
+      // Static members are not inherited.
+      if (e.modifiers.isStatic()) continue;
+      return e;
+    }
+    return null;
+  }
+
+  /**
+   * Find the first member in the class chain with the given [selector].
+   *
+   * This method is NOT to be used for resolving
+   * unqualified sends because it does not implement the scoping
+   * rules, where library scope comes before superclass scope.
+   *
+   * When called on the implementation element both members declared in the
+   * origin and the patch class are returned.
+   */
+  Element lookupSelector(Selector selector) {
+    SourceString memberName = selector.name;
+    LibraryElement library = selector.library;
+    Element localMember = lookupLocalMember(memberName);
+    if (localMember != null &&
+        (!memberName.isPrivate() || getLibrary() == library)) {
+      return localMember;
+    }
+    return lookupSuperMemberInLibrary(memberName, library);
+  }
+
+  /**
+   * Find the first member in the class chain with the given
+   * [memberName]. This method is NOT to be used for resolving
+   * unqualified sends because it does not implement the scoping
+   * rules, where library scope comes before superclass scope.
+   */
+  Element lookupMember(SourceString memberName) {
+    Element localMember = lookupLocalMember(memberName);
+    return localMember == null ? lookupSuperMember(memberName) : localMember;
+  }
+
+  /**
+   * Returns true if the [fieldMember] is shadowed by another field. The given
+   * [fieldMember] must be a member of this class.
+   *
+   * This method also works if the [fieldMember] is private.
+   */
+  bool isShadowedByField(Element fieldMember) {
+    assert(fieldMember.isField());
+    // Note that we cannot use [lookupMember] or [lookupSuperMember] since it
+    // will not do the right thing for private elements.
+    ClassElement lookupClass = this;
+    LibraryElement memberLibrary = fieldMember.getLibrary();
+    if (fieldMember.name.isPrivate()) {
+      // We find a super class in the same library as the field. This way the
+      // lookupMember will work.
+      while (lookupClass.getLibrary() != memberLibrary) {
+        lookupClass = lookupClass.superclass;
+      }
+    }
+    SourceString fieldName = fieldMember.name;
+    while (true) {
+      Element foundMember = lookupClass.lookupMember(fieldName);
+      if (foundMember == fieldMember) return false;
+      if (foundMember.isField()) return true;
+      lookupClass = foundMember.getEnclosingClass().superclass;
+    }
+  }
+
+  Element validateConstructorLookupResults(Selector selector,
+                                           Element result,
+                                           Element noMatch(Element)) {
+    if (result == null
+        || !result.isConstructor()
+        || (selector.name.isPrivate()
+            && result.getLibrary() != selector.library)) {
+      result = noMatch != null ? noMatch(result) : null;
+    }
+    return result;
+  }
+
+  // TODO(aprelev@gmail.com): Peter believes that it would be great to
+  // make noMatch a required argument. Peter's suspicion is that most
+  // callers of this method would benefit from using the noMatch method.
+  Element lookupConstructor(Selector selector, [Element noMatch(Element)]) {
+    SourceString normalizedName;
+    SourceString className = this.name;
+    SourceString constructorName = selector.name;
+    if (constructorName != const SourceString('')) {
+      normalizedName = Elements.constructConstructorName(className,
+                                                         constructorName);
+    } else {
+      normalizedName = className;
+    }
+    Element result = localLookup(normalizedName);
+    return validateConstructorLookupResults(selector, result, noMatch);
+  }
+
+  Element lookupFactoryConstructor(Selector selector,
+                                   [Element noMatch(Element)]) {
+    SourceString constructorName = selector.name;
+    Element result = localLookup(constructorName);
+    return validateConstructorLookupResults(selector, result, noMatch);
+  }
+
+  Link<Element> get constructors {
+    // TODO(ajohnsen): See if we can avoid this method at some point.
+    Link<Element> result = const Link<Element>();
+    // TODO(johnniwinther): Should we include injected constructors?
+    forEachMember((_, Element member) {
+      if (member.isConstructor()) result = result.prepend(member);
+    });
+    return result;
+  }
+
+  /**
+   * Returns the super class, if any.
+   *
+   * The returned element may not be resolved yet.
+   */
+  ClassElement get superclass {
+    assert(supertypeLoadState == STATE_DONE);
+    return supertype == null ? null : supertype.element;
+  }
+
+  /**
+   * Runs through all members of this class.
+   *
+   * The enclosing class is passed to the callback. This is useful when
+   * [includeSuperMembers] is [:true:].
+   *
+   * When called on an implementation element both the members in the origin
+   * and patch class are included.
+   */
+  // TODO(johnniwinther): Clean up lookup to get rid of the include predicates.
+  void forEachMember(void f(ClassElement enclosingClass, Element member),
+                     {includeBackendMembers: false,
+                      includeSuperMembers: false}) {
+    bool includeInjectedMembers = isPatch;
+    Set<ClassElement> seen = new Set<ClassElement>();
+    ClassElement classElement = declaration;
+    do {
+      if (seen.contains(classElement)) return;
+      seen.add(classElement);
+
+      // Iterate through the members in textual order, which requires
+      // to reverse the data structure [localMembers] we created.
+      // Textual order may be important for certain operations, for
+      // example when emitting the initializers of fields.
+      classElement.forEachLocalMember((e) => f(classElement, e));
+      if (includeBackendMembers) {
+        classElement.forEachBackendMember((e) => f(classElement, e));
+      }
+      if (includeInjectedMembers) {
+        if (classElement.patch != null) {
+          classElement.patch.forEachLocalMember((e) {
+            if (!e.isPatch) f(classElement, e);
+          });
+        }
+      }
+      classElement = includeSuperMembers ? classElement.superclass : null;
+    } while(classElement != null);
+  }
+
+  /**
+   * Runs through all instance-field members of this class.
+   *
+   * The enclosing class is passed to the callback. This is useful when
+   * [includeSuperMembers] is [:true:].
+   *
+   * When [includeBackendMembers] and [includeSuperMembers] are both [:true:]
+   * then the fields are visited in the same order as they need to be given
+   * to the JavaScript constructor.
+   *
+   * When called on the implementation element both the fields declared in the
+   * origin and in the patch are included.
+   */
+  void forEachInstanceField(void f(ClassElement enclosingClass, Element field),
+                            {includeBackendMembers: false,
+                             includeSuperMembers: false}) {
+    // Filters so that [f] is only invoked with instance fields.
+    void fieldFilter(ClassElement enclosingClass, Element member) {
+      if (member.isInstanceMember() && member.kind == ElementKind.FIELD) {
+        f(enclosingClass, member);
+      }
+    }
+
+    forEachMember(fieldFilter,
+                  includeBackendMembers: includeBackendMembers,
+                  includeSuperMembers: includeSuperMembers);
+  }
+
+  void forEachBackendMember(void f(Element member)) {
+    backendMembers.forEach(f);
+  }
+
+  bool implementsInterface(ClassElement intrface) {
+    for (DartType implementedInterfaceType in allSupertypes) {
+      ClassElement implementedInterface = implementedInterfaceType.element;
+      if (identical(implementedInterface, intrface)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if [this] is a subclass of [cls].
+   *
+   * This method is not to be used for checking type hierarchy and
+   * assignments, because it does not take parameterized types into
+   * account.
+   */
+  bool isSubclassOf(ClassElement cls) {
+    // Use [declaration] for both [this] and [cls], because
+    // declaration classes hold the superclass hierarchy.
+    cls = cls.declaration;
+    for (ClassElement s = declaration; s != null; s = s.superclass) {
+      if (identical(s, cls)) return true;
+    }
+    return false;
+  }
+
+  bool isInterface() => false;
+  bool isNative() => nativeTagInfo != null;
+  void setNative(String name) {
+    nativeTagInfo = new SourceString(name);
+  }
+}
+
+abstract class ClassElementX extends BaseClassElementX {
+  // Lazily applied patch of class members.
+  ClassElement patch = null;
+  ClassElement origin = null;
+
+  Link<Element> localMembers = const Link<Element>();
+  final ScopeX localScope = new ScopeX();
+
+  ClassElementX(SourceString name, Element enclosing, int id, int initialState)
+      : super(name, enclosing, id, initialState);
+
+  ClassNode parseNode(Compiler compiler);
+
+  bool get isMixinApplication => false;
+  bool get isPatched => patch != null;
+  bool get isPatch => origin != null;
+  bool get hasLocalScopeMembers => !localScope.isEmpty;
+
+  void addMember(Element element, DiagnosticListener listener) {
+    localMembers = localMembers.prepend(element);
+    addToScope(element, listener);
+  }
+
+  void addToScope(Element element, DiagnosticListener listener) {
+    localScope.add(element, listener);
+  }
+
+  Element localLookup(SourceString elementName) {
+    Element result = localScope.lookup(elementName);
+    if (result == null && isPatch) {
+      result = origin.localLookup(elementName);
+    }
+    return result;
+  }
+
+  void forEachLocalMember(void f(Element member)) {
+    localMembers.reverse().forEach(f);
+  }
+
+  bool get hasConstructor {
+    // Search in scope to be sure we search patched constructors.
+    for (var element in localScope.values) {
+      if (element.isConstructor()) return true;
+    }
+    return false;
+  }
+
+  void setDefaultConstructor(FunctionElement constructor, Compiler compiler) {
+    addToScope(constructor, compiler);
+  }
+
+  Link<DartType> computeTypeParameters(Compiler compiler) {
+    ClassNode node = parseNode(compiler);
+    return TypeDeclarationElementX.createTypeVariables(
+        this, node.typeParameters);
+  }
+
+  Scope buildScope() => new ClassScope(enclosingElement.buildScope(), this);
+
+  String toString() {
+    if (origin != null) {
+      return 'patch ${super.toString()}';
+    } else if (patch != null) {
+      return 'origin ${super.toString()}';
+    } else {
+      return super.toString();
+    }
+  }
+}
+
+class MixinApplicationElementX extends BaseClassElementX
+    implements MixinApplicationElement {
+  final Node node;
+  final Modifiers modifiers;
+
+  FunctionElement constructor;
+  ClassElement mixin;
+
+  // TODO(kasperl): The analyzer complains when I don't have these two
+  // fields. This is pretty weird. I cannot replace them with getters.
+  final ClassElement patch = null;
+  final ClassElement origin = null;
+
+  MixinApplicationElementX(SourceString name, Element enclosing, int id,
+                           this.node, this.modifiers)
+      : super(name, enclosing, id, STATE_NOT_STARTED);
+
+  bool get isMixinApplication => true;
+  bool get hasConstructor => constructor != null;
+  bool get hasLocalScopeMembers => false;
+
+  Token position() => node.getBeginToken();
+
+  Node parseNode(DiagnosticListener listener) => node;
+
+  Element localLookup(SourceString name) {
+    if (this.name == name) return constructor;
+    if (mixin == null) return null;
+    Element mixedInElement = mixin.localLookup(name);
+    if (mixedInElement == null) return null;
+    return mixedInElement.isInstanceMember() ? mixedInElement : null;
+  }
+
+  void forEachLocalMember(void f(Element member)) {
+    if (mixin != null) mixin.forEachLocalMember((Element mixedInElement) {
+      if (mixedInElement.isInstanceMember()) f(mixedInElement);
+    });
+  }
+
+  void addMember(Element element, DiagnosticListener listener) {
+    throw new UnsupportedError("cannot add member to $this");
+  }
+
+  void addToScope(Element element, DiagnosticListener listener) {
+    throw new UnsupportedError("cannot add to scope of $this");
+  }
+
+  void setDefaultConstructor(FunctionElement constructor, Compiler compiler) {
+    assert(!hasConstructor);
+    this.constructor = constructor;
+  }
+
+  Link<DartType> computeTypeParameters(Compiler compiler) {
+    NamedMixinApplication named = node.asNamedMixinApplication();
+    if (named == null) return const Link<DartType>();
+    return TypeDeclarationElementX.createTypeVariables(
+        this, named.typeParameters);
+  }
+}
+
+class LabelElementX extends ElementX implements LabelElement {
+
+  // We store the original label here so it can be returned by [parseNode].
+  final Label label;
+  final String labelName;
+  final TargetElement target;
+  bool isBreakTarget = false;
+  bool isContinueTarget = false;
+  LabelElementX(Label label, String labelName, this.target,
+                Element enclosingElement)
+      : this.label = label,
+        this.labelName = labelName,
+        // In case of a synthetic label, just use [labelName] for
+        // identifying the element.
+        super(label == null
+                  ? new SourceString(labelName)
+                  : label.identifier.source,
+              ElementKind.LABEL,
+              enclosingElement);
+
+  void setBreakTarget() {
+    isBreakTarget = true;
+    target.isBreakTarget = true;
+  }
+  void setContinueTarget() {
+    isContinueTarget = true;
+    target.isContinueTarget = true;
+  }
+
+  bool get isTarget => isBreakTarget || isContinueTarget;
+  Node parseNode(DiagnosticListener l) => label;
+
+  Token position() => label.getBeginToken();
+  String toString() => "${labelName}:";
+}
+
+// Represents a reference to a statement or switch-case, either by label or the
+// default target of a break or continue.
+class TargetElementX extends ElementX implements TargetElement {
+  final Node statement;
+  final int nestingLevel;
+  Link<LabelElement> labels = const Link<LabelElement>();
+  bool isBreakTarget = false;
+  bool isContinueTarget = false;
+
+  TargetElementX(this.statement, this.nestingLevel, Element enclosingElement)
+      : super(const SourceString(""), ElementKind.STATEMENT, enclosingElement);
+  bool get isTarget => isBreakTarget || isContinueTarget;
+
+  LabelElement addLabel(Label label, String labelName) {
+    LabelElement result = new LabelElementX(label, labelName, this,
+                                            enclosingElement);
+    labels = labels.prepend(result);
+    return result;
+  }
+
+  Node parseNode(DiagnosticListener l) => statement;
+
+  bool get isSwitch => statement is SwitchStatement;
+
+  Token position() => statement.getBeginToken();
+  String toString() => statement.toString();
+}
+
+class TypeVariableElementX extends ElementX implements TypeVariableElement {
+  final Node cachedNode;
+  TypeVariableType type;
+  DartType bound;
+
+  TypeVariableElementX(name, Element enclosing, this.cachedNode,
+                       [this.type, this.bound])
+    : super(name, ElementKind.TYPE_VARIABLE, enclosing);
+
+  TypeVariableType computeType(compiler) => type;
+
+  Node parseNode(compiler) => cachedNode;
+
+  String toString() => "${enclosingElement.toString()}.${name.slowToString()}";
+
+  Token position() => cachedNode.getBeginToken();
+}
+
+/**
+ * A single metadata annotation.
+ *
+ * For example, consider:
+ *
+ * [:
+ * class Data {
+ *   const Data();
+ * }
+ *
+ * const data = const Data();
+ *
+ * @data
+ * class Foo {}
+ *
+ * @data @data
+ * class Bar {}
+ * :]
+ *
+ * In this example, there are three instances of [MetadataAnnotation]
+ * and they correspond each to a location in the source code where
+ * there is an at-sign, '@'. The [value] of each of these instances
+ * are the same compile-time constant, [: const Data() :].
+ *
+ * The mirror system does not have a concept matching this class.
+ */
+abstract class MetadataAnnotationX implements MetadataAnnotation {
+  /**
+   * The compile-time constant which this annotation resolves to.
+   * In the mirror system, this would be an object mirror.
+   */
+  Constant get value;
+  Element annotatedElement;
+  int resolutionState;
+
+  /**
+   * The beginning token of this annotation, or [:null:] if it is synthetic.
+   */
+  Token get beginToken;
+
+  MetadataAnnotationX([this.resolutionState = STATE_NOT_STARTED]);
+
+  MetadataAnnotation ensureResolved(Compiler compiler) {
+    if (resolutionState == STATE_NOT_STARTED) {
+      compiler.resolver.resolveMetadataAnnotation(this);
+    }
+    return this;
+  }
+
+  String toString() => 'MetadataAnnotation($value, $resolutionState)';
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/enqueue.dart b/pkgs/markdown/lib/src/compiler/implementation/enqueue.dart
new file mode 100644
index 0000000..4a4fea8
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/enqueue.dart
@@ -0,0 +1,552 @@
+// 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.
+
+part of dart2js;
+
+class EnqueueTask extends CompilerTask {
+  final ResolutionEnqueuer resolution;
+  final CodegenEnqueuer codegen;
+
+  String get name => 'Enqueue';
+
+  EnqueueTask(Compiler compiler)
+    : resolution = new ResolutionEnqueuer(
+          compiler, compiler.backend.createItemCompilationContext),
+      codegen = new CodegenEnqueuer(
+          compiler, compiler.backend.createItemCompilationContext),
+      super(compiler) {
+    codegen.task = this;
+    resolution.task = this;
+
+    codegen.nativeEnqueuer = compiler.backend.nativeCodegenEnqueuer(codegen);
+    resolution.nativeEnqueuer =
+        compiler.backend.nativeResolutionEnqueuer(resolution);
+  }
+}
+
+abstract class Enqueuer {
+  final String name;
+  final Compiler compiler; // TODO(ahe): Remove this dependency.
+  final Function itemCompilationContextCreator;
+  final Map<String, Link<Element>> instanceMembersByName;
+  final Set<ClassElement> seenClasses;
+  final Universe universe;
+
+  bool queueIsClosed = false;
+  EnqueueTask task;
+  native.NativeEnqueuer nativeEnqueuer;  // Set by EnqueueTask
+
+  Enqueuer(this.name, this.compiler,
+           ItemCompilationContext itemCompilationContextCreator())
+    : this.itemCompilationContextCreator = itemCompilationContextCreator,
+      instanceMembersByName = new Map<String, Link<Element>>(),
+      universe = new Universe(),
+      seenClasses = new Set<ClassElement>();
+
+  /// Returns [:true:] if this enqueuer is the resolution enqueuer.
+  bool get isResolutionQueue => false;
+
+  /// Returns [:true:] if [member] has been processed by this enqueuer.
+  bool isProcessed(Element member);
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void addToWorkList(Element element, [TreeElements elements]) {
+    assert(invariant(element, element.isDeclaration));
+    if (element.isForeign(compiler)) return;
+
+    if (!addElementToWorkList(element, elements)) return;
+
+    // Enable runtime type support if we discover a getter called runtimeType.
+    // We have to enable runtime type before hitting the codegen, so
+    // that constructors know whether they need to generate code for
+    // runtime type.
+    if (element.isGetter() && element.name == Compiler.RUNTIME_TYPE) {
+      compiler.enabledRuntimeType = true;
+    } else if (element == compiler.functionApplyMethod) {
+      compiler.enabledFunctionApply = true;
+    } else if (element == compiler.invokeOnMethod) {
+      compiler.enabledInvokeOn = true;
+    }
+
+    nativeEnqueuer.registerElement(element);
+  }
+
+  /**
+   * Adds [element] to the work list if it has not already been processed.
+   *
+   * Returns [:true:] if the [element] should be processed.
+   */
+  // TODO(johnniwinther): Change to 'Returns true if the element was added to
+  // the work list'?
+  bool addElementToWorkList(Element element, [TreeElements elements]);
+
+  void registerInstantiatedClass(ClassElement cls) {
+    if (universe.instantiatedClasses.contains(cls)) return;
+    if (!cls.isAbstract(compiler)) {
+      universe.instantiatedClasses.add(cls);
+      onRegisterInstantiatedClass(cls);
+    }
+    compiler.backend.registerInstantiatedClass(cls, this);
+  }
+
+  bool checkNoEnqueuedInvokedInstanceMethods() {
+    task.measure(() {
+      // Run through the classes and see if we need to compile methods.
+      for (ClassElement classElement in universe.instantiatedClasses) {
+        for (ClassElement currentClass = classElement;
+             currentClass != null;
+             currentClass = currentClass.superclass) {
+          processInstantiatedClass(currentClass);
+        }
+      }
+    });
+    return true;
+  }
+
+  void processInstantiatedClass(ClassElement cls) {
+    cls.implementation.forEachMember(processInstantiatedClassMember);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   */
+  void processInstantiatedClassMember(ClassElement cls, Element member) {
+    assert(invariant(member, member.isDeclaration));
+    if (isProcessed(member)) return;
+    if (!member.isInstanceMember()) return;
+    if (member.isField()) {
+      // Native fields need to go into instanceMembersByName as they are virtual
+      // instantiation points and escape points.
+      // Test the enclosing class, since the metadata has not been parsed yet.
+      if (!member.enclosingElement.isNative()) return;
+    }
+
+    String memberName = member.name.slowToString();
+    Link<Element> members = instanceMembersByName.putIfAbsent(
+        memberName, () => const Link<Element>());
+    instanceMembersByName[memberName] = members.prepend(member);
+
+    if (member.kind == ElementKind.FUNCTION) {
+      if (member.name == Compiler.NO_SUCH_METHOD) {
+        enableNoSuchMethod(member);
+      }
+      if (universe.hasInvocation(member, compiler)) {
+        return addToWorkList(member);
+      }
+      // If there is a property access with the same name as a method we
+      // need to emit the method.
+      if (universe.hasInvokedGetter(member, compiler)) {
+        // We will emit a closure, so make sure the closure class is
+        // generated.
+        compiler.closureClass.ensureResolved(compiler);
+        registerInstantiatedClass(compiler.closureClass);
+        return addToWorkList(member);
+      }
+    } else if (member.kind == ElementKind.GETTER) {
+      if (universe.hasInvokedGetter(member, compiler)) {
+        return addToWorkList(member);
+      }
+      // We don't know what selectors the returned closure accepts. If
+      // the set contains any selector we have to assume that it matches.
+      if (universe.hasInvocation(member, compiler)) {
+        return addToWorkList(member);
+      }
+    } else if (member.kind == ElementKind.SETTER) {
+      if (universe.hasInvokedSetter(member, compiler)) {
+        return addToWorkList(member);
+      }
+    } else if (member.kind == ElementKind.FIELD &&
+               member.enclosingElement.isNative()) {
+      nativeEnqueuer.handleFieldAnnotations(member);
+      if (universe.hasInvokedGetter(member, compiler) ||
+          universe.hasInvocation(member, compiler)) {
+        nativeEnqueuer.registerFieldLoad(member);
+        // In handleUnseenSelector we can't tell if the field is loaded or
+        // stored.  We need the basic algorithm to be Church-Rosser, since the
+        // resolution 'reduction' order is different to the codegen order. So
+        // register that the field is also stored.  In other words: if we don't
+        // register the store here during resolution, the store could be
+        // registered during codegen on the handleUnseenSelector path, and cause
+        // the set of codegen elements to include unresolved elements.
+        nativeEnqueuer.registerFieldStore(member);
+      }
+      if (universe.hasInvokedSetter(member, compiler)) {
+        nativeEnqueuer.registerFieldStore(member);
+        // See comment after registerFieldLoad above.
+        nativeEnqueuer.registerFieldLoad(member);
+      }
+    }
+  }
+
+  void enableNoSuchMethod(Element element) {}
+
+  void onRegisterInstantiatedClass(ClassElement cls) {
+    task.measure(() {
+      // The class must be resolved to compute the set of all
+      // supertypes.
+      cls.ensureResolved(compiler);
+
+      void processClass(ClassElement cls) {
+        if (seenClasses.contains(cls)) return;
+
+        seenClasses.add(cls);
+        cls.ensureResolved(compiler);
+        cls.implementation.forEachMember(processInstantiatedClassMember);
+        if (isResolutionQueue) {
+          compiler.resolver.checkClass(cls);
+        }
+
+        if (compiler.enableTypeAssertions) {
+          // We need to register is checks and helpers for checking
+          // assignments to fields.
+          // TODO(ngeoffray): This should really move to the backend.
+          cls.forEachLocalMember((Element member) {
+            if (!member.isInstanceMember() || !member.isField()) return;
+            DartType type = member.computeType(compiler);
+            registerIsCheck(type);
+            SourceString helper = compiler.backend.getCheckedModeHelper(type);
+            if (helper != null) {
+              Element helperElement = compiler.findHelper(helper);
+              registerStaticUse(helperElement);
+            }
+          });
+        }
+      }
+      processClass(cls);
+      for (Link<DartType> supertypes = cls.allSupertypes;
+           !supertypes.isEmpty; supertypes = supertypes.tail) {
+        processClass(supertypes.head.element);
+      }
+    });
+  }
+
+  void registerNewSelector(SourceString name,
+                           Selector selector,
+                           Map<SourceString, Set<Selector>> selectorsMap) {
+    if (name != selector.name) {
+      String message = "$name != ${selector.name} (${selector.kind})";
+      compiler.internalError("Wrong selector name: $message.");
+    }
+    Set<Selector> selectors =
+        selectorsMap.putIfAbsent(name, () => new Set<Selector>());
+    if (!selectors.contains(selector)) {
+      selectors.add(selector);
+      handleUnseenSelector(name, selector);
+    }
+  }
+
+  void registerInvocation(SourceString methodName, Selector selector) {
+    task.measure(() {
+      registerNewSelector(methodName, selector, universe.invokedNames);
+    });
+  }
+
+  void registerInvokedGetter(SourceString getterName, Selector selector) {
+    task.measure(() {
+      registerNewSelector(getterName, selector, universe.invokedGetters);
+    });
+  }
+
+  void registerInvokedSetter(SourceString setterName, Selector selector) {
+    task.measure(() {
+      registerNewSelector(setterName, selector, universe.invokedSetters);
+    });
+  }
+
+  processInstanceMembers(SourceString n, bool f(Element e)) {
+    String memberName = n.slowToString();
+    Link<Element> members = instanceMembersByName[memberName];
+    if (members != null) {
+      LinkBuilder<Element> remaining = new LinkBuilder<Element>();
+      for (; !members.isEmpty; members = members.tail) {
+        if (!f(members.head)) remaining.addLast(members.head);
+      }
+      instanceMembersByName[memberName] = remaining.toLink();
+    }
+  }
+
+  void handleUnseenSelector(SourceString methodName, Selector selector) {
+    processInstanceMembers(methodName, (Element member) {
+      if (selector.appliesUnnamed(member, compiler)) {
+        if (member.isField() && member.enclosingElement.isNative()) {
+          if (selector.isGetter() || selector.isCall()) {
+            nativeEnqueuer.registerFieldLoad(member);
+            // We have to also handle storing to the field because we only get
+            // one look at each member and there might be a store we have not
+            // seen yet.
+            // TODO(sra): Process fields for storing separately.
+            nativeEnqueuer.registerFieldStore(member);
+          } else {
+            nativeEnqueuer.registerFieldStore(member);
+            // We have to also handle loading from the field because we only get
+            // one look at each member and there might be a load we have not
+            // seen yet.
+            // TODO(sra): Process fields for storing separately.
+            nativeEnqueuer.registerFieldLoad(member);
+          }
+        } else {
+          addToWorkList(member);
+        }
+        return true;
+      }
+      return false;
+    });
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void registerStaticUse(Element element) {
+    if (element == null) return;
+    assert(invariant(element, element.isDeclaration));
+    addToWorkList(element);
+  }
+
+  void registerGetOfStaticFunction(FunctionElement element) {
+    registerStaticUse(element);
+    universe.staticFunctionsNeedingGetter.add(element);
+  }
+
+  void registerDynamicInvocation(SourceString methodName, Selector selector) {
+    assert(selector != null);
+    registerInvocation(methodName, selector);
+  }
+
+  void registerDynamicInvocationOf(Element element, Selector selector) {
+    assert(selector.isCall()
+           || selector.isOperator()
+           || selector.isIndex()
+           || selector.isIndexSet());
+    if (element.isFunction()) {
+      addToWorkList(element);
+    } else if (element.isAbstractField()) {
+      AbstractFieldElement field = element;
+      // Since the invocation is a dynamic call on a getter, we only
+      // need to schedule the getter on the work list.
+      addToWorkList(field.getter);
+    } else {
+      assert(element.isField());
+    }
+    // We also need to add the selector to the invoked names map,
+    // because the emitter uses that map to generate parameter stubs.
+    Set<Selector> selectors = universe.invokedNames.putIfAbsent(
+        element.name, () => new Set<Selector>());
+    selectors.add(selector);
+  }
+
+  void registerDynamicGetter(SourceString methodName, Selector selector) {
+    registerInvokedGetter(methodName, selector);
+  }
+
+  void registerDynamicSetter(SourceString methodName, Selector selector) {
+    registerInvokedSetter(methodName, selector);
+  }
+
+  void registerFieldGetter(SourceString getterName,
+                           LibraryElement library,
+                           DartType type) {
+    task.measure(() {
+      Selector getter = new Selector.getter(getterName, library);
+      registerNewSelector(getterName,
+                          new TypedSelector(type, getter),
+                          universe.fieldGetters);
+    });
+  }
+
+  void registerFieldSetter(SourceString setterName,
+                           LibraryElement library,
+                           DartType type) {
+    task.measure(() {
+      Selector setter = new Selector.setter(setterName, library);
+      registerNewSelector(setterName,
+                          new TypedSelector(type, setter),
+                          universe.fieldSetters);
+    });
+  }
+
+  void registerIsCheck(DartType type) {
+    universe.isChecks.add(type);
+  }
+
+  void forEach(f(WorkItem work));
+
+  void logSummary(log(message)) {
+    _logSpecificSummary(log);
+    nativeEnqueuer.logSummary(log);
+  }
+
+  /// Log summary specific to the concrete enqueuer.
+  void _logSpecificSummary(log(message));
+
+  String toString() => 'Enqueuer($name)';
+}
+
+/// [Enqueuer] which is specific to resolution.
+class ResolutionEnqueuer extends Enqueuer {
+  /**
+   * Map from declaration elements to the [TreeElements] object holding the
+   * resolution mapping for the element implementation.
+   *
+   * Invariant: Key elements are declaration elements.
+   */
+  final Map<Element, TreeElements> resolvedElements;
+
+  final Queue<ResolutionWorkItem> queue;
+
+  ResolutionEnqueuer(Compiler compiler,
+                     ItemCompilationContext itemCompilationContextCreator())
+      : super('resolution enqueuer', compiler, itemCompilationContextCreator),
+        resolvedElements = new Map<Element, TreeElements>(),
+        queue = new Queue<ResolutionWorkItem>();
+
+  bool get isResolutionQueue => true;
+
+  bool isProcessed(Element member) => resolvedElements.containsKey(member);
+
+  TreeElements getCachedElements(Element element) {
+    // TODO(ngeoffray): Get rid of this check.
+    if (element.enclosingElement.isClosure()) {
+      closureMapping.ClosureClassElement cls = element.enclosingElement;
+      element = cls.methodElement;
+    }
+    Element owner = element.getOutermostEnclosingMemberOrTopLevel();
+    return resolvedElements[owner.declaration];
+  }
+
+  /**
+   * Sets the resolved elements of [element] to [elements], or if [elements] is
+   * [:null:], to the elements found through [getCachedElements].
+   *
+   * Returns the resolved elements.
+   */
+  TreeElements ensureCachedElements(Element element, TreeElements elements) {
+    if (elements == null) {
+      elements = getCachedElements(element);
+    }
+    resolvedElements[element] = elements;
+    return elements;
+  }
+
+  bool addElementToWorkList(Element element, [TreeElements elements]) {
+    if (queueIsClosed) {
+      if (getCachedElements(element) != null) return false;
+      throw new SpannableAssertionFailure(element,
+                                          "Resolution work list is closed.");
+    }
+    if (elements == null) {
+      elements = getCachedElements(element);
+    }
+    compiler.world.registerUsedElement(element);
+
+    if (elements == null) {
+      queue.add(
+          new ResolutionWorkItem(element, itemCompilationContextCreator()));
+    }
+
+    // Enable isolate support if we start using something from the
+    // isolate library, or timers for the async library.
+    LibraryElement library = element.getLibrary();
+    if (!compiler.hasIsolateSupport()) {
+      String uri = library.canonicalUri.toString();
+      if (uri == 'dart:isolate') {
+        enableIsolateSupport(library);
+      } else if (uri == 'dart:async') {
+        ClassElement cls = element.getEnclosingClass();
+        if (cls != null && cls.name == const SourceString('Timer')) {
+          // The [:Timer:] class uses the event queue of the isolate
+          // library, so we make sure that event queue is generated.
+          enableIsolateSupport(library);
+        }
+      }
+    }
+
+    return true;
+  }
+
+  void enableIsolateSupport(LibraryElement element) {
+    compiler.isolateLibrary = element.patch;
+    addToWorkList(
+        compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE));
+    addToWorkList(compiler.isolateHelperLibrary.find(
+        const SourceString('_currentIsolate')));
+    addToWorkList(compiler.isolateHelperLibrary.find(
+        const SourceString('_callInIsolate')));
+  }
+
+  void enableNoSuchMethod(Element element) {
+    if (compiler.enabledNoSuchMethod) return;
+    Selector selector = new Selector.noSuchMethod();
+    if (identical(element.getEnclosingClass(), compiler.objectClass)) {
+      registerDynamicInvocationOf(element, selector);
+      return;
+    }
+    compiler.enabledNoSuchMethod = true;
+    registerInvocation(Compiler.NO_SUCH_METHOD, selector);
+
+    compiler.createInvocationMirrorElement =
+        compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR);
+    addToWorkList(compiler.createInvocationMirrorElement);
+  }
+
+  void forEach(f(WorkItem work)) {
+    while (!queue.isEmpty) {
+      // TODO(johnniwinther): Find an optimal process order for resolution.
+      f(queue.removeLast());
+    }
+  }
+
+  void registerJsCall(Send node, ResolverVisitor resolver) {
+    nativeEnqueuer.registerJsCall(node, resolver);
+  }
+
+  void _logSpecificSummary(log(message)) {
+    log('Resolved ${resolvedElements.length} elements.');
+  }
+}
+
+/// [Enqueuer] which is specific to code generation.
+class CodegenEnqueuer extends Enqueuer {
+  final Queue<CodegenWorkItem> queue;
+  final Map<Element, js.Expression> generatedCode =
+      new Map<Element, js.Expression>();
+
+  CodegenEnqueuer(Compiler compiler,
+                  ItemCompilationContext itemCompilationContextCreator())
+      : super('codegen enqueuer', compiler, itemCompilationContextCreator),
+        queue = new Queue<CodegenWorkItem>();
+
+  bool isProcessed(Element member) => generatedCode.containsKey(member);
+
+  bool addElementToWorkList(Element element, [TreeElements elements]) {
+    if (queueIsClosed) {
+      throw new SpannableAssertionFailure(element,
+                                          "Codegen work list is closed.");
+    }
+    elements =
+        compiler.enqueuer.resolution.ensureCachedElements(element, elements);
+
+    CodegenWorkItem workItem = new CodegenWorkItem(
+        element, elements, itemCompilationContextCreator());
+    queue.add(workItem);
+
+    return true;
+  }
+
+  void forEach(f(WorkItem work)) {
+    while(!queue.isEmpty) {
+      // TODO(johnniwinther): Find an optimal process order for codegen.
+      f(queue.removeLast());
+    }
+  }
+
+  void _logSpecificSummary(log(message)) {
+    log('Compiled ${generatedCode.length} methods.');
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/filenames.dart b/pkgs/markdown/lib/src/compiler/implementation/filenames.dart
new file mode 100644
index 0000000..198a28f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/filenames.dart
@@ -0,0 +1,29 @@
+// 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.
+
+library filenames;
+
+import 'dart:io';
+import 'dart:uri';
+
+// TODO(ahe): This library should be replaced by a general
+// path-munging library.
+//
+// See also:
+// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx
+
+String nativeToUriPath(String filename) {
+  return new Path(filename).toString();
+}
+
+String uriPathToNative(String path) {
+  return new Path(path).toNativePath();
+}
+
+Uri getCurrentDirectory() {
+  final String dir = nativeToUriPath(new File('.').fullPathSync());
+  return new Uri.fromComponents(scheme: 'file', path: appendSlash(dir));
+}
+
+String appendSlash(String path) => path.endsWith('/') ? path : '$path/';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/js.dart b/pkgs/markdown/lib/src/compiler/implementation/js/js.dart
new file mode 100644
index 0000000..b7df642
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js/js.dart
@@ -0,0 +1,15 @@
+// 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.
+
+library js;
+
+import 'precedence.dart';
+import '../util/characters.dart' as charCodes;
+
+// TODO(floitsch): remove this dependency (currently necessary for the
+// CodeBuffer).
+import '../dart2jslib.dart' as leg;
+
+part 'nodes.dart';
+part 'printer.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart b/pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart
new file mode 100644
index 0000000..da8cd44
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart
@@ -0,0 +1,906 @@
+// 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.
+
+part of js;
+
+abstract class NodeVisitor<T> {
+  T visitProgram(Program node);
+
+  T visitBlock(Block node);
+  T visitExpressionStatement(ExpressionStatement node);
+  T visitEmptyStatement(EmptyStatement node);
+  T visitIf(If node);
+  T visitFor(For node);
+  T visitForIn(ForIn node);
+  T visitWhile(While node);
+  T visitDo(Do node);
+  T visitContinue(Continue node);
+  T visitBreak(Break node);
+  T visitReturn(Return node);
+  T visitThrow(Throw node);
+  T visitTry(Try node);
+  T visitCatch(Catch node);
+  T visitSwitch(Switch node);
+  T visitCase(Case node);
+  T visitDefault(Default node);
+  T visitFunctionDeclaration(FunctionDeclaration node);
+  T visitLabeledStatement(LabeledStatement node);
+  T visitLiteralStatement(LiteralStatement node);
+
+  T visitLiteralExpression(LiteralExpression node);
+  T visitVariableDeclarationList(VariableDeclarationList node);
+  T visitSequence(Sequence node);
+  T visitAssignment(Assignment node);
+  T visitVariableInitialization(VariableInitialization node);
+  T visitConditional(Conditional cond);
+  T visitNew(New node);
+  T visitCall(Call node);
+  T visitBinary(Binary node);
+  T visitPrefix(Prefix node);
+  T visitPostfix(Postfix node);
+
+  T visitVariableUse(VariableUse node);
+  T visitThis(This node);
+  T visitVariableDeclaration(VariableDeclaration node);
+  T visitParameter(Parameter node);
+  T visitAccess(PropertyAccess node);
+
+  T visitNamedFunction(NamedFunction node);
+  T visitFun(Fun node);
+
+  T visitLiteralBool(LiteralBool node);
+  T visitLiteralString(LiteralString node);
+  T visitLiteralNumber(LiteralNumber node);
+  T visitLiteralNull(LiteralNull node);
+
+  T visitArrayInitializer(ArrayInitializer node);
+  T visitArrayElement(ArrayElement node);
+  T visitObjectInitializer(ObjectInitializer node);
+  T visitProperty(Property node);
+  T visitRegExpLiteral(RegExpLiteral node);
+}
+
+class BaseVisitor<T> implements NodeVisitor<T> {
+  T visitNode(Node node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  T visitProgram(Program node) => visitNode(node);
+
+  T visitStatement(Statement node) => visitNode(node);
+  T visitLoop(Loop node) => visitStatement(node);
+  T visitJump(Statement node) => visitStatement(node);
+
+  T visitBlock(Block node) => visitStatement(node);
+  T visitExpressionStatement(ExpressionStatement node)
+      => visitStatement(node);
+  T visitEmptyStatement(EmptyStatement node) => visitStatement(node);
+  T visitIf(If node) => visitStatement(node);
+  T visitFor(For node) => visitLoop(node);
+  T visitForIn(ForIn node) => visitLoop(node);
+  T visitWhile(While node) => visitLoop(node);
+  T visitDo(Do node) => visitLoop(node);
+  T visitContinue(Continue node) => visitJump(node);
+  T visitBreak(Break node) => visitJump(node);
+  T visitReturn(Return node) => visitJump(node);
+  T visitThrow(Throw node) => visitJump(node);
+  T visitTry(Try node) => visitStatement(node);
+  T visitSwitch(Switch node) => visitStatement(node);
+  T visitFunctionDeclaration(FunctionDeclaration node)
+      => visitStatement(node);
+  T visitLabeledStatement(LabeledStatement node) => visitStatement(node);
+  T visitLiteralStatement(LiteralStatement node) => visitStatement(node);
+
+  T visitCatch(Catch node) => visitNode(node);
+  T visitCase(Case node) => visitNode(node);
+  T visitDefault(Default node) => visitNode(node);
+
+  T visitExpression(Expression node) => visitNode(node);
+  T visitVariableReference(VariableReference node) => visitExpression(node);
+
+  T visitLiteralExpression(LiteralExpression node) => visitExpression(node);
+  T visitVariableDeclarationList(VariableDeclarationList node)
+      => visitExpression(node);
+  T visitSequence(Sequence node) => visitExpression(node);
+  T visitAssignment(Assignment node) => visitExpression(node);
+  T visitVariableInitialization(VariableInitialization node) {
+    if (node.value != null) {
+      visitAssignment(node);
+    } else {
+      visitExpression(node);
+    }
+  }
+  T visitConditional(Conditional node) => visitExpression(node);
+  T visitNew(New node) => visitExpression(node);
+  T visitCall(Call node) => visitExpression(node);
+  T visitBinary(Binary node) => visitCall(node);
+  T visitPrefix(Prefix node) => visitCall(node);
+  T visitPostfix(Postfix node) => visitCall(node);
+  T visitAccess(PropertyAccess node) => visitExpression(node);
+
+  T visitVariableUse(VariableUse node) => visitVariableReference(node);
+  T visitVariableDeclaration(VariableDeclaration node)
+      => visitVariableReference(node);
+  T visitParameter(Parameter node) => visitVariableDeclaration(node);
+  T visitThis(This node) => visitParameter(node);
+
+  T visitNamedFunction(NamedFunction node) => visitExpression(node);
+  T visitFun(Fun node) => visitExpression(node);
+
+  T visitLiteral(Literal node) => visitExpression(node);
+
+  T visitLiteralBool(LiteralBool node) => visitLiteral(node);
+  T visitLiteralString(LiteralString node) => visitLiteral(node);
+  T visitLiteralNumber(LiteralNumber node) => visitLiteral(node);
+  T visitLiteralNull(LiteralNull node) => visitLiteral(node);
+
+  T visitArrayInitializer(ArrayInitializer node) => visitExpression(node);
+  T visitArrayElement(ArrayElement node) => visitNode(node);
+  T visitObjectInitializer(ObjectInitializer node) => visitExpression(node);
+  T visitProperty(Property node) => visitNode(node);
+  T visitRegExpLiteral(RegExpLiteral node) => visitExpression(node);
+}
+
+abstract class Node {
+  var sourcePosition;
+  var endSourcePosition;
+
+  accept(NodeVisitor visitor);
+  void visitChildren(NodeVisitor visitor);
+
+  VariableUse asVariableUse() => null;
+}
+
+class Program extends Node {
+  final List<Statement> body;
+  Program(this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitProgram(this);
+  void visitChildren(NodeVisitor visitor) {
+    for (Statement statement in body) statement.accept(visitor);
+  }
+}
+
+abstract class Statement extends Node {
+}
+
+class Block extends Statement {
+  final List<Statement> statements;
+  Block(this.statements);
+  Block.empty() : this.statements = <Statement>[];
+
+  accept(NodeVisitor visitor) => visitor.visitBlock(this);
+  void visitChildren(NodeVisitor visitor) {
+    for (Statement statement in statements) statement.accept(visitor);
+  }
+}
+
+class ExpressionStatement extends Statement {
+  final Expression expression;
+  ExpressionStatement(this.expression);
+
+  accept(NodeVisitor visitor) => visitor.visitExpressionStatement(this);
+  void visitChildren(NodeVisitor visitor) { expression.accept(visitor); }
+}
+
+class EmptyStatement extends Statement {
+  EmptyStatement();
+
+  accept(NodeVisitor visitor) => visitor.visitEmptyStatement(this);
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class If extends Statement {
+  final Expression condition;
+  final Node then;
+  final Node otherwise;
+
+  If(this.condition, this.then, this.otherwise);
+  If.noElse(this.condition, this.then) : this.otherwise = new EmptyStatement();
+
+  bool get hasElse => otherwise is !EmptyStatement;
+
+  accept(NodeVisitor visitor) => visitor.visitIf(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    condition.accept(visitor);
+    then.accept(visitor);
+    otherwise.accept(visitor);
+  }
+}
+
+abstract class Loop extends Statement {
+  final Statement body;
+  Loop(this.body);
+}
+
+class For extends Loop {
+  final Expression init;
+  final Expression condition;
+  final Expression update;
+
+  For(this.init, this.condition, this.update, Statement body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitFor(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    if (init != null) init.accept(visitor);
+    if (condition != null) condition.accept(visitor);
+    if (update != null) update.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class ForIn extends Loop {
+  // Note that [VariableDeclarationList] is a subclass of [Expression].
+  // Therefore we can type the leftHandSide as [Expression].
+  final Expression leftHandSide;
+  final Expression object;
+
+  ForIn(this.leftHandSide, this.object, Statement body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitForIn(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    leftHandSide.accept(visitor);
+    object.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class While extends Loop {
+  final Node condition;
+
+  While(this.condition, Statement body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitWhile(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    condition.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class Do extends Loop {
+  final Expression condition;
+
+  Do(Statement body, this.condition) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitDo(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+    condition.accept(visitor);
+  }
+}
+
+class Continue extends Statement {
+  final String targetLabel;  // Can be null.
+
+  Continue(this.targetLabel);
+
+  accept(NodeVisitor visitor) => visitor.visitContinue(this);
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class Break extends Statement {
+  final String targetLabel;  // Can be null.
+
+  Break(this.targetLabel);
+
+  accept(NodeVisitor visitor) => visitor.visitBreak(this);
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class Return extends Statement {
+  final Expression value;  // Can be null.
+
+  Return([this.value = null]);
+
+  accept(NodeVisitor visitor) => visitor.visitReturn(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    if (value != null) value.accept(visitor);
+  }
+}
+
+class Throw extends Statement {
+  final Expression expression;
+
+  Throw(this.expression);
+
+  accept(NodeVisitor visitor) => visitor.visitThrow(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    expression.accept(visitor);
+  }
+}
+
+class Try extends Statement {
+  final Block body;
+  final Catch catchPart;  // Can be null if [finallyPart] is non-null.
+  final Block finallyPart;  // Can be null if [catchPart] is non-null.
+
+  Try(this.body, this.catchPart, this.finallyPart) {
+    assert(catchPart != null || finallyPart != null);
+  }
+
+  accept(NodeVisitor visitor) => visitor.visitTry(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+    if (catchPart != null) catchPart.accept(visitor);
+    if (finallyPart != null) finallyPart.accept(visitor);
+  }
+}
+
+class Catch extends Node {
+  final VariableDeclaration declaration;
+  final Block body;
+
+  Catch(this.declaration, this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitCatch(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    declaration.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class Switch extends Statement {
+  final Expression key;
+  final List<SwitchClause> cases;
+
+  Switch(this.key, this.cases);
+
+  accept(NodeVisitor visitor) => visitor.visitSwitch(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    key.accept(visitor);
+    for (SwitchClause clause in cases) clause.accept(visitor);
+  }
+}
+
+abstract class SwitchClause extends Node {
+  final Block body;
+
+  SwitchClause(this.body);
+}
+
+class Case extends SwitchClause {
+  final Expression expression;
+
+  Case(this.expression, Block body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitCase(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    expression.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class Default extends SwitchClause {
+  Default(Block body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitDefault(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+  }
+}
+
+class FunctionDeclaration extends Statement {
+  final VariableDeclaration name;
+  final Fun function;
+
+  FunctionDeclaration(this.name, this.function);
+
+  accept(NodeVisitor visitor) => visitor.visitFunctionDeclaration(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    name.accept(visitor);
+    function.accept(visitor);
+  }
+}
+
+class LabeledStatement extends Statement {
+  final String label;
+  final Statement body;
+
+  LabeledStatement(this.label, this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitLabeledStatement(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+  }
+}
+
+class LiteralStatement extends Statement {
+  final String code;
+
+  LiteralStatement(this.code);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralStatement(this);
+  void visitChildren(NodeVisitor visitor) { }
+}
+
+abstract class Expression extends Node {
+  int get precedenceLevel;
+
+  PropertyAccess dot(String name) => new PropertyAccess.field(this, name);
+  Call callWith(List<Expression> arguments) => new Call(this, arguments);
+}
+
+class LiteralExpression extends Expression {
+  final String template;
+  final List<Expression> inputs;
+
+  LiteralExpression(this.template) : inputs = const [];
+  LiteralExpression.withData(this.template, this.inputs);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralExpression(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Expression expr in inputs) expr.accept(visitor);
+  }
+
+  // Code that uses JS must take care of operator precedences, and
+  // put parenthesis if needed.
+  int get precedenceLevel => PRIMARY;
+}
+
+/**
+ * [VariableDeclarationList] is a subclass of [Expression] to simplify the
+ * AST.
+ */
+class VariableDeclarationList extends Expression {
+  final List<VariableInitialization> declarations;
+
+  VariableDeclarationList(this.declarations);
+
+  accept(NodeVisitor visitor) => visitor.visitVariableDeclarationList(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (VariableInitialization declaration in declarations) {
+      declaration.accept(visitor);
+    }
+  }
+
+  int get precedenceLevel => EXPRESSION;
+}
+
+class Sequence extends Expression {
+  final List<Expression> expressions;
+
+  Sequence(this.expressions);
+
+  accept(NodeVisitor visitor) => visitor.visitSequence(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Expression expr in expressions) expr.accept(visitor);
+  }
+
+  int get precedenceLevel => EXPRESSION;
+}
+
+class Assignment extends Expression {
+  final Expression leftHandSide;
+  // Null, if the assignment is not compound.
+  final VariableReference compoundTarget;
+  final Expression value;  // May be null, for [VariableInitialization]s.
+
+  Assignment(this.leftHandSide, this.value) : compoundTarget = null;
+  Assignment.compound(this.leftHandSide, String op, this.value)
+      : compoundTarget = new VariableUse(op);
+
+  int get precedenceLevel => ASSIGNMENT;
+
+  bool get isCompound => compoundTarget != null;
+  String get op => compoundTarget == null ? null : compoundTarget.name;
+
+  accept(NodeVisitor visitor) => visitor.visitAssignment(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    leftHandSide.accept(visitor);
+    if (compoundTarget != null) compoundTarget.accept(visitor);
+    if (value != null) value.accept(visitor);
+  }
+}
+
+class VariableInitialization extends Assignment {
+  /** [value] may be null. */
+  VariableInitialization(VariableDeclaration declaration, Expression value)
+      : super(declaration, value);
+
+  VariableDeclaration get declaration => leftHandSide;
+
+  accept(NodeVisitor visitor) => visitor.visitVariableInitialization(this);
+}
+
+class Conditional extends Expression {
+  final Expression condition;
+  final Expression then;
+  final Expression otherwise;
+
+  Conditional(this.condition, this.then, this.otherwise);
+
+  accept(NodeVisitor visitor) => visitor.visitConditional(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    condition.accept(visitor);
+    then.accept(visitor);
+    otherwise.accept(visitor);
+  }
+
+  int get precedenceLevel => ASSIGNMENT;
+}
+
+class Call extends Expression {
+  Expression target;
+  List<Expression> arguments;
+
+  Call(this.target, this.arguments);
+
+  accept(NodeVisitor visitor) => visitor.visitCall(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    target.accept(visitor);
+    for (Expression arg in arguments) arg.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+class New extends Call {
+  New(Expression cls, List<Expression> arguments) : super(cls, arguments);
+
+  accept(NodeVisitor visitor) => visitor.visitNew(this);
+}
+
+class Binary extends Call {
+  Binary(String op, Expression left, Expression right)
+      : super(new VariableUse(op), <Expression>[left, right]);
+
+  String get op {
+    VariableUse use = target;
+    return use.name;
+  }
+
+  Expression get left => arguments[0];
+  Expression get right => arguments[1];
+
+  accept(NodeVisitor visitor) => visitor.visitBinary(this);
+
+  int get precedenceLevel {
+    // TODO(floitsch): switch to constant map.
+    switch (op) {
+      case "*":
+      case "/":
+      case "%":
+        return MULTIPLICATIVE;
+      case "+":
+      case "-":
+        return ADDITIVE;
+      case "<<":
+      case ">>":
+      case ">>>":
+        return SHIFT;
+      case "<":
+      case ">":
+      case "<=":
+      case ">=":
+      case "instanceof":
+      case "in":
+        return RELATIONAL;
+      case "==":
+      case "===":
+      case "!=":
+      case "!==":
+        return EQUALITY;
+      case "&":
+        return BIT_AND;
+      case "^":
+        return BIT_XOR;
+      case "|":
+        return BIT_OR;
+      case "&&":
+        return LOGICAL_AND;
+      case "||":
+        return LOGICAL_OR;
+      default:
+        throw new leg.CompilerCancelledException(
+            "Internal Error: Unhandled binary operator: $op");
+    }
+  }
+}
+
+class Prefix extends Call {
+  Prefix(String op, Expression arg)
+      : super(new VariableUse(op), <Expression>[arg]);
+
+  String get op => (target as VariableUse).name;
+  Expression get argument => arguments[0];
+
+  accept(NodeVisitor visitor) => visitor.visitPrefix(this);
+
+  int get precedenceLevel => UNARY;
+}
+
+class Postfix extends Call {
+  Postfix(String op, Expression arg)
+      : super(new VariableUse(op), <Expression>[arg]);
+
+  String get op => (target as VariableUse).name;
+  Expression get argument => arguments[0];
+
+  accept(NodeVisitor visitor) => visitor.visitPostfix(this);
+
+  int get precedenceLevel => UNARY;
+}
+
+abstract class VariableReference extends Expression {
+  final String name;
+
+  // We treat operators as if they were special functions. They can thus be
+  // referenced like other variables.
+  VariableReference(this.name);
+
+  accept(NodeVisitor visitor);
+  int get precedenceLevel => PRIMARY;
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class VariableUse extends VariableReference {
+  VariableUse(String name) : super(name);
+
+  accept(NodeVisitor visitor) => visitor.visitVariableUse(this);
+
+  VariableUse asVariableUse() => this;
+}
+
+class VariableDeclaration extends VariableReference {
+  VariableDeclaration(String name) : super(name);
+
+  accept(NodeVisitor visitor) => visitor.visitVariableDeclaration(this);
+}
+
+class Parameter extends VariableDeclaration {
+  Parameter(String id) : super(id);
+
+  accept(NodeVisitor visitor) => visitor.visitParameter(this);
+}
+
+class This extends Parameter {
+  This() : super("this");
+
+  accept(NodeVisitor visitor) => visitor.visitThis(this);
+}
+
+class NamedFunction extends Expression {
+  final VariableDeclaration name;
+  final Fun function;
+
+  NamedFunction(this.name, this.function);
+
+  accept(NodeVisitor visitor) => visitor.visitNamedFunction(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    name.accept(visitor);
+    function.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+class Fun extends Expression {
+  final List<Parameter> params;
+  final Block body;
+
+  Fun(this.params, this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitFun(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Parameter param in params) param.accept(visitor);
+    body.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+class PropertyAccess extends Expression {
+  final Expression receiver;
+  final Expression selector;
+
+  PropertyAccess(this.receiver, this.selector);
+  PropertyAccess.field(this.receiver, String fieldName)
+      : selector = new LiteralString("'$fieldName'");
+  PropertyAccess.indexed(this.receiver, int index)
+      : selector = new LiteralNumber('$index');
+
+  accept(NodeVisitor visitor) => visitor.visitAccess(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    receiver.accept(visitor);
+    selector.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+abstract class Literal extends Expression {
+  void visitChildren(NodeVisitor visitor) {}
+
+  int get precedenceLevel => PRIMARY;
+}
+
+class LiteralBool extends Literal {
+  final bool value;
+
+  LiteralBool(this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralBool(this);
+  // [visitChildren] inherited from [Literal].
+}
+
+class LiteralNull extends Literal {
+  LiteralNull();
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralNull(this);
+}
+
+class LiteralString extends Literal {
+  final String value;
+
+  LiteralString(this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralString(this);
+}
+
+class LiteralNumber extends Literal {
+  final String value;
+
+  LiteralNumber(this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralNumber(this);
+}
+
+class ArrayInitializer extends Expression {
+  final int length;
+  // We represent the array as sparse list of elements. Each element knows its
+  // position in the array.
+  final List<ArrayElement> elements;
+
+  ArrayInitializer(this.length, this.elements);
+
+  factory ArrayInitializer.from(Iterable<Expression> expressions) =>
+      new ArrayInitializer(expressions.length, _convert(expressions));
+
+  accept(NodeVisitor visitor) => visitor.visitArrayInitializer(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (ArrayElement element in elements) element.accept(visitor);
+  }
+
+  int get precedenceLevel => PRIMARY;
+
+  static List<ArrayElement> _convert(Iterable<Expression> expressions) {
+    int index = 0;
+    return expressions.map(
+        (expression) => new ArrayElement(index++, expression))
+        .toList();
+  }
+}
+
+/**
+ * An expression inside an [ArrayInitialization]. An [ArrayElement] knows
+ * its position in the containing [ArrayInitialization].
+ */
+class ArrayElement extends Node {
+  int index;
+  Expression value;
+
+  ArrayElement(this.index, this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitArrayElement(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    value.accept(visitor);
+  }
+}
+
+class ObjectInitializer extends Expression {
+  List<Property> properties;
+
+  ObjectInitializer(this.properties);
+
+  accept(NodeVisitor visitor) => visitor.visitObjectInitializer(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Property init in properties) init.accept(visitor);
+  }
+
+  int get precedenceLevel => PRIMARY;
+}
+
+class Property extends Node {
+  Literal name;
+  Expression value;
+
+  Property(this.name, this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitProperty(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    name.accept(visitor);
+    value.accept(visitor);
+  }
+}
+
+/**
+ * [RegExpLiteral]s, despite being called "Literal", are not inheriting from
+ * [Literal]. Indeed, regular expressions in JavaScript have a side-effect and
+ * are thus not in the same category as numbers or strings.
+ */
+class RegExpLiteral extends Expression {
+  /** Contains the pattern and the flags.*/
+  String pattern;
+
+  RegExpLiteral(this.pattern);
+
+  accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this);
+  void visitChildren(NodeVisitor visitor) {}
+
+  int get precedenceLevel => PRIMARY;
+}
+
+Prefix typeOf(Expression argument) => new Prefix('typeof', argument);
+
+Binary equals(Expression left, Expression right) {
+  return new Binary('==', left, right);
+}
+
+Binary strictEquals(Expression left, Expression right) {
+  return new Binary('===', left, right);
+}
+
+LiteralString string(String value) => new LiteralString('"$value"');
+
+If if_(Expression condition, Node then, [Node otherwise]) {
+  return (otherwise == null)
+      ? new If.noElse(condition, then)
+      : new If(condition, then, otherwise);
+}
+
+Return return_([Expression value]) => new Return(value);
+
+VariableUse use(String name) => new VariableUse(name);
+
+PropertyAccess fieldAccess(Expression receiver, String fieldName) {
+  return new PropertyAccess.field(receiver, fieldName);
+}
+
+Block emptyBlock() => new Block.empty();
+
+Block block1(Statement statement) => new Block(<Statement>[statement]);
+
+Block block2(Statement s1, Statement s2) => new Block(<Statement>[s1, s2]);
+
+Call call(Expression target, List<Expression> arguments) {
+  return new Call(target, arguments);
+}
+
+Fun fun(List<String> parameterNames, Block body) {
+  return new Fun(parameterNames.map((n) => new Parameter(n)).toList(), body);
+}
+
+Assignment assign(Expression leftHandSide, Expression value) {
+  return new Assignment(leftHandSide, value);
+}
+
+Expression undefined() => new Prefix('void', new LiteralNumber('0'));
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart b/pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart
new file mode 100644
index 0000000..6d66f1f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart
@@ -0,0 +1,25 @@
+// 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.
+
+library precedence;
+
+const EXPRESSION = 0;
+const ASSIGNMENT = EXPRESSION + 1;
+const LOGICAL_OR = ASSIGNMENT + 1;
+const LOGICAL_AND = LOGICAL_OR + 1;
+const BIT_OR = LOGICAL_AND + 1;
+const BIT_XOR = BIT_OR + 1;
+const BIT_AND = BIT_XOR + 1;
+const EQUALITY = BIT_AND + 1;
+const RELATIONAL = EQUALITY + 1;
+const SHIFT = RELATIONAL + 1;
+const ADDITIVE = SHIFT + 1;
+const MULTIPLICATIVE = ADDITIVE + 1;
+const UNARY = MULTIPLICATIVE + 1;
+const LEFT_HAND_SIDE = UNARY + 1;
+// We merge new, call and member expressions.
+// This means that we have to emit parenthesis for 'new's. For example `new X;`
+// should be printed as `new X();`. This simplifies the requirements.
+const CALL = LEFT_HAND_SIDE;
+const PRIMARY = CALL + 1;
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/printer.dart b/pkgs/markdown/lib/src/compiler/implementation/js/printer.dart
new file mode 100644
index 0000000..504933d
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js/printer.dart
@@ -0,0 +1,1111 @@
+// 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.
+
+part of js;
+
+class Printer implements NodeVisitor {
+  final bool shouldCompressOutput;
+  leg.Compiler compiler;
+  leg.CodeBuffer outBuffer;
+  int indentLevel = 0;
+  bool inForInit = false;
+  bool atStatementBegin = false;
+  final DanglingElseVisitor danglingElseVisitor;
+  final LocalNamer localNamer;
+  bool pendingSemicolon = false;
+  bool pendingSpace = false;
+  static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]');
+  static final expressionContinuationRegExp = new RegExp(r'^[-+([]');
+
+  Printer(leg.Compiler compiler, { allowVariableMinification: true })
+      : shouldCompressOutput = compiler.enableMinification,
+        this.compiler = compiler,
+        outBuffer = new leg.CodeBuffer(),
+        danglingElseVisitor = new DanglingElseVisitor(compiler),
+        localNamer = determineRenamer(compiler.enableMinification,
+                                      allowVariableMinification);
+
+  static LocalNamer determineRenamer(bool shouldCompressOutput,
+                                     bool allowVariableMinification) {
+    return (shouldCompressOutput && allowVariableMinification)
+        ? new MinifyRenamer() : new IdentityNamer();
+  }
+
+  /// Always emit a newline, even under `enableMinification`.
+  void forceLine() {
+    out("\n");
+  }
+  /// Emits a newline for readability.
+  void lineOut() {
+    if (!shouldCompressOutput) forceLine();
+  }
+  void spaceOut() {
+    if (!shouldCompressOutput) out(" ");
+  }
+
+  String lastAddedString = null;
+  int get lastCharCode {
+    if (lastAddedString == null) return 0;
+    assert(lastAddedString.length != "");
+    return lastAddedString.charCodeAt(lastAddedString.length - 1);
+  }
+
+  void out(String str) {
+    if (str != "") {
+      if (pendingSemicolon) {
+        if (!shouldCompressOutput) {
+          outBuffer.add(";");
+        } else if (str != "}") {
+          // We want to output newline instead of semicolon because it makes
+          // the raw stack traces much easier to read and it also makes line-
+          // based tools like diff work much better.  JavaScript will
+          // automatically insert the semicolon at the newline if it means a
+          // parsing error is avoided, so we can only do this trick if the
+          // next line is not something that can be glued onto a valid
+          // expression to make a new valid expression.
+          if (expressionContinuationRegExp.hasMatch(str)) {
+            outBuffer.add(";");
+          } else {
+            outBuffer.add("\n");
+          }
+        }
+      }
+      if (pendingSpace &&
+          (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) {
+        outBuffer.add(" ");
+      }
+      pendingSpace = false;
+      pendingSemicolon = false;
+      outBuffer.add(str);
+      lastAddedString = str;
+    }
+  }
+
+  void outLn(String str) {
+    out(str);
+    lineOut();
+  }
+
+  void outSemicolonLn() {
+    if (shouldCompressOutput) {
+      pendingSemicolon = true;
+    } else {
+      out(";");
+      forceLine();
+    }
+  }
+
+  void outIndent(String str) { indent(); out(str); }
+  void outIndentLn(String str) { indent(); outLn(str); }
+  void indent() {
+    if (!shouldCompressOutput) {
+      for (int i = 0; i < indentLevel; i++) out("  ");
+    }
+  }
+
+  void recordSourcePosition(var position) {
+    if (position != null) {
+      outBuffer.setSourceLocation(position);
+    }
+  }
+
+  visit(Node node) {
+    if (node.sourcePosition != null) outBuffer.beginMappedRange();
+    recordSourcePosition(node.sourcePosition);
+    node.accept(this);
+    recordSourcePosition(node.endSourcePosition);
+    if (node.sourcePosition != null) outBuffer.endMappedRange();
+  }
+
+  visitCommaSeparated(List<Node> nodes, int hasRequiredType,
+                      {bool newInForInit, bool newAtStatementBegin}) {
+    for (int i = 0; i < nodes.length; i++) {
+      if (i != 0) {
+        atStatementBegin = false;
+        out(",");
+        spaceOut();
+      }
+      visitNestedExpression(nodes[i], hasRequiredType,
+                            newInForInit: newInForInit,
+                            newAtStatementBegin: newAtStatementBegin);
+    }
+  }
+
+  visitAll(List<Node> nodes) {
+    nodes.forEach(visit);
+  }
+
+  visitProgram(Program program) {
+    visitAll(program.body);
+  }
+
+  bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) {
+    if (body is Block) {
+      spaceOut();
+      blockOut(body, false, needsNewline);
+      return true;
+    }
+    if (shouldCompressOutput && needsSeparation) {
+      // If [shouldCompressOutput] is false, then the 'lineOut' will insert
+      // the separation.
+      out(" ");
+    } else {
+      lineOut();
+    }
+    indentLevel++;
+    visit(body);
+    indentLevel--;
+    return false;
+  }
+
+  void blockOutWithoutBraces(Node node) {
+    if (node is Block) {
+      node.statements.forEach(blockOutWithoutBraces);
+    } else {
+      visit(node);
+    }
+  }
+
+  void blockOut(Block node, bool shouldIndent, bool needsNewline) {
+    if (shouldIndent) indent();
+    out("{");
+    lineOut();
+    indentLevel++;
+    node.statements.forEach(blockOutWithoutBraces);
+    indentLevel--;
+    indent();
+    out("}");
+    if (needsNewline) lineOut();
+  }
+
+  visitBlock(Block block) {
+    blockOut(block, true, true);
+  }
+
+  visitExpressionStatement(ExpressionStatement expressionStatement) {
+    indent();
+    visitNestedExpression(expressionStatement.expression, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: true);
+    outSemicolonLn();
+  }
+
+  visitEmptyStatement(EmptyStatement nop) {
+    outIndentLn(";");
+  }
+
+  void ifOut(If node, bool shouldIndent) {
+    Node then = node.then;
+    Node elsePart = node.otherwise;
+    bool hasElse = node.hasElse;
+
+    // Handle dangling elses.
+    if (hasElse) {
+      bool needsBraces = node.then.accept(danglingElseVisitor);
+      if (needsBraces) {
+        then = new Block(<Statement>[then]);
+      }
+    }
+    if (shouldIndent) indent();
+    out("if");
+    spaceOut();
+    out("(");
+    visitNestedExpression(node.condition, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    bool thenWasBlock =
+        blockBody(then, needsSeparation: false, needsNewline: !hasElse);
+    if (hasElse) {
+      if (thenWasBlock) {
+        spaceOut();
+      } else {
+        indent();
+      }
+      out("else");
+      if (elsePart is If) {
+        pendingSpace = true;
+        ifOut(elsePart, false);
+      } else {
+        blockBody(elsePart, needsSeparation: true, needsNewline: true);
+      }
+    }
+  }
+
+  visitIf(If node) {
+    ifOut(node, true);
+  }
+
+  visitFor(For loop) {
+    outIndent("for");
+    spaceOut();
+    out("(");
+    if (loop.init != null) {
+      visitNestedExpression(loop.init, EXPRESSION,
+                            newInForInit: true, newAtStatementBegin: false);
+    }
+    out(";");
+    if (loop.condition != null) {
+      spaceOut();
+      visitNestedExpression(loop.condition, EXPRESSION,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    out(";");
+    if (loop.update != null) {
+      spaceOut();
+      visitNestedExpression(loop.update, EXPRESSION,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    out(")");
+    blockBody(loop.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitForIn(ForIn loop) {
+    outIndent("for");
+    spaceOut();
+    out("(");
+    visitNestedExpression(loop.leftHandSide, EXPRESSION,
+                          newInForInit: true, newAtStatementBegin: false);
+    out(" in");
+    pendingSpace = true;
+    visitNestedExpression(loop.object, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    blockBody(loop.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitWhile(While loop) {
+    outIndent("while");
+    spaceOut();
+    out("(");
+    visitNestedExpression(loop.condition, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    blockBody(loop.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitDo(Do loop) {
+    outIndent("do");
+    if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) {
+      spaceOut();
+    } else {
+      indent();
+    }
+    out("while");
+    spaceOut();
+    out("(");
+    visitNestedExpression(loop.condition, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    outSemicolonLn();
+  }
+
+  visitContinue(Continue node) {
+    if (node.targetLabel == null) {
+      outIndent("continue");
+    } else {
+      outIndent("continue ${node.targetLabel}");
+    }
+    outSemicolonLn();
+  }
+
+  visitBreak(Break node) {
+    if (node.targetLabel == null) {
+      outIndent("break");
+    } else {
+      outIndent("break ${node.targetLabel}");
+    }
+    outSemicolonLn();
+  }
+
+  visitReturn(Return node) {
+    if (node.value == null) {
+      outIndent("return");
+    } else {
+      outIndent("return");
+      pendingSpace = true;
+      visitNestedExpression(node.value, EXPRESSION,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    outSemicolonLn();
+  }
+
+  visitThrow(Throw node) {
+    outIndent("throw");
+    pendingSpace = true;
+    visitNestedExpression(node.expression, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    outSemicolonLn();
+  }
+
+  visitTry(Try node) {
+    outIndent("try");
+    blockBody(node.body, needsSeparation: true, needsNewline: false);
+    if (node.catchPart != null) {
+      visit(node.catchPart);
+    }
+    if (node.finallyPart != null) {
+      spaceOut();
+      out("finally");
+      blockBody(node.finallyPart, needsSeparation: true, needsNewline: true);
+    } else {
+      lineOut();
+    }
+  }
+
+  visitCatch(Catch node) {
+    spaceOut();
+    out("catch");
+    spaceOut();
+    out("(");
+    visitNestedExpression(node.declaration, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    blockBody(node.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitSwitch(Switch node) {
+    outIndent("switch");
+    spaceOut();
+    out("(");
+    visitNestedExpression(node.key, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    spaceOut();
+    outLn("{");
+    indentLevel++;
+    visitAll(node.cases);
+    indentLevel--;
+    outIndentLn("}");
+  }
+
+  visitCase(Case node) {
+    outIndent("case");
+    pendingSpace = true;
+    visitNestedExpression(node.expression, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    outLn(":");
+    if (!node.body.statements.isEmpty) {
+      indentLevel++;
+      blockOutWithoutBraces(node.body);
+      indentLevel--;
+    }
+  }
+
+  visitDefault(Default node) {
+    outIndentLn("default:");
+    if (!node.body.statements.isEmpty) {
+      indentLevel++;
+      blockOutWithoutBraces(node.body);
+      indentLevel--;
+    }
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    outIndent("${node.label}:");
+    blockBody(node.body, needsSeparation: false, needsNewline: true);
+  }
+
+  void functionOut(Fun fun, Node name, VarCollector vars) {
+    out("function");
+    if (name != null) {
+      out(" ");
+      // Name must be a [Decl]. Therefore only test for primary expressions.
+      visitNestedExpression(name, PRIMARY,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    localNamer.enterScope(vars);
+    out("(");
+    if (fun.params != null) {
+      visitCommaSeparated(fun.params, PRIMARY,
+                          newInForInit: false, newAtStatementBegin: false);
+    }
+    out(")");
+    blockBody(fun.body, needsSeparation: false, needsNewline: false);
+    localNamer.leaveScope();
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration declaration) {
+    VarCollector vars = new VarCollector();
+    vars.visitFunctionDeclaration(declaration);
+    indent();
+    functionOut(declaration.function, declaration.name, vars);
+    lineOut();
+  }
+
+  visitNestedExpression(Expression node, int requiredPrecedence,
+                        {bool newInForInit, bool newAtStatementBegin}) {
+    bool needsParentheses =
+        // a - (b + c).
+        (requiredPrecedence != EXPRESSION &&
+         node.precedenceLevel < requiredPrecedence) ||
+        // for (a = (x in o); ... ; ... ) { ... }
+        (newInForInit && node is Binary && (node as Binary).op == "in") ||
+        // (function() { ... })().
+        // ({a: 2, b: 3}.toString()).
+        (newAtStatementBegin && (node is NamedFunction ||
+                                 node is Fun ||
+                                 node is ObjectInitializer));
+    if (needsParentheses) {
+      inForInit = false;
+      atStatementBegin = false;
+      out("(");
+      visit(node);
+      out(")");
+    } else {
+      inForInit = newInForInit;
+      atStatementBegin = newAtStatementBegin;
+      visit(node);
+    }
+  }
+
+  visitVariableDeclarationList(VariableDeclarationList list) {
+    out("var ");
+    visitCommaSeparated(list.declarations, ASSIGNMENT,
+                        newInForInit: inForInit, newAtStatementBegin: false);
+  }
+
+  visitSequence(Sequence sequence) {
+    // Note that we only require that the entries are expressions and not
+    // assignments. This means that nested sequences are not put into
+    // parenthesis.
+    visitCommaSeparated(sequence.expressions, EXPRESSION,
+                        newInForInit: false,
+                        newAtStatementBegin: atStatementBegin);
+  }
+
+  visitAssignment(Assignment assignment) {
+    visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    if (assignment.value != null) {
+      spaceOut();
+      String op = assignment.op;
+      if (op != null) out(op);
+      out("=");
+      spaceOut();
+      visitNestedExpression(assignment.value, ASSIGNMENT,
+                            newInForInit: inForInit,
+                            newAtStatementBegin: false);
+    }
+  }
+
+  visitVariableInitialization(VariableInitialization initialization) {
+    visitAssignment(initialization);
+  }
+
+  visitConditional(Conditional cond) {
+    visitNestedExpression(cond.condition, LOGICAL_OR,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    spaceOut();
+    out("?");
+    spaceOut();
+    // The then part is allowed to have an 'in'.
+    visitNestedExpression(cond.then, ASSIGNMENT,
+                          newInForInit: false, newAtStatementBegin: false);
+    spaceOut();
+    out(":");
+    spaceOut();
+    visitNestedExpression(cond.otherwise, ASSIGNMENT,
+                          newInForInit: inForInit, newAtStatementBegin: false);
+  }
+
+  visitNew(New node) {
+    out("new ");
+    visitNestedExpression(node.target, CALL,
+                          newInForInit: inForInit, newAtStatementBegin: false);
+    out("(");
+    visitCommaSeparated(node.arguments, ASSIGNMENT,
+                        newInForInit: false, newAtStatementBegin: false);
+    out(")");
+  }
+
+  visitCall(Call call) {
+    visitNestedExpression(call.target, LEFT_HAND_SIDE,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    out("(");
+    visitCommaSeparated(call.arguments, ASSIGNMENT,
+                        newInForInit: false, newAtStatementBegin: false);
+    out(")");
+  }
+
+  visitBinary(Binary binary) {
+    Expression left = binary.left;
+    Expression right = binary.right;
+    String op = binary.op;
+    int leftPrecedenceRequirement;
+    int rightPrecedenceRequirement;
+    switch (op) {
+      case "||":
+        leftPrecedenceRequirement = LOGICAL_OR;
+        // x || (y || z) <=> (x || y) || z.
+        rightPrecedenceRequirement = LOGICAL_OR;
+        break;
+      case "&&":
+        leftPrecedenceRequirement = LOGICAL_AND;
+        // x && (y && z) <=> (x && y) && z.
+        rightPrecedenceRequirement = LOGICAL_AND;
+        break;
+      case "|":
+        leftPrecedenceRequirement = BIT_OR;
+        // x | (y | z) <=> (x | y) | z.
+        rightPrecedenceRequirement = BIT_OR;
+        break;
+      case "^":
+        leftPrecedenceRequirement = BIT_XOR;
+        // x ^ (y ^ z) <=> (x ^ y) ^ z.
+        rightPrecedenceRequirement = BIT_XOR;
+        break;
+      case "&":
+        leftPrecedenceRequirement = BIT_AND;
+        // x & (y & z) <=> (x & y) & z.
+        rightPrecedenceRequirement = BIT_AND;
+        break;
+      case "==":
+      case "!=":
+      case "===":
+      case "!==":
+        leftPrecedenceRequirement = EQUALITY;
+        rightPrecedenceRequirement = RELATIONAL;
+        break;
+      case "<":
+      case ">":
+      case "<=":
+      case ">=":
+      case "instanceof":
+      case "in":
+        leftPrecedenceRequirement = RELATIONAL;
+        rightPrecedenceRequirement = SHIFT;
+        break;
+      case ">>":
+      case "<<":
+      case ">>>":
+        leftPrecedenceRequirement = SHIFT;
+        rightPrecedenceRequirement = ADDITIVE;
+        break;
+      case "+":
+      case "-":
+        leftPrecedenceRequirement = ADDITIVE;
+        // We cannot remove parenthesis for "+" because
+        //   x + (y + z) <!=> (x + y) + z:
+        // Example:
+        //   "a" + (1 + 2) => "a3";
+        //   ("a" + 1) + 2 => "a12";
+        rightPrecedenceRequirement = MULTIPLICATIVE;
+        break;
+      case "*":
+      case "/":
+      case "%":
+        leftPrecedenceRequirement = MULTIPLICATIVE;
+        // We cannot remove parenthesis for "*" because of precision issues.
+        rightPrecedenceRequirement = UNARY;
+        break;
+      default:
+        compiler.internalError("Forgot operator: $op");
+    }
+
+    visitNestedExpression(left, leftPrecedenceRequirement,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+
+    if (op == "in" || op == "instanceof") {
+      // There are cases where the space is not required but without further
+      // analysis we cannot know.
+      out(" ");
+      out(op);
+      out(" ");
+    } else {
+      spaceOut();
+      out(op);
+      spaceOut();
+    }
+    visitNestedExpression(right, rightPrecedenceRequirement,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: false);
+  }
+
+  visitPrefix(Prefix unary) {
+    String op = unary.op;
+    switch (op) {
+      case "delete":
+      case "void":
+      case "typeof":
+        // There are cases where the space is not required but without further
+        // analysis we cannot know.
+        out(op);
+        out(" ");
+        break;
+      case "+":
+      case "++":
+        if (lastCharCode == charCodes.$PLUS) out(" ");
+        out(op);
+        break;
+      case "-":
+      case "--":
+        if (lastCharCode == charCodes.$MINUS) out(" ");
+        out(op);
+        break;
+      default:
+        out(op);
+    }
+    visitNestedExpression(unary.argument, UNARY,
+                          newInForInit: inForInit, newAtStatementBegin: false);
+  }
+
+  visitPostfix(Postfix postfix) {
+    visitNestedExpression(postfix.argument, LEFT_HAND_SIDE,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    out(postfix.op);
+  }
+
+  visitVariableUse(VariableUse ref) {
+    out(localNamer.getName(ref.name));
+  }
+
+  visitThis(This node) {
+    out("this");
+  }
+
+  visitVariableDeclaration(VariableDeclaration decl) {
+    out(localNamer.getName(decl.name));
+  }
+
+  visitParameter(Parameter param) {
+    out(localNamer.getName(param.name));
+  }
+
+  bool isDigit(int charCode) {
+    return charCodes.$0 <= charCode && charCode <= charCodes.$9;
+  }
+
+  bool isValidJavaScriptId(String field) {
+    if (field.length < 3) return false;
+    // Ignore the leading and trailing string-delimiter.
+    for (int i = 1; i < field.length - 1; i++) {
+      // TODO(floitsch): allow more characters.
+      int charCode = field.charCodeAt(i);
+      if (!(charCodes.$a <= charCode && charCode <= charCodes.$z ||
+            charCodes.$A <= charCode && charCode <= charCodes.$Z ||
+            charCode == charCodes.$$ ||
+            charCode == charCodes.$_ ||
+            i != 1 && isDigit(charCode))) {
+        return false;
+      }
+    }
+    // TODO(floitsch): normally we should also check that the field is not a
+    // reserved word.  We don't generate fields with reserved word names except
+    // for 'super'.
+    if (field == '"super"') return false;
+    return true;
+  }
+
+  visitAccess(PropertyAccess access) {
+    visitNestedExpression(access.receiver, CALL,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    Node selector = access.selector;
+    if (selector is LiteralString) {
+      LiteralString selectorString = selector;
+      String fieldWithQuotes = selectorString.value;
+      if (isValidJavaScriptId(fieldWithQuotes)) {
+        if (access.receiver is LiteralNumber) out(" ");
+        out(".");
+        out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1));
+        return;
+      }
+    }
+    out("[");
+    visitNestedExpression(selector, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out("]");
+  }
+
+  visitNamedFunction(NamedFunction namedFunction) {
+    VarCollector vars = new VarCollector();
+    vars.visitNamedFunction(namedFunction);
+    functionOut(namedFunction.function, namedFunction.name, vars);
+  }
+
+  visitFun(Fun fun) {
+    VarCollector vars = new VarCollector();
+    vars.visitFun(fun);
+    functionOut(fun, null, vars);
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    out(node.value ? "true" : "false");
+  }
+
+  visitLiteralString(LiteralString node) {
+    out(node.value);
+  }
+
+  visitLiteralNumber(LiteralNumber node) {
+    int charCode = node.value.charCodeAt(0);
+    if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) {
+      out(" ");
+    }
+    out(node.value);
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    out("null");
+  }
+
+  visitArrayInitializer(ArrayInitializer node) {
+    out("[");
+    List<ArrayElement> elements = node.elements;
+    int elementIndex = 0;
+    for (int i = 0; i < node.length; i++) {
+      if (elementIndex < elements.length &&
+          elements[elementIndex].index == i) {
+        visitNestedExpression(elements[elementIndex].value, ASSIGNMENT,
+                              newInForInit: false, newAtStatementBegin: false);
+        elementIndex++;
+        // We can avoid a trailing "," if there was an element just before. So
+        // `[1]` and `[1,]` are the same, but `[,]` and `[]` are not.
+        if (i != node.length - 1) {
+          out(",");
+          spaceOut();
+        }
+      } else {
+        out(",");
+      }
+    }
+    out("]");
+  }
+
+  visitArrayElement(ArrayElement node) {
+    throw "Unreachable";
+  }
+
+  visitObjectInitializer(ObjectInitializer node) {
+    // Print all the properties on one line until we see a function-valued
+    // property.  Ideally, we would use a proper pretty-printer to make the
+    // decision based on layout.
+    bool onePerLine = false;
+    List<Property> properties = node.properties;
+    out("{");
+    ++indentLevel;
+    for (int i = 0; i < properties.length; i++) {
+      Expression value = properties[i].value;
+      if (value is Fun || value is NamedFunction) onePerLine = true;
+      if (i != 0) {
+        out(",");
+        if (!onePerLine) spaceOut();
+      }
+      if (onePerLine) {
+        forceLine();
+        indent();
+      }
+      visitProperty(properties[i]);
+    }
+    --indentLevel;
+    if (onePerLine) lineOut();
+    out("}");
+  }
+
+  visitProperty(Property node) {
+    if (node.name is LiteralString) {
+      LiteralString nameString = node.name;
+      String name = nameString.value;
+      if (isValidJavaScriptId(name)) {
+        out(name.substring(1, name.length - 1));
+      } else {
+        out(name);
+      }
+    } else {
+      assert(node.name is LiteralNumber);
+      LiteralNumber nameNumber = node.name;
+      out(nameNumber.value);
+    }
+    out(":");
+    spaceOut();
+    visitNestedExpression(node.value, ASSIGNMENT,
+                          newInForInit: false, newAtStatementBegin: false);
+  }
+
+  visitRegExpLiteral(RegExpLiteral node) {
+    out(node.pattern);
+  }
+
+  visitLiteralExpression(LiteralExpression node) {
+    String template = node.template;
+    List<Expression> inputs = node.inputs;
+
+    List<String> parts = template.split('#');
+    if (parts.length != inputs.length + 1) {
+      compiler.internalError('Wrong number of arguments for JS: $template');
+    }
+    // Code that uses JS must take care of operator precedences, and
+    // put parenthesis if needed.
+    out(parts[0]);
+    for (int i = 0; i < inputs.length; i++) {
+      visit(inputs[i]);
+      out(parts[i + 1]);
+    }
+  }
+
+  visitLiteralStatement(LiteralStatement node) {
+    outLn(node.code);
+  }
+}
+
+
+class OrderedSet<T> {
+  final Set<T> set;
+  final List<T> list;
+
+  OrderedSet() : set = new Set<T>(), list = <T>[];
+
+  void add(T x) {
+    if (!set.contains(x)) {
+      set.add(x);
+      list.add(x);
+    }
+  }
+
+  void forEach(void fun(T x)) {
+    list.forEach(fun);
+  }
+}
+
+// Collects all the var declarations in the function.  We need to do this in a
+// separate pass because JS vars are lifted to the top of the function.
+class VarCollector extends BaseVisitor {
+  bool nested;
+  final OrderedSet<String> vars;
+  final OrderedSet<String> params;
+
+  VarCollector() : nested = false,
+                   vars = new OrderedSet<String>(),
+                   params = new OrderedSet<String>();
+
+  void forEachVar(void fn(String v)) => vars.forEach(fn);
+  void forEachParam(void fn(String p)) => params.forEach(fn);
+
+  void collectVarsInFunction(Fun fun) {
+    if (!nested) {
+      nested = true;
+      if (fun.params != null) {
+        for (int i = 0; i < fun.params.length; i++) {
+          params.add(fun.params[i].name);
+        }
+      }
+      visitBlock(fun.body);
+      nested = false;
+    }
+  }
+
+  void visitFunctionDeclaration(FunctionDeclaration declaration) {
+    // Note that we don't bother collecting the name of the function.
+    collectVarsInFunction(declaration.function);
+  }
+
+  void visitNamedFunction(NamedFunction namedFunction) {
+    // Note that we don't bother collecting the name of the function.
+    collectVarsInFunction(namedFunction.function);
+  }
+
+  void visitFun(Fun fun) {
+    collectVarsInFunction(fun);
+  }
+
+  void visitThis(This node) {}
+
+  void visitVariableDeclaration(VariableDeclaration decl) {
+    vars.add(decl.name);
+  }
+}
+
+
+/**
+ * Returns true, if the given node must be wrapped into braces when used
+ * as then-statement in an [If] that has an else branch.
+ */
+class DanglingElseVisitor extends BaseVisitor<bool> {
+  leg.Compiler compiler;
+
+  DanglingElseVisitor(this.compiler);
+
+  bool visitProgram(Program node) => false;
+
+  bool visitNode(Statement node) {
+    compiler.internalError("Forgot node: $node");
+  }
+
+  bool visitBlock(Block node) => false;
+  bool visitExpressionStatement(ExpressionStatement node) => false;
+  bool visitEmptyStatement(EmptyStatement node) => false;
+  bool visitIf(If node) {
+    if (!node.hasElse) return true;
+    return node.otherwise.accept(this);
+  }
+  bool visitFor(For node) => node.body.accept(this);
+  bool visitForIn(ForIn node) => node.body.accept(this);
+  bool visitWhile(While node) => node.body.accept(this);
+  bool visitDo(Do node) => false;
+  bool visitContinue(Continue node) => false;
+  bool visitBreak(Break node) => false;
+  bool visitReturn(Return node) => false;
+  bool visitThrow(Throw node) => false;
+  bool visitTry(Try node) {
+    if (node.finallyPart != null) {
+      return node.finallyPart.accept(this);
+    } else {
+      return node.catchPart.accept(this);
+    }
+  }
+  bool visitCatch(Catch node) => node.body.accept(this);
+  bool visitSwitch(Switch node) => false;
+  bool visitCase(Case node) => false;
+  bool visitDefault(Default node) => false;
+  bool visitFunctionDeclaration(FunctionDeclaration node) => false;
+  bool visitLabeledStatement(LabeledStatement node)
+      => node.body.accept(this);
+  bool visitLiteralStatement(LiteralStatement node) => true;
+
+  bool visitExpression(Expression node) => false;
+}
+
+
+leg.CodeBuffer prettyPrint(Node node, leg.Compiler compiler,
+                           { allowVariableMinification: true }) {
+  Printer printer =
+      new Printer(compiler,
+                  allowVariableMinification: allowVariableMinification);
+  printer.visit(node);
+  return printer.outBuffer;
+}
+
+
+abstract class LocalNamer {
+  String getName(String oldName);
+  String declareVariable(String oldName);
+  String declareParameter(String oldName);
+  void enterScope(VarCollector vars);
+  void leaveScope();
+}
+
+
+class IdentityNamer implements LocalNamer {
+  String getName(String oldName) => oldName;
+  String declareVariable(String oldName) => oldName;
+  String declareParameter(String oldName) => oldName;
+  void enterScope(VarCollector vars) {}
+  void leaveScope() {}
+}
+
+
+class MinifyRenamer implements LocalNamer {
+  final List<Map<String, String>> maps = [];
+  final List<int> parameterNumberStack = [];
+  final List<int> variableNumberStack = [];
+  int parameterNumber = 0;
+  int variableNumber = 0;
+
+  MinifyRenamer();
+
+  void enterScope(VarCollector vars) {
+    maps.add(new Map<String, String>());
+    variableNumberStack.add(variableNumber);
+    parameterNumberStack.add(parameterNumber);
+    vars.forEachVar(declareVariable);
+    vars.forEachParam(declareParameter);
+  }
+
+  void leaveScope() {
+    maps.removeLast();
+    variableNumber = variableNumberStack.removeLast();
+    parameterNumber = parameterNumberStack.removeLast();
+  }
+
+  String getName(String oldName) {
+    // Go from inner scope to outer looking for mapping of name.
+    for (int i = maps.length - 1; i >= 0; i--) {
+      var map = maps[i];
+      var replacement = map[oldName];
+      if (replacement != null) return replacement;
+    }
+    return oldName;
+  }
+
+  static const LOWER_CASE_LETTERS = 26;
+  static const LETTERS = 52;
+  static const DIGITS = 10;
+
+  static int nthLetter(int n) {
+    return (n < LOWER_CASE_LETTERS) ?
+           charCodes.$a + n :
+           charCodes.$A + n - LOWER_CASE_LETTERS;
+  }
+
+  // Parameters go from a to z and variables go from z to a.  This makes each
+  // argument list and each top-of-function var declaration look similar and
+  // helps gzip compress the file.  If we have more than 26 arguments and
+  // variables then we meet somewhere in the middle of the alphabet.  After
+  // that we give up trying to be nice to the compression algorithm and just
+  // use the same namespace for arguments and variables, starting with A, and
+  // moving on to a0, a1, etc.
+  String declareVariable(String oldName) {
+    var newName;
+    if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) {
+      // Variables start from z and go backwards, for better gzipability.
+      newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber);
+    } else {
+      // After 26 variables and parameters we allocate them in the same order.
+      newName = getNameNumber(oldName, variableNumber + parameterNumber);
+    }
+    variableNumber++;
+    return newName;
+  }
+
+  String declareParameter(String oldName) {
+    var newName;
+    if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) {
+      newName = getNameNumber(oldName, parameterNumber);
+    } else {
+      newName = getNameNumber(oldName, variableNumber + parameterNumber);
+    }
+    parameterNumber++;
+    return newName;
+  }
+
+  String getNameNumber(String oldName, int n) {
+    if (maps.isEmpty) return oldName;
+
+    String newName;
+    if (n < LETTERS) {
+      // Start naming variables a, b, c, ..., z, A, B, C, ..., Z.
+      newName = new String.fromCharCodes([nthLetter(n)]);
+    } else {
+      // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ...
+      // For all functions with fewer than 500 locals this is just as compact
+      // as using aa, ab, etc. but avoids clashes with keywords.
+      n -= LETTERS;
+      int digit = n % DIGITS;
+      n ~/= DIGITS;
+      int alphaChars = 1;
+      int nameSpaceSize = LETTERS;
+      // Find out whether we should use the 1-character namespace (size 52), the
+      // 2-character namespace (size 52*52), etc.
+      while (n >= nameSpaceSize) {
+        n -= nameSpaceSize;
+        alphaChars++;
+        nameSpaceSize *= LETTERS;
+      }
+      var codes = <int>[];
+      for (var i = 0; i < alphaChars; i++) {
+        nameSpaceSize ~/= LETTERS;
+        codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS));
+      }
+      codes.add(charCodes.$0 + digit);
+      newName = new String.fromCharCodes(codes);
+    }
+    assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName));
+    maps.last[oldName] = newName;
+    return newName;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart
new file mode 100644
index 0000000..2a681d8
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart
@@ -0,0 +1,1262 @@
+// 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.
+
+part of js_backend;
+
+typedef void Recompile(Element element);
+
+class ReturnInfo {
+  HType returnType;
+  List<Element> compiledFunctions;
+
+  ReturnInfo(HType this.returnType)
+      : compiledFunctions = new List<Element>();
+
+  ReturnInfo.unknownType() : this(null);
+
+  void update(HType type, Recompile recompile, Compiler compiler) {
+    HType newType =
+        returnType != null ? returnType.union(type, compiler) : type;
+    if (newType != returnType) {
+      if (returnType == null && identical(newType, HType.UNKNOWN)) {
+        // If the first actual piece of information is not providing any type
+        // information there is no need to recompile callers.
+        compiledFunctions.clear();
+      }
+      returnType = newType;
+      if (recompile != null) {
+        compiledFunctions.forEach(recompile);
+      }
+      compiledFunctions.clear();
+    }
+  }
+
+  // Note that lazy initializers are treated like functions (but are not
+  // of type [FunctionElement].
+  addCompiledFunction(Element function) => compiledFunctions.add(function);
+}
+
+class OptionalParameterTypes {
+  final List<SourceString> names;
+  final List<HType> types;
+
+  OptionalParameterTypes(int optionalArgumentsCount)
+      : names = new List<SourceString>.fixedLength(optionalArgumentsCount),
+        types = new List<HType>.fixedLength(optionalArgumentsCount);
+
+  int get length => names.length;
+  SourceString name(int index) => names[index];
+  HType type(int index) => types[index];
+  int indexOf(SourceString name) => names.indexOf(name);
+
+  HType typeFor(SourceString name) {
+    int index = indexOf(name);
+    if (index == -1) return null;
+    return type(index);
+  }
+
+  void update(int index, SourceString name, HType type) {
+    names[index] = name;
+    types[index] = type;
+  }
+
+  String toString() => "OptionalParameterTypes($names, $types)";
+}
+
+class HTypeList {
+  final List<HType> types;
+  final List<SourceString> namedArguments;
+
+  HTypeList(int length)
+      : types = new List<HType>.fixedLength(length),
+        namedArguments = null;
+  HTypeList.withNamedArguments(int length, this.namedArguments)
+      : types = new List<HType>.fixedLength(length);
+  const HTypeList.withAllUnknown()
+      : types = null,
+        namedArguments = null;
+
+  factory HTypeList.fromStaticInvocation(HInvokeStatic node, HTypeMap types) {
+    bool allUnknown = true;
+    for (int i = 1; i < node.inputs.length; i++) {
+      if (types[node.inputs[i]] != HType.UNKNOWN) {
+        allUnknown = false;
+        break;
+      }
+    }
+    if (allUnknown) return HTypeList.ALL_UNKNOWN;
+
+    HTypeList result = new HTypeList(node.inputs.length - 1);
+    for (int i = 0; i < result.types.length; i++) {
+      result.types[i] = types[node.inputs[i + 1]];
+    }
+    return result;
+  }
+
+  factory HTypeList.fromDynamicInvocation(HInvokeDynamic node,
+                                          Selector selector,
+                                          HTypeMap types) {
+    HTypeList result;
+    int argumentsCount = node.inputs.length - 1;
+    int startInvokeIndex = HInvoke.ARGUMENTS_OFFSET;
+
+    if (node.isInterceptorCall) {
+      argumentsCount--;
+      startInvokeIndex++;
+    }
+
+    if (selector.namedArgumentCount > 0) {
+      result =
+          new HTypeList.withNamedArguments(
+              argumentsCount, selector.namedArguments);
+    } else {
+      result = new HTypeList(argumentsCount);
+    }
+
+    for (int i = 0; i < result.types.length; i++) {
+      result.types[i] = types[node.inputs[i + startInvokeIndex]];
+    }
+    return result;
+  }
+
+  static const HTypeList ALL_UNKNOWN = const HTypeList.withAllUnknown();
+
+  bool get allUnknown => types == null;
+  bool get hasNamedArguments => namedArguments != null;
+  int get length => types.length;
+  HType operator[](int index) => types[index];
+  void operator[]=(int index, HType type) { types[index] = type; }
+
+  HTypeList union(HTypeList other, Compiler compiler) {
+    if (allUnknown) return this;
+    if (other.allUnknown) return other;
+    if (length != other.length) return HTypeList.ALL_UNKNOWN;
+    bool onlyUnknown = true;
+    HTypeList result = this;
+    for (int i = 0; i < length; i++) {
+      HType newType = this[i].union(other[i], compiler);
+      if (result == this && newType != this[i]) {
+        // Create a new argument types object with the matching types copied.
+        result = new HTypeList(length);
+        result.types.setRange(0, i, this.types);
+      }
+      if (result != this) {
+        result.types[i] = newType;
+      }
+      if (result[i] != HType.UNKNOWN) onlyUnknown = false;
+    }
+    return onlyUnknown ? HTypeList.ALL_UNKNOWN : result;
+  }
+
+  HTypeList unionWithOptionalParameters(
+      Selector selector,
+      FunctionSignature signature,
+      OptionalParameterTypes defaultValueTypes) {
+    assert(allUnknown || selector.argumentCount == this.length);
+    // Create a new HTypeList for holding types for all parameters.
+    HTypeList result = new HTypeList(signature.parameterCount);
+
+    // First fill in the type of the positional arguments.
+    int nextTypeIndex = -1;
+    if (allUnknown) {
+      for (int i = 0; i < selector.positionalArgumentCount; i++) {
+        result.types[i] = HType.UNKNOWN;
+      }
+    } else {
+      result.types.setRange(0, selector.positionalArgumentCount, this.types);
+      nextTypeIndex = selector.positionalArgumentCount;
+    }
+
+    // Next fill the type of the optional arguments.
+    // As the selector can pass optional arguments positionally some of the
+    // optional arguments might already have a type set. We only need to look
+    // at the optional arguments not passed positionally.
+    // The variable 'index' is counting the signatures optional arguments, the
+    // variable 'next' is set to the next optional arguments to look at and
+    // is used to skip some optional arguments.
+    int next = selector.positionalArgumentCount;
+    int index = signature.requiredParameterCount;
+    signature.forEachOptionalParameter((Element element) {
+      // If some optional parameters were passed positionally these have
+      // already been filled.
+      if (index == next) {
+        assert(result.types[index] == null);
+        HType type = null;
+        if (hasNamedArguments &&
+            selector.namedArguments.indexOf(element.name) >= 0) {
+          type = types[nextTypeIndex++];
+        } else {
+          type = defaultValueTypes.typeFor(element.name);
+        }
+        result.types[index] = type;
+        next++;
+      }
+      index++;
+    });
+    return result;
+  }
+
+  String toString() =>
+      allUnknown ? "HTypeList.ALL_UNKNOWN" : "HTypeList $types";
+}
+
+class FieldTypesRegistry {
+  final JavaScriptBackend backend;
+
+  /**
+   * For each class, [constructors] holds the set of constructors. If there is
+   * more than one constructor for a class it is currently not possible to
+   * infer the field types from construction, as the information collected does
+   * not correlate the generative constructors and generative constructor
+   * body/bodies.
+   */
+  final Map<ClassElement, Set<Element>> constructors;
+
+  /**
+   * The collected type information is stored in three maps. One for types
+   * assigned in the initializer list(s) [fieldInitializerTypeMap], one for
+   * types assigned in the constructor(s) [fieldConstructorTypeMap], and one
+   * for types assigned in the rest of the code, where the field can be
+   * resolved [fieldTypeMap].
+   *
+   * If a field has a type both from constructors and from the initializer
+   * list(s), then the type from the constructor(s) will owerride the one from
+   * the initializer list(s).
+   *
+   * Because the order in which generative constructors, generative constructor
+   * bodies and normal method/function bodies are compiled is undefined, and
+   * because they can all be recompiled, it is not possible to combine this
+   * information into one map at the moment.
+   */
+  final Map<Element, HType> fieldInitializerTypeMap;
+  final Map<Element, HType> fieldConstructorTypeMap;
+  final Map<Element, HType> fieldTypeMap;
+
+  /**
+   * The set of current names setter selectors used. If a named selector is
+   * used it is currently not possible to infer the type of the field.
+   */
+  final Set<SourceString> setterSelectorsUsed;
+
+  final Map<Element, Set<Element>> optimizedStaticFunctions;
+  final Map<Element, FunctionSet> optimizedFunctions;
+
+  FieldTypesRegistry(JavaScriptBackend backend)
+      : constructors =  new Map<ClassElement, Set<Element>>(),
+        fieldInitializerTypeMap = new Map<Element, HType>(),
+        fieldConstructorTypeMap = new Map<Element, HType>(),
+        fieldTypeMap = new Map<Element, HType>(),
+        setterSelectorsUsed = new Set<SourceString>(),
+        optimizedStaticFunctions = new Map<Element, Set<Element>>(),
+        optimizedFunctions = new Map<Element, FunctionSet>(),
+        this.backend = backend;
+
+  Compiler get compiler => backend.compiler;
+
+  void scheduleRecompilation(Element field) {
+    Set optimizedStatics = optimizedStaticFunctions[field];
+    if (optimizedStatics != null) {
+      optimizedStatics.forEach(backend.scheduleForRecompilation);
+      optimizedStaticFunctions.remove(field);
+    }
+    FunctionSet optimized = optimizedFunctions[field];
+    if (optimized != null) {
+      optimized.forEach(backend.scheduleForRecompilation);
+      optimizedFunctions.remove(field);
+    }
+  }
+
+  int constructorCount(Element element) {
+    assert(element.isClass());
+    Set<Element> ctors = constructors[element];
+    return ctors == null ? 0 : ctors.length;
+  }
+
+  void registerFieldType(Map<Element, HType> typeMap,
+                         Element field,
+                         HType type) {
+    assert(field.isField());
+    HType before = optimisticFieldType(field);
+
+    HType oldType = typeMap[field];
+    HType newType;
+
+    if (oldType != null) {
+      newType = oldType.union(type, compiler);
+    } else {
+      newType = type;
+    }
+    typeMap[field] = newType;
+    if (oldType != newType) {
+      scheduleRecompilation(field);
+    }
+  }
+
+  void registerConstructor(Element element) {
+    assert(element.isGenerativeConstructor());
+    Element cls = element.getEnclosingClass();
+    constructors.putIfAbsent(cls, () => new Set<Element>());
+    Set<Element> ctors = constructors[cls];
+    if (ctors.contains(element)) return;
+    ctors.add(element);
+    // We cannot infer field types for classes with more than one constructor.
+    // When the second constructor is seen, recompile all functions relying on
+    // optimistic field types for that class.
+    // TODO(sgjesse): Handle field types for classes with more than one
+    // constructor.
+    if (ctors.length == 2) {
+      optimizedFunctions.forEach((Element field, _) {
+        if (identical(field.enclosingElement, cls)) {
+          scheduleRecompilation(field);
+        }
+      });
+    }
+  }
+
+  void registerFieldInitializer(Element field, HType type) {
+    registerFieldType(fieldInitializerTypeMap, field, type);
+  }
+
+  void registerFieldConstructor(Element field, HType type) {
+    registerFieldType(fieldConstructorTypeMap, field, type);
+  }
+
+  void registerFieldSetter(FunctionElement element, Element field, HType type) {
+    HType initializerType = fieldInitializerTypeMap[field];
+    HType constructorType = fieldConstructorTypeMap[field];
+    HType setterType = fieldTypeMap[field];
+    if (type == HType.UNKNOWN
+        && initializerType == null
+        && constructorType == null
+        && setterType == null) {
+      // Don't register UNKONWN if there is currently no type information
+      // present for the field. Instead register the function holding the
+      // setter for recompilation if better type information for the field
+      // becomes available.
+      registerOptimizedFunction(element, field, type);
+      return;
+    }
+    registerFieldType(fieldTypeMap, field, type);
+  }
+
+  void addedDynamicSetter(Selector setter, HType type) {
+    // Field type optimizations are disabled for all fields matching a
+    // setter selector.
+    assert(setter.isSetter());
+    // TODO(sgjesse): Take the type of the setter into account.
+    if (setterSelectorsUsed.contains(setter.name)) return;
+    setterSelectorsUsed.add(setter.name);
+    optimizedStaticFunctions.forEach((Element field, _) {
+      if (field.name == setter.name) {
+        scheduleRecompilation(field);
+      }
+    });
+    optimizedFunctions.forEach((Element field, _) {
+      if (field.name == setter.name) {
+        scheduleRecompilation(field);
+      }
+    });
+  }
+
+  HType optimisticFieldType(Element field) {
+    assert(field.isField());
+    if (constructorCount(field.getEnclosingClass()) > 1) {
+      return HType.UNKNOWN;
+    }
+    if (setterSelectorsUsed.contains(field.name)) {
+      return HType.UNKNOWN;
+    }
+    HType initializerType = fieldInitializerTypeMap[field];
+    HType constructorType = fieldConstructorTypeMap[field];
+    if (initializerType == null && constructorType == null) {
+      // If there are no constructor type information return UNKNOWN. This
+      // ensures that the function will be recompiled if useful constructor
+      // type information becomes available.
+      return HType.UNKNOWN;
+    }
+    // A type set through the constructor overrides the type from the
+    // initializer list.
+    HType result = constructorType != null ? constructorType : initializerType;
+    HType type = fieldTypeMap[field];
+    if (type != null) result = result.union(type, compiler);
+    return result;
+  }
+
+  void registerOptimizedFunction(FunctionElement element,
+                                 Element field,
+                                 HType type) {
+    assert(field.isField());
+    if (Elements.isStaticOrTopLevel(element)) {
+      optimizedStaticFunctions.putIfAbsent(
+          field, () => new Set<Element>());
+      optimizedStaticFunctions[field].add(element);
+    } else {
+      optimizedFunctions.putIfAbsent(
+          field, () => new FunctionSet(backend.compiler));
+      optimizedFunctions[field].add(element);
+    }
+  }
+
+  void dump() {
+    Set<Element> allFields = new Set<Element>();
+    fieldInitializerTypeMap.keys.forEach(allFields.add);
+    fieldConstructorTypeMap.keys.forEach(allFields.add);
+    fieldTypeMap.keys.forEach(allFields.add);
+    allFields.forEach((Element field) {
+      print("Inferred $field has type ${optimisticFieldType(field)}");
+    });
+  }
+}
+
+class ArgumentTypesRegistry {
+  final JavaScriptBackend backend;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Keys must be declaration elements.
+   */
+  final Map<Element, HTypeList> staticTypeMap;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements must be declaration elements.
+   */
+  final Set<Element> optimizedStaticFunctions;
+  final SelectorMap<HTypeList> selectorTypeMap;
+  final FunctionSet optimizedFunctions;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Keys must be declaration elements.
+   */
+  final Map<Element, HTypeList> optimizedTypes;
+  final Map<Element, OptionalParameterTypes> optimizedDefaultValueTypes;
+
+  ArgumentTypesRegistry(JavaScriptBackend backend)
+      : staticTypeMap = new Map<Element, HTypeList>(),
+        optimizedStaticFunctions = new Set<Element>(),
+        selectorTypeMap = new SelectorMap<HTypeList>(backend.compiler),
+        optimizedFunctions = new FunctionSet(backend.compiler),
+        optimizedTypes = new Map<Element, HTypeList>(),
+        optimizedDefaultValueTypes =
+            new Map<Element, OptionalParameterTypes>(),
+        this.backend = backend;
+
+  Compiler get compiler => backend.compiler;
+
+  bool updateTypes(HTypeList oldTypes, HTypeList newTypes, var key, var map) {
+    if (oldTypes.allUnknown) return false;
+    newTypes = oldTypes.union(newTypes, backend.compiler);
+    if (identical(newTypes, oldTypes)) return false;
+    map[key] = newTypes;
+    return true;
+  }
+
+  void registerStaticInvocation(HInvokeStatic node, HTypeMap types) {
+    Element element = node.element;
+    assert(invariant(node, element.isDeclaration));
+    HTypeList oldTypes = staticTypeMap[element];
+    HTypeList newTypes = new HTypeList.fromStaticInvocation(node, types);
+    if (oldTypes == null) {
+      staticTypeMap[element] = newTypes;
+    } else if (updateTypes(oldTypes, newTypes, element, staticTypeMap)) {
+      if (optimizedStaticFunctions.contains(element)) {
+        backend.scheduleForRecompilation(element);
+      }
+    }
+  }
+
+  void registerNonCallStaticUse(HStatic node) {
+    // When a static is used for anything else than a call target we cannot
+    // infer anything about its parameter types.
+    Element element = node.element;
+    assert(invariant(node, element.isDeclaration));
+    if (optimizedStaticFunctions.contains(element)) {
+      backend.scheduleForRecompilation(element);
+    }
+    staticTypeMap[element] = HTypeList.ALL_UNKNOWN;
+  }
+
+  void registerDynamicInvocation(HTypeList providedTypes, Selector selector) {
+    if (selector.isClosureCall()) {
+      // We cannot use the current framework to do optimizations based
+      // on the 'call' selector because we are also generating closure
+      // calls during the emitter phase, which at this point, does not
+      // track parameter types, nor invalidates optimized methods.
+      return;
+    }
+    if (!selectorTypeMap.containsKey(selector)) {
+      selectorTypeMap[selector] = providedTypes;
+    } else {
+      HTypeList oldTypes = selectorTypeMap[selector];
+      updateTypes(oldTypes, providedTypes, selector, selectorTypeMap);
+    }
+
+    // If we're not compiling, we don't have to do anything.
+    if (compiler.phase != Compiler.PHASE_COMPILING) return;
+
+    // Run through all optimized functions and figure out if they need
+    // to be recompiled because of this new invocation.
+    optimizedFunctions.filterBySelector(selector).forEach((Element element) {
+      // TODO(kasperl): Maybe check if the element is already marked for
+      // recompilation? Could be pretty cheap compared to computing
+      // union types.
+      HTypeList newTypes =
+          parameterTypes(element, optimizedDefaultValueTypes[element]);
+      bool recompile = false;
+      if (newTypes.allUnknown) {
+        recompile = true;
+      } else {
+        HTypeList oldTypes = optimizedTypes[element];
+        assert(newTypes.length == oldTypes.length);
+        for (int i = 0; i < oldTypes.length; i++) {
+          if (newTypes[i] != oldTypes[i]) {
+            recompile = true;
+            break;
+          }
+        }
+      }
+      if (recompile) backend.scheduleForRecompilation(element);
+    });
+  }
+
+  HTypeList parameterTypes(FunctionElement element,
+                           OptionalParameterTypes defaultValueTypes) {
+    assert(invariant(element, element.isDeclaration));
+    // Handle static functions separately.
+    if (Elements.isStaticOrTopLevelFunction(element) ||
+        element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
+      HTypeList types = staticTypeMap[element];
+      if (types != null) {
+        if (!optimizedStaticFunctions.contains(element)) {
+          optimizedStaticFunctions.add(element);
+        }
+        return types;
+      } else {
+        return HTypeList.ALL_UNKNOWN;
+      }
+    }
+
+    // Getters have no parameters.
+    if (element.isGetter()) return HTypeList.ALL_UNKNOWN;
+
+    // TODO(kasperl): What kind of non-members do we get here?
+    if (!element.isMember()) return HTypeList.ALL_UNKNOWN;
+
+    // If there are any getters for this method we cannot know anything about
+    // the types of the provided parameters. Use resolverWorld for now as that
+    // information does not change during compilation.
+    // TODO(ngeoffray): These checks should use the codegenWorld and keep track
+    // of changes to this information.
+    if (compiler.resolverWorld.hasInvokedGetter(element, compiler)) {
+      return HTypeList.ALL_UNKNOWN;
+    }
+
+    FunctionSignature signature = element.computeSignature(compiler);
+    HTypeList found = null;
+    selectorTypeMap.visitMatching(element,
+        (Selector selector, HTypeList types) {
+      if (selector.argumentCount != signature.parameterCount ||
+          selector.namedArgumentCount > 0) {
+        types = types.unionWithOptionalParameters(selector,
+                                                  signature,
+                                                  defaultValueTypes);
+      }
+      assert(types.allUnknown || types.length == signature.parameterCount);
+      found = (found == null) ? types : found.union(types, compiler);
+      return !found.allUnknown;
+    });
+    return found != null ? found : HTypeList.ALL_UNKNOWN;
+  }
+
+  void registerOptimizedFunction(Element element,
+                                 HTypeList parameterTypes,
+                                 OptionalParameterTypes defaultValueTypes) {
+    if (Elements.isStaticOrTopLevelFunction(element)) {
+      if (parameterTypes.allUnknown) {
+        optimizedStaticFunctions.remove(element);
+      } else {
+        optimizedStaticFunctions.add(element);
+      }
+    }
+
+    // TODO(kasperl): What kind of non-members do we get here?
+    if (!element.isMember()) return;
+
+    if (parameterTypes.allUnknown) {
+      optimizedFunctions.remove(element);
+      optimizedTypes.remove(element);
+      optimizedDefaultValueTypes.remove(element);
+    } else {
+      optimizedFunctions.add(element);
+      optimizedTypes[element] = parameterTypes;
+      optimizedDefaultValueTypes[element] = defaultValueTypes;
+    }
+  }
+
+  void dump() {
+    optimizedFunctions.forEach((Element element) {
+      HTypeList types = optimizedTypes[element];
+      print("Inferred $element has argument types ${types.types}");
+    });
+  }
+}
+
+class JavaScriptItemCompilationContext extends ItemCompilationContext {
+  final HTypeMap types;
+  final Set<HInstruction> boundsChecked;
+
+  JavaScriptItemCompilationContext()
+      : types = new HTypeMap(),
+        boundsChecked = new Set<HInstruction>();
+}
+
+class JavaScriptBackend extends Backend {
+  SsaBuilderTask builder;
+  SsaOptimizerTask optimizer;
+  SsaCodeGeneratorTask generator;
+  CodeEmitterTask emitter;
+
+  /**
+   * The generated code as a js AST for compiled methods. 
+   */
+  Map<Element, js.Expression> get generatedCode {
+    return compiler.enqueuer.codegen.generatedCode;
+  }
+
+  /**
+   * The generated code as a js AST for compiled bailout methods. 
+   */
+  final Map<Element, js.Expression> generatedBailoutCode =
+      new Map<Element, js.Expression>();
+
+  ClassElement jsStringClass;
+  ClassElement jsArrayClass;
+  ClassElement jsNumberClass;
+  ClassElement jsIntClass;
+  ClassElement jsDoubleClass;
+  ClassElement jsFunctionClass;
+  ClassElement jsNullClass;
+  ClassElement jsBoolClass;
+  ClassElement objectInterceptorClass;
+  Element jsArrayLength;
+  Element jsStringLength;
+  Element jsArrayRemoveLast;
+  Element jsArrayAdd;
+  Element jsStringSplit;
+  Element jsStringConcat;
+  Element jsStringToString;
+  Element getInterceptorMethod;
+  Element fixedLengthListConstructor;
+  bool seenAnyClass = false;
+
+  final Namer namer;
+
+  /**
+   * Interface used to determine if an object has the JavaScript
+   * indexing behavior. The interface is only visible to specific
+   * libraries.
+   */
+  ClassElement jsIndexingBehaviorInterface;
+
+  final Map<Element, ReturnInfo> returnInfo;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements must be declaration elements.
+   */
+  final List<Element> invalidateAfterCodegen;
+  ArgumentTypesRegistry argumentTypes;
+  FieldTypesRegistry fieldTypes;
+
+  /**
+   * A collection of selectors of intercepted method calls. The
+   * emitter uses this set to generate the [:ObjectInterceptor:] class
+   * whose members just forward the call to the intercepted receiver.
+   */
+  final Set<Selector> usedInterceptors;
+
+  /**
+   * A collection of selectors that must have a one shot interceptor
+   * generated.
+   */
+  final Set<Selector> oneShotInterceptors;
+
+  /**
+   * The members of instantiated interceptor classes: maps a member
+   * name to the list of members that have that name. This map is used
+   * by the codegen to know whether a send must be intercepted or not.
+   */
+  final Map<SourceString, Set<Element>> interceptedElements;
+
+  /**
+   * A map of specialized versions of the [getInterceptorMethod].
+   * Since [getInterceptorMethod] is a hot method at runtime, we're
+   * always specializing it based on the incoming type. The keys in
+   * the map are the names of these specialized versions. Note that
+   * the generic version that contains all possible type checks is
+   * also stored in this map.
+   */
+  final Map<String, Collection<ClassElement>> specializedGetInterceptors;
+
+  /**
+   * Set of classes whose instances are intercepted. Implemented as a
+   * [LinkedHashMap] to preserve the insertion order.
+   * TODO(ngeoffray): No need to preserve order anymore.
+   */
+  final Map<ClassElement, ClassElement> interceptedClasses;
+
+  List<CompilerTask> get tasks {
+    return <CompilerTask>[builder, optimizer, generator, emitter];
+  }
+
+  final RuntimeTypeInformation rti;
+
+  JavaScriptBackend(Compiler compiler, bool generateSourceMap, bool disableEval)
+      : namer = determineNamer(compiler),
+        returnInfo = new Map<Element, ReturnInfo>(),
+        invalidateAfterCodegen = new List<Element>(),
+        usedInterceptors = new Set<Selector>(),
+        oneShotInterceptors = new Set<Selector>(),
+        interceptedElements = new Map<SourceString, Set<Element>>(),
+        rti = new RuntimeTypeInformation(compiler),
+        specializedGetInterceptors =
+            new Map<String, Collection<ClassElement>>(),
+        interceptedClasses = new LinkedHashMap<ClassElement, ClassElement>(),
+        super(compiler, JAVA_SCRIPT_CONSTANT_SYSTEM) {
+    emitter = disableEval
+        ? new CodeEmitterNoEvalTask(compiler, namer, generateSourceMap)
+        : new CodeEmitterTask(compiler, namer, generateSourceMap);
+    builder = new SsaBuilderTask(this);
+    optimizer = new SsaOptimizerTask(this);
+    generator = new SsaCodeGeneratorTask(this);
+    argumentTypes = new ArgumentTypesRegistry(this);
+    fieldTypes = new FieldTypesRegistry(this);
+  }
+
+  static Namer determineNamer(Compiler compiler) {
+    return compiler.enableMinification ?
+        new MinifyNamer(compiler) :
+        new Namer(compiler);
+  }
+
+  bool isInterceptorClass(Element element) {
+    if (element == null) return false;
+    return interceptedClasses.containsKey(element);
+  }
+
+  void addInterceptedSelector(Selector selector) {
+    usedInterceptors.add(selector);
+  }
+
+  void addOneShotInterceptor(Selector selector) {
+    oneShotInterceptors.add(selector);
+  }
+
+  /**
+   * Returns a set of interceptor classes that contain a member whose
+   * signature matches the given [selector]. Returns [:null:] if there
+   * is no class.
+   */
+  Set<ClassElement> getInterceptedClassesOn(Selector selector) {
+    Set<Element> intercepted = interceptedElements[selector.name];
+    if (intercepted == null) return null;
+    Set<ClassElement> result = new Set<ClassElement>();
+    for (Element element in intercepted) {
+      if (selector.applies(element, compiler)) {
+        result.add(element.getEnclosingClass());
+      }
+    }
+    if (result.isEmpty) return null;
+    return result;
+  }
+
+  List<ClassElement> getListOfInterceptedClasses() {
+      return <ClassElement>[jsStringClass, jsArrayClass, jsIntClass,
+                            jsDoubleClass, jsNumberClass, jsNullClass,
+                            jsFunctionClass, jsBoolClass];
+  }
+
+  void initializeInterceptorElements() {
+    objectInterceptorClass =
+        compiler.findInterceptor(const SourceString('ObjectInterceptor'));
+    getInterceptorMethod =
+        compiler.findInterceptor(const SourceString('getInterceptor'));
+    List<ClassElement> classes = [
+      jsStringClass = compiler.findInterceptor(const SourceString('JSString')),
+      jsArrayClass = compiler.findInterceptor(const SourceString('JSArray')),
+      // The int class must be before the double class, because the
+      // emitter relies on this list for the order of type checks.
+      jsIntClass = compiler.findInterceptor(const SourceString('JSInt')),
+      jsDoubleClass = compiler.findInterceptor(const SourceString('JSDouble')),
+      jsNumberClass = compiler.findInterceptor(const SourceString('JSNumber')),
+      jsNullClass = compiler.findInterceptor(const SourceString('JSNull')),
+      jsFunctionClass =
+          compiler.findInterceptor(const SourceString('JSFunction')),
+      jsBoolClass = compiler.findInterceptor(const SourceString('JSBool'))];
+
+    jsArrayClass.ensureResolved(compiler);
+    jsArrayLength = compiler.lookupElementIn(
+        jsArrayClass, const SourceString('length'));
+    jsArrayRemoveLast = compiler.lookupElementIn(
+        jsArrayClass, const SourceString('removeLast'));
+    jsArrayAdd = compiler.lookupElementIn(
+        jsArrayClass, const SourceString('add'));
+
+    jsStringClass.ensureResolved(compiler);
+    jsStringLength = compiler.lookupElementIn(
+        jsStringClass, const SourceString('length'));
+    jsStringSplit = compiler.lookupElementIn(
+        jsStringClass, const SourceString('split'));
+    jsStringConcat = compiler.lookupElementIn(
+        jsStringClass, const SourceString('concat'));
+    jsStringToString = compiler.lookupElementIn(
+        jsStringClass, const SourceString('toString'));
+
+    for (ClassElement cls in classes) {
+      if (cls != null) interceptedClasses[cls] = null;
+    }
+  }
+
+  void addInterceptors(ClassElement cls, Enqueuer enqueuer) {
+    if (enqueuer.isResolutionQueue) {
+      cls.ensureResolved(compiler);
+      cls.forEachMember((ClassElement classElement, Element member) {
+          Set<Element> set = interceptedElements.putIfAbsent(
+              member.name, () => new Set<Element>());
+          set.add(member);
+        },
+        includeSuperMembers: true);
+    }
+    enqueuer.registerInstantiatedClass(cls);
+  }
+
+  void registerSpecializedGetInterceptor(Set<ClassElement> classes) {
+    compiler.enqueuer.codegen.registerInstantiatedClass(objectInterceptorClass);
+    String name = namer.getInterceptorName(getInterceptorMethod, classes);
+    if (classes.contains(compiler.objectClass)) {
+      // We can't use a specialized [getInterceptorMethod], so we make
+      // sure we emit the one with all checks.
+      specializedGetInterceptors.putIfAbsent(name, () {
+        // It is important to take the order provided by the map,
+        // because we want the int type check to happen before the
+        // double type check: the double type check covers the int
+        // type check. Also we don't need to do a number type check
+        // because that is covered by the double type check.
+        List<ClassElement> keys = <ClassElement>[];
+        interceptedClasses.forEach((ClassElement cls, _) {
+          if (cls != jsNumberClass) keys.add(cls);
+        });
+        return keys;
+      });
+    } else {
+      specializedGetInterceptors[name] = classes;
+    }
+  }
+
+  void initializeNoSuchMethod() {
+    // In case the emitter generates noSuchMethod calls, we need to
+    // make sure all [noSuchMethod] methods know they might take a
+    // [JsInvocationMirror] as parameter.
+    HTypeList types = new HTypeList(1);
+    types[0] = new HType.fromBoundedType(
+        compiler.jsInvocationMirrorClass.computeType(compiler),
+        compiler,
+        false);
+    argumentTypes.registerDynamicInvocation(types, new Selector.noSuchMethod());
+  }
+
+  void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {
+    if (!seenAnyClass) {
+      initializeInterceptorElements();
+      initializeNoSuchMethod();
+      seenAnyClass = true;
+    }
+    ClassElement result = null;
+    if (cls == compiler.stringClass) {
+      addInterceptors(jsStringClass, enqueuer);
+    } else if (cls == compiler.listClass) {
+      addInterceptors(jsArrayClass, enqueuer);
+      // The backend will try to optimize array access and use the
+      // `ioore` and `iae` helpers directly.
+      if (enqueuer.isResolutionQueue) {
+        enqueuer.registerStaticUse(
+            compiler.findHelper(const SourceString('ioore')));
+        enqueuer.registerStaticUse(
+            compiler.findHelper(const SourceString('iae')));
+      }
+    } else if (cls == compiler.intClass) {
+      addInterceptors(jsIntClass, enqueuer);
+      addInterceptors(jsNumberClass, enqueuer);
+    } else if (cls == compiler.doubleClass) {
+      addInterceptors(jsDoubleClass, enqueuer);
+      addInterceptors(jsNumberClass, enqueuer);
+    } else if (cls == compiler.functionClass) {
+      addInterceptors(jsFunctionClass, enqueuer);
+    } else if (cls == compiler.boolClass) {
+      addInterceptors(jsBoolClass, enqueuer);
+    } else if (cls == compiler.nullClass) {
+      addInterceptors(jsNullClass, enqueuer);
+    } else if (cls == compiler.numClass) {
+      addInterceptors(jsIntClass, enqueuer);
+      addInterceptors(jsDoubleClass, enqueuer);
+      addInterceptors(jsNumberClass, enqueuer);
+    } else if (cls == compiler.mapClass) {
+      // The backend will use a literal list to initialize the entries
+      // of the map.
+      if (enqueuer.isResolutionQueue) {
+        enqueuer.registerInstantiatedClass(compiler.listClass); 
+      }
+    }
+  }
+
+  Element get cyclicThrowHelper {
+    return compiler.findHelper(const SourceString("throwCyclicInit"));
+  }
+
+  JavaScriptItemCompilationContext createItemCompilationContext() {
+    return new JavaScriptItemCompilationContext();
+  }
+
+  void enqueueHelpers(ResolutionEnqueuer world) {
+    enqueueAllTopLevelFunctions(compiler.jsHelperLibrary, world);
+
+    jsIndexingBehaviorInterface =
+        compiler.findHelper(const SourceString('JavaScriptIndexingBehavior'));
+    if (jsIndexingBehaviorInterface != null) {
+      world.registerIsCheck(jsIndexingBehaviorInterface.computeType(compiler));
+    }
+
+    for (var helper in [const SourceString('Closure'),
+                        const SourceString('ConstantMap'),
+                        const SourceString('ConstantProtoMap')]) {
+      var e = compiler.findHelper(helper);
+      if (e != null) world.registerInstantiatedClass(e);
+    }
+  }
+
+  void codegen(CodegenWorkItem work) {
+    Element element = work.element;
+    if (element.kind.category == ElementCategory.VARIABLE) {
+      Constant initialValue = compiler.constantHandler.compileWorkItem(work);
+      if (initialValue != null) {
+        return;
+      } else {
+        // If the constant-handler was not able to produce a result we have to
+        // go through the builder (below) to generate the lazy initializer for
+        // the static variable.
+        // We also need to register the use of the cyclic-error helper.
+        compiler.enqueuer.codegen.registerStaticUse(cyclicThrowHelper);
+      }
+    }
+
+    HGraph graph = builder.build(work);
+    optimizer.optimize(work, graph, false);
+    if (work.allowSpeculativeOptimization
+        && optimizer.trySpeculativeOptimizations(work, graph)) {
+      js.Expression code = generator.generateBailoutMethod(work, graph);
+      generatedBailoutCode[element] = code;
+      optimizer.prepareForSpeculativeOptimizations(work, graph);
+      optimizer.optimize(work, graph, true);
+    }
+    js.Expression code = generator.generateCode(work, graph);
+    generatedCode[element] = code;
+    invalidateAfterCodegen.forEach(eagerRecompile);
+    invalidateAfterCodegen.clear();
+  }
+
+  native.NativeEnqueuer nativeResolutionEnqueuer(Enqueuer world) {
+    return new native.NativeResolutionEnqueuer(world, compiler);
+  }
+
+  native.NativeEnqueuer nativeCodegenEnqueuer(Enqueuer world) {
+    return new native.NativeCodegenEnqueuer(world, compiler, emitter);
+  }
+
+  /**
+   * Unit test hook that returns code of an element as a String.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  String assembleCode(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    return js.prettyPrint(generatedCode[element], compiler).getText();
+  }
+
+  void assembleProgram() {
+    emitter.assembleProgram();
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void scheduleForRecompilation(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    if (compiler.phase == Compiler.PHASE_COMPILING) {
+      invalidateAfterCodegen.add(element);
+    }
+  }
+
+  /**
+   *  Register a dynamic invocation and collect the provided types for the
+   *  named selector.
+   */
+  void registerDynamicInvocation(HInvokeDynamic node,
+                                 Selector selector,
+                                 HTypeMap types) {
+    HTypeList providedTypes =
+        new HTypeList.fromDynamicInvocation(node, selector, types);
+    argumentTypes.registerDynamicInvocation(providedTypes, selector);
+  }
+
+  /**
+   *  Register a static invocation and collect the provided types for the
+   *  named selector.
+   */
+  void registerStaticInvocation(HInvokeStatic node, HTypeMap types) {
+    argumentTypes.registerStaticInvocation(node, types);
+  }
+
+  /**
+   *  Register that a static is used for something else than a direct call
+   *  target.
+   */
+  void registerNonCallStaticUse(HStatic node) {
+    argumentTypes.registerNonCallStaticUse(node);
+  }
+
+  /**
+   * Retrieve the types of the parameters used for calling the [element]
+   * function. The types are optimistic in the sense as they are based on the
+   * possible invocations of the function seen so far.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  HTypeList optimisticParameterTypes(
+      FunctionElement element,
+      OptionalParameterTypes defaultValueTypes) {
+    assert(invariant(element, element.isDeclaration));
+    if (element.parameterCount(compiler) == 0) return HTypeList.ALL_UNKNOWN;
+    return argumentTypes.parameterTypes(element, defaultValueTypes);
+  }
+
+  /**
+   * Register that the function [element] has been optimized under the
+   * assumptions that the types [parameterType] will be used for calling it.
+   * The passed [defaultValueTypes] holds the types of default values for
+   * the optional parameters. If this assumption fail the function will be
+   * scheduled for recompilation.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  registerParameterTypesOptimization(
+      FunctionElement element,
+      HTypeList parameterTypes,
+      OptionalParameterTypes defaultValueTypes) {
+    assert(invariant(element, element.isDeclaration));
+    if (element.parameterCount(compiler) == 0) return;
+    argumentTypes.registerOptimizedFunction(
+        element, parameterTypes, defaultValueTypes);
+  }
+
+  registerFieldTypesOptimization(FunctionElement element,
+                                 Element field,
+                                 HType type) {
+    fieldTypes.registerOptimizedFunction(element, field, type);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void registerReturnType(FunctionElement element, HType returnType) {
+    assert(invariant(element, element.isDeclaration));
+    ReturnInfo info = returnInfo[element];
+    if (info != null) {
+      info.update(returnType, scheduleForRecompilation, compiler);
+    } else {
+      returnInfo[element] = new ReturnInfo(returnType);
+    }
+  }
+
+  /**
+   * Retrieve the return type of the function [callee]. The type is optimistic
+   * in the sense that is is based on the compilation of [callee]. If [callee]
+   * is recompiled the return type might change to someting broader. For that
+   * reason [caller] is registered for recompilation if this happens. If the
+   * function [callee] has not yet been compiled the returned type is [null].
+   *
+   * Invariant: Both [caller] and [callee] must be declaration elements.
+   */
+  HType optimisticReturnTypesWithRecompilationOnTypeChange(
+      Element caller, FunctionElement callee) {
+    assert(invariant(callee, callee.isDeclaration));
+    returnInfo.putIfAbsent(callee, () => new ReturnInfo.unknownType());
+    ReturnInfo info = returnInfo[callee];
+    HType returnType = info.returnType;
+    if (returnType != HType.UNKNOWN && returnType != null && caller != null) {
+      assert(invariant(caller, caller.isDeclaration));
+      info.addCompiledFunction(caller);
+    }
+    return info.returnType;
+  }
+
+  void dumpReturnTypes() {
+    returnInfo.forEach((Element element, ReturnInfo info) {
+      if (info.returnType != HType.UNKNOWN) {
+        print("Inferred $element has return type ${info.returnType}");
+      }
+    });
+  }
+
+  void registerConstructor(Element element) {
+    fieldTypes.registerConstructor(element);
+  }
+
+  void registerFieldInitializer(Element field, HType type) {
+    fieldTypes.registerFieldInitializer(field, type);
+  }
+
+  void registerFieldConstructor(Element field, HType type) {
+    fieldTypes.registerFieldConstructor(field, type);
+  }
+
+  void registerFieldSetter(FunctionElement element, Element field, HType type) {
+    fieldTypes.registerFieldSetter(element, field, type);
+  }
+
+  void addedDynamicSetter(Selector setter, HType type) {
+    fieldTypes.addedDynamicSetter(setter, type);
+  }
+
+  HType optimisticFieldType(Element element) {
+    return fieldTypes.optimisticFieldType(element);
+  }
+
+  /**
+   * Return the checked mode helper name that will be needed to do a
+   * type check on [type] at runtime. Note that this method is being
+   * called both by the resolver with interface types (int, String,
+   * ...), and by the SSA backend with implementation types (JSInt,
+   * JSString, ...).
+   */
+  SourceString getCheckedModeHelper(DartType type) {
+    Element element = type.element;
+    bool nativeCheck =
+          emitter.nativeEmitter.requiresNativeIsCheck(element);
+    if (type.isMalformed) {
+      // Check for malformed types first, because the type may be a list type
+      // with a malformed argument type.
+      return const SourceString('malformedTypeCheck');
+    } else if (type == compiler.types.voidType) {
+      return const SourceString('voidTypeCheck');
+    } else if (element == jsStringClass || element == compiler.stringClass) {
+      return const SourceString('stringTypeCheck');
+    } else if (element == jsDoubleClass || element == compiler.doubleClass) {
+      return const SourceString('doubleTypeCheck');
+    } else if (element == jsNumberClass || element == compiler.numClass) {
+      return const SourceString('numTypeCheck');
+    } else if (element == jsBoolClass || element == compiler.boolClass) {
+      return const SourceString('boolTypeCheck');
+    } else if (element == jsFunctionClass
+               || element == compiler.functionClass) {
+      return const SourceString('functionTypeCheck');
+    } else if (element == jsIntClass || element == compiler.intClass) {
+      return const SourceString('intTypeCheck');
+    } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
+      return nativeCheck
+          ? const SourceString('numberOrStringSuperNativeTypeCheck')
+          : const SourceString('numberOrStringSuperTypeCheck');
+    } else if (Elements.isStringOnlySupertype(element, compiler)) {
+      return nativeCheck
+          ? const SourceString('stringSuperNativeTypeCheck')
+          : const SourceString('stringSuperTypeCheck');
+    } else if (element == compiler.listClass || element == jsArrayClass) {
+      return const SourceString('listTypeCheck');
+    } else {
+      if (Elements.isListSupertype(element, compiler)) {
+        return nativeCheck
+            ? const SourceString('listSuperNativeTypeCheck')
+            : const SourceString('listSuperTypeCheck');
+      } else {
+        return nativeCheck
+            ? const SourceString('callTypeCheck')
+            : const SourceString('propertyTypeCheck');
+      }
+    }
+  }
+
+  void dumpInferredTypes() {
+    print("Inferred argument types:");
+    print("------------------------");
+    argumentTypes.dump();
+    print("");
+    print("Inferred return types:");
+    print("----------------------");
+    dumpReturnTypes();
+    print("");
+    print("Inferred field types:");
+    print("------------------------");
+    fieldTypes.dump();
+    print("");
+  }
+
+  Element getExceptionUnwrapper() {
+    return compiler.findHelper(const SourceString('unwrapException'));
+  }
+
+  Element getThrowRuntimeError() {
+    return compiler.findHelper(const SourceString('throwRuntimeError'));
+  }
+
+  Element getThrowMalformedSubtypeError() {
+    return compiler.findHelper(
+        const SourceString('throwMalformedSubtypeError'));
+  }
+
+  Element getThrowAbstractClassInstantiationError() {
+    return compiler.findHelper(
+        const SourceString('throwAbstractClassInstantiationError'));
+  }
+
+  Element getClosureConverter() {
+    return compiler.findHelper(const SourceString('convertDartClosureToJS'));
+  }
+
+  Element getTraceFromException() {
+    return compiler.findHelper(const SourceString('getTraceFromException'));
+  }
+
+  Element getMapMaker() {
+    return compiler.findHelper(const SourceString('makeLiteralMap'));
+  }
+
+  Element getSetRuntimeTypeInfo() {
+    return compiler.findHelper(const SourceString('setRuntimeTypeInfo'));
+  }
+
+  Element getGetRuntimeTypeInfo() {
+    return compiler.findHelper(const SourceString('getRuntimeTypeInfo'));
+  }
+
+  /**
+   * Remove [element] from the set of generated code, and put it back
+   * into the worklist.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void eagerRecompile(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    generatedCode.remove(element);
+    generatedBailoutCode.remove(element);
+    compiler.enqueuer.codegen.addToWorkList(element);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart
new file mode 100644
index 0000000..86717ae
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart
@@ -0,0 +1,325 @@
+// 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.
+
+part of js_backend;
+
+class ConstantEmitter  {
+  ConstantReferenceEmitter _referenceEmitter;
+  ConstantInitializerEmitter _initializerEmitter;
+
+  ConstantEmitter(Compiler compiler, Namer namer) {
+    _referenceEmitter = new ConstantReferenceEmitter(compiler, namer);
+    _initializerEmitter = new ConstantInitializerEmitter(
+        compiler, namer, _referenceEmitter);
+  }
+
+  /**
+   * Constructs an expression that is a reference to the constant.  Uses a
+   * canonical name unless the constant can be emitted multiple times (as for
+   * numbers and strings).
+   */
+  js.Expression reference(Constant constant) {
+    return _referenceEmitter.generate(constant);
+  }
+
+  /**
+   * Constructs an expression like [reference], but the expression is valid
+   * during isolate initialization.
+   */
+  js.Expression referenceInInitializationContext(Constant constant) {
+    return _referenceEmitter.generateInInitializationContext(constant);
+  }
+
+  /**
+   * Constructs an expression used to initialize a canonicalized constant.
+   */
+  js.Expression initializationExpression(Constant constant) {
+    return _initializerEmitter.generate(constant);
+  }
+}
+
+/**
+ * Visitor for generating JavaScript expressions to refer to [Constant]s.
+ * Do not use directly, use methods from [ConstantEmitter].
+ */
+class ConstantReferenceEmitter implements ConstantVisitor<js.Expression> {
+  final Compiler compiler;
+  final Namer namer;
+  bool inIsolateInitializationContext = false;
+
+  ConstantReferenceEmitter(this.compiler, this.namer);
+
+  js.Expression generate(Constant constant) {
+    inIsolateInitializationContext = false;
+    return _visit(constant);
+  }
+
+  js.Expression generateInInitializationContext(Constant constant) {
+    inIsolateInitializationContext = true;
+    return _visit(constant);
+  }
+
+  js.Expression _visit(Constant constant) {
+    return constant.accept(this);
+  }
+
+  js.Expression visitSentinel(SentinelConstant constant) {
+    return new js.VariableUse(namer.CURRENT_ISOLATE);
+  }
+
+  js.Expression visitFunction(FunctionConstant constant) {
+    return inIsolateInitializationContext
+        ? new js.VariableUse(namer.isolatePropertiesAccess(constant.element))
+        : new js.VariableUse(namer.isolateAccess(constant.element));
+  }
+
+  js.Expression visitNull(NullConstant constant) {
+    return new js.LiteralNull();
+  }
+
+  js.Expression visitInt(IntConstant constant) {
+    return new js.LiteralNumber('${constant.value}');
+  }
+
+  js.Expression visitDouble(DoubleConstant constant) {
+    double value = constant.value;
+    if (value.isNaN) {
+      return new js.LiteralNumber("(0/0)");
+    } else if (value == double.INFINITY) {
+      return new js.LiteralNumber("(1/0)");
+    } else if (value == -double.INFINITY) {
+      return new js.LiteralNumber("(-1/0)");
+    } else {
+      return new js.LiteralNumber("$value");
+    }
+  }
+
+  js.Expression visitTrue(TrueConstant constant) {
+    if (compiler.enableMinification) {
+      // Use !0 for true.
+      return new js.Prefix("!", new js.LiteralNumber("0"));
+    } else {
+      return new js.LiteralBool(true);
+    }
+
+  }
+
+  js.Expression visitFalse(FalseConstant constant) {
+    if (compiler.enableMinification) {
+      // Use !1 for false.
+      return new js.Prefix("!", new js.LiteralNumber("1"));
+    } else {
+      return new js.LiteralBool(false);
+    }
+  }
+
+  /**
+   * Write the contents of the quoted string to a [CodeBuffer] in
+   * a form that is valid as JavaScript string literal content.
+   * The string is assumed quoted by double quote characters.
+   */
+  js.Expression visitString(StringConstant constant) {
+    // TODO(sra): If the string is long *and repeated* (and not on a hot path)
+    // then it should be assigned to a name.  We don't have reference counts (or
+    // profile information) here, so this is the wrong place.
+    StringBuffer sb = new StringBuffer();
+    writeJsonEscapedCharsOn(constant.value.slowToString(), sb);
+    return new js.LiteralString('"$sb"');
+  }
+
+  js.Expression emitCanonicalVersion(Constant constant) {
+    String name = namer.constantName(constant);
+    if (inIsolateInitializationContext) {
+      //  $isolateName.$isolatePropertiesName.$name
+      return new js.PropertyAccess.field(
+          new js.PropertyAccess.field(
+              new js.VariableUse(namer.isolateName),
+              namer.isolatePropertiesName),
+          name);
+    } else {
+      return new js.PropertyAccess.field(
+          new js.VariableUse(namer.CURRENT_ISOLATE),
+          name);
+    }
+  }
+
+  js.Expression visitList(ListConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+
+  js.Expression visitMap(MapConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+
+  js.Expression visitType(TypeConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+
+  js.Expression visitConstructed(ConstructedConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+}
+
+/**
+ * Visitor for generating JavaScript expressions to initialize [Constant]s.
+ * Do not use directly; use methods from [ConstantEmitter].
+ */
+class ConstantInitializerEmitter implements ConstantVisitor<js.Expression> {
+  final Compiler compiler;
+  final Namer namer;
+  final ConstantReferenceEmitter referenceEmitter;
+
+  ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter);
+
+  js.Expression generate(Constant constant) {
+    return _visit(constant);
+  }
+
+  js.Expression _visit(Constant constant) {
+    return constant.accept(this);
+  }
+
+  js.Expression _reference(Constant constant) {
+    return referenceEmitter.generateInInitializationContext(constant);
+  }
+
+  js.Expression visitSentinel(SentinelConstant constant) {
+    compiler.internalError(
+        "The parameter sentinel constant does not need specific JS code");
+  }
+
+  js.Expression visitFunction(FunctionConstant constant) {
+    compiler.internalError(
+        "The function constant does not need specific JS code");
+  }
+
+  js.Expression visitNull(NullConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitInt(IntConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitDouble(DoubleConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitTrue(TrueConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitFalse(FalseConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitString(StringConstant constant) {
+    // TODO(sra): Some larger strings are worth sharing.
+    return _reference(constant);
+  }
+
+  js.Expression visitList(ListConstant constant) {
+    return new js.Call(
+        new js.PropertyAccess.field(
+            new js.VariableUse(namer.isolateName),
+            'makeConstantList'),
+        [new js.ArrayInitializer.from(_array(constant.entries))]);
+  }
+
+  String getJsConstructor(ClassElement element) {
+    return namer.isolatePropertiesAccess(element);
+  }
+
+  js.Expression visitMap(MapConstant constant) {
+    js.Expression jsMap() {
+      List<js.Property> properties = <js.Property>[];
+      int valueIndex = 0;
+      for (int i = 0; i < constant.keys.entries.length; i++) {
+        StringConstant key = constant.keys.entries[i];
+        if (key.value == MapConstant.PROTO_PROPERTY) continue;
+
+        // Keys in literal maps must be emitted in place.
+        js.Literal keyExpression = _visit(key);
+        js.Expression valueExpression =
+            _reference(constant.values[valueIndex++]);
+        properties.add(new js.Property(keyExpression, valueExpression));
+      }
+      if (valueIndex != constant.values.length) {
+        compiler.internalError("Bad value count.");
+      }
+      return new js.ObjectInitializer(properties);
+    }
+
+    void badFieldCountError() {
+      compiler.internalError(
+          "Compiler and ConstantMap disagree on number of fields.");
+    }
+
+    ClassElement classElement = constant.type.element;
+
+    List<js.Expression> arguments = <js.Expression>[];
+
+    // The arguments of the JavaScript constructor for any given Dart class
+    // are in the same order as the members of the class element.
+    int emittedArgumentCount = 0;
+    classElement.implementation.forEachInstanceField(
+        (ClassElement enclosing, Element field) {
+          if (field.name == MapConstant.LENGTH_NAME) {
+            arguments.add(
+                new js.LiteralNumber('${constant.keys.entries.length}'));
+          } else if (field.name == MapConstant.JS_OBJECT_NAME) {
+            arguments.add(jsMap());
+          } else if (field.name == MapConstant.KEYS_NAME) {
+            arguments.add(_reference(constant.keys));
+          } else if (field.name == MapConstant.PROTO_VALUE) {
+            assert(constant.protoValue != null);
+            arguments.add(_reference(constant.protoValue));
+          } else {
+            badFieldCountError();
+          }
+          emittedArgumentCount++;
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: true);
+
+    if ((constant.protoValue == null && emittedArgumentCount != 3) ||
+        (constant.protoValue != null && emittedArgumentCount != 4)) {
+      badFieldCountError();
+    }
+
+    return new js.New(
+        new js.VariableUse(getJsConstructor(classElement)),
+        arguments);
+  }
+
+  js.Expression visitType(TypeConstant constant) {
+    SourceString helperSourceName = const SourceString('createRuntimeType');
+    Element helper = compiler.findHelper(helperSourceName);
+    JavaScriptBackend backend = compiler.backend;
+    String helperName = backend.namer.getName(helper);
+    DartType type = constant.representedType;
+    Element element = type.element;
+    String name = backend.rti.getRawTypeRepresentation(type);
+    js.Expression typeName = new js.LiteralString("'$name'");
+    return new js.Call(
+        new js.PropertyAccess.field(
+            new js.VariableUse(namer.CURRENT_ISOLATE),
+            helperName),
+        [typeName]);
+  }
+
+  js.Expression visitConstructed(ConstructedConstant constant) {
+    return new js.New(
+        new js.VariableUse(getJsConstructor(constant.type.element)),
+        _array(constant.fields));
+  }
+
+  List<js.Expression> _array(List<Constant> values) {
+    List<js.Expression> valueList = <js.Expression>[];
+    for (int i = 0; i < values.length; i++) {
+      valueList.add(_reference(values[i]));
+    }
+    return valueList;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart
new file mode 100644
index 0000000..30c8bc5
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart
@@ -0,0 +1,240 @@
+// 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.
+
+part of js_backend;
+
+const JAVA_SCRIPT_CONSTANT_SYSTEM = const JavaScriptConstantSystem();
+
+class JavaScriptBitNotOperation extends BitNotOperation {
+  const JavaScriptBitNotOperation();
+
+  Constant fold(Constant constant) {
+    if (JAVA_SCRIPT_CONSTANT_SYSTEM.isInt(constant)) {
+      // In JavaScript we don't check for -0 and treat it as if it was zero.
+      if (constant.isMinusZero()) constant = DART_CONSTANT_SYSTEM.createInt(0);
+      IntConstant intConstant = constant;
+      // We convert the result of bit-operations to 32 bit unsigned integers.
+      return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(~intConstant.value);
+    }
+    return null;
+  }
+}
+
+/**
+ * In JavaScript we truncate the result to an unsigned 32 bit integer. Also, -0
+ * is treated as if it was the integer 0.
+ */
+class JavaScriptBinaryBitOperation implements BinaryOperation {
+  final BinaryBitOperation dartBitOperation;
+
+  const JavaScriptBinaryBitOperation(this.dartBitOperation);
+
+  bool isUserDefinable() => dartBitOperation.isUserDefinable();
+  SourceString get name => dartBitOperation.name;
+
+  Constant fold(Constant left, Constant right) {
+    // In JavaScript we don't check for -0 and treat it as if it was zero.
+    if (left.isMinusZero()) left = DART_CONSTANT_SYSTEM.createInt(0);
+    if (right.isMinusZero()) right = DART_CONSTANT_SYSTEM.createInt(0);
+    IntConstant result = dartBitOperation.fold(left, right);
+    if (result != null) {
+      // We convert the result of bit-operations to 32 bit unsigned integers.
+      return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(result.value);
+    }
+    return result;
+  }
+
+  apply(left, right) => dartBitOperation.apply(left, right);
+}
+
+class JavaScriptShiftRightOperation extends JavaScriptBinaryBitOperation {
+  const JavaScriptShiftRightOperation() : super(const ShiftRightOperation());
+
+  Constant fold(Constant left, Constant right) {
+    // Truncate the input value to 32 bits if necessary.
+    if (left.isInt()) {
+      IntConstant intConstant = left;
+      int value = intConstant.value;
+      int truncatedValue = value & JAVA_SCRIPT_CONSTANT_SYSTEM.BITS32;
+      // TODO(floitsch): we should treat the input to right shifts as unsigned.
+
+      // Sign-extend. A 32 bit complement-two value x can be computed by:
+      //    x_u - 2^32 (where x_u is its unsigned representation).
+      // Example: 0xFFFFFFFF - 0x100000000 => -1.
+      // We simply and with the sign-bit and multiply by two. If the sign-bit
+      // was set, then the result is 0. Otherwise it will become 2^32.
+      final int SIGN_BIT = 0x80000000;
+      truncatedValue -= 2 * (truncatedValue & SIGN_BIT);
+      if (value != truncatedValue) {
+        left = DART_CONSTANT_SYSTEM.createInt(truncatedValue);
+      }
+    }
+    return super.fold(left, right);
+  }
+}
+
+class JavaScriptNegateOperation implements UnaryOperation {
+  final NegateOperation dartNegateOperation = const NegateOperation();
+
+  const JavaScriptNegateOperation();
+
+  bool isUserDefinable() => dartNegateOperation.isUserDefinable();
+  SourceString get name => dartNegateOperation.name;
+
+  Constant fold(Constant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      if (intConstant.value == 0) {
+        return JAVA_SCRIPT_CONSTANT_SYSTEM.createDouble(-0.0);
+      }
+    }
+    return dartNegateOperation.fold(constant);
+  }
+  apply(value) => -value;
+}
+
+class JavaScriptBinaryArithmeticOperation implements BinaryOperation {
+  final BinaryOperation dartArithmeticOperation;
+
+  const JavaScriptBinaryArithmeticOperation(this.dartArithmeticOperation);
+
+  bool isUserDefinable() => dartArithmeticOperation.isUserDefinable();
+  SourceString get name => dartArithmeticOperation.name;
+
+  Constant fold(Constant left, Constant right) {
+    Constant result = dartArithmeticOperation.fold(left, right);
+    if (result == null) return result;
+    return JAVA_SCRIPT_CONSTANT_SYSTEM.convertToJavaScriptConstant(result);
+  }
+
+  apply(left, right) => dartArithmeticOperation.apply(left, right);
+}
+
+class JavaScriptIdentityOperation implements BinaryOperation {
+  final IdentityOperation dartIdentityOperation = const IdentityOperation();
+
+  const JavaScriptIdentityOperation();
+
+  bool isUserDefinable() => dartIdentityOperation.isUserDefinable();
+  SourceString get name => dartIdentityOperation.name;
+
+  BoolConstant fold(Constant left, Constant right) {
+    BoolConstant result = dartIdentityOperation.fold(left, right);
+    if (result == null || result.value) return result;
+    // In JavaScript -0.0 === 0 and all doubles are equal to their integer
+    // values. Furthermore NaN !== NaN.
+    if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      double leftDouble = leftNum.value.toDouble();
+      double rightDouble = rightNum.value.toDouble();
+      return new BoolConstant(leftDouble == rightDouble);
+    }
+    return result;
+  }
+
+  apply(left, right) => identical(left, right);
+}
+
+/**
+ * Constant system following the semantics for Dart code that has been
+ * compiled to JavaScript.
+ */
+class JavaScriptConstantSystem extends ConstantSystem {
+  const int BITS31 = 0x8FFFFFFF;
+  const int BITS32 = 0xFFFFFFFF;
+  // The maximum integer value a double can represent without losing
+  // precision.
+  const int BITS53 = 0x1FFFFFFFFFFFFF;
+
+  final add = const JavaScriptBinaryArithmeticOperation(const AddOperation());
+  final bitAnd = const JavaScriptBinaryBitOperation(const BitAndOperation());
+  final bitNot = const JavaScriptBitNotOperation();
+  final bitOr = const JavaScriptBinaryBitOperation(const BitOrOperation());
+  final bitXor = const JavaScriptBinaryBitOperation(const BitXorOperation());
+  final booleanAnd = const BooleanAndOperation();
+  final booleanOr = const BooleanOrOperation();
+  final divide =
+      const JavaScriptBinaryArithmeticOperation(const DivideOperation());
+  final equal = const EqualsOperation();
+  final greaterEqual = const GreaterEqualOperation();
+  final greater = const GreaterOperation();
+  final identity = const JavaScriptIdentityOperation();
+  final lessEqual = const LessEqualOperation();
+  final less = const LessOperation();
+  final modulo =
+      const JavaScriptBinaryArithmeticOperation(const ModuloOperation());
+  final multiply =
+      const JavaScriptBinaryArithmeticOperation(const MultiplyOperation());
+  final negate = const JavaScriptNegateOperation();
+  final not = const NotOperation();
+  final shiftLeft =
+      const JavaScriptBinaryBitOperation(const ShiftLeftOperation());
+  final shiftRight = const JavaScriptShiftRightOperation();
+  final subtract =
+      const JavaScriptBinaryArithmeticOperation(const SubtractOperation());
+  final truncatingDivide = const JavaScriptBinaryArithmeticOperation(
+      const TruncatingDivideOperation());
+
+  const JavaScriptConstantSystem();
+
+  /**
+   * Returns true if the given [value] fits into a double without losing
+   * precision.
+   */
+  bool integerFitsIntoDouble(int value) {
+    int absValue = value.abs();
+    return (absValue & BITS53) == absValue;
+  }
+
+  NumConstant convertToJavaScriptConstant(NumConstant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      int intValue = intConstant.value;
+      if (!integerFitsIntoDouble(intValue)) {
+        return new DoubleConstant(intValue.toDouble());
+      }
+    } else if (constant.isDouble()) {
+      DoubleConstant doubleResult = constant;
+      double doubleValue = doubleResult.value;
+      if (!doubleValue.isInfinite && !doubleValue.isNaN &&
+          !constant.isMinusZero()) {
+        int intValue = doubleValue.toInt();
+        if (intValue == doubleValue && integerFitsIntoDouble(intValue)) {
+          return new IntConstant(intValue);
+        }
+      }
+    }
+    return constant;
+  }
+
+  NumConstant createInt(int i)
+      => convertToJavaScriptConstant(new IntConstant(i));
+  NumConstant createInt32(int i) => new IntConstant(i & BITS32);
+  NumConstant createDouble(double d)
+      => convertToJavaScriptConstant(new DoubleConstant(d));
+  StringConstant createString(DartString string, Node diagnosticNode)
+      => new StringConstant(string, diagnosticNode);
+  BoolConstant createBool(bool value) => new BoolConstant(value);
+  NullConstant createNull() => new NullConstant();
+
+  // Integer checks don't verify that the number is not -0.0.
+  bool isInt(Constant constant) => constant.isInt() || constant.isMinusZero();
+  bool isDouble(Constant constant)
+      => constant.isDouble() && !constant.isMinusZero();
+  bool isString(Constant constant) => constant.isString();
+  bool isBool(Constant constant) => constant.isBool();
+  bool isNull(Constant constant) => constant.isNull();
+
+  bool isSubtype(Compiler compiler, DartType s, DartType t) {
+    // At runtime, an integer is both an integer and a double: the
+    // integer type check is Math.floor, which will return true only
+    // for real integers, and our double type check is 'typeof number'
+    // which will return true for both integers and doubles.
+    if (s.element == compiler.intClass && t.element == compiler.doubleClass) {
+      return true;
+    }
+    return compiler.types.isSubtype(s, t);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart
new file mode 100644
index 0000000..f401235
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart
@@ -0,0 +1,2359 @@
+// 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.
+
+part of js_backend;
+
+/**
+ * A function element that represents a closure call. The signature is copied
+ * from the given element.
+ */
+class ClosureInvocationElement extends FunctionElementX {
+  ClosureInvocationElement(SourceString name,
+                           FunctionElement other)
+      : super.from(name, other, other.enclosingElement),
+        methodElement = other;
+
+  isInstanceMember() => true;
+
+  Element getOutermostEnclosingMemberOrTopLevel() => methodElement;
+
+  /**
+   * The [member] this invocation refers to.
+   */
+  Element methodElement;
+}
+
+/**
+ * A convenient type alias for some functions that emit keyed values.
+ */
+typedef void DefineStubFunction(String invocationName, js.Expression value);
+
+/**
+ * A data structure for collecting fragments of a class definition.
+ */
+class ClassBuilder {
+  final List<js.Property> properties = <js.Property>[];
+
+  // Has the same signature as [DefineStubFunction].
+  void addProperty(String name, js.Expression value) {
+    properties.add(new js.Property(js.string(name), value));
+  }
+
+  js.Expression toObjectInitializer() => new js.ObjectInitializer(properties);
+}
+
+/**
+ * Generates the code for all used classes in the program. Static fields (even
+ * in classes) are ignored, since they can be treated as non-class elements.
+ *
+ * The code for the containing (used) methods must exist in the [:universe:].
+ */
+class CodeEmitterTask extends CompilerTask {
+  bool needsInheritFunction = false;
+  bool needsDefineClass = false;
+  bool needsClosureClass = false;
+  bool needsLazyInitializer = false;
+  final Namer namer;
+  ConstantEmitter constantEmitter;
+  NativeEmitter nativeEmitter;
+  CodeBuffer boundClosureBuffer;
+  CodeBuffer mainBuffer;
+  /** Shorter access to [isolatePropertiesName]. Both here in the code, as
+      well as in the generated code. */
+  String isolateProperties;
+  String classesCollector;
+  Set<ClassElement> neededClasses;
+  // TODO(ngeoffray): remove this field.
+  Set<ClassElement> instantiatedClasses;
+
+  String get _ => compiler.enableMinification ? "" : " ";
+  String get n => compiler.enableMinification ? "" : "\n";
+  String get N => compiler.enableMinification ? "\n" : ";\n";
+
+  /**
+   * A cache of closures that are used to closurize instance methods.
+   * A closure is dynamically bound to the instance used when
+   * closurized.
+   */
+  final Map<int, String> boundClosureCache;
+
+  /**
+   * A cache of closures that are used to closurize instance methods
+   * of interceptors. These closures are dynamically bound to the
+   * interceptor instance, and the actual receiver of the method.
+   */
+  final Map<int, String> interceptorClosureCache;
+
+  /**
+   * Raw ClassElement symbols occuring in is-checks and type assertions.  If the
+   * program contains parameterized checks `x is Set<int>` and
+   * `x is Set<String>` then the ClassElement `Set` will occur once in
+   * [checkedClasses].
+   */
+  Set<ClassElement> checkedClasses;
+
+  /**
+   * Raw Typedef symbols occuring in is-checks and type assertions.  If the
+   * program contains `x is F<int>` and `x is F<bool>` then the TypedefElement
+   * `F` will occur once in [checkedTypedefs].
+   */
+  Set<TypedefElement> checkedTypedefs;
+
+  final bool generateSourceMap;
+
+  CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap)
+      : boundClosureBuffer = new CodeBuffer(),
+        mainBuffer = new CodeBuffer(),
+        this.namer = namer,
+        boundClosureCache = new Map<int, String>(),
+        interceptorClosureCache = new Map<int, String>(),
+        constantEmitter = new ConstantEmitter(compiler, namer),
+        super(compiler) {
+    nativeEmitter = new NativeEmitter(this);
+  }
+
+  void computeRequiredTypeChecks() {
+    assert(checkedClasses == null);
+    checkedClasses = new Set<ClassElement>();
+    checkedTypedefs = new Set<TypedefElement>();
+    compiler.codegenWorld.isChecks.forEach((DartType t) {
+      if (t is InterfaceType) {
+        checkedClasses.add(t.element);
+      } else if (t is TypedefType) {
+        checkedTypedefs.add(t.element);
+      }
+    });
+  }
+
+  js.Expression constantReference(Constant value) {
+    return constantEmitter.reference(value);
+  }
+
+  js.Expression constantInitializerExpression(Constant value) {
+    return constantEmitter.initializationExpression(value);
+  }
+
+  String get name => 'CodeEmitter';
+
+  String get defineClassName
+      => '${namer.isolateName}.\$defineClass';
+  String get currentGenerateAccessorName
+      => '${namer.CURRENT_ISOLATE}.\$generateAccessor';
+  String get generateAccessorHolder
+      => '$isolatePropertiesName.\$generateAccessor';
+  String get finishClassesName
+      => '${namer.isolateName}.\$finishClasses';
+  String get finishIsolateConstructorName
+      => '${namer.isolateName}.\$finishIsolateConstructor';
+  String get pendingClassesName
+      => '${namer.isolateName}.\$pendingClasses';
+  String get isolatePropertiesName
+      => '${namer.isolateName}.${namer.isolatePropertiesName}';
+  String get supportsProtoName
+      => 'supportsProto';
+  String get lazyInitializerName
+      => '${namer.isolateName}.\$lazy';
+
+  // Property name suffixes.  If the accessors are renaming then the format
+  // is <accessorName>:<fieldName><suffix>.  We use the suffix to know whether
+  // to look for the ':' separator in order to avoid doing the indexOf operation
+  // on every single property (they are quite rare).  None of these characters
+  // are legal in an identifier and they are related by bit patterns.
+  // setter          <          0x3c
+  // both            =          0x3d
+  // getter          >          0x3e
+  // renaming setter |          0x7c
+  // renaming both   }          0x7d
+  // renaming getter ~          0x7e
+  const SUFFIX_MASK = 0x3f;
+  const FIRST_SUFFIX_CODE = 0x3c;
+  const SETTER_CODE = 0x3c;
+  const GETTER_SETTER_CODE = 0x3d;
+  const GETTER_CODE = 0x3e;
+  const RENAMING_FLAG = 0x40;
+  String needsGetterCode(String variable) => '($variable & 3) > 0';
+  String needsSetterCode(String variable) => '($variable & 2) == 0';
+  String isRenaming(String variable) => '($variable & $RENAMING_FLAG) != 0';
+
+  String get generateAccessorFunction {
+    return """
+function generateAccessor(field, prototype) {
+  var len = field.length;
+  var lastCharCode = field.charCodeAt(len - 1);
+  var needsAccessor = (lastCharCode & $SUFFIX_MASK) >= $FIRST_SUFFIX_CODE;
+  if (needsAccessor) {
+    var needsGetter = ${needsGetterCode('lastCharCode')};
+    var needsSetter = ${needsSetterCode('lastCharCode')};
+    var renaming = ${isRenaming('lastCharCode')};
+    var accessorName = field = field.substring(0, len - 1);
+    if (renaming) {
+      var divider = field.indexOf(":");
+      accessorName = field.substring(0, divider);
+      field = field.substring(divider + 1);
+    }
+    if (needsGetter) {
+      var getterString = "return this." + field + ";";
+      prototype["get\$" + accessorName] = new Function(getterString);
+    }
+    if (needsSetter) {
+      var setterString = "this." + field + " = v;";
+      prototype["set\$" + accessorName] = new Function("v", setterString);
+    }
+  }
+  return field;
+}""";
+  }
+
+  String get defineClassFunction {
+    // First the class name, then the field names in an array and the members
+    // (inside an Object literal).
+    // The caller can also pass in the constructor as a function if needed.
+    //
+    // Example:
+    // defineClass("A", ["x", "y"], {
+    //  foo$1: function(y) {
+    //   print(this.x + y);
+    //  },
+    //  bar$2: function(t, v) {
+    //   this.x = t - v;
+    //  },
+    // });
+    return """
+function(cls, fields, prototype) {
+  var constructor;
+  if (typeof fields == 'function') {
+    constructor = fields;
+  } else {
+    var str = "function " + cls + "(";
+    var body = "";
+    for (var i = 0; i < fields.length; i++) {
+      if (i != 0) str += ", ";
+      var field = fields[i];
+      field = generateAccessor(field, prototype);
+      str += field;
+      body += "this." + field + " = " + field + ";\\n";
+    }
+    str += ") {" + body + "}\\n";
+    str += "return " + cls + ";";
+    constructor = new Function(str)();
+  }
+  constructor.prototype = prototype;
+  constructor.builtin\$cls = cls;
+  return constructor;
+}""";
+  }
+
+  /** Needs defineClass to be defined. */
+  String get protoSupportCheck {
+    // On Firefox and Webkit browsers we can manipulate the __proto__
+    // directly. Opera claims to have __proto__ support, but it is buggy.
+    // So we have to do more checks.
+    // Opera bug was filed as DSK-370158, and fixed as CORE-47615
+    // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes).
+    // If the browser does not support __proto__ we need to instantiate an
+    // object with the correct (internal) prototype set up correctly, and then
+    // copy the members.
+
+    return '''
+var $supportsProtoName = false;
+var tmp = $defineClassName('c', ['f?'], {}).prototype;
+if (tmp.__proto__) {
+  tmp.__proto__ = {};
+  if (typeof tmp.get\$f !== 'undefined') $supportsProtoName = true;
+}
+''';
+  }
+
+  String get finishClassesFunction {
+    // 'defineClass' does not require the classes to be constructed in order.
+    // Classes are initially just stored in the 'pendingClasses' field.
+    // 'finishClasses' takes all pending classes and sets up the prototype.
+    // Once set up, the constructors prototype field satisfy:
+    //  - it contains all (local) members.
+    //  - its internal prototype (__proto__) points to the superclass'
+    //    prototype field.
+    //  - the prototype's constructor field points to the JavaScript
+    //    constructor.
+    // For engines where we have access to the '__proto__' we can manipulate
+    // the object literal directly. For other engines we have to create a new
+    // object and copy over the members.
+    return '''
+function(collectedClasses) {
+  var hasOwnProperty = Object.prototype.hasOwnProperty;
+  for (var cls in collectedClasses) {
+    if (hasOwnProperty.call(collectedClasses, cls)) {
+      var desc = collectedClasses[cls];
+'''/* The 'fields' are either a constructor function or a string encoding
+      fields, constructor and superclass.  Get the superclass and the fields
+      in the format Super;field1,field2 from the null-string property on the
+      descriptor. */'''
+      var fields = desc[''], supr;
+      if (typeof fields == 'string') {
+        var s = fields.split(';'); supr = s[0];
+        fields = s[1] == '' ? [] : s[1].split(',');
+      } else {
+        supr = desc['super'];
+      }
+      $isolatePropertiesName[cls] = $defineClassName(cls, fields, desc);
+      if (supr) $pendingClassesName[cls] = supr;
+    }
+  }
+  var pendingClasses = $pendingClassesName;
+'''/* FinishClasses can be called multiple times. This means that we need to
+      clear the pendingClasses property. */'''
+  $pendingClassesName = {};
+  var finishedClasses = {};
+  function finishClass(cls) {
+'''/* Opera does not support 'getOwnPropertyNames'. Therefore we use
+      hasOwnProperty instead. */'''
+    var hasOwnProperty = Object.prototype.hasOwnProperty;
+    if (hasOwnProperty.call(finishedClasses, cls)) return;
+    finishedClasses[cls] = true;
+    var superclass = pendingClasses[cls];
+'''/* The superclass is only false (empty string) for Dart's Object class. */'''
+    if (!superclass) return;
+    finishClass(superclass);
+    var constructor = $isolatePropertiesName[cls];
+    var superConstructor = $isolatePropertiesName[superclass];
+    var prototype = constructor.prototype;
+    if ($supportsProtoName) {
+      prototype.__proto__ = superConstructor.prototype;
+      prototype.constructor = constructor;
+    } else {
+      function tmp() {};
+      tmp.prototype = superConstructor.prototype;
+      var newPrototype = new tmp();
+      constructor.prototype = newPrototype;
+      newPrototype.constructor = constructor;
+      for (var member in prototype) {
+        if (!member) continue;  '''/* Short version of: if (member == '') */'''
+        if (hasOwnProperty.call(prototype, member)) {
+          newPrototype[member] = prototype[member];
+        }
+      }
+    }
+  }
+  for (var cls in pendingClasses) finishClass(cls);
+}''';
+  }
+
+  String get finishIsolateConstructorFunction {
+    String isolate = namer.isolateName;
+    // We replace the old Isolate function with a new one that initializes
+    // all its field with the initial (and often final) value of all globals.
+    // This has two advantages:
+    //   1. the properties are in the object itself (thus avoiding to go through
+    //      the prototype when looking up globals.
+    //   2. a new isolate goes through a (usually well optimized) constructor
+    //      function of the form: "function() { this.x = ...; this.y = ...; }".
+    //
+    // Example: If [isolateProperties] is an object containing: x = 3 and
+    // A = function A() { /* constructor of class A. */ }, then we generate:
+    // str = "{
+    //   var isolateProperties = Isolate.$isolateProperties;
+    //   this.x = isolateProperties.x;
+    //   this.A = isolateProperties.A;
+    // }";
+    // which is then dynamically evaluated:
+    //   var newIsolate = new Function(str);
+    //
+    // We also copy over old values like the prototype, and the
+    // isolateProperties themselves.
+    return """function(oldIsolate) {
+  var isolateProperties = oldIsolate.${namer.isolatePropertiesName};
+  var isolatePrototype = oldIsolate.prototype;
+  var str = "{\\n";
+  str += "var properties = $isolate.${namer.isolatePropertiesName};\\n";
+  for (var staticName in isolateProperties) {
+    if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) {
+      str += "this." + staticName + "= properties." + staticName + ";\\n";
+    }
+  }
+  str += "}\\n";
+  var newIsolate = new Function(str);
+  newIsolate.prototype = isolatePrototype;
+  isolatePrototype.constructor = newIsolate;
+  newIsolate.${namer.isolatePropertiesName} = isolateProperties;
+  return newIsolate;
+}""";
+  }
+
+  String get lazyInitializerFunction {
+    String isolate = namer.CURRENT_ISOLATE;
+    return """
+function(prototype, staticName, fieldName, getterName, lazyValue) {
+  var getter = new Function("{ return $isolate." + fieldName + ";}");
+$lazyInitializerLogic
+}""";
+  }
+
+  String get lazyInitializerLogic {
+    String isolate = namer.CURRENT_ISOLATE;
+    JavaScriptBackend backend = compiler.backend;
+    String cyclicThrow = namer.isolateAccess(backend.cyclicThrowHelper);
+    return """
+  var sentinelUndefined = {};
+  var sentinelInProgress = {};
+  prototype[fieldName] = sentinelUndefined;
+  prototype[getterName] = function() {
+    var result = $isolate[fieldName];
+    try {
+      if (result === sentinelUndefined) {
+        $isolate[fieldName] = sentinelInProgress;
+        try {
+          result = $isolate[fieldName] = lazyValue();
+        } finally {
+""" // Use try-finally, not try-catch/throw as it destroys the stack trace.
+"""
+          if (result === sentinelUndefined) {
+            if ($isolate[fieldName] === sentinelInProgress) {
+              $isolate[fieldName] = null;
+            }
+          }
+        }
+      } else if (result === sentinelInProgress) {
+        $cyclicThrow(staticName);
+      }
+      return result;
+    } finally {
+      $isolate[getterName] = getter;
+    }
+  };""";
+  }
+
+  void addDefineClassAndFinishClassFunctionsIfNecessary(CodeBuffer buffer) {
+    if (needsDefineClass) {
+      // Declare function called generateAccessor.  This is used in
+      // defineClassFunction (it's a local declaration in init()).
+      buffer.add("$generateAccessorFunction$N");
+      buffer.add("$generateAccessorHolder = generateAccessor$N");
+      buffer.add("$defineClassName = $defineClassFunction$N");
+      buffer.add(protoSupportCheck);
+      buffer.add("$pendingClassesName = {}$N");
+      buffer.add("$finishClassesName = $finishClassesFunction$N");
+    }
+  }
+
+  void addLazyInitializerFunctionIfNecessary(CodeBuffer buffer) {
+    if (needsLazyInitializer) {
+      buffer.add("$lazyInitializerName = $lazyInitializerFunction$N");
+    }
+  }
+
+  void emitFinishIsolateConstructor(CodeBuffer buffer) {
+    String name = finishIsolateConstructorName;
+    String value = finishIsolateConstructorFunction;
+    buffer.add("$name = $value$N");
+  }
+
+  void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) {
+    String isolate = namer.isolateName;
+    buffer.add("$isolate = $finishIsolateConstructorName($isolate)$N");
+  }
+
+  /**
+   * Generate stubs to handle invocation of methods with optional
+   * arguments.
+   *
+   * A method like [: foo([x]) :] may be invoked by the following
+   * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this
+   * function for detailed examples.
+   */
+  void addParameterStub(FunctionElement member,
+                        Selector selector,
+                        DefineStubFunction defineStub,
+                        Set<String> alreadyGenerated) {
+    FunctionSignature parameters = member.computeSignature(compiler);
+    int positionalArgumentCount = selector.positionalArgumentCount;
+    if (positionalArgumentCount == parameters.parameterCount) {
+      assert(selector.namedArgumentCount == 0);
+      return;
+    }
+    if (parameters.optionalParametersAreNamed
+        && selector.namedArgumentCount == parameters.optionalParameterCount) {
+      // If the selector has the same number of named arguments as
+      // the element, we don't need to add a stub. The call site will
+      // hit the method directly.
+      return;
+    }
+    ConstantHandler handler = compiler.constantHandler;
+    List<SourceString> names = selector.getOrderedNamedArguments();
+
+    String invocationName = namer.invocationName(selector);
+    if (alreadyGenerated.contains(invocationName)) return;
+    alreadyGenerated.add(invocationName);
+
+    JavaScriptBackend backend = compiler.backend;
+    bool isInterceptorClass =
+        backend.isInterceptorClass(member.getEnclosingClass());
+
+    // If the method is in an interceptor class, we need to also pass
+    // the actual receiver.
+    int extraArgumentCount = isInterceptorClass ? 1 : 0;
+    // Use '$receiver' to avoid clashes with other parameter names. Using
+    // '$receiver' works because [:namer.safeName:] used for getting parameter
+    // names never returns a name beginning with a single '$'.
+    String receiverArgumentName = r'$receiver';
+
+    // The parameters that this stub takes.
+    List<js.Parameter> parametersBuffer =
+        new List<js.Parameter>.fixedLength(
+            selector.argumentCount + extraArgumentCount);
+    // The arguments that will be passed to the real method.
+    List<js.Expression> argumentsBuffer =
+        new List<js.Expression>.fixedLength(
+            parameters.parameterCount + extraArgumentCount);
+
+    int count = 0;
+    if (isInterceptorClass) {
+      count++;
+      parametersBuffer[0] = new js.Parameter(receiverArgumentName);
+      argumentsBuffer[0] = new js.VariableUse(receiverArgumentName);
+    }
+
+    int indexOfLastOptionalArgumentInParameters = positionalArgumentCount - 1;
+    TreeElements elements =
+        compiler.enqueuer.resolution.getCachedElements(member);
+
+    parameters.orderedForEachParameter((Element element) {
+      String jsName = backend.namer.safeName(element.name.slowToString());
+      assert(jsName != receiverArgumentName);
+      int optionalParameterStart = positionalArgumentCount + extraArgumentCount;
+      if (count < optionalParameterStart) {
+        parametersBuffer[count] = new js.Parameter(jsName);
+        argumentsBuffer[count] = new js.VariableUse(jsName);
+      } else {
+        int index = names.indexOf(element.name);
+        if (index != -1) {
+          indexOfLastOptionalArgumentInParameters = count;
+          // The order of the named arguments is not the same as the
+          // one in the real method (which is in Dart source order).
+          argumentsBuffer[count] = new js.VariableUse(jsName);
+          parametersBuffer[optionalParameterStart + index] =
+              new js.Parameter(jsName);
+        // Note that [elements] may be null for a synthesized [member].
+        } else if (elements != null && elements.isParameterChecked(element)) {
+          argumentsBuffer[count] = constantReference(SentinelConstant.SENTINEL);
+        } else {
+          Constant value = handler.initialVariableValues[element];
+          if (value == null) {
+            argumentsBuffer[count] = constantReference(new NullConstant());
+          } else {
+            if (!value.isNull()) {
+              // If the value is the null constant, we should not pass it
+              // down to the native method.
+              indexOfLastOptionalArgumentInParameters = count;
+            }
+            argumentsBuffer[count] = constantReference(value);
+          }
+        }
+      }
+      count++;
+    });
+
+    List<js.Statement> body;
+    if (member.hasFixedBackendName()) {
+      body = nativeEmitter.generateParameterStubStatements(
+          member, invocationName, parametersBuffer, argumentsBuffer,
+          indexOfLastOptionalArgumentInParameters);
+    } else {
+      body = <js.Statement>[
+          new js.Return(
+              new js.VariableUse('this')
+                  .dot(namer.getName(member))
+                  .callWith(argumentsBuffer))];
+    }
+
+    js.Fun function = new js.Fun(parametersBuffer, new js.Block(body));
+
+    defineStub(invocationName, function);
+  }
+
+  void addParameterStubs(FunctionElement member,
+                         DefineStubFunction defineStub) {
+    // We fill the lists depending on the selector. For example,
+    // take method foo:
+    //    foo(a, b, {c, d});
+    //
+    // We may have multiple ways of calling foo:
+    // (1) foo(1, 2);
+    // (2) foo(1, 2, c: 3);
+    // (3) foo(1, 2, d: 4);
+    // (4) foo(1, 2, c: 3, d: 4);
+    // (5) foo(1, 2, d: 4, c: 3);
+    //
+    // What we generate at the call sites are:
+    // (1) foo$2(1, 2);
+    // (2) foo$3$c(1, 2, 3);
+    // (3) foo$3$d(1, 2, 4);
+    // (4) foo$4$c$d(1, 2, 3, 4);
+    // (5) foo$4$c$d(1, 2, 3, 4);
+    //
+    // The stubs we generate are (expressed in Dart):
+    // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null)
+    // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null);
+    // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d);
+    // (4) No stub generated, call is direct.
+    // (5) No stub generated, call is direct.
+
+    // Keep a cache of which stubs have already been generated, to
+    // avoid duplicates. Note that even if selectors are
+    // canonicalized, we would still need this cache: a typed selector
+    // on A and a typed selector on B could yield the same stub.
+    Set<String> generatedStubNames = new Set<String>();
+    if (compiler.enabledFunctionApply
+        && member.name == namer.closureInvocationSelectorName) {
+      // If [Function.apply] is called, we pessimistically compile all
+      // possible stubs for this closure.
+      FunctionSignature signature = member.computeSignature(compiler);
+      Set<Selector> selectors = signature.optionalParametersAreNamed
+          ? computeNamedSelectors(signature, member)
+          : computeOptionalSelectors(signature, member);
+      for (Selector selector in selectors) {
+        addParameterStub(member, selector, defineStub, generatedStubNames);
+      }
+    } else {
+      Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
+      if (selectors == null) return;
+      for (Selector selector in selectors) {
+        if (!selector.applies(member, compiler)) continue;
+        addParameterStub(member, selector, defineStub, generatedStubNames);
+      }
+    }
+  }
+
+  /**
+   * Compute the set of possible selectors in the presence of named
+   * parameters.
+   */
+  Set<Selector> computeNamedSelectors(FunctionSignature signature,
+                                      FunctionElement element) {
+    Set<Selector> selectors = new Set<Selector>();
+    // Add the selector that does not have any optional argument.
+    selectors.add(new Selector(SelectorKind.CALL,
+                               element.name,
+                               element.getLibrary(),
+                               signature.requiredParameterCount,
+                               <SourceString>[]));
+
+    // For each optional parameter, we iterator over the set of
+    // already computed selectors and create new selectors with that
+    // parameter now being passed.
+    signature.forEachOptionalParameter((Element element) {
+      Set<Selector> newSet = new Set<Selector>();
+      selectors.forEach((Selector other) {
+        List<SourceString> namedArguments = [element.name];
+        namedArguments.addAll(other.namedArguments);
+        newSet.add(new Selector(other.kind,
+                                other.name,
+                                other.library,
+                                other.argumentCount + 1,
+                                namedArguments));
+      });
+      selectors.addAll(newSet);
+    });
+    return selectors;
+  }
+
+  /**
+   * Compute the set of possible selectors in the presence of optional
+   * non-named parameters.
+   */
+  Set<Selector> computeOptionalSelectors(FunctionSignature signature,
+                                         FunctionElement element) {
+    Set<Selector> selectors = new Set<Selector>();
+    // Add the selector that does not have any optional argument.
+    selectors.add(new Selector(SelectorKind.CALL,
+                               element.name,
+                               element.getLibrary(),
+                               signature.requiredParameterCount,
+                               <SourceString>[]));
+
+    // For each optional parameter, we increment the number of passed
+    // argument.
+    for (int i = 1; i <= signature.optionalParameterCount; i++) {
+      selectors.add(new Selector(SelectorKind.CALL,
+                                 element.name,
+                                 element.getLibrary(),
+                                 signature.requiredParameterCount + i,
+                                 <SourceString>[]));
+    }
+    return selectors;
+  }
+
+  bool instanceFieldNeedsGetter(Element member) {
+    assert(member.isField());
+    return compiler.codegenWorld.hasInvokedGetter(member, compiler);
+  }
+
+  bool instanceFieldNeedsSetter(Element member) {
+    assert(member.isField());
+    return (!member.modifiers.isFinalOrConst())
+        && compiler.codegenWorld.hasInvokedSetter(member, compiler);
+  }
+
+  String compiledFieldName(Element member) {
+    assert(member.isField());
+    return member.hasFixedBackendName()
+        ? member.fixedBackendName()
+        : namer.getName(member);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void addInstanceMember(Element member, ClassBuilder builder) {
+    assert(invariant(member, member.isDeclaration));
+    // TODO(floitsch): we don't need to deal with members of
+    // uninstantiated classes, that have been overwritten by subclasses.
+
+    if (member.isFunction()
+        || member.isGenerativeConstructorBody()
+        || member.isAccessor()) {
+      if (member.isAbstract(compiler)) return;
+      JavaScriptBackend backend = compiler.backend;
+      js.Expression code = backend.generatedCode[member];
+      if (code == null) return;
+      builder.addProperty(namer.getName(member), code);
+      code = backend.generatedBailoutCode[member];
+      if (code != null) {
+        builder.addProperty(namer.getBailoutName(member), code);
+      }
+      FunctionElement function = member;
+      FunctionSignature parameters = function.computeSignature(compiler);
+      if (!parameters.optionalParameters.isEmpty) {
+        addParameterStubs(member, builder.addProperty);
+      }
+    } else if (!member.isField()) {
+      compiler.internalError('unexpected kind: "${member.kind}"',
+                             element: member);
+    }
+    emitExtraAccessors(member, builder);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [classElement] must be a declaration element.
+   */
+  void emitInstanceMembers(ClassElement classElement,
+                           ClassBuilder builder) {
+    assert(invariant(classElement, classElement.isDeclaration));
+    JavaScriptBackend backend = compiler.backend;
+    if (classElement == backend.objectInterceptorClass) {
+      emitInterceptorMethods(builder);
+      // The ObjectInterceptor does not have any instance methods.
+      return;
+    }
+
+    void visitMember(ClassElement enclosing, Element member) {
+      assert(invariant(classElement, member.isDeclaration));
+      if (member.isInstanceMember()) {
+        addInstanceMember(member, builder);
+      }
+    }
+
+    // TODO(kasperl): We should make sure to only emit one version of
+    // overridden methods. Right now, we rely on the ordering so the
+    // methods pulled in from mixins are replaced with the members
+    // from the class definition.
+
+    // If the class is a native class, we have to add the instance
+    // members defined in the non-native mixin applications used by
+    // the class.
+    visitNativeMixins(classElement, (MixinApplicationElement mixin) {
+      mixin.forEachMember(
+          visitMember,
+          includeBackendMembers: true,
+          includeSuperMembers: false);
+    });
+
+    classElement.implementation.forEachMember(
+        visitMember,
+        includeBackendMembers: true,
+        includeSuperMembers: false);
+
+    generateIsTestsOn(classElement, (Element other) {
+      js.Expression code;
+      if (compiler.objectClass == other) return;
+      if (nativeEmitter.requiresNativeIsCheck(other)) {
+        code = js.fun([], js.block1(js.return_(new js.LiteralBool(true))));
+      } else {
+        code = new js.LiteralBool(true);
+      }
+      builder.addProperty(namer.operatorIs(other), code);
+    });
+
+    if (identical(classElement, compiler.objectClass)
+        && compiler.enabledNoSuchMethod) {
+      // Emit the noSuchMethod handlers on the Object prototype now,
+      // so that the code in the dynamicFunction helper can find
+      // them. Note that this helper is invoked before analyzing the
+      // full JS script.
+      if (!nativeEmitter.handleNoSuchMethod) {
+        emitNoSuchMethodHandlers(builder.addProperty);
+      }
+    }
+
+    if (backend.isInterceptorClass(classElement)) {
+      // The operator== method in [:Object:] does not take the same
+      // number of arguments as an intercepted method, therefore we
+      // explicitely add one to all interceptor classes. Note that we
+      // would not have do do that if all intercepted methods had
+      // a calling convention where the receiver is the first
+      // parameter.
+      String name = backend.namer.publicInstanceMethodNameByArity(
+          const SourceString('=='), 1);
+      Function kind = (classElement == backend.jsNullClass)
+          ? js.equals
+          : js.strictEquals;
+      builder.addProperty(name, js.fun(['receiver', 'a'],
+          js.block1(js.return_(kind(js.use('receiver'), js.use('a'))))));
+    }
+  }
+
+  void emitRuntimeClassesAndTests(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    RuntimeTypeInformation rti = backend.rti;
+
+    TypeChecks typeChecks = rti.computeRequiredChecks();
+
+    bool needsHolder(ClassElement cls) {
+      return !neededClasses.contains(cls) || cls.isNative() ||
+          rti.isJsNative(cls);
+    }
+
+    void maybeGenerateHolder(ClassElement cls) {
+      if (!needsHolder(cls)) return;
+
+      String holder = namer.isolateAccess(cls);
+      String name = namer.getName(cls);
+      buffer.add("$holder$_=$_{builtin\$cls:$_'$name'");
+      for (ClassElement check in typeChecks[cls]) {
+        buffer.add(',$_${namer.operatorIs(check)}:${_}true');
+      };
+      buffer.add('}$N');
+    }
+
+    // Create representation objects for classes that we do not have a class
+    // definition for (because they are uninstantiated or native).
+    for (ClassElement cls in rti.allArguments) {
+      maybeGenerateHolder(cls);
+    }
+
+    // Add checks to the constructors of instantiated classes.
+    for (ClassElement cls in typeChecks) {
+      if (needsHolder(cls)) {
+        // We already emitted the is-checks in the object definition for this
+        // class.
+        continue;
+      }
+      String holder = namer.isolateAccess(cls);
+      for (ClassElement check in typeChecks[cls]) {
+        buffer.add('$holder.${namer.operatorIs(check)}$_=${_}true$N');
+      };
+    }
+  }
+
+  void visitNativeMixins(ClassElement classElement,
+                         void visit(MixinApplicationElement mixinApplication)) {
+    if (!classElement.isNative()) return;
+    // Use recursion to make sure to visit the superclasses before the
+    // subclasses. Once we start keeping track of the emitted fields
+    // and members, we're going to want to visit these in the other
+    // order so we get the most specialized definition first.
+    void recurse(ClassElement cls) {
+      if (cls == null || !cls.isMixinApplication) return;
+      recurse(cls.superclass);
+      assert(!cls.isNative());
+      visit(cls);
+    }
+    recurse(classElement.superclass);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [classElement] must be a declaration element.
+   */
+  void visitClassFields(ClassElement classElement,
+                        void addField(Element member,
+                                      String name,
+                                      String accessorName,
+                                      bool needsGetter,
+                                      bool needsSetter,
+                                      bool needsCheckedSetter)) {
+    assert(invariant(classElement, classElement.isDeclaration));
+    // If the class is never instantiated we still need to set it up for
+    // inheritance purposes, but we can simplify its JavaScript constructor.
+    bool isInstantiated =
+        compiler.codegenWorld.instantiatedClasses.contains(classElement);
+
+    void visitField(ClassElement enclosingClass, Element member) {
+      assert(invariant(classElement, member.isDeclaration));
+      LibraryElement library = member.getLibrary();
+      SourceString name = member.name;
+      bool isPrivate = name.isPrivate();
+
+      // Keep track of whether or not we're dealing with a field mixin
+      // into a native class.
+      bool isMixinNativeField =
+          classElement.isNative() && enclosingClass.isMixinApplication;
+
+      // See if we can dynamically create getters and setters.
+      // We can only generate getters and setters for [classElement] since
+      // the fields of super classes could be overwritten with getters or
+      // setters.
+      bool needsGetter = false;
+      bool needsSetter = false;
+      // We need to name shadowed fields differently, so they don't clash with
+      // the non-shadowed field.
+      bool isShadowed = false;
+      if (isMixinNativeField || identical(enclosingClass, classElement)) {
+        needsGetter = instanceFieldNeedsGetter(member);
+        needsSetter = instanceFieldNeedsSetter(member);
+      } else {
+        isShadowed = classElement.isShadowedByField(member);
+      }
+
+      if ((isInstantiated && !enclosingClass.isNative())
+          || needsGetter
+          || needsSetter) {
+        String accessorName = isShadowed
+            ? namer.shadowedFieldName(member)
+            : namer.getName(member);
+        String fieldName = member.hasFixedBackendName()
+            ? member.fixedBackendName()
+            : (isMixinNativeField ? member.name.slowToString() : accessorName);
+        bool needsCheckedSetter = false;
+        if (needsSetter && compiler.enableTypeAssertions
+            && canGenerateCheckedSetter(member)) {
+          needsCheckedSetter = true;
+          needsSetter = false;
+        }
+        // Getters and setters with suffixes will be generated dynamically.
+        addField(member,
+                 fieldName,
+                 accessorName,
+                 needsGetter,
+                 needsSetter,
+                 needsCheckedSetter);
+      }
+    }
+
+    // TODO(kasperl): We should make sure to only emit one version of
+    // overridden fields. Right now, we rely on the ordering so the
+    // fields pulled in from mixins are replaced with the fields from
+    // the class definition.
+
+    // If the class is a native class, we have to add the fields
+    // defined in the non-native mixin applications used by the class.
+    visitNativeMixins(classElement, (MixinApplicationElement mixin) {
+      mixin.forEachInstanceField(
+          visitField,
+          includeBackendMembers: true,
+          includeSuperMembers: false);
+    });
+
+    // If a class is not instantiated then we add the field just so we can
+    // generate the field getter/setter dynamically. Since this is only
+    // allowed on fields that are in [classElement] we don't need to visit
+    // superclasses for non-instantiated classes.
+    classElement.implementation.forEachInstanceField(
+        visitField,
+        includeBackendMembers: true,
+        includeSuperMembers: isInstantiated && !classElement.isNative());
+  }
+
+  void generateGetter(Element member, String fieldName, String accessorName,
+                      ClassBuilder builder) {
+    String getterName = namer.getterNameFromAccessorName(accessorName);
+    builder.addProperty(getterName,
+        js.fun([], js.block1(js.return_(js.use('this').dot(fieldName)))));
+  }
+
+  void generateSetter(Element member, String fieldName, String accessorName,
+                      ClassBuilder builder) {
+    String setterName = namer.setterNameFromAccessorName(accessorName);
+    builder.addProperty(setterName,
+        js.fun(['v'],
+            js.block1(
+                new js.ExpressionStatement(
+                    js.assign(js.use('this').dot(fieldName), js.use('v'))))));
+  }
+
+  bool canGenerateCheckedSetter(Element member) {
+    DartType type = member.computeType(compiler);
+    if (type.element.isTypeVariable()
+        || type.element == compiler.dynamicClass
+        || type.element == compiler.objectClass) {
+      // TODO(ngeoffray): Support type checks on type parameters.
+      return false;
+    }
+    return true;
+  }
+
+  void generateCheckedSetter(Element member,
+                             String fieldName,
+                             String accessorName,
+                             ClassBuilder builder) {
+    assert(canGenerateCheckedSetter(member));
+    DartType type = member.computeType(compiler);
+    // TODO(ahe): Generate a dynamic type error here.
+    if (type.element.isErroneous()) return;
+    SourceString helper = compiler.backend.getCheckedModeHelper(type);
+    FunctionElement helperElement = compiler.findHelper(helper);
+    String helperName = namer.isolateAccess(helperElement);
+    List<js.Expression> arguments = <js.Expression>[js.use('v')];
+    if (helperElement.computeSignature(compiler).parameterCount != 1) {
+      arguments.add(js.string(namer.operatorIs(type.element)));
+    }
+
+    String setterName = namer.setterNameFromAccessorName(accessorName);
+    builder.addProperty(setterName,
+        js.fun(['v'],
+            js.block1(
+                new js.ExpressionStatement(
+                    js.assign(
+                        js.use('this').dot(fieldName),
+                        js.call(js.use(helperName), arguments))))));
+  }
+
+  void emitClassConstructor(ClassElement classElement, ClassBuilder builder) {
+    /* Do nothing. */
+  }
+
+  void emitSuper(String superName, ClassBuilder builder) {
+    /* Do nothing. */
+  }
+
+  void emitClassFields(ClassElement classElement,
+                       ClassBuilder builder,
+                       { String superClass: "",
+                         bool classIsNative: false}) {
+    bool isFirstField = true;
+    StringBuffer buffer = new StringBuffer();
+    if (!classIsNative) {
+      buffer.add('$superClass;');
+    }
+    visitClassFields(classElement, (Element member,
+                                    String name,
+                                    String accessorName,
+                                    bool needsGetter,
+                                    bool needsSetter,
+                                    bool needsCheckedSetter) {
+      // Ignore needsCheckedSetter - that is handled below.
+      bool needsAccessor = (needsGetter || needsSetter);
+      // We need to output the fields for non-native classes so we can auto-
+      // generate the constructor.  For native classes there are no
+      // constructors, so we don't need the fields unless we are generating
+      // accessors at runtime.
+      if (!classIsNative || needsAccessor) {
+        // Emit correct commas.
+        if (isFirstField) {
+          isFirstField = false;
+        } else {
+          buffer.add(',');
+        }
+        int flag = 0;
+        if (!needsAccessor) {
+          // Emit field for constructor generation.
+          assert(!classIsNative);
+          buffer.add(name);
+        } else {
+          // Emit (possibly renaming) field name so we can add accessors at
+          // runtime.
+          buffer.add(accessorName);
+          if (name != accessorName) {
+            buffer.add(':$name');
+            // Only the native classes can have renaming accessors.
+            assert(classIsNative);
+            flag = RENAMING_FLAG;
+          }
+        }
+        if (needsGetter && needsSetter) {
+          buffer.addCharCode(GETTER_SETTER_CODE + flag);
+        } else if (needsGetter) {
+          buffer.addCharCode(GETTER_CODE + flag);
+        } else if (needsSetter) {
+          buffer.addCharCode(SETTER_CODE + flag);
+        }
+      }
+    });
+
+    String compactClassData = buffer.toString();
+    if (compactClassData.length > 0) {
+      builder.addProperty('', js.string(compactClassData));
+    }
+  }
+
+  void emitClassGettersSetters(ClassElement classElement,
+                               ClassBuilder builder) {
+
+    visitClassFields(classElement, (Element member,
+                                    String name,
+                                    String accessorName,
+                                    bool needsGetter,
+                                    bool needsSetter,
+                                    bool needsCheckedSetter) {
+      compiler.withCurrentElement(member, () {
+        if (needsCheckedSetter) {
+          assert(!needsSetter);
+          generateCheckedSetter(member, name, accessorName, builder);
+        }
+        if (!getterAndSetterCanBeImplementedByFieldSpec) {
+          if (needsGetter) {
+            generateGetter(member, name, accessorName, builder);
+          }
+          if (needsSetter) {
+            generateSetter(member, name, accessorName, builder);
+          }
+        }
+      });
+    });
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [classElement] must be a declaration element.
+   */
+  void generateClass(ClassElement classElement, CodeBuffer buffer) {
+    assert(invariant(classElement, classElement.isDeclaration));
+    if (classElement.isNative()) {
+      nativeEmitter.generateNativeClass(classElement);
+      return;
+    }
+
+    needsDefineClass = true;
+    String className = namer.getName(classElement);
+
+    // Find the first non-native superclass.
+    ClassElement superclass = classElement.superclass;
+    while (superclass != null && superclass.isNative()) {
+      superclass = superclass.superclass;
+    }
+
+    String superName = "";
+    if (superclass != null) {
+      superName = namer.getName(superclass);
+    }
+
+    ClassBuilder builder = new ClassBuilder();
+
+    emitClassConstructor(classElement, builder);
+    emitSuper(superName, builder);
+    emitClassFields(classElement, builder,
+                    superClass: superName, classIsNative: false);
+    emitClassGettersSetters(classElement, builder);
+    emitInstanceMembers(classElement, builder);
+
+    js.Expression init =
+        js.assign(
+            js.use(classesCollector).dot(className),
+            builder.toObjectInitializer());
+    buffer.add(js.prettyPrint(init, compiler));
+    buffer.add('$N$n');
+  }
+
+  bool get getterAndSetterCanBeImplementedByFieldSpec => true;
+
+  int _selectorRank(Selector selector) {
+    int arity = selector.argumentCount * 3;
+    if (selector.isGetter()) return arity + 2;
+    if (selector.isSetter()) return arity + 1;
+    return arity;
+  }
+
+  int _compareSelectorNames(Selector selector1, Selector selector2) {
+    String name1 = selector1.name.toString();
+    String name2 = selector2.name.toString();
+    if (name1 != name2) return Comparable.compare(name1, name2);
+    return _selectorRank(selector1) - _selectorRank(selector2);
+  }
+
+  void emitInterceptorMethods(ClassBuilder builder) {
+    JavaScriptBackend backend = compiler.backend;
+    // Emit forwarders for the ObjectInterceptor class. We need to
+    // emit all possible sends on intercepted methods.
+    for (Selector selector in
+         backend.usedInterceptors.toList()..sort(_compareSelectorNames)) {
+      List<js.Parameter> parameters = <js.Parameter>[];
+      List<js.Expression> arguments = <js.Expression>[];
+      parameters.add(new js.Parameter('receiver'));
+
+      String name = backend.namer.invocationName(selector);
+      if (selector.isSetter()) {
+        parameters.add(new js.Parameter('value'));
+        arguments.add(new js.VariableUse('value'));
+      } else {
+        for (int i = 0; i < selector.argumentCount; i++) {
+          String argName = 'a$i';
+          parameters.add(new js.Parameter(argName));
+          arguments.add(new js.VariableUse(argName));
+        }
+      }
+      js.Fun function =
+          new js.Fun(parameters,
+              new js.Block(
+                  <js.Statement>[
+                      new js.Return(
+                          new js.VariableUse('receiver')
+                              .dot(name)
+                              .callWith(arguments))]));
+      builder.addProperty(name, function);
+    }
+  }
+
+  Iterable<Element> getTypedefChecksOn(DartType type) {
+    bool isSubtype(TypedefElement typedef) {
+      FunctionType typedefType =
+          typedef.computeType(compiler).unalias(compiler);
+      return compiler.types.isSubtype(type, typedefType);
+    }
+    return checkedTypedefs.where(isSubtype).toList()
+        ..sort(Elements.compareByPosition);
+  }
+
+  /**
+   * Generate "is tests" for [cls]: itself, and the "is tests" for the
+   * classes it implements. We don't need to add the "is tests" of the
+   * super class because they will be inherited at runtime.
+   */
+  void generateIsTestsOn(ClassElement cls,
+                         void emitIsTest(Element element)) {
+    if (checkedClasses.contains(cls)) {
+      emitIsTest(cls);
+    }
+
+    Set<Element> generated = new Set<Element>();
+    // A class that defines a [:call:] method implicitly implements
+    // [Function] and needs checks for all typedefs that are used in is-checks.
+    if (checkedClasses.contains(compiler.functionClass) ||
+        !checkedTypedefs.isEmpty) {
+      FunctionElement call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME);
+      if (call == null) {
+        // If [cls] is a closure, it has a synthetic call operator method.
+        call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME);
+      }
+      if (call != null) {
+        generateInterfacesIsTests(compiler.functionClass,
+                                  emitIsTest,
+                                  generated);
+        getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest);
+      }
+    }
+
+    for (DartType interfaceType in cls.interfaces) {
+      generateInterfacesIsTests(interfaceType.element, emitIsTest, generated);
+    }
+
+    // For native classes, we also have to run through their mixin
+    // applications and make sure we deal with 'is' tests correctly
+    // for those.
+    visitNativeMixins(cls, (MixinApplicationElement mixin) {
+      for (DartType interfaceType in mixin.interfaces) {
+        ClassElement interfaceElement = interfaceType.element;
+        generateInterfacesIsTests(interfaceType.element, emitIsTest, generated);
+      }
+    });
+  }
+
+  /**
+   * Generate "is tests" where [cls] is being implemented.
+   */
+  void generateInterfacesIsTests(ClassElement cls,
+                                 void emitIsTest(ClassElement element),
+                                 Set<Element> alreadyGenerated) {
+    void tryEmitTest(ClassElement cls) {
+      if (!alreadyGenerated.contains(cls) && checkedClasses.contains(cls)) {
+        alreadyGenerated.add(cls);
+        emitIsTest(cls);
+      }
+    };
+
+    tryEmitTest(cls);
+
+    for (DartType interfaceType in cls.interfaces) {
+      Element element = interfaceType.element;
+      tryEmitTest(element);
+      generateInterfacesIsTests(element, emitIsTest, alreadyGenerated);
+    }
+
+    // We need to also emit "is checks" for the superclass and its supertypes.
+    ClassElement superclass = cls.superclass;
+    if (superclass != null) {
+      tryEmitTest(superclass);
+      generateInterfacesIsTests(superclass, emitIsTest, alreadyGenerated);
+    }
+  }
+
+  /**
+   * Return a function that returns true if its argument is a class
+   * that needs to be emitted.
+   */
+  Function computeClassFilter() {
+    Set<ClassElement> unneededClasses = new Set<ClassElement>();
+    // The [Bool] class is not marked as abstract, but has a factory
+    // constructor that always throws. We never need to emit it.
+    unneededClasses.add(compiler.boolClass);
+
+    JavaScriptBackend backend = compiler.backend;
+
+    // Go over specialized interceptors and then constants to know which
+    // interceptors are needed.
+    Set<ClassElement> needed = new Set<ClassElement>();
+    backend.specializedGetInterceptors.forEach(
+        (_, Collection<ClassElement> elements) {
+          needed.addAll(elements);
+        }
+    );
+
+    ConstantHandler handler = compiler.constantHandler;
+    List<Constant> constants = handler.getConstantsForEmission();
+    for (Constant constant in constants) {
+      if (constant is ConstructedConstant) {
+        Element element = constant.computeType(compiler).element;
+        if (backend.isInterceptorClass(element)) {
+          needed.add(element);
+        }
+      }
+    }
+
+    // Add unneeded interceptors to the [unneededClasses] set.
+    for (ClassElement interceptor in backend.interceptedClasses.keys) {
+      if (!needed.contains(interceptor)) {
+        unneededClasses.add(interceptor);
+      }
+    }
+
+    return (ClassElement cls) => !unneededClasses.contains(cls);
+  }
+
+  void emitClasses(CodeBuffer buffer) {
+    // Compute the required type checks to know which classes need a
+    // 'is$' method.
+    computeRequiredTypeChecks();
+    List<ClassElement> sortedClasses =
+        new List<ClassElement>.from(neededClasses);
+    sortedClasses.sort((ClassElement class1, ClassElement class2) {
+      // We sort by the ids of the classes. There is no guarantee that these
+      // ids are meaningful (or even deterministic), but in the current
+      // implementation they are increasing within a source file.
+      return class1.id - class2.id;
+    });
+
+    // If we need noSuchMethod support, we run through all needed
+    // classes to figure out if we need the support on any native
+    // class. If so, we let the native emitter deal with it.
+    if (compiler.enabledNoSuchMethod) {
+      SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD;
+      Selector noSuchMethodSelector = new Selector.noSuchMethod();
+      for (ClassElement element in sortedClasses) {
+        if (!element.isNative()) continue;
+        Element member = element.lookupLocalMember(noSuchMethodName);
+        if (member == null) continue;
+        if (noSuchMethodSelector.applies(member, compiler)) {
+          nativeEmitter.handleNoSuchMethod = true;
+          break;
+        }
+      }
+    }
+
+    for (ClassElement element in sortedClasses) {
+      generateClass(element, buffer);
+    }
+
+    // The closure class could have become necessary because of the generation
+    // of stubs.
+    ClassElement closureClass = compiler.closureClass;
+    if (needsClosureClass && !instantiatedClasses.contains(closureClass)) {
+      generateClass(closureClass, buffer);
+    }
+  }
+
+  void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) {
+    if (needsDefineClass) {
+      buffer.add("$finishClassesName($classesCollector)$N");
+      // Reset the map.
+      buffer.add("$classesCollector$_=$_{}$N");
+    }
+  }
+
+  void emitStaticFunction(CodeBuffer buffer,
+                          String name,
+                          js.Expression functionExpression) {
+    js.Expression assignment =
+        js.assign(js.use(isolateProperties).dot(name), functionExpression);
+    buffer.add(js.prettyPrint(assignment, compiler));
+    buffer.add('$N$n');
+  }
+
+  void emitStaticFunctions(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    bool isStaticFunction(Element element) =>
+        !element.isInstanceMember() && !element.isField();
+
+    Iterable<Element> elements =
+        backend.generatedCode.keys.where(isStaticFunction);
+    Set<Element> pendingElementsWithBailouts =
+        backend.generatedBailoutCode.keys
+            .where(isStaticFunction)
+            .toSet();
+
+    for (Element element in Elements.sortedByPosition(elements)) {
+      js.Expression code = backend.generatedCode[element];
+      emitStaticFunction(buffer, namer.getName(element), code);
+      js.Expression bailoutCode = backend.generatedBailoutCode[element];
+      if (bailoutCode != null) {
+        pendingElementsWithBailouts.remove(element);
+        emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode);
+      }
+    }
+
+    // Is it possible the primary function was inlined but the bailout was not?
+    for (Element element in
+             Elements.sortedByPosition(pendingElementsWithBailouts)) {
+      js.Expression bailoutCode = backend.generatedBailoutCode[element];
+      emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode);
+    }
+  }
+
+  void emitStaticFunctionGetters(CodeBuffer buffer) {
+    Set<FunctionElement> functionsNeedingGetter =
+        compiler.codegenWorld.staticFunctionsNeedingGetter;
+    for (FunctionElement element in
+             Elements.sortedByPosition(functionsNeedingGetter)) {
+      // The static function does not have the correct name. Since
+      // [addParameterStubs] use the name to create its stubs we simply
+      // create a fake element with the correct name.
+      // Note: the callElement will not have any enclosingElement.
+      FunctionElement callElement =
+          new ClosureInvocationElement(namer.closureInvocationSelectorName,
+                                       element);
+      String staticName = namer.getName(element);
+      String invocationName = namer.instanceMethodName(callElement);
+      String fieldAccess = '$isolateProperties.$staticName';
+      buffer.add("$fieldAccess.$invocationName$_=$_$fieldAccess$N");
+
+      addParameterStubs(callElement, (String name, js.Expression value) {
+        js.Expression assignment =
+            js.assign(
+                js.use(isolateProperties).dot(staticName).dot(name),
+                value);
+        buffer.add(
+            js.prettyPrint(new js.ExpressionStatement(assignment), compiler));
+        buffer.add('$N');
+      });
+
+      // If a static function is used as a closure we need to add its name
+      // in case it is used in spawnFunction.
+      String fieldName = namer.STATIC_CLOSURE_NAME_NAME;
+      buffer.add('$fieldAccess.$fieldName$_=$_"$staticName"$N');
+      getTypedefChecksOn(element.computeType(compiler)).forEach(
+        (Element typedef) {
+          String operator = namer.operatorIs(typedef);
+          buffer.add('$fieldAccess.$operator$_=${_}true$N');
+        }
+      );
+    }
+  }
+
+  void emitBoundClosureClassHeader(String mangledName,
+                                   String superName,
+                                   List<String> fieldNames,
+                                   ClassBuilder builder) {
+    builder.addProperty('',
+        js.string("$superName;${Strings.join(fieldNames,',')}"));
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void emitDynamicFunctionGetter(FunctionElement member,
+                                 DefineStubFunction defineStub) {
+    assert(invariant(member, member.isDeclaration));
+    // For every method that has the same name as a property-get we create a
+    // getter that returns a bound closure. Say we have a class 'A' with method
+    // 'foo' and somewhere in the code there is a dynamic property get of
+    // 'foo'. Then we generate the following code (in pseudo Dart/JavaScript):
+    //
+    // class A {
+    //    foo(x, y, z) { ... } // Original function.
+    //    get foo { return new BoundClosure499(this, "foo"); }
+    // }
+    // class BoundClosure499 extends Closure {
+    //   var self;
+    //   BoundClosure499(this.self, this.name);
+    //   $call3(x, y, z) { return self[name](x, y, z); }
+    // }
+
+    // TODO(floitsch): share the closure classes with other classes
+    // if they share methods with the same signature. Currently we do this only
+    // if there are no optional parameters. Closures with optional parameters
+    // are more difficult to canonicalize because they would need to have the
+    // same default values.
+
+    bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0;
+    int parameterCount = member.parameterCount(compiler);
+
+    Map<int, String> cache;
+    String extraArg = null;
+    // Methods on interceptor classes take an extra parameter, which is the
+    // actual receiver of the call.
+    JavaScriptBackend backend = compiler.backend;
+    bool inInterceptor = backend.isInterceptorClass(member.getEnclosingClass());
+    if (inInterceptor) {
+      cache = interceptorClosureCache;
+      extraArg = 'receiver';
+    } else {
+      cache = boundClosureCache;
+    }
+    List<String> fieldNames = compiler.enableMinification
+        ? inInterceptor ? const ['a', 'b', 'c']
+                        : const ['a', 'b']
+        : inInterceptor ? const ['self', 'target', 'receiver']
+                        : const ['self', 'target'];
+
+    Iterable<Element> typedefChecks =
+        getTypedefChecksOn(member.computeType(compiler));
+    bool hasTypedefChecks = !typedefChecks.isEmpty;
+
+    bool canBeShared = !hasOptionalParameters && !hasTypedefChecks;
+
+    String closureClass = canBeShared ? cache[parameterCount] : null;
+    if (closureClass == null) {
+      // Either the class was not cached yet, or there are optional parameters.
+      // Create a new closure class.
+      String name;
+      if (canBeShared) {
+        if (inInterceptor) {
+          name = 'BoundClosure\$i${parameterCount}';
+        } else {
+          name = 'BoundClosure\$${parameterCount}';
+        }
+      } else {
+        name = 'Bound_${member.name.slowToString()}'
+            '_${member.enclosingElement.name.slowToString()}';
+      }
+
+      ClassElement closureClassElement = new ClosureClassElement(
+          new SourceString(name), compiler, member, member.getCompilationUnit());
+      String mangledName = namer.getName(closureClassElement);
+      String superName = namer.getName(closureClassElement.superclass);
+      needsClosureClass = true;
+
+      // Define the constructor with a name so that Object.toString can
+      // find the class name of the closure class.
+      ClassBuilder boundClosureBuilder = new ClassBuilder();
+      emitBoundClosureClassHeader(
+          mangledName, superName, fieldNames, boundClosureBuilder);
+      // Now add the methods on the closure class. The instance method does not
+      // have the correct name. Since [addParameterStubs] use the name to create
+      // its stubs we simply create a fake element with the correct name.
+      // Note: the callElement will not have any enclosingElement.
+      FunctionElement callElement =
+          new ClosureInvocationElement(namer.closureInvocationSelectorName,
+                                       member);
+
+      String invocationName = namer.instanceMethodName(callElement);
+
+      List<String> parameters = <String>[];
+      List<js.Expression> arguments = <js.Expression>[];
+      if (inInterceptor) {
+        arguments.add(js.use('this').dot(fieldNames[2]));
+      }
+      for (int i = 0; i < parameterCount; i++) {
+        String name = 'p$i';
+        parameters.add(name);
+        arguments.add(js.use(name));
+      }
+
+      js.Expression fun =
+          js.fun(parameters,
+              js.block1(
+                  js.return_(
+                      new js.PropertyAccess(
+                          js.use('this').dot(fieldNames[0]),
+                          js.use('this').dot(fieldNames[1]))
+                      .callWith(arguments))));
+      boundClosureBuilder.addProperty(invocationName, fun);
+
+      addParameterStubs(callElement, boundClosureBuilder.addProperty);
+      typedefChecks.forEach((Element typedef) {
+        String operator = namer.operatorIs(typedef);
+        boundClosureBuilder.addProperty(operator, new js.LiteralBool(true));
+      });
+
+      js.Expression init =
+          js.assign(
+              js.use(classesCollector).dot(mangledName),
+              boundClosureBuilder.toObjectInitializer());
+      boundClosureBuffer.add(js.prettyPrint(init, compiler));
+      boundClosureBuffer.add("$N");
+
+      closureClass = namer.isolateAccess(closureClassElement);
+
+      // Cache it.
+      if (canBeShared) {
+        cache[parameterCount] = closureClass;
+      }
+    }
+
+    // And finally the getter.
+    String getterName = namer.getterName(member);
+    String targetName = namer.instanceMethodName(member);
+
+    List<String> parameters = <String>[];
+    List<js.Expression> arguments = <js.Expression>[];
+    arguments.add(js.use('this'));
+    arguments.add(js.string(targetName));
+    if (inInterceptor) {
+      parameters.add(extraArg);
+      arguments.add(js.use(extraArg));
+    }
+
+    js.Expression getterFunction =
+        js.fun(parameters,
+            js.block1(
+                js.return_(
+                    new js.New(js.use(closureClass), arguments))));
+
+    defineStub(getterName, getterFunction);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void emitCallStubForGetter(Element member,
+                             Set<Selector> selectors,
+                             DefineStubFunction defineStub) {
+    assert(invariant(member, member.isDeclaration));
+    LibraryElement memberLibrary = member.getLibrary();
+    JavaScriptBackend backend = compiler.backend;
+    // If the class is an interceptor class, the stub gets the
+    // receiver explicitely and we need to pass it to the getter call.
+    bool isInterceptorClass =
+        backend.isInterceptorClass(member.getEnclosingClass());
+
+    const String receiverArgumentName = r'$receiver';
+
+    js.Expression buildGetter() {
+      if (member.isGetter()) {
+        String getterName = namer.getterName(member);
+        return new js.VariableUse('this').dot(getterName).callWith(
+            isInterceptorClass
+                ? <js.Expression>[new js.VariableUse(receiverArgumentName)]
+                : <js.Expression>[]);
+      } else {
+        String fieldName = member.hasFixedBackendName()
+            ? member.fixedBackendName()
+            : namer.instanceFieldName(member);
+        return new js.VariableUse('this').dot(fieldName);
+      }
+    }
+
+    // Two selectors may match but differ only in type.  To avoid generating
+    // identical stubs for each we track untyped selectors which already have
+    // stubs.
+    Set<Selector> generatedSelectors = new Set<Selector>();
+
+    for (Selector selector in selectors) {
+      if (selector.applies(member, compiler)) {
+        selector = selector.asUntyped;
+        if (generatedSelectors.contains(selector)) continue;
+        generatedSelectors.add(selector);
+
+        String invocationName = namer.invocationName(selector);
+        Selector callSelector = new Selector.callClosureFrom(selector);
+        String closureCallName = namer.invocationName(callSelector);
+
+        List<js.Parameter> parameters = <js.Parameter>[];
+        List<js.Expression> arguments = <js.Expression>[];
+        if (isInterceptorClass) {
+          parameters.add(new js.Parameter(receiverArgumentName));
+        }
+
+        for (int i = 0; i < selector.argumentCount; i++) {
+          String name = 'arg$i';
+          parameters.add(new js.Parameter(name));
+          arguments.add(new js.VariableUse(name));
+        }
+
+        js.Fun function =
+            new js.Fun(parameters,
+                new js.Block(
+                    <js.Statement>[
+                        new js.Return(
+                            buildGetter().dot(closureCallName)
+                                .callWith(arguments))]));
+
+        defineStub(invocationName, function);
+      }
+    }
+  }
+
+  void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) {
+    ConstantHandler handler = compiler.constantHandler;
+    Iterable<VariableElement> staticNonFinalFields =
+        handler.getStaticNonFinalFieldsForEmission();
+    for (Element element in Elements.sortedByPosition(staticNonFinalFields)) {
+      compiler.withCurrentElement(element, () {
+        Constant initialValue = handler.getInitialValueFor(element);
+        js.Expression init =
+            new js.Assignment(
+                new js.PropertyAccess.field(
+                    new js.VariableUse(isolateProperties),
+                    namer.getName(element)),
+                constantEmitter.referenceInInitializationContext(initialValue));
+        buffer.add(js.prettyPrint(init, compiler));
+        buffer.add('$N');
+      });
+    }
+  }
+
+  void emitLazilyInitializedStaticFields(CodeBuffer buffer) {
+    ConstantHandler handler = compiler.constantHandler;
+    List<VariableElement> lazyFields =
+        handler.getLazilyInitializedFieldsForEmission();
+    JavaScriptBackend backend = compiler.backend;
+    if (!lazyFields.isEmpty) {
+      needsLazyInitializer = true;
+      for (VariableElement element in Elements.sortedByPosition(lazyFields)) {
+        assert(backend.generatedBailoutCode[element] == null);
+        js.Expression code = backend.generatedCode[element];
+        assert(code != null);
+        // The code only computes the initial value. We build the lazy-check
+        // here:
+        //   lazyInitializer(prototype, 'name', fieldName, getterName, initial);
+        // The name is used for error reporting. The 'initial' must be a
+        // closure that constructs the initial value.
+        List<js.Expression> arguments = <js.Expression>[];
+        arguments.add(js.use(isolateProperties));
+        arguments.add(js.string(element.name.slowToString()));
+        arguments.add(js.string(namer.getName(element)));
+        arguments.add(js.string(namer.getLazyInitializerName(element)));
+        arguments.add(code);
+        js.Expression getter = buildLazyInitializedGetter(element);
+        if (getter != null) {
+          arguments.add(getter);
+        }
+        js.Expression init = js.call(js.use(lazyInitializerName), arguments);
+        buffer.add(js.prettyPrint(init, compiler));
+        buffer.add("$N");
+      }
+    }
+  }
+
+  js.Expression buildLazyInitializedGetter(VariableElement element) {
+    // Nothing to do, the 'lazy' function will create the getter.
+    return null;
+  }
+
+  void emitCompileTimeConstants(CodeBuffer buffer) {
+    ConstantHandler handler = compiler.constantHandler;
+    List<Constant> constants = handler.getConstantsForEmission();
+    bool addedMakeConstantList = false;
+    for (Constant constant in constants) {
+      // No need to emit functions. We already did that.
+      if (constant.isFunction()) continue;
+      // Numbers, strings and booleans are currently always inlined.
+      if (constant.isPrimitive()) continue;
+
+      String name = namer.constantName(constant);
+      // The name is null when the constant is already a JS constant.
+      // TODO(floitsch): every constant should be registered, so that we can
+      // share the ones that take up too much space (like some strings).
+      if (name == null) continue;
+      if (!addedMakeConstantList && constant.isList()) {
+        addedMakeConstantList = true;
+        emitMakeConstantList(buffer);
+      }
+      js.Expression init =
+          new js.Assignment(
+              new js.PropertyAccess.field(
+                  new js.VariableUse(isolateProperties),
+                  name),
+              constantInitializerExpression(constant));
+      buffer.add(js.prettyPrint(init, compiler));
+      buffer.add('$N');
+    }
+  }
+
+  void emitMakeConstantList(CodeBuffer buffer) {
+    buffer.add(namer.isolateName);
+    buffer.add(r'''.makeConstantList = function(list) {
+  list.immutable$list = true;
+  list.fixed$length = true;
+  return list;
+};
+''');
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void emitExtraAccessors(Element member, ClassBuilder builder) {
+    assert(invariant(member, member.isDeclaration));
+    if (member.isGetter() || member.isField()) {
+      Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
+      if (selectors != null && !selectors.isEmpty) {
+        emitCallStubForGetter(member, selectors, builder.addProperty);
+      }
+    } else if (member.isFunction()) {
+      if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) {
+        emitDynamicFunctionGetter(member, builder.addProperty);
+      }
+    }
+  }
+
+  void emitNoSuchMethodHandlers(DefineStubFunction defineStub) {
+    // Do not generate no such method handlers if there is no class.
+    if (compiler.codegenWorld.instantiatedClasses.isEmpty) return;
+
+    String noSuchMethodName = namer.publicInstanceMethodNameByArity(
+        Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT);
+
+    Element createInvocationMirrorElement =
+        compiler.findHelper(const SourceString("createInvocationMirror"));
+    String createInvocationMirrorName =
+        namer.getName(createInvocationMirrorElement);
+
+    // Keep track of the JavaScript names we've already added so we
+    // do not introduce duplicates (bad for code size).
+    Set<String> addedJsNames = new Set<String>();
+
+    // Keep track of the noSuchMethod holders for each possible
+    // receiver type.
+    Map<ClassElement, Set<ClassElement>> noSuchMethodHolders =
+        new Map<ClassElement, Set<ClassElement>>();
+    Set<ClassElement> noSuchMethodHoldersFor(DartType type) {
+      ClassElement element = type.element;
+      Set<ClassElement> result = noSuchMethodHolders[element];
+      if (result == null) {
+        // For now, we check the entire world to see if an object of
+        // the given type may have a user-defined noSuchMethod
+        // implementation. We could do better by only looking at
+        // instantiated (or otherwise needed) classes.
+        result = compiler.world.findNoSuchMethodHolders(type);
+        noSuchMethodHolders[element] = result;
+      }
+      return result;
+    }
+
+    js.Expression generateMethod(String jsName, Selector selector) {
+      // Values match JSInvocationMirror in js-helper library.
+      int type = selector.invocationMirrorKind;
+      String methodName = selector.invocationMirrorMemberName;
+      List<js.Parameter> parameters = <js.Parameter>[];
+      CodeBuffer args = new CodeBuffer();
+      for (int i = 0; i < selector.argumentCount; i++) {
+        parameters.add(new js.Parameter('\$$i'));
+      }
+
+      List<js.Expression> argNames =
+          selector.getOrderedNamedArguments().map((SourceString name) =>
+              js.string(name.slowToString())).toList();
+
+      String internalName = namer.invocationMirrorInternalName(selector);
+
+      String createInvocationMirror = namer.getName(
+          compiler.createInvocationMirrorElement);
+
+      js.Expression expression =
+          new js.This()
+          .dot(noSuchMethodName)
+          .callWith(
+              <js.Expression>[
+                  new js.VariableUse(namer.CURRENT_ISOLATE)
+                  .dot(createInvocationMirror)
+                  .callWith(
+                      <js.Expression>[
+                          js.string(methodName),
+                          js.string(internalName),
+                          new js.LiteralNumber('$type'),
+                          new js.ArrayInitializer.from(
+                              parameters.map((param) => js.use(param.name))
+                                        .toList()),
+                          new js.ArrayInitializer.from(argNames)])]);
+      js.Expression function =
+          new js.Fun(parameters,
+              new js.Block(<js.Statement>[new js.Return(expression)]));
+      return function;
+    }
+
+    void addNoSuchMethodHandlers(SourceString ignore, Set<Selector> selectors) {
+      // Cache the object class and type.
+      ClassElement objectClass = compiler.objectClass;
+      DartType objectType = objectClass.computeType(compiler);
+
+      for (Selector selector in selectors) {
+        // Introduce a helper function that determines if the given
+        // class has a member that matches the current name and
+        // selector (grabbed from the scope).
+        bool hasMatchingMember(ClassElement holder) {
+          Element element = holder.lookupMember(selector.name);
+          if (element == null) return false;
+
+          // TODO(kasperl): Consider folding this logic into the
+          // Selector.applies() method.
+          if (element is AbstractFieldElement) {
+            AbstractFieldElement field = element;
+            if (selector.isGetter()) {
+              return field.getter != null;
+            } else if (selector.isSetter()) {
+              return field.setter != null;
+            } else {
+              return false;
+            }
+          } else if (element is VariableElement) {
+            if (selector.isSetter() && element.modifiers.isFinalOrConst()) {
+              return false;
+            }
+          }
+          return selector.applies(element, compiler);
+        }
+
+        // If the selector is typed, we check to see if that type may
+        // have a user-defined noSuchMethod implementation. If not, we
+        // skip the selector altogether.
+        DartType receiverType = objectType;
+        ClassElement receiverClass = objectClass;
+        if (selector is TypedSelector) {
+          TypedSelector typedSelector = selector;
+          receiverType = typedSelector.receiverType;
+          receiverClass = receiverType.element;
+        }
+
+        // If the receiver class is guaranteed to have a member that
+        // matches what we're looking for, there's no need to
+        // introduce a noSuchMethod handler. It will never be called.
+        //
+        // As an example, consider this class hierarchy:
+        //
+        //                   A    <-- noSuchMethod
+        //                  / \
+        //                 C   B  <-- foo
+        //
+        // If we know we're calling foo on an object of type B we
+        // don't have to worry about the noSuchMethod method in A
+        // because objects of type B implement foo. On the other hand,
+        // if we end up calling foo on something of type C we have to
+        // add a handler for it.
+        if (hasMatchingMember(receiverClass)) continue;
+
+        // If the holders of all user-defined noSuchMethod
+        // implementations that might be applicable to the receiver
+        // type have a matching member for the current name and
+        // selector, we avoid introducing a noSuchMethod handler.
+        //
+        // As an example, consider this class hierarchy:
+        //
+        //                       A    <-- foo
+        //                      / \
+        //   noSuchMethod -->  B   C  <-- bar
+        //                     |   |
+        //                     C   D  <-- noSuchMethod
+        //
+        // When calling foo on an object of type A, we know that the
+        // implementations of noSuchMethod are in the classes B and D
+        // that also (indirectly) implement foo, so we do not need a
+        // handler for it.
+        //
+        // If we're calling bar on an object of type D, we don't need
+        // the handler either because all objects of type D implement
+        // bar through inheritance.
+        //
+        // If we're calling bar on an object of type A we do need the
+        // handler because we may have to call B.noSuchMethod since B
+        // does not implement bar.
+        Set<ClassElement> holders = noSuchMethodHoldersFor(receiverType);
+        if (holders.every(hasMatchingMember)) continue;
+        String jsName = namer.invocationMirrorInternalName(selector);
+        if (!addedJsNames.contains(jsName)) {
+          js.Expression method = generateMethod(jsName, selector);
+          defineStub(jsName, method);
+          addedJsNames.add(jsName);
+        }
+      }
+    }
+
+    compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers);
+    compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers);
+    compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers);
+  }
+
+  String buildIsolateSetup(CodeBuffer buffer,
+                           Element appMain,
+                           Element isolateMain) {
+    String mainAccess = "${namer.isolateAccess(appMain)}";
+    String currentIsolate = "${namer.CURRENT_ISOLATE}";
+    // Since we pass the closurized version of the main method to
+    // the isolate method, we must make sure that it exists.
+    if (!compiler.codegenWorld.staticFunctionsNeedingGetter.contains(appMain)) {
+      Selector selector = new Selector.callClosure(0);
+      String invocationName = namer.invocationName(selector);
+      buffer.add("$mainAccess.$invocationName = $mainAccess$N");
+    }
+    return "${namer.isolateAccess(isolateMain)}($mainAccess)";
+  }
+
+  emitMain(CodeBuffer buffer) {
+    if (compiler.isMockCompilation) return;
+    Element main = compiler.mainApp.find(Compiler.MAIN);
+    String mainCall = null;
+    if (compiler.hasIsolateSupport()) {
+      Element isolateMain =
+        compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE);
+      mainCall = buildIsolateSetup(buffer, main, isolateMain);
+    } else {
+      mainCall = '${namer.isolateAccess(main)}()';
+    }
+    if (!compiler.enableMinification) {
+      buffer.add("""
+
+//
+// BEGIN invoke [main].
+//
+""");
+    }
+    buffer.add("""
+if (typeof document !== 'undefined' && document.readyState !== 'complete') {
+  document.addEventListener('readystatechange', function () {
+    if (document.readyState == 'complete') {
+      if (typeof dartMainRunner === 'function') {
+        dartMainRunner(function() { ${mainCall}; });
+      } else {
+        ${mainCall};
+      }
+    }
+  }, false);
+} else {
+  if (typeof dartMainRunner === 'function') {
+    dartMainRunner(function() { ${mainCall}; });
+  } else {
+    ${mainCall};
+  }
+}
+""");
+    if (!compiler.enableMinification) {
+      buffer.add("""
+//
+// END invoke [main].
+//
+
+""");
+    }
+  }
+
+  void emitGetInterceptorMethod(CodeBuffer buffer,
+                                String objectName,
+                                String key,
+                                Collection<ClassElement> classes) {
+    js.Statement buildReturnInterceptor(ClassElement cls) {
+      return js.return_(js.fieldAccess(js.use(namer.isolateAccess(cls)),
+                                       'prototype'));
+    }
+
+    js.VariableUse receiver = js.use('receiver');
+    JavaScriptBackend backend = compiler.backend;
+
+    /**
+     * Build a JavaScrit AST node for doing a type check on
+     * [cls]. [cls] must be an interceptor class.
+     */
+    js.Statement buildInterceptorCheck(ClassElement cls) {
+      js.Expression condition;
+      assert(backend.isInterceptorClass(cls));
+      if (cls == backend.jsBoolClass) {
+        condition = js.equals(js.typeOf(receiver), js.string('boolean'));
+      } else if (cls == backend.jsIntClass ||
+                 cls == backend.jsDoubleClass ||
+                 cls == backend.jsNumberClass) {
+        throw 'internal error';
+      } else if (cls == backend.jsArrayClass) {
+        condition = js.equals(js.fieldAccess(receiver, 'constructor'),
+                              js.use('Array'));
+      } else if (cls == backend.jsStringClass) {
+        condition = js.equals(js.typeOf(receiver), js.string('string'));
+      } else if (cls == backend.jsNullClass) {
+        condition = js.equals(receiver, new js.LiteralNull());
+      } else if (cls == backend.jsFunctionClass) {
+        condition = js.equals(js.typeOf(receiver), js.string('function'));
+      } else {
+        throw 'internal error';
+      }
+      return js.if_(condition, buildReturnInterceptor(cls));
+    }
+
+    bool hasArray = false;
+    bool hasBool = false;
+    bool hasDouble = false;
+    bool hasFunction = false;
+    bool hasInt = false;
+    bool hasNull = false;
+    bool hasNumber = false;
+    bool hasString = false;
+    for (ClassElement cls in classes) {
+      if (cls == backend.jsArrayClass) hasArray = true;
+      else if (cls == backend.jsBoolClass) hasBool = true;
+      else if (cls == backend.jsDoubleClass) hasDouble = true;
+      else if (cls == backend.jsFunctionClass) hasFunction = true;
+      else if (cls == backend.jsIntClass) hasInt = true;
+      else if (cls == backend.jsNullClass) hasNull = true;
+      else if (cls == backend.jsNumberClass) hasNumber = true;
+      else if (cls == backend.jsStringClass) hasString = true;
+      else throw 'Internal error: $cls';
+    }
+    if (hasDouble) {
+      assert(!hasNumber);
+      hasNumber = true;
+    }
+    if (hasInt) hasNumber = true;
+
+    js.Block block = new js.Block.empty();
+
+    if (hasNumber) {
+      js.Statement whenNumber;
+
+      /// Note: there are two number classes in play: Dart's [num],
+      /// and JavaScript's Number (typeof receiver == 'number').  This
+      /// is the fallback used when we have determined that receiver
+      /// is a JavaScript Number.
+      js.Return returnNumberClass = buildReturnInterceptor(
+          hasDouble ? backend.jsDoubleClass : backend.jsNumberClass);
+
+      if (hasInt) {
+        js.Expression isInt =
+            js.equals(js.call(js.fieldAccess(js.use('Math'), 'floor'),
+                              [receiver]),
+                      receiver);
+        (whenNumber = js.emptyBlock()).statements
+          ..add(js.if_(isInt, buildReturnInterceptor(backend.jsIntClass)))
+          ..add(returnNumberClass);
+      } else {
+        whenNumber = returnNumberClass;
+      }
+      block.statements.add(
+          js.if_(js.equals(js.typeOf(receiver), js.string('number')),
+                 whenNumber));
+    }
+
+    if (hasString) {
+      block.statements.add(buildInterceptorCheck(backend.jsStringClass));
+    }
+    if (hasNull) {
+      block.statements.add(buildInterceptorCheck(backend.jsNullClass));
+    } else {
+      // Returning "undefined" here will provoke a JavaScript
+      // TypeError which is later identified as a null-error by
+      // [unwrapException] in js_helper.dart.
+      block.statements.add(js.if_(js.equals(receiver, new js.LiteralNull()),
+                                  js.return_(js.undefined())));
+    }
+    if (hasFunction) {
+      block.statements.add(buildInterceptorCheck(backend.jsFunctionClass));
+    }
+    if (hasBool) {
+      block.statements.add(buildInterceptorCheck(backend.jsBoolClass));
+    }
+    // TODO(ahe): It might be faster to check for Array before
+    // function and bool.
+    if (hasArray) {
+      block.statements.add(buildInterceptorCheck(backend.jsArrayClass));
+    }
+    block.statements.add(js.return_(js.fieldAccess(js.use(objectName),
+                                                   'prototype')));
+
+    js.PropertyAccess name = js.fieldAccess(js.use(isolateProperties), key);
+    buffer.add(js.prettyPrint(js.assign(name, js.fun(['receiver'], block)),
+                              compiler));
+    buffer.add(N);
+  }
+
+  /**
+   * Emit all versions of the [:getInterceptor:] method.
+   */
+  void emitGetInterceptorMethods(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    // If no class needs to be intercepted, just return.
+    if (backend.objectInterceptorClass == null) return;
+    String objectName = namer.isolateAccess(backend.objectInterceptorClass);
+    var specializedGetInterceptors = backend.specializedGetInterceptors;
+    for (String name in specializedGetInterceptors.keys.toList()..sort()) {
+      Collection<ClassElement> classes = specializedGetInterceptors[name];
+      emitGetInterceptorMethod(buffer, objectName, name, classes);
+    }
+  }
+
+  void computeNeededClasses() {
+    instantiatedClasses =
+        compiler.codegenWorld.instantiatedClasses.where(computeClassFilter())
+            .toSet();
+    neededClasses = new Set<ClassElement>.from(instantiatedClasses);
+    for (ClassElement element in instantiatedClasses) {
+      for (ClassElement superclass = element.superclass;
+          superclass != null;
+          superclass = superclass.superclass) {
+        if (neededClasses.contains(superclass)) break;
+        neededClasses.add(superclass);
+      }
+    }
+  }
+
+  int _compareSelectors(Selector selector1, Selector selector2) {
+    int comparison = _compareSelectorNames(selector1, selector2);
+    if (comparison != 0) return comparison;
+
+    JavaScriptBackend backend = compiler.backend;
+    Set<ClassElement> classes1 = backend.getInterceptedClassesOn(selector1);
+    Set<ClassElement> classes2 = backend.getInterceptedClassesOn(selector2);
+    if (classes1.length != classes2.length) {
+      return classes1.length - classes2.length;
+    }
+    String getInterceptor1 =
+        namer.getInterceptorName(backend.getInterceptorMethod, classes1);
+    String getInterceptor2 =
+        namer.getInterceptorName(backend.getInterceptorMethod, classes2);
+    return Comparable.compare(getInterceptor1, getInterceptor2);
+  }
+
+  void emitOneShotInterceptors(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    for (Selector selector in
+         backend.oneShotInterceptors.toList()..sort(_compareSelectors)) {
+      Set<ClassElement> classes = backend.getInterceptedClassesOn(selector);
+      String oneShotInterceptorName = namer.oneShotInterceptorName(selector);
+      String getInterceptorName =
+          namer.getInterceptorName(backend.getInterceptorMethod, classes);
+
+      List<js.Parameter> parameters = <js.Parameter>[];
+      List<js.Expression> arguments = <js.Expression>[];
+      parameters.add(new js.Parameter('receiver'));
+      arguments.add(js.use('receiver'));
+
+      if (selector.isSetter()) {
+        parameters.add(new js.Parameter('value'));
+        arguments.add(js.use('value'));
+      } else {
+        for (int i = 0; i < selector.argumentCount; i++) {
+          String argName = 'a$i';
+          parameters.add(new js.Parameter(argName));
+          arguments.add(js.use(argName));
+        }
+      }
+
+      String invocationName = backend.namer.invocationName(selector);
+      js.Fun function =
+          new js.Fun(parameters,
+              js.block1(js.return_(
+                        js.use(isolateProperties)
+                            .dot(getInterceptorName)
+                            .callWith([js.use('receiver')])
+                            .dot(invocationName)
+                            .callWith(arguments))));
+
+      js.PropertyAccess property =
+          js.fieldAccess(js.use(isolateProperties), oneShotInterceptorName);
+
+      buffer.add(js.prettyPrint(js.assign(property, function), compiler));
+      buffer.add(N);
+    }
+  }
+
+  String assembleProgram() {
+    measure(() {
+      computeNeededClasses();
+
+      mainBuffer.add(GENERATED_BY);
+      if (!compiler.enableMinification) mainBuffer.add(HOOKS_API_USAGE);
+      mainBuffer.add('function ${namer.isolateName}()$_{}\n');
+      mainBuffer.add('init()$N$n');
+      // Shorten the code by using "$$" as temporary.
+      classesCollector = r"$$";
+      mainBuffer.add('var $classesCollector$_=$_{}$N');
+      // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary.
+      isolateProperties = namer.CURRENT_ISOLATE;
+      mainBuffer.add(
+          'var $isolateProperties$_=$_$isolatePropertiesName$N');
+      emitClasses(mainBuffer);
+      mainBuffer.add(boundClosureBuffer);
+      // Clear the buffer, so that we can reuse it for the native classes.
+      boundClosureBuffer.clear();
+      emitStaticFunctions(mainBuffer);
+      emitStaticFunctionGetters(mainBuffer);
+      // We need to finish the classes before we construct compile time
+      // constants.
+      emitFinishClassesInvocationIfNecessary(mainBuffer);
+      emitRuntimeClassesAndTests(mainBuffer);
+      emitCompileTimeConstants(mainBuffer);
+      // Static field initializations require the classes and compile-time
+      // constants to be set up.
+      emitStaticNonFinalFieldInitializations(mainBuffer);
+      emitOneShotInterceptors(mainBuffer);
+      emitGetInterceptorMethods(mainBuffer);
+      emitLazilyInitializedStaticFields(mainBuffer);
+
+      isolateProperties = isolatePropertiesName;
+      // The following code should not use the short-hand for the
+      // initialStatics.
+      mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_=${_}null$N');
+      mainBuffer.add(boundClosureBuffer);
+      emitFinishClassesInvocationIfNecessary(mainBuffer);
+      // After this assignment we will produce invalid JavaScript code if we use
+      // the classesCollector variable.
+      classesCollector = 'classesCollector should not be used from now on';
+
+      emitFinishIsolateConstructorInvocation(mainBuffer);
+      mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_='
+                     '${_}new ${namer.isolateName}()$N');
+
+      nativeEmitter.assembleCode(mainBuffer);
+      emitMain(mainBuffer);
+      mainBuffer.add('function init()$_{\n');
+      mainBuffer.add('$isolateProperties$_=$_{}$N');
+      addDefineClassAndFinishClassFunctionsIfNecessary(mainBuffer);
+      addLazyInitializerFunctionIfNecessary(mainBuffer);
+      emitFinishIsolateConstructor(mainBuffer);
+      mainBuffer.add('}\n');
+      compiler.assembledCode = mainBuffer.getText();
+
+      if (generateSourceMap) {
+        SourceFile compiledFile = new SourceFile(null, compiler.assembledCode);
+        String sourceMap = buildSourceMap(mainBuffer, compiledFile);
+        compiler.outputProvider('', 'js.map')
+            ..add(sourceMap)
+            ..close();
+      }
+    });
+    return compiler.assembledCode;
+  }
+
+  String buildSourceMap(CodeBuffer buffer, SourceFile compiledFile) {
+    SourceMapBuilder sourceMapBuilder = new SourceMapBuilder();
+    buffer.forEachSourceLocation(sourceMapBuilder.addMapping);
+    return sourceMapBuilder.build(compiledFile);
+  }
+}
+
+const String GENERATED_BY = """
+// Generated by dart2js, the Dart to JavaScript compiler.
+""";
+const String HOOKS_API_USAGE = """
+// The code supports the following hooks:
+// dartPrint(message)   - if this function is defined it is called
+//                        instead of the Dart [print] method.
+// dartMainRunner(main) - if this function is defined, the Dart [main]
+//                        method will not be invoked directly.
+//                        Instead, a closure that will invoke [main] is
+//                        passed to [dartMainRunner].
+""";
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart
new file mode 100644
index 0000000..06d5d01
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart
@@ -0,0 +1,138 @@
+// 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.
+
+part of js_backend;
+
+class CodeEmitterNoEvalTask extends CodeEmitterTask {
+  CodeEmitterNoEvalTask(Compiler compiler,
+                        Namer namer,
+                        bool generateSourceMap)
+      : super(compiler, namer, generateSourceMap);
+
+  String get generateGetterSetterFunction {
+    return """
+function() {
+  throw 'Internal Error: no dynamic generation of getters and setters allowed';
+}""";
+  }
+
+  String get defineClassFunction {
+    return """
+function(cls, constructor, prototype) {
+  constructor.prototype = prototype;
+  constructor.builtin\$cls = cls;
+  return constructor;
+}""";
+  }
+
+  String get protoSupportCheck {
+    // We don't modify the prototypes in CSP mode. Therefore we can have an
+    // easier prototype-check.
+    return 'var $supportsProtoName = !!{}.__proto__;\n';
+  }
+
+  String get finishIsolateConstructorFunction {
+    // We replace the old Isolate function with a new one that initializes
+    // all its field with the initial (and often final) value of all globals.
+    //
+    // We also copy over old values like the prototype, and the
+    // isolateProperties themselves.
+    return """
+function(oldIsolate) {
+  var isolateProperties = oldIsolate.${namer.isolatePropertiesName};
+  function Isolate() {
+    for (var staticName in isolateProperties) {
+      if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) {
+        this[staticName] = isolateProperties[staticName];
+      }
+    }
+    // Use the newly created object as prototype. In Chrome this creates a
+    // hidden class for the object and makes sure it is fast to access.
+    function ForceEfficientMap() {}
+    ForceEfficientMap.prototype = this;
+    new ForceEfficientMap;
+  }
+  Isolate.prototype = oldIsolate.prototype;
+  Isolate.prototype.constructor = Isolate;
+  Isolate.${namer.isolatePropertiesName} = isolateProperties;
+  return Isolate;
+}""";
+  }
+
+  String get lazyInitializerFunction {
+    return """
+function(prototype, staticName, fieldName, getterName, lazyValue, getter) {
+$lazyInitializerLogic
+}""";
+  }
+
+  js.Expression buildLazyInitializedGetter(VariableElement element) {
+    String isolate = namer.CURRENT_ISOLATE;
+    return js.fun([],
+        js.block1(
+            js.return_(
+                js.fieldAccess(js.use(isolate), namer.getName(element)))));
+  }
+
+  js.Expression buildConstructor(String mangledName, List<String> fieldNames) {
+    return new js.NamedFunction(
+        new js.VariableDeclaration(mangledName),
+        new js.Fun(
+            fieldNames
+                .map((fieldName) => new js.Parameter(fieldName))
+                .toList(),
+            new js.Block(
+                fieldNames.map((fieldName) =>
+                    new js.ExpressionStatement(
+                        new js.Assignment(
+                            new js.This().dot(fieldName),
+                            new js.VariableUse(fieldName))))
+                    .toList())));
+  }
+
+  void emitBoundClosureClassHeader(String mangledName,
+                                   String superName,
+                                   List<String> fieldNames,
+                                   ClassBuilder builder) {
+    builder.addProperty('', buildConstructor(mangledName, fieldNames));
+    builder.addProperty('super', js.string(superName));
+  }
+
+  void emitClassConstructor(ClassElement classElement, ClassBuilder builder) {
+    // Say we have a class A with fields b, c and d, where c needs a getter and
+    // d needs both a getter and a setter. Then we produce:
+    // - a constructor (directly into the given [buffer]):
+    //   function A(b, c, d) { this.b = b, this.c = c, this.d = d; }
+    // - getters and setters (stored in the [explicitGettersSetters] list):
+    //   get$c : function() { return this.c; }
+    //   get$d : function() { return this.d; }
+    //   set$d : function(x) { this.d = x; }
+    List<String> fields = <String>[];
+    visitClassFields(classElement, (Element member,
+                                    String name,
+                                    String accessorName,
+                                    bool needsGetter,
+                                    bool needsSetter,
+                                    bool needsCheckedSetter) {
+      fields.add(name);
+    });
+    String constructorName = namer.safeName(classElement.name.slowToString());
+
+    builder.addProperty('', buildConstructor(constructorName, fields));
+  }
+
+  void emitSuper(String superName, ClassBuilder builder) {
+    if (superName != '') {
+      builder.addProperty('super', js.string(superName));
+    }
+  }
+
+  void emitClassFields(ClassElement classElement,
+                       ClassBuilder builder,
+                       { String superClass: "",
+                         bool classIsNative: false}) {
+  }
+
+  bool get getterAndSetterCanBeImplementedByFieldSpec => false;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart
new file mode 100644
index 0000000..52e6ee0
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart
@@ -0,0 +1,33 @@
+// 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.
+
+library js_backend;
+
+import 'dart:collection' show LinkedHashMap;
+
+import '../closure.dart';
+import '../../compiler.dart' as api;
+import '../elements/elements.dart';
+import '../elements/modelx.dart' show FunctionElementX;
+import '../dart2jslib.dart' hide Selector;
+import '../dart_types.dart';
+import '../js/js.dart' as js;
+import '../native_handler.dart' as native;
+import '../source_file.dart';
+import '../source_map_builder.dart';
+import '../ssa/ssa.dart';
+import '../tree/tree.dart';
+import '../universe/universe.dart';
+import '../util/characters.dart';
+import '../util/util.dart';
+
+part 'backend.dart';
+part 'constant_emitter.dart';
+part 'constant_system_javascript.dart';
+part 'emitter.dart';
+part 'emitter_no_eval.dart';
+part 'minify_namer.dart';
+part 'namer.dart';
+part 'native_emitter.dart';
+part 'runtime_types.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart
new file mode 100644
index 0000000..98344a1
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart
@@ -0,0 +1,200 @@
+// Copyright (c) 2011, 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.
+
+part of js_backend;
+
+/**
+ * Assigns JavaScript identifiers to Dart variables, class-names and members.
+ */
+class MinifyNamer extends Namer {
+  MinifyNamer(Compiler compiler) : super(compiler) {
+    reserveBackendNames();
+  }
+
+  String get isolateName => 'I';
+  String get isolatePropertiesName => 'p';
+  bool get shouldMinify => true;
+
+  const ALPHABET_CHARACTERS = 52;  // a-zA-Z.
+  const ALPHANUMERIC_CHARACTERS = 62;  // a-zA-Z0-9.
+
+  // You can pass an invalid identifier to this and unlike its non-minifying
+  // counterpart it will never return the proposedName as the new fresh name.
+  String getFreshName(String proposedName,
+                      Set<String> usedNames,
+                      Map<String, String> suggestedNames,
+                      {bool ensureSafe: true}) {
+    var freshName;
+    var suggestion = suggestedNames[proposedName];
+    if (suggestion != null && !usedNames.contains(suggestion)) {
+      freshName = suggestion;
+    } else {
+      freshName = _getUnusedName(proposedName, usedNames);
+    }
+    usedNames.add(freshName);
+    return freshName;
+  }
+
+  SourceString getClosureVariableName(SourceString name, int id) {
+    if (id < ALPHABET_CHARACTERS) {
+      return new SourceString(new String.fromCharCodes([_letterNumber(id)]));
+    }
+    return new SourceString("${getMappedInstanceName('closure')}_$id");
+  }
+
+  void reserveBackendNames() {
+    // From issue 7554.  These should not be used on objects (as instance
+    // variables) because they clash with names from the DOM.
+    const reservedNativeProperties = const <String>[
+        'Q', 'a', 'b', 'c', 'd', 'e', 'f', 'r', 'x', 'y', 'z',
+        // 2-letter:
+        'ch', 'cx', 'cy', 'db', 'dx', 'dy', 'fr', 'fx', 'fy', 'go', 'id', 'k1',
+        'k2', 'k3', 'k4', 'r1', 'r2', 'rx', 'ry', 'x1', 'x2', 'y1', 'y2',
+        // 3-letter:
+        'add', 'all', 'alt', 'arc', 'CCW', 'cmp', 'dir', 'end', 'get', 'in1',
+        'in2', 'INT', 'key', 'log', 'low', 'm11', 'm12', 'm13', 'm14', 'm21',
+        'm22', 'm23', 'm24', 'm31', 'm32', 'm33', 'm34', 'm41', 'm42', 'm43',
+        'm44', 'max', 'min', 'now', 'ONE', 'put', 'red', 'rel', 'rev', 'RGB',
+        'sdp', 'set', 'src', 'tag', 'top', 'uid', 'uri', 'url', 'URL',
+        // 4-letter:
+        'abbr', 'atob', 'Attr', 'axes', 'axis', 'back', 'BACK', 'beta', 'bias',
+        'Blob', 'blue', 'blur', 'BLUR', 'body', 'BOOL', 'BOTH', 'btoa', 'BYTE',
+        'cite', 'clip', 'code', 'cols', 'cues', 'data', 'DECR', 'DONE', 'face',
+        'file', 'File', 'fill', 'find', 'font', 'form', 'gain', 'hash', 'head',
+        'high', 'hint', 'host', 'href', 'HRTF', 'IDLE', 'INCR', 'info', 'INIT',
+        'isId', 'item', 'KEEP', 'kind', 'knee', 'lang', 'left', 'LESS', 'line',
+        'link', 'list', 'load', 'loop', 'mode', 'name', 'Node', 'None', 'NONE',
+        'only', 'open', 'OPEN', 'ping', 'play', 'port', 'rect', 'Rect', 'refX',
+        'refY', 'RGBA', 'root', 'rows', 'save', 'seed', 'seek', 'self', 'send',
+        'show', 'SINE', 'size', 'span', 'stat', 'step', 'stop', 'tags', 'text',
+        'Text', 'time', 'type', 'view', 'warn', 'wrap', 'ZERO'];
+    for (var name in reservedNativeProperties) {
+      if (name.length < 2) {
+        instanceNameMap[name] = name;
+      }
+      usedInstanceNames.add(name);
+    }
+
+    // This list of popular instance variable names generated with:
+    // cat out.js |
+    // perl -ne '$_=~s/(?<![^a-z0-9_\$]\$)\.([a-z0-9_\$]+)/print("$1\n")/gei' |
+    // sort | uniq -c | sort -nr | head -40
+    // Removed: html, call*, hasOwnProperty.
+    _populateSuggestedNames(
+        suggestedInstanceNames,
+        usedInstanceNames,
+        const <String>[
+            r'$add', r'add$1', r'box_0', r'charCodeAt$1', r'constructor',
+            r'current', r'$defineNativeClass', r'$eq', r'$ne',
+            r'getPrototypeOf', r'hasOwnProperty', r'$index', r'$indexSet',
+            r'$isJavaScriptIndexingBehavior', r'$isolateProperties',
+            r'iterator', r'length', r'$lt', r'$gt', r'$le', r'$ge',
+            r'moveNext$0', r'node', r'on', r'prototype', r'push', r'self',
+            r'start', r'target', r'this_0', r'value', r'width', r'style']);
+
+    _populateSuggestedNames(
+        suggestedGlobalNames,
+        usedGlobalNames,
+        const <String>[
+            r'Object', r'$throw', r'$eq', r'S', r'ioore', r'UnsupportedError$',
+            r'length', r'$sub', r'getInterceptor$JSStringJSArray', r'$add',
+            r'$gt', r'$ge', r'$lt', r'$le', r'add', r'getInterceptor$JSNumber',
+            r'iterator', r'$index', r'iae', r'getInterceptor$JSArray',
+            r'ArgumentError$', r'BoundClosure', r'StateError$',
+            r'getInterceptor', r'max', r'$mul', r'List_List', r'Map_Map',
+            r'getInterceptor$JSString', r'$div', r'$indexSet',
+            r'List_List$from', r'Set_Set$from', r'toString', r'toInt', r'min',
+            r'StringBuffer_StringBuffer', r'contains1', r'WhereIterable$',
+            r'RangeError$value', r'JSString', r'JSNumber',
+            r'JSArray'
+            ]);
+  }
+
+  void _populateSuggestedNames(Map<String, String> suggestionMap,
+                               Set<String> used,
+                               List<String> suggestions) {
+    int c = $a - 1;
+    String letter;
+    for (String name in suggestions) {
+      do {
+        assert(c != $Z);
+        c = (c == $z) ? $A : c + 1;
+        letter = new String.fromCharCodes([c]);
+      } while (used.contains(letter));
+      assert(suggestionMap[name] == null);
+      suggestionMap[name] = letter;
+    }
+  }
+
+
+  // This gets a minified name based on a hash of the proposed name.  This
+  // is slightly less efficient than just getting the next name in a series,
+  // but it means that small changes in the input program will give smallish
+  // changes in the output, which can be useful for diffing etc.
+  String _getUnusedName(String proposedName, Set<String> usedNames) {
+    int hash = _calculateHash(proposedName);
+    // Avoid very small hashes that won't try many names.
+    hash = hash < 1000 ? hash * 314159 : hash;  // Yes, it's prime.
+
+    // Try other n-character names based on the hash.  We try one to three
+    // character identifiers.  For each length we try around 10 different names
+    // in a predictable order determined by the proposed name.  This is in order
+    // to make the renamer stable: small changes in the input should nornally
+    // result in relatively small changes in the output.
+    for (var n = 2; n <= 3; n++) {
+      int h = hash;
+      while (h > 10) {
+        var codes = <int>[_letterNumber(h)];
+        int h2 = h ~/ ALPHABET_CHARACTERS;
+        for (var i = 1; i < n; i++) {
+          codes.add(_alphaNumericNumber(h2));
+          h2 ~/= ALPHANUMERIC_CHARACTERS;
+        }
+        final candidate = new String.fromCharCodes(codes);
+        if (!usedNames.contains(candidate) && !jsReserved.contains(candidate)) {
+          return candidate;
+        }
+        // Try again with a slightly different hash.  After around 10 turns
+        // around this loop h is zero and we try a longer name.
+        h ~/= 7;
+      }
+    }
+
+    // If we can't find a hash based name in the three-letter space, then base
+    // the name on a letter and a counter.
+    var startLetter = new String.fromCharCodes([_letterNumber(hash)]);
+    var i = 0;
+    while (usedNames.contains("$startLetter$i")) {
+      i++;
+    }
+    return "$startLetter$i";
+  }
+
+  int _calculateHash(String name) {
+    int h = 0;
+    for (int i = 0; i < name.length; i++) {
+      h += name.charCodeAt(i);
+      h &= 0xffffffff;
+      h += h << 10;
+      h &= 0xffffffff;
+      h ^= h >> 6;
+      h &= 0xffffffff;
+    }
+    return h;
+  }
+
+  int _letterNumber(int x) {
+    if (x >= ALPHABET_CHARACTERS) x %= ALPHABET_CHARACTERS;
+    if (x < 26) return $a + x;
+    return $A + x - 26;
+  }
+
+  int _alphaNumericNumber(int x) {
+    if (x >= ALPHANUMERIC_CHARACTERS) x %= ALPHANUMERIC_CHARACTERS;
+    if (x < 26) return $a + x;
+    if (x < 52) return $A + x - 26;
+    return $0 + x - 52;
+  }
+
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart
new file mode 100644
index 0000000..e5bb967
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart
@@ -0,0 +1,754 @@
+// Copyright (c) 2011, 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.
+
+part of js_backend;
+
+/**
+ * Assigns JavaScript identifiers to Dart variables, class-names and members.
+ */
+class Namer implements ClosureNamer {
+
+  static const javaScriptKeywords = const <String>[
+    // These are current keywords.
+    "break", "delete", "function", "return", "typeof", "case", "do", "if",
+    "switch", "var", "catch", "else", "in", "this", "void", "continue",
+    "false", "instanceof", "throw", "while", "debugger", "finally", "new",
+    "true", "with", "default", "for", "null", "try",
+
+    // These are future keywords.
+    "abstract", "double", "goto", "native", "static", "boolean", "enum",
+    "implements", "package", "super", "byte", "export", "import", "private",
+    "synchronized", "char", "extends", "int", "protected", "throws",
+    "class", "final", "interface", "public", "transient", "const", "float",
+    "long", "short", "volatile"
+  ];
+
+  static const reservedPropertySymbols =
+      const <String>["__proto__", "prototype", "constructor", "call"];
+
+  // Symbols that we might be using in our JS snippets.
+  static const reservedGlobalSymbols = const <String>[
+    // Section references are from Ecma-262
+    // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)
+
+    // 15.1.1 Value Properties of the Global Object
+    "NaN", "Infinity", "undefined",
+
+    // 15.1.2 Function Properties of the Global Object
+    "eval", "parseInt", "parseFloat", "isNaN", "isFinite",
+
+    // 15.1.3 URI Handling Function Properties
+    "decodeURI", "decodeURIComponent",
+    "encodeURI",
+    "encodeURIComponent",
+
+    // 15.1.4 Constructor Properties of the Global Object
+    "Object", "Function", "Array", "String", "Boolean", "Number", "Date",
+    "RegExp", "Error", "EvalError", "RangeError", "ReferenceError",
+    "SyntaxError", "TypeError", "URIError",
+
+    // 15.1.5 Other Properties of the Global Object
+    "Math",
+
+    // 10.1.6 Activation Object
+    "arguments",
+
+    // B.2 Additional Properties (non-normative)
+    "escape", "unescape",
+
+    // Window props (https://developer.mozilla.org/en/DOM/window)
+    "applicationCache", "closed", "Components", "content", "controllers",
+    "crypto", "defaultStatus", "dialogArguments", "directories",
+    "document", "frameElement", "frames", "fullScreen", "globalStorage",
+    "history", "innerHeight", "innerWidth", "length",
+    "location", "locationbar", "localStorage", "menubar",
+    "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel",
+    "name", "navigator", "opener", "outerHeight", "outerWidth",
+    "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11",
+    "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY",
+    "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar",
+    "top", "window",
+
+    // Window methods (https://developer.mozilla.org/en/DOM/window)
+    "alert", "addEventListener", "atob", "back", "blur", "btoa",
+    "captureEvents", "clearInterval", "clearTimeout", "close", "confirm",
+    "disableExternalCapture", "dispatchEvent", "dump",
+    "enableExternalCapture", "escape", "find", "focus", "forward",
+    "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount",
+    "getComputedStyle", "getSelection", "home", "maximize", "minimize",
+    "moveBy", "moveTo", "open", "openDialog", "postMessage", "print",
+    "prompt", "QueryInterface", "releaseEvents", "removeEventListener",
+    "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy",
+    "scrollByLines", "scrollByPages", "scrollTo", "setInterval",
+    "setResizeable", "setTimeout", "showModalDialog", "sizeToContent",
+    "stop", "uuescape", "updateCommands", "XPCNativeWrapper",
+    "XPCSafeJSOjbectWrapper",
+
+    // Mozilla Window event handlers, same cite
+    "onabort", "onbeforeunload", "onchange", "onclick", "onclose",
+    "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange",
+    "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown",
+    "onmousemove", "onmouseout", "onmouseover", "onmouseup",
+    "onmozorientation", "onpaint", "onreset", "onresize", "onscroll",
+    "onselect", "onsubmit", "onunload",
+
+    // Safari Web Content Guide
+    // http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/SafariWebContent.pdf
+    // WebKit Window member data, from WebKit DOM Reference
+    // (http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html)
+    "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart",
+    "ongesturestart", "ongesturechange", "ongestureend",
+
+    // extra window methods
+    "uneval",
+
+    // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7,
+    // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1
+    "getPrototypeOf", "let", "yield",
+
+    // "future reserved words"
+    "abstract", "int", "short", "boolean", "interface", "static", "byte",
+    "long", "char", "final", "native", "synchronized", "float", "package",
+    "throws", "goto", "private", "transient", "implements", "protected",
+    "volatile", "double", "public",
+
+    // IE methods
+    // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#)
+    "attachEvent", "clientInformation", "clipboardData", "createPopup",
+    "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth",
+    "onafterprint", "onbeforedeactivate", "onbeforeprint",
+    "oncontrolselect", "ondeactivate", "onhelp", "onresizeend",
+
+    // Common browser-defined identifiers not defined in ECMAScript
+    "event", "external", "Debug", "Enumerator", "Global", "Image",
+    "ActiveXObject", "VBArray", "Components",
+
+    // Functions commonly defined on Object
+    "toString", "getClass", "constructor", "prototype", "valueOf",
+
+    // Client-side JavaScript identifiers
+    "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient",
+    "CanvasPattern", "CanvasRenderingContext2D", "CDATASection",
+    "CharacterData", "Comment", "CSS2Properties", "CSSRule",
+    "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType",
+    "DOMException", "DOMImplementation", "DOMParser", "Element", "Event",
+    "ExternalInterface", "FlashPlayer", "Form", "Frame", "History",
+    "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image",
+    "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType",
+    "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin",
+    "ProcessingInstruction", "Range", "RangeException", "Screen", "Select",
+    "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea",
+    "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer",
+    "XPathException", "XPathResult", "XSLTProcessor",
+
+    // These keywords trigger the loading of the java-plugin. For the
+    // next-generation plugin, this results in starting a new Java process.
+    "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass",
+    "JavaArray", "JavaMember"
+  ];
+
+  Set<String> _jsReserved = null;
+  /// Names that cannot be used by members, top level and static
+  /// methods.
+  Set<String> get jsReserved {
+    if (_jsReserved == null) {
+      _jsReserved = new Set<String>();
+      _jsReserved.addAll(javaScriptKeywords);
+      _jsReserved.addAll(reservedPropertySymbols);
+    }
+    return _jsReserved;
+  }
+
+  Set<String> _jsVariableReserved = null;
+  /// Names that cannot be used by local variables and parameters.
+  Set<String> get jsVariableReserved {
+    if (_jsVariableReserved == null) {
+      _jsVariableReserved = new Set<String>();
+      _jsVariableReserved.addAll(javaScriptKeywords);
+      _jsVariableReserved.addAll(reservedPropertySymbols);
+      _jsVariableReserved.addAll(reservedGlobalSymbols);
+    }
+    return _jsVariableReserved;
+  }
+
+  final String CURRENT_ISOLATE = r'$';
+
+  /**
+   * Map from top-level or static elements to their unique identifiers provided
+   * by [getName].
+   *
+   * Invariant: Keys must be declaration elements.
+   */
+  final Compiler compiler;
+  final Map<Element, String> globals;
+  final Map<Selector, String> oneShotInterceptorNames;
+  final Map<String, LibraryElement> shortPrivateNameOwners;
+
+  final Set<String> usedGlobalNames;
+  final Set<String> usedInstanceNames;
+  final Map<String, String> globalNameMap;
+  final Map<String, String> suggestedGlobalNames;
+  final Map<String, String> instanceNameMap;
+  final Map<String, String> suggestedInstanceNames;
+      
+  final Map<String, String> operatorNameMap;
+  final Map<String, int> popularNameCounters;
+
+  final Map<Element, String> bailoutNames;
+
+  final Map<Constant, String> constantNames;
+
+  Namer(this.compiler)
+      : globals = new Map<Element, String>(),
+        oneShotInterceptorNames = new Map<Selector, String>(),
+        shortPrivateNameOwners = new Map<String, LibraryElement>(),
+        bailoutNames = new Map<Element, String>(),
+        usedGlobalNames = new Set<String>(),
+        usedInstanceNames = new Set<String>(),
+        instanceNameMap = new Map<String, String>(),
+        operatorNameMap = new Map<String, String>(),
+        globalNameMap = new Map<String, String>(),
+        suggestedGlobalNames = new Map<String, String>(),
+        suggestedInstanceNames = new Map<String, String>(),
+        constantNames = new Map<Constant, String>(),
+        popularNameCounters = new Map<String, int>();
+
+  String get isolateName => 'Isolate';
+  String get isolatePropertiesName => r'$isolateProperties';
+  /**
+   * Some closures must contain their name. The name is stored in
+   * [STATIC_CLOSURE_NAME_NAME].
+   */
+  String get STATIC_CLOSURE_NAME_NAME => r'$name';
+  SourceString get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME;
+  bool get shouldMinify => false;
+
+  bool isReserved(String name) => name == isolateName;
+
+  String constantName(Constant constant) {
+    // In the current implementation it doesn't make sense to give names to
+    // function constants since the function-implementation itself serves as
+    // constant and can be accessed directly.
+    assert(!constant.isFunction());
+    String result = constantNames[constant];
+    if (result == null) {
+      String longName;
+      if (shouldMinify) {
+        if (constant.isString()) {
+          StringConstant stringConstant = constant;
+          // The minifier always constructs a new name, using the argument as
+          // input to its hashing algorithm.  The given name does not need to be
+          // valid.
+          longName = stringConstant.value.slowToString();
+        } else {
+          longName = "C";
+        }
+      } else {
+        longName = "CONSTANT";
+      }
+      result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames,
+                            ensureSafe: true);
+      constantNames[constant] = result;
+    }
+    return result;
+  }
+
+  String breakLabelName(LabelElement label) {
+    return '\$${label.labelName}\$${label.target.nestingLevel}';
+  }
+
+  String implicitBreakLabelName(TargetElement target) {
+    return '\$${target.nestingLevel}';
+  }
+
+  // We sometimes handle continue targets differently from break targets,
+  // so we have special continue-only labels.
+  String continueLabelName(LabelElement label) {
+    return 'c\$${label.labelName}\$${label.target.nestingLevel}';
+  }
+
+  String implicitContinueLabelName(TargetElement target) {
+    return 'c\$${target.nestingLevel}';
+  }
+
+  /**
+   * If the [name] is not private returns [:name.slowToString():]. Otherwise
+   * mangles the [name] so that each library has a unique name.
+   */
+  String privateName(LibraryElement library, SourceString name) {
+    // Public names are easy.
+    String nameString = name.slowToString();
+    if (!name.isPrivate()) return nameString;
+
+    // The first library asking for a short private name wins.
+    LibraryElement owner = shouldMinify
+        ? library
+        : shortPrivateNameOwners.putIfAbsent(nameString, () => library);
+
+    // If a private name could clash with a mangled private name we don't
+    // use the short name. For example a private name "_lib3_foo" would
+    // clash with "_foo" from "lib3".
+    if (owner == library &&
+        !nameString.startsWith('_$LIBRARY_PREFIX') &&
+        !shouldMinify) {
+      return nameString;
+    }
+
+    // If a library name does not start with the [LIBRARY_PREFIX] then our
+    // assumptions about clashing with mangled private members do not hold.
+    String libraryName = getName(library);
+    assert(shouldMinify || libraryName.startsWith(LIBRARY_PREFIX));
+    // TODO(erikcorry): Fix this with other manglings to avoid clashes.
+    return '_lib$libraryName\$$nameString';
+  }
+
+  String instanceMethodName(FunctionElement element) {
+    SourceString elementName = element.name;
+    SourceString name = operatorNameToIdentifier(elementName);
+    if (name != elementName) return getMappedOperatorName(name.slowToString());
+
+    LibraryElement library = element.getLibrary();
+    if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
+      ConstructorBodyElement bodyElement = element;
+      name = bodyElement.constructor.name;
+    }
+    FunctionSignature signature = element.computeSignature(compiler);
+    String methodName =
+        '${privateName(library, name)}\$${signature.parameterCount}';
+    if (signature.optionalParametersAreNamed &&
+        !signature.optionalParameters.isEmpty) {
+      StringBuffer buffer = new StringBuffer();
+      signature.orderedOptionalParameters.forEach((Element element) {
+        buffer.add('\$${safeName(element.name.slowToString())}');
+      });
+      methodName = '$methodName$buffer';
+    }
+    if (name == closureInvocationSelectorName) return methodName;
+    return getMappedInstanceName(methodName);
+  }
+
+  String publicInstanceMethodNameByArity(SourceString name, int arity) {
+    SourceString newName = operatorNameToIdentifier(name);
+    if (newName != name) return getMappedOperatorName(newName.slowToString());
+    assert(!name.isPrivate());
+    var base = name.slowToString();
+    // We don't mangle the closure invoking function name because it
+    // is generated by string concatenation in applyFunction from
+    // js_helper.dart.
+    var proposedName = '$base\$$arity';
+    if (name == closureInvocationSelectorName) return proposedName;
+    return getMappedInstanceName(proposedName);
+  }
+
+  String invocationName(Selector selector) {
+    if (selector.isGetter()) {
+      String proposedName = privateName(selector.library, selector.name);
+      return 'get\$${getMappedInstanceName(proposedName)}';
+    } else if (selector.isSetter()) {
+      String proposedName = privateName(selector.library, selector.name);
+      return 'set\$${getMappedInstanceName(proposedName)}';
+    } else {
+      SourceString name = selector.name;
+      if (selector.kind == SelectorKind.OPERATOR
+          || selector.kind == SelectorKind.INDEX) {
+        name = operatorNameToIdentifier(name);
+        assert(name != selector.name);
+        return getMappedOperatorName(name.slowToString());
+      }
+      assert(name == operatorNameToIdentifier(name));
+      StringBuffer buffer = new StringBuffer();
+      for (SourceString argumentName in selector.getOrderedNamedArguments()) {
+        buffer.add(r'$');
+        argumentName.printOn(buffer);
+      }
+      String suffix = '\$${selector.argumentCount}$buffer';
+      // We don't mangle the closure invoking function name because it
+      // is generated by string concatenation in applyFunction from
+      // js_helper.dart.
+      if (selector.isClosureCall()) {
+        return "${name.slowToString()}$suffix";
+      } else {
+        String proposedName = privateName(selector.library, name);
+        return getMappedInstanceName('$proposedName$suffix');
+      }
+    }
+  }
+
+  /**
+   * Returns the internal name used for an invocation mirror of this selector.
+   */
+  String invocationMirrorInternalName(Selector selector)
+      => invocationName(selector);
+
+  String instanceFieldName(Element element) {
+    String proposedName = privateName(element.getLibrary(), element.name);
+    return getMappedInstanceName(proposedName);
+  }
+
+  // Construct a new name for the element based on the library and class it is
+  // in.  The name here is not important, we just need to make sure it is
+  // unique.  If we are minifying, we actually construct the name from the
+  // minified versions of the class and instance names, but the result is
+  // minified once again, so that is not visible in the end result.
+  String shadowedFieldName(Element fieldElement) {
+    // Check for following situation: Native field ${fieldElement.name} has
+    // fixed JSName ${fieldElement.nativeName()}, but a subclass shadows this
+    // name.  We normally handle that by renaming the superclass field, but we
+    // can't do that because native fields have fixed JavaScript names.
+    // In practice this can't happen because we can't inherit from native
+    // classes.
+    assert (!fieldElement.hasFixedBackendName());
+
+    String libraryName = getName(fieldElement.getLibrary());
+    String className = getName(fieldElement.getEnclosingClass());
+    String instanceName = instanceFieldName(fieldElement);
+    return getMappedInstanceName('$libraryName\$$className\$$instanceName');
+  }
+
+  String setterName(Element element) {
+    // We dynamically create setters from the field-name. The setter name must
+    // therefore be derived from the instance field-name.
+    LibraryElement library = element.getLibrary();
+    String name = getMappedInstanceName(privateName(library, element.name));
+    return 'set\$$name';
+  }
+
+  String setterNameFromAccessorName(String name) {
+    // We dynamically create setters from the field-name. The setter name must
+    // therefore be derived from the instance field-name.
+    return 'set\$$name';
+  }
+
+  String publicGetterName(SourceString name) {
+    // We dynamically create getters from the field-name. The getter name must
+    // therefore be derived from the instance field-name.
+    String fieldName = getMappedInstanceName(name.slowToString());
+    return 'get\$$fieldName';
+  }
+
+  String getterNameFromAccessorName(String name) {
+    // We dynamically create getters from the field-name. The getter name must
+    // therefore be derived from the instance field-name.
+    return 'get\$$name';
+  }
+
+  String getterName(Element element) {
+    // We dynamically create getters from the field-name. The getter name must
+    // therefore be derived from the instance field-name.
+    LibraryElement library = element.getLibrary();
+    String name = getMappedInstanceName(privateName(library, element.name));
+    return 'get\$$name';
+  }
+
+  String getMappedGlobalName(String proposedName) {
+    var newName = globalNameMap[proposedName];
+    if (newName == null) {
+      newName = getFreshName(proposedName, usedGlobalNames,
+                             suggestedGlobalNames, ensureSafe: true);
+      globalNameMap[proposedName] = newName;
+    }
+    return newName;
+  }
+
+  String getMappedInstanceName(String proposedName) {
+    var newName = instanceNameMap[proposedName];
+    if (newName == null) {
+      newName = getFreshName(proposedName, usedInstanceNames,
+                             suggestedInstanceNames, ensureSafe: true);
+      instanceNameMap[proposedName] = newName;
+    }
+    return newName;
+  }
+
+  String getMappedOperatorName(String proposedName) {
+    var newName = operatorNameMap[proposedName];
+    if (newName == null) {
+      newName = getFreshName(proposedName, usedInstanceNames,
+                             suggestedInstanceNames, ensureSafe: false);
+      operatorNameMap[proposedName] = newName;
+    }
+    return newName;
+  }
+
+  String getFreshName(String proposedName,
+                      Set<String> usedNames,
+                      Map<String, String> suggestedNames,
+                      {bool ensureSafe: true}) {
+    var candidate;
+    if (ensureSafe) {
+      proposedName = safeName(proposedName);
+    }
+    assert(!jsReserved.contains(proposedName));
+    if (!usedNames.contains(proposedName)) {
+      candidate = proposedName;
+    } else {
+      var counter = popularNameCounters[proposedName];
+      var i = counter == null ? 0 : counter;
+      while (usedNames.contains("$proposedName$i")) {
+        i++;
+      }
+      popularNameCounters[proposedName] = i + 1;
+      candidate = "$proposedName$i";
+    }
+    usedNames.add(candidate);
+    return candidate;
+  }
+
+  SourceString getClosureVariableName(SourceString name, int id) {
+    return new SourceString("${name.slowToString()}_$id");
+  }
+
+  static const String LIBRARY_PREFIX = "lib";
+
+  /**
+   * Returns a preferred JS-id for the given top-level or static element.
+   * The returned id is guaranteed to be a valid JS-id.
+   */
+  String _computeGuess(Element element) {
+    assert(!element.isInstanceMember());
+    String name;
+    if (element.isGenerativeConstructor()) {
+      if (element.name == element.getEnclosingClass().name) {
+        // Keep the class name for the class and not the factory.
+        name = "${element.name.slowToString()}\$";
+      } else {
+        name = element.name.slowToString();
+      }
+    } else if (Elements.isStaticOrTopLevel(element)) {
+      if (element.isMember()) {
+        ClassElement enclosingClass = element.getEnclosingClass();
+        name = "${enclosingClass.name.slowToString()}_"
+               "${element.name.slowToString()}";
+      } else {
+        name = element.name.slowToString();
+      }
+    } else if (element.isLibrary()) {
+      name = LIBRARY_PREFIX;
+    } else {
+      name = element.name.slowToString();
+    }
+    return name;
+  }
+
+  String getInterceptorName(Element element, Collection<ClassElement> classes) {
+    if (classes.contains(compiler.objectClass)) {
+      // If the object class is in the set of intercepted classes, we
+      // need to go through the generic getInterceptorMethod.
+      return getName(element);
+    }
+    // Use the unminified names here to construct the interceptor names.  This
+    // helps ensure that they don't all suddenly change names due to a name
+    // clash in the minifier, which would affect the diff size.
+    StringBuffer buffer = new StringBuffer('${element.name.slowToString()}\$');
+    for (ClassElement cls in classes) {
+      buffer.add(cls.name.slowToString());
+    }
+    return getMappedGlobalName(buffer.toString());
+  }
+
+  String getBailoutName(Element element) {
+    String name = bailoutNames[element];
+    if (name != null) return name;
+    bool global = !element.isInstanceMember();
+    // Despite the name of the variable, this gets the minified name when we
+    // are minifying, but it doesn't really make much difference.  The
+    // important thing is that it is a unique name.  We add $bailout and, if we
+    // are minifying, we minify the minified name and '$bailout'.
+    String unminifiedName = '${getName(element)}\$bailout';
+    if (global) {
+      name = getMappedGlobalName(unminifiedName);
+    } else {
+      // Make sure two bailout methods on the same inheritance chain do not have
+      // the same name to prevent a subclass bailout method being accidentally
+      // called from the superclass main method.  Use the count of the number of
+      // elements with the same name on the superclass chain to disambiguate
+      // based on 'level'.
+      int level = 0;
+      ClassElement classElement = element.getEnclosingClass().superclass;
+      while (classElement != null) {
+        if (classElement.localLookup(element.name) != null) level++;
+        classElement = classElement.superclass;
+      }
+      name = unminifiedName;
+      if (level != 0) {
+        name = '$unminifiedName$level';
+      }
+      name = getMappedInstanceName(name);
+    }
+    bailoutNames[element] = name;
+    return name;
+  }
+
+  /**
+   * Returns a preferred JS-id for the given element. The returned id is
+   * guaranteed to be a valid JS-id. Globals and static fields are furthermore
+   * guaranteed to be unique.
+   *
+   * For accessing statics consider calling
+   * [isolateAccess]/[isolateBailoutAccess] or [isolatePropertyAccess] instead.
+   */
+  String getName(Element element) {
+    if (element.isInstanceMember()) {
+      if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY
+          || element.kind == ElementKind.FUNCTION) {
+        return instanceMethodName(element);
+      } else if (element.kind == ElementKind.GETTER) {
+        return getterName(element);
+      } else if (element.kind == ElementKind.SETTER) {
+        return setterName(element);
+      } else if (element.kind == ElementKind.FIELD) {
+        return instanceFieldName(element);
+      } else {
+        compiler.internalError('getName for bad kind: ${element.kind}',
+                               node: element.parseNode(compiler));
+      }
+    } else {
+      // Use declaration element to ensure invariant on [globals].
+      element = element.declaration;
+      // Dealing with a top-level or static element.
+      String cached = globals[element];
+      if (cached != null) return cached;
+
+      String guess = _computeGuess(element);
+      ElementKind kind = element.kind;
+      if (kind == ElementKind.VARIABLE ||
+          kind == ElementKind.PARAMETER) {
+        // The name is not guaranteed to be unique.
+        return safeName(guess);
+      }
+      if (kind == ElementKind.GENERATIVE_CONSTRUCTOR ||
+          kind == ElementKind.FUNCTION ||
+          kind == ElementKind.CLASS ||
+          kind == ElementKind.FIELD ||
+          kind == ElementKind.GETTER ||
+          kind == ElementKind.SETTER ||
+          kind == ElementKind.TYPEDEF ||
+          kind == ElementKind.LIBRARY ||
+          kind == ElementKind.MALFORMED_TYPE) {
+        bool fixedName = false;
+        if (kind == ElementKind.CLASS) {
+          ClassElement classElement = element;
+        }
+        if (Elements.isInstanceField(element)) {
+          fixedName = element.hasFixedBackendName();
+        }
+        String result = fixedName
+            ? guess
+            : getFreshName(guess, usedGlobalNames, suggestedGlobalNames,
+                           ensureSafe: true);
+        globals[element] = result;
+        return result;
+      }
+      compiler.internalError('getName for unknown kind: ${element.kind}',
+                              node: element.parseNode(compiler));
+    }
+  }
+
+  String getLazyInitializerName(Element element) {
+    assert(Elements.isStaticOrTopLevelField(element));
+    return getMappedGlobalName("get\$${getName(element)}");
+  }
+
+  String isolatePropertiesAccess(Element element) {
+    return "$isolateName.$isolatePropertiesName.${getName(element)}";
+  }
+
+  String isolateAccess(Element element) {
+    return "$CURRENT_ISOLATE.${getName(element)}";
+  }
+
+  String isolateBailoutAccess(Element element) {
+    String newName = getMappedGlobalName('${getName(element)}\$bailout');
+    return '$CURRENT_ISOLATE.$newName';
+  }
+
+  String isolateLazyInitializerAccess(Element element) {
+    return "$CURRENT_ISOLATE.${getLazyInitializerName(element)}";
+  }
+
+  String operatorIsPrefix() => r'$is';
+
+  String operatorIs(Element element) {
+    // TODO(erikcorry): Reduce from $isx to ix when we are minifying.
+    return '${operatorIsPrefix()}${getName(element)}';
+  }
+
+  /*
+   * Returns a name that does not clash with reserved JS keywords,
+   * and also ensures it won't clash with other identifiers.
+   */
+  String _safeName(String name, Set<String> reserved) {
+    if (reserved.contains(name) || name.startsWith(r'$')) {
+      name = '\$$name';
+    }
+    assert(!reserved.contains(name));
+    return name;
+  }
+
+  String safeName(String name) => _safeName(name, jsReserved);
+  String safeVariableName(String name) => _safeName(name, jsVariableReserved);
+
+  String oneShotInterceptorName(Selector selector) {
+    // TODO(ngeoffray): What to do about typed selectors? We could
+    // filter them out, or keep them and hope the generated one shot
+    // interceptor takes advantage of the type.
+    String cached = oneShotInterceptorNames[selector];
+    if (cached != null) return cached;
+    SourceString name = operatorNameToIdentifier(selector.name);
+    String result = getFreshName(name.slowToString(), usedGlobalNames,
+                                 suggestedGlobalNames);
+    oneShotInterceptorNames[selector] = result;
+    return result;
+  }
+
+  SourceString operatorNameToIdentifier(SourceString name) {
+    if (name == null) return null;
+    String value = name.stringValue;
+    if (value == null) {
+      return name;
+    } else if (value == '==') {
+      return const SourceString(r'$eq');
+    } else if (value == '~') {
+      return const SourceString(r'$not');
+    } else if (value == '[]') {
+      return const SourceString(r'$index');
+    } else if (value == '[]=') {
+      return const SourceString(r'$indexSet');
+    } else if (value == '*') {
+      return const SourceString(r'$mul');
+    } else if (value == '/') {
+      return const SourceString(r'$div');
+    } else if (value == '%') {
+      return const SourceString(r'$mod');
+    } else if (value == '~/') {
+      return const SourceString(r'$tdiv');
+    } else if (value == '+') {
+      return const SourceString(r'$add');
+    } else if (value == '<<') {
+      return const SourceString(r'$shl');
+    } else if (value == '>>') {
+      return const SourceString(r'$shr');
+    } else if (value == '>=') {
+      return const SourceString(r'$ge');
+    } else if (value == '>') {
+      return const SourceString(r'$gt');
+    } else if (value == '<=') {
+      return const SourceString(r'$le');
+    } else if (value == '<') {
+      return const SourceString(r'$lt');
+    } else if (value == '&') {
+      return const SourceString(r'$and');
+    } else if (value == '^') {
+      return const SourceString(r'$xor');
+    } else if (value == '|') {
+      return const SourceString(r'$or');
+    } else if (value == '-') {
+      return const SourceString(r'$sub');
+    } else if (value == 'unary-') {
+      return const SourceString(r'$negate');
+    } else {
+      return name;
+    }
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart
new file mode 100644
index 0000000..9400f5f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart
@@ -0,0 +1,546 @@
+// 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.
+
+part of js_backend;
+
+class NativeEmitter {
+
+  CodeEmitterTask emitter;
+  CodeBuffer nativeBuffer;
+
+  // Classes that participate in dynamic dispatch. These are the
+  // classes that contain used members.
+  Set<ClassElement> classesWithDynamicDispatch;
+
+  // Native classes found in the application.
+  Set<ClassElement> nativeClasses;
+
+  // Caches the native subtypes of a native class.
+  Map<ClassElement, List<ClassElement>> subtypes;
+
+  // Caches the direct native subtypes of a native class.
+  Map<ClassElement, List<ClassElement>> directSubtypes;
+
+  // Caches the native methods that are overridden by a native class.
+  // Note that the method that overrides does not have to be native:
+  // it's the overridden method that must make sure it will dispatch
+  // to its subclass if it sees an instance whose class is a subclass.
+  Set<FunctionElement> overriddenMethods;
+
+  // Caches the methods that have a native body.
+  Set<FunctionElement> nativeMethods;
+
+  // Do we need the native emitter to take care of handling
+  // noSuchMethod for us? This flag is set to true in the emitter if
+  // it finds any native class that needs noSuchMethod handling.
+  bool handleNoSuchMethod = false;
+
+  NativeEmitter(this.emitter)
+      : classesWithDynamicDispatch = new Set<ClassElement>(),
+        nativeClasses = new Set<ClassElement>(),
+        subtypes = new Map<ClassElement, List<ClassElement>>(),
+        directSubtypes = new Map<ClassElement, List<ClassElement>>(),
+        overriddenMethods = new Set<FunctionElement>(),
+        nativeMethods = new Set<FunctionElement>(),
+        nativeBuffer = new CodeBuffer();
+
+  Compiler get compiler => emitter.compiler;
+  JavaScriptBackend get backend => compiler.backend;
+
+  String get _ => emitter._;
+  String get n => emitter.n;
+  String get N => emitter.N;
+
+  String get dynamicName {
+    Element element = compiler.findHelper(
+        const SourceString('dynamicFunction'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get dynamicSetMetadataName {
+    Element element = compiler.findHelper(
+        const SourceString('dynamicSetMetadata'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get typeNameOfName {
+    Element element = compiler.findHelper(
+        const SourceString('getTypeNameOf'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get defPropName {
+    Element element = compiler.findHelper(
+        const SourceString('defineProperty'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get toStringHelperName {
+    Element element = compiler.findHelper(
+        const SourceString('toStringForNativeObject'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get hashCodeHelperName {
+    Element element = compiler.findHelper(
+        const SourceString('hashCodeForNativeObject'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get defineNativeClassName
+      => '${backend.namer.CURRENT_ISOLATE}.\$defineNativeClass';
+
+  String get defineNativeClassFunction {
+    return """
+function(cls, desc) {
+  var fields = desc[''];
+  var fields_array = fields ? fields.split(',') : [];
+  for (var i = 0; i < fields_array.length; i++) {
+    ${emitter.currentGenerateAccessorName}(fields_array[i], desc);
+  }
+  var hasOwnProperty = Object.prototype.hasOwnProperty;
+  for (var method in desc) {
+    if (method) {
+      if (hasOwnProperty.call(desc, method)) {
+        $dynamicName(method)[cls] = desc[method];
+      }
+    }
+  }
+}""";
+  }
+
+  bool isNativeGlobal(String quotedName) {
+    return identical(quotedName[1], '@');
+  }
+
+  String toNativeTag(ClassElement cls) {
+    String quotedName = cls.nativeTagInfo.slowToString();
+    if (isNativeGlobal(quotedName)) {
+      // Global object, just be like the other types for now.
+      return quotedName.substring(3, quotedName.length - 1);
+    } else {
+      return quotedName.substring(2, quotedName.length - 1);
+    }
+  }
+
+  void generateNativeClass(ClassElement classElement) {
+    assert(!classElement.hasBackendMembers);
+    nativeClasses.add(classElement);
+
+    ClassBuilder builder = new ClassBuilder();
+    emitter.emitClassFields(classElement, builder, classIsNative: true);
+    emitter.emitClassGettersSetters(classElement, builder);
+    emitter.emitInstanceMembers(classElement, builder);
+
+    // An empty native class may be omitted since the superclass methods can be
+    // located via the dispatch metadata.
+    if (builder.properties.isEmpty) return;
+
+    String nativeTag = toNativeTag(classElement);
+    js.Expression definition =
+        js.call(js.use(defineNativeClassName),
+                [js.string(nativeTag), builder.toObjectInitializer()]);
+
+    nativeBuffer.add(js.prettyPrint(definition, compiler));
+    nativeBuffer.add('$N$n');
+
+    classesWithDynamicDispatch.add(classElement);
+  }
+
+  List<ClassElement> getDirectSubclasses(ClassElement cls) {
+    List<ClassElement> result = directSubtypes[cls];
+    return result == null ? const<ClassElement>[] : result;
+  }
+
+  void potentiallyConvertDartClosuresToJs(List<js.Statement> statements,
+                                          FunctionElement member,
+                                          List<js.Parameter> stubParameters) {
+    FunctionSignature parameters = member.computeSignature(compiler);
+    Element converter =
+        compiler.findHelper(const SourceString('convertDartClosureToJS'));
+    String closureConverter = backend.namer.isolateAccess(converter);
+    Set<String> stubParameterNames = new Set<String>.from(
+        stubParameters.map((param) => param.name));
+    parameters.forEachParameter((Element parameter) {
+      String name = parameter.name.slowToString();
+      // If [name] is not in [stubParameters], then the parameter is an optional
+      // parameter that was not provided for this stub.
+      for (js.Parameter stubParameter in stubParameters) {
+        if (stubParameter.name == name) {
+          DartType type = parameter.computeType(compiler).unalias(compiler);
+          if (type is FunctionType) {
+            // The parameter type is a function type either directly or through
+            // typedef(s).
+            int arity = type.computeArity();
+
+            statements.add(
+                new js.ExpressionStatement(
+                    js.assign(
+                        js.use(name),
+                        js.use(closureConverter).callWith(
+                            [js.use(name), new js.LiteralNumber('$arity')]))));
+            break;
+          }
+        }
+      }
+    });
+  }
+
+  List<js.Statement> generateParameterStubStatements(
+      Element member,
+      String invocationName,
+      List<js.Parameter> stubParameters,
+      List<js.Expression> argumentsBuffer,
+      int indexOfLastOptionalArgumentInParameters) {
+    // The target JS function may check arguments.length so we need to
+    // make sure not to pass any unspecified optional arguments to it.
+    // For example, for the following Dart method:
+    //   foo([x, y, z]);
+    // The call:
+    //   foo(y: 1)
+    // must be turned into a JS call to:
+    //   foo(null, y).
+
+    ClassElement classElement = member.enclosingElement;
+    String nativeTagInfo = classElement.nativeTagInfo.slowToString();
+
+    List<js.Statement> statements = <js.Statement>[];
+    potentiallyConvertDartClosuresToJs(statements, member, stubParameters);
+
+    String target;
+    List<js.Expression> arguments;
+
+    if (!nativeMethods.contains(member)) {
+      // When calling a method that has a native body, we call it with our
+      // calling conventions.
+      target = backend.namer.getName(member);
+      arguments = argumentsBuffer;
+    } else {
+      // When calling a JS method, we call it with the native name, and only the
+      // arguments up until the last one provided.
+      target = member.fixedBackendName();
+      arguments = argumentsBuffer.getRange(
+          0, indexOfLastOptionalArgumentInParameters + 1);
+    }
+    statements.add(
+        new js.Return(
+            new js.VariableUse('this').dot(target).callWith(arguments)));
+
+    if (!overriddenMethods.contains(member)) {
+      // Call the method directly.
+      return statements;
+    } else {
+      return <js.Statement>[
+          generateMethodBodyWithPrototypeCheck(
+              invocationName, new js.Block(statements), stubParameters)];
+    }
+  }
+
+  // If a method is overridden, we must check if the prototype of 'this' has the
+  // method available. Otherwise, we may end up calling the method from the
+  // super class. If the method is not available, we make a direct call to
+  // Object.prototype.$methodName.  This method will patch the prototype of
+  // 'this' to the real method.
+  js.Statement generateMethodBodyWithPrototypeCheck(
+      String methodName,
+      js.Statement body,
+      List<js.Parameter> parameters) {
+    return js.if_(
+        js.use('Object').dot('getPrototypeOf')
+            .callWith([js.use('this')])
+            .dot('hasOwnProperty').callWith([js.string(methodName)]),
+        body,
+        js.return_(
+            js.use('Object').dot('prototype').dot(methodName).dot('call')
+            .callWith(
+                <js.Expression>[js.use('this')]..addAll(
+                    parameters.map((param) => js.use(param.name))))));
+  }
+
+  js.Block generateMethodBodyWithPrototypeCheckForElement(
+      FunctionElement element,
+      js.Block body,
+      List<js.Parameter> parameters) {
+    ElementKind kind = element.kind;
+    if (kind != ElementKind.FUNCTION &&
+        kind != ElementKind.GETTER &&
+        kind != ElementKind.SETTER) {
+      compiler.internalError("unexpected kind: '$kind'", element: element);
+    }
+
+    String methodName = backend.namer.getName(element);
+    return new js.Block(
+        [generateMethodBodyWithPrototypeCheck(methodName, body, parameters)]);
+  }
+
+
+  void emitDynamicDispatchMetadata() {
+    if (classesWithDynamicDispatch.isEmpty) return;
+    int length = classesWithDynamicDispatch.length;
+    if (!compiler.enableMinification) {
+      nativeBuffer.add('// $length dynamic classes.\n');
+    }
+
+    // Build a pre-order traversal over all the classes and their subclasses.
+    Set<ClassElement> seen = new Set<ClassElement>();
+    List<ClassElement> classes = <ClassElement>[];
+    void visit(ClassElement cls) {
+      if (seen.contains(cls)) return;
+      seen.add(cls);
+      getDirectSubclasses(cls).forEach(visit);
+      classes.add(cls);
+    }
+    classesWithDynamicDispatch.forEach(visit);
+
+    List<ClassElement> preorderDispatchClasses = classes.where(
+        (cls) => !getDirectSubclasses(cls).isEmpty &&
+                  classesWithDynamicDispatch.contains(cls)).toList();
+
+    if (!compiler.enableMinification) {
+      nativeBuffer.add('// ${classes.length} classes\n');
+    }
+    Iterable<ClassElement> classesThatHaveSubclasses = classes.where(
+        (ClassElement t) => !getDirectSubclasses(t).isEmpty);
+    if (!compiler.enableMinification) {
+      nativeBuffer.add('// ${classesThatHaveSubclasses.length} !leaf\n');
+    }
+
+    // Generate code that builds the map from cls tags used in dynamic dispatch
+    // to the set of cls tags of classes that extend (TODO: or implement) those
+    // classes.  The set is represented as a string of tags joined with '|'.
+    // This is easily split into an array of tags, or converted into a regexp.
+    //
+    // To reduce the size of the sets, subsets are CSE-ed out into variables.
+    // The sets could be much smaller if we could make assumptions about the
+    // cls tags of other classes (which are constructor names or part of the
+    // result of Object.protocls.toString).  For example, if objects that are
+    // Dart objects could be easily excluded, then we might be able to simplify
+    // the test, replacing dozens of HTMLxxxElement classes with the regexp
+    // /HTML.*Element/.
+
+    // Temporary variables for common substrings.
+    List<String> varNames = <String>[];
+    // Values of temporary variables.
+    Map<String, js.Expression> varDefns = new Map<String, js.Expression>();
+
+    // Expression to compute tags string for a class.  The expression will
+    // initially be a string or expression building a string, but may be
+    // replaced with a variable reference to the common substring.
+    Map<ClassElement, js.Expression> tagDefns =
+        new Map<ClassElement, js.Expression>();
+
+    js.Expression makeExpression(ClassElement classElement) {
+      // Expression fragments for this set of cls keys.
+      List<js.Expression> expressions = <js.Expression>[];
+      // TODO: Remove if cls is abstract.
+      List<String> subtags = [toNativeTag(classElement)];
+      void walk(ClassElement cls) {
+        for (final ClassElement subclass in getDirectSubclasses(cls)) {
+          ClassElement tag = subclass;
+          js.Expression existing = tagDefns[tag];
+          if (existing == null) {
+            // [subclass] is still within the subtree between dispatch classes.
+            subtags.add(toNativeTag(tag));
+            walk(subclass);
+          } else {
+            // [subclass] is one of the preorderDispatchClasses, so CSE this
+            // reference with the previous reference.
+            js.VariableUse use = existing.asVariableUse();
+            if (use != null && varDefns.containsKey(use.name)) {
+              // We end up here if the subclasses have a DAG structure.  We
+              // don't have DAGs yet, but if the dispatch is used for mixins
+              // that will be a possibility.
+              // Re-use the previously created temporary variable.
+              expressions.add(new js.VariableUse(use.name));
+            } else {
+              String varName = 'v${varNames.length}_${tag.name.slowToString()}';
+              varNames.add(varName);
+              varDefns[varName] = existing;
+              tagDefns[tag] = new js.VariableUse(varName);
+              expressions.add(new js.VariableUse(varName));
+            }
+          }
+        }
+      }
+      walk(classElement);
+
+      if (!subtags.isEmpty) {
+        expressions.add(js.string(Strings.join(subtags, '|')));
+      }
+      js.Expression expression;
+      if (expressions.length == 1) {
+        expression = expressions[0];
+      } else {
+        js.Expression array = new js.ArrayInitializer.from(expressions);
+        expression = js.call(array.dot('join'), [js.string('|')]);
+      }
+      return expression;
+    }
+
+    for (final ClassElement classElement in preorderDispatchClasses) {
+      tagDefns[classElement] = makeExpression(classElement);
+    }
+
+    // Write out a thunk that builds the metadata.
+    if (!tagDefns.isEmpty) {
+      List<js.Statement> statements = <js.Statement>[];
+
+      List<js.VariableInitialization> initializations =
+          <js.VariableInitialization>[];
+      for (final String varName in varNames) {
+        initializations.add(
+            new js.VariableInitialization(
+                new js.VariableDeclaration(varName),
+                varDefns[varName]));
+      }
+      if (!initializations.isEmpty) {
+        statements.add(
+            new js.ExpressionStatement(
+                new js.VariableDeclarationList(initializations)));
+      }
+
+      // [table] is a list of lists, each inner list of the form:
+      //   [dynamic-dispatch-tag, tags-of-classes-implementing-dispatch-tag]
+      // E.g.
+      //   [['Node', 'Text|HTMLElement|HTMLDivElement|...'], ...]
+      js.Expression table =
+          new js.ArrayInitializer.from(
+              preorderDispatchClasses.map((cls) =>
+                  new js.ArrayInitializer.from([
+                      js.string(toNativeTag(cls)),
+                      tagDefns[cls]])));
+
+      //  $.dynamicSetMetadata(table);
+      statements.add(
+          new js.ExpressionStatement(
+              new js.Call(
+                  new js.VariableUse(dynamicSetMetadataName),
+                  [table])));
+
+      //  (function(){statements})();
+      if (emitter.compiler.enableMinification) nativeBuffer.add(';');
+      nativeBuffer.add(
+          js.prettyPrint(
+              new js.ExpressionStatement(
+                  new js.Call(new js.Fun([], new js.Block(statements)), [])),
+              compiler));
+    }
+  }
+
+  bool isSupertypeOfNativeClass(Element element) {
+    if (element.isTypeVariable()) {
+      compiler.cancel("Is check for type variable", element: element);
+      return false;
+    }
+    if (element.computeType(compiler).unalias(compiler) is FunctionType) {
+      // The element type is a function type either directly or through
+      // typedef(s).
+      return false;
+    }
+
+    if (!element.isClass()) {
+      compiler.cancel("Is check does not handle element", element: element);
+      return false;
+    }
+
+    return subtypes[element] != null;
+  }
+
+  bool requiresNativeIsCheck(Element element) {
+    if (!element.isClass()) return false;
+    ClassElement cls = element;
+    if (cls.isNative()) return true;
+    return isSupertypeOfNativeClass(element);
+  }
+
+  void assembleCode(CodeBuffer targetBuffer) {
+    if (nativeClasses.isEmpty) return;
+    emitDynamicDispatchMetadata();
+    targetBuffer.add('$defineNativeClassName = '
+                     '$defineNativeClassFunction$N$n');
+
+    List<js.Property> objectProperties = <js.Property>[];
+
+    void addProperty(String name, js.Expression value) {
+      objectProperties.add(new js.Property(js.string(name), value));
+    }
+
+    // Because of native classes, we have to generate some is checks
+    // by calling a method, instead of accessing a property. So we
+    // attach to the JS Object prototype these methods that return
+    // false, and will be overridden by subclasses when they have to
+    // return true.
+    void emitIsChecks() {
+      for (ClassElement element in
+               Elements.sortedByPosition(emitter.checkedClasses)) {
+        if (!requiresNativeIsCheck(element)) continue;
+        if (element.isObject(compiler)) continue;
+        String name = backend.namer.operatorIs(element);
+        addProperty(name,
+            js.fun([], js.block1(js.return_(new js.LiteralBool(false)))));
+      }
+    }
+    emitIsChecks();
+
+    js.Expression makeCallOnThis(String functionName) =>
+        js.fun([],
+            js.block1(
+                js.return_(
+                    js.call(js.use(functionName), [js.use('this')]))));
+
+    // In order to have the toString method on every native class,
+    // we must patch the JS Object prototype with a helper method.
+    String toStringName = backend.namer.publicInstanceMethodNameByArity(
+        const SourceString('toString'), 0);
+    addProperty(toStringName, makeCallOnThis(toStringHelperName));
+
+    // Same as above, but for hashCode.
+    String hashCodeName =
+        backend.namer.publicGetterName(const SourceString('hashCode'));
+    addProperty(hashCodeName, makeCallOnThis(hashCodeHelperName));
+
+    // Same as above, but for operator==.
+    String equalsName = backend.namer.publicInstanceMethodNameByArity(
+        const SourceString('=='), 1);
+    addProperty(equalsName, js.fun(['a'], js.block1(
+        js.return_(js.strictEquals(new js.This(), js.use('a'))))));
+
+    // If the native emitter has been asked to take care of the
+    // noSuchMethod handlers, we do that now.
+    if (handleNoSuchMethod) {
+      emitter.emitNoSuchMethodHandlers(addProperty);
+    }
+
+    // If we have any properties to add to Object.prototype, we run
+    // through them and add them using defineProperty.
+    if (!objectProperties.isEmpty) {
+      js.Expression init =
+          js.call(
+              js.fun(['table'],
+                  js.block1(
+                      new js.ForIn(
+                          new js.VariableDeclarationList(
+                              [new js.VariableInitialization(
+                                  new js.VariableDeclaration('key'),
+                                  null)]),
+                          js.use('table'),
+                          new js.ExpressionStatement(
+                              js.call(
+                                  js.use(defPropName),
+                                  [js.use('Object').dot('prototype'),
+                                   js.use('key'),
+                                   new js.PropertyAccess(js.use('table'),
+                                                         js.use('key'))]))))),
+              [new js.ObjectInitializer(objectProperties)]);
+
+      if (emitter.compiler.enableMinification) targetBuffer.add(';');
+      targetBuffer.add(js.prettyPrint(
+          new js.ExpressionStatement(init), compiler));
+      targetBuffer.add('\n');
+    }
+
+    targetBuffer.add(nativeBuffer);
+    targetBuffer.add('\n');
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart
new file mode 100644
index 0000000..a3a0339
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart
@@ -0,0 +1,173 @@
+// 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.
+
+part of js_backend;
+
+/// For each class, stores the possible class subtype tests that could succeed.
+abstract class TypeChecks {
+  /// Get the set of checks required for class [element].
+  Iterable<ClassElement> operator[](ClassElement element);
+  // Get the iterator for all classes that need type checks.
+  Iterator<ClassElement> get iterator;
+}
+
+class RuntimeTypeInformation {
+  final Compiler compiler;
+
+  RuntimeTypeInformation(this.compiler);
+
+  /// Contains the classes of all arguments that have been used in
+  /// instantiations and checks.
+  Set<ClassElement> allArguments;
+
+  bool isJsNative(Element element) {
+    return (element == compiler.intClass ||
+            element == compiler.boolClass ||
+            element == compiler.numClass ||
+            element == compiler.doubleClass ||
+            element == compiler.stringClass ||
+            element == compiler.listClass ||
+            element == compiler.objectClass ||
+            element == compiler.dynamicClass);
+  }
+
+  TypeChecks computeRequiredChecks() {
+    Set<ClassElement> instantiatedArguments = new Set<ClassElement>();
+    for (DartType type in compiler.codegenWorld.instantiatedTypes) {
+      addAllInterfaceTypeArguments(type, instantiatedArguments);
+    }
+
+    Set<ClassElement> checkedArguments = new Set<ClassElement>();
+    for (DartType type in compiler.enqueuer.codegen.universe.isChecks) {
+      addAllInterfaceTypeArguments(type, checkedArguments);
+    }
+
+    allArguments = new Set<ClassElement>.from(instantiatedArguments)
+        ..addAll(checkedArguments);
+
+    TypeCheckMapping requiredChecks = new TypeCheckMapping();
+    for (ClassElement element in instantiatedArguments) {
+      if (element == compiler.dynamicClass) continue;
+      if (checkedArguments.contains(element)) {
+        requiredChecks.add(element, element);
+      }
+      // Find all supertypes of [element] in [checkedArguments] and add checks.
+      for (DartType supertype in element.allSupertypes) {
+        ClassElement superelement = supertype.element;
+        if (checkedArguments.contains(superelement)) {
+          requiredChecks.add(element, superelement);
+        }
+      }
+    }
+
+    return requiredChecks;
+  }
+
+  void addAllInterfaceTypeArguments(DartType type, Set<ClassElement> classes) {
+    if (type is !InterfaceType) return;
+    for (DartType argument in type.typeArguments) {
+      forEachInterfaceType(argument, (InterfaceType t) {
+        ClassElement cls = t.element;
+        if (cls != compiler.dynamicClass && cls != compiler.objectClass) {
+          classes.add(cls);
+        }
+      });
+    }
+  }
+
+  void forEachInterfaceType(DartType type, f(InterfaceType type)) {
+    if (type.kind == TypeKind.INTERFACE) {
+      f(type);
+      InterfaceType interface = type;
+      for (DartType argument in interface.typeArguments) {
+        forEachInterfaceType(argument, f);
+      }
+    }
+  }
+
+  /// Return the unique name for the element as an unquoted string.
+  String getNameAsString(Element element) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.namer.getName(element);
+  }
+
+  /// Return the unique JS name for the element, which is a quoted string for
+  /// native classes and the isolate acccess to the constructor for classes.
+  String getJsName(Element element) {
+    JavaScriptBackend backend = compiler.backend;
+    Namer namer = backend.namer;
+    return namer.isolateAccess(element);
+  }
+
+  String getRawTypeRepresentation(DartType type) {
+    String name = getNameAsString(type.element);
+    if (!type.element.isClass()) return name;
+    InterfaceType interface = type;
+    Link<DartType> variables = interface.element.typeVariables;
+    if (variables.isEmpty) return name;
+    List<String> arguments = [];
+    variables.forEach((_) => arguments.add('dynamic'));
+    return '$name<${Strings.join(arguments, ', ')}>';
+  }
+
+  String getTypeRepresentation(DartType type, void onVariable(variable)) {
+    StringBuffer builder = new StringBuffer();
+    void build(DartType part) {
+      if (part is TypeVariableType) {
+        builder.add('#');
+        onVariable(part);
+      } else {
+        bool hasArguments = part is InterfaceType && !part.isRaw;
+        Element element = part.element;
+        if (hasArguments) {
+          builder.add('[');
+        }
+        builder.add(getJsName(element));
+        if (!hasArguments) return;
+        InterfaceType interface = part;
+        for (DartType argument in interface.typeArguments) {
+          builder.add(', ');
+          build(argument);
+        }
+        builder.add(']');
+      }
+    }
+    build(type);
+    return builder.toString();
+  }
+
+  static bool hasTypeArguments(DartType type) {
+    if (type is InterfaceType) {
+      InterfaceType interfaceType = type;
+      return !interfaceType.isRaw;
+    }
+    return false;
+  }
+
+  static int getTypeVariableIndex(TypeVariableType variable) {
+    ClassElement classElement = variable.element.getEnclosingClass();
+    Link<DartType> variables = classElement.typeVariables;
+    for (int index = 0; !variables.isEmpty;
+         index++, variables = variables.tail) {
+      if (variables.head == variable) return index;
+    }
+  }
+}
+
+class TypeCheckMapping implements TypeChecks {
+  final Map<ClassElement, Set<ClassElement>> map =
+      new Map<ClassElement, Set<ClassElement>>();
+
+  Iterable<ClassElement> operator[](ClassElement element) {
+    Set<ClassElement> result = map[element];
+    return result != null ? result : const <ClassElement>[];
+  }
+
+  void add(ClassElement cls, ClassElement check) {
+    map.putIfAbsent(cls, () => new Set<ClassElement>());
+    map[cls].add(check);
+  }
+
+  Iterator<ClassElement> get iterator => map.keys.iterator;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart
new file mode 100644
index 0000000..0fccce2
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart
@@ -0,0 +1,21 @@
+// 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.
+
+// Patch file for the dart:async library.
+
+import 'dart:_isolate_helper' show TimerImpl;
+
+patch class Timer {
+  patch factory Timer(int milliseconds, void callback(Timer timer)) {
+    return new TimerImpl(milliseconds, callback);
+  }
+
+  /**
+   * Creates a new repeating timer. The [callback] is invoked every
+   * [milliseconds] millisecond until cancelled.
+   */
+  patch factory Timer.repeating(int milliseconds, void callback(Timer timer)) {
+    return new TimerImpl.repeating(milliseconds, callback);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart
new file mode 100644
index 0000000..75446c7
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart
@@ -0,0 +1,75 @@
+// 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.
+
+part of _js_helper;
+
+// This class has no constructor. This is on purpose since the instantiation
+// is shortcut by the compiler.
+class ConstantMap<V> implements Map<String, V> {
+  final int length;
+  // A constant map is backed by a JavaScript object.
+  final _jsObject;
+  final List<String> _keys;
+
+  bool containsValue(V needle) {
+    return values.any((V value) => value == needle);
+  }
+
+  bool containsKey(String key) {
+    if (key == '__proto__') return false;
+    return jsHasOwnProperty(_jsObject, key);
+  }
+
+  V operator [](String key) {
+    if (!containsKey(key)) return null;
+    return jsPropertyAccess(_jsObject, key);
+  }
+
+  void forEach(void f(String key, V value)) {
+    _keys.forEach((String key) => f(key, this[key]));
+  }
+
+  Iterable<String> get keys {
+    return new _ConstantMapKeyIterable(this);
+  }
+
+  Iterable<V> get values {
+    return _keys.map((String key) => this[key]);
+  }
+
+  bool get isEmpty => length == 0;
+
+  String toString() => Maps.mapToString(this);
+
+  _throwUnmodifiable() {
+    throw new UnsupportedError("Cannot modify unmodifiable Map");
+  }
+  void operator []=(String key, V val) => _throwUnmodifiable();
+  V putIfAbsent(String key, V ifAbsent()) => _throwUnmodifiable();
+  V remove(String key) => _throwUnmodifiable();
+  void clear() => _throwUnmodifiable();
+}
+
+// This class has no constructor. This is on purpose since the instantiation
+// is shortcut by the compiler.
+class ConstantProtoMap<V> extends ConstantMap<V> {
+  final V _protoValue;
+
+  bool containsKey(String key) {
+    if (key == '__proto__') return true;
+    return super.containsKey(key);
+  }
+
+  V operator [](String key) {
+    if (key == '__proto__') return _protoValue;
+    return super[key];
+  }
+}
+
+class _ConstantMapKeyIterable extends Iterable<String> {
+  ConstantMap _map;
+  _ConstantMapKeyIterable(this._map);
+
+  Iterator<String> get iterator => _map._keys.iterator;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart
new file mode 100644
index 0000000..09e31da
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart
@@ -0,0 +1,250 @@
+// 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.
+
+// Patch file for dart:core classes.
+
+import 'dart:_interceptors';
+import 'dart:_js_helper' show checkNull,
+                              getRuntimeTypeString,
+                              isJsArray,
+                              JSSyntaxRegExp,
+                              Primitives,
+                              TypeImpl,
+                              stringJoinUnchecked,
+                              JsStringBuffer;
+
+patch void print(var object) {
+  Primitives.printString(object.toString());
+}
+
+// Patch for Object implementation.
+patch class Object {
+  patch int get hashCode => Primitives.objectHashCode(this);
+
+  patch String toString() => Primitives.objectToString(this);
+
+  patch dynamic noSuchMethod(InvocationMirror invocation) {
+    throw new NoSuchMethodError(this,
+                                invocation.memberName,
+                                invocation.positionalArguments,
+                                invocation.namedArguments);
+  }
+
+  patch Type get runtimeType {
+    String type = getRuntimeTypeString(this);
+    return new TypeImpl(type);
+  }
+}
+
+// Patch for Function implementation.
+patch class Function {
+  patch static apply(Function function,
+                     List positionalArguments,
+                     [Map<String,dynamic> namedArguments]) {
+    return Primitives.applyFunction(
+        function, positionalArguments, namedArguments);
+  }
+}
+
+// Patch for Expando implementation.
+patch class Expando<T> {
+  patch Expando([String name]) : this.name = name;
+
+  patch T operator[](Object object) {
+    var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME);
+    return (values == null) ? null : Primitives.getProperty(values, _getKey());
+  }
+
+  patch void operator[]=(Object object, T value) {
+    var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME);
+    if (values == null) {
+      values = new Object();
+      Primitives.setProperty(object, _EXPANDO_PROPERTY_NAME, values);
+    }
+    Primitives.setProperty(values, _getKey(), value);
+  }
+
+  String _getKey() {
+    String key = Primitives.getProperty(this, _KEY_PROPERTY_NAME);
+    if (key == null) {
+      key = "expando\$key\$${_keyCount++}";
+      Primitives.setProperty(this, _KEY_PROPERTY_NAME, key);
+    }
+    return key;
+  }
+
+  static const String _KEY_PROPERTY_NAME = 'expando\$key';
+  static const String _EXPANDO_PROPERTY_NAME = 'expando\$values';
+  static int _keyCount = 0;
+}
+
+patch class int {
+  patch static int parse(String source,
+                         { int radix,
+                           int onError(String source) }) {
+    return Primitives.parseInt(source, radix, onError);
+  }
+}
+
+patch class double {
+  patch static double parse(String source, [int handleError(String source)]) {
+    return Primitives.parseDouble(source, handleError);
+  }
+}
+
+patch class Error {
+  patch static String _objectToString(Object object) {
+    return Primitives.objectToString(object);
+  }
+}
+
+
+// Patch for DateTime implementation.
+patch class DateTime {
+  patch DateTime._internal(int year,
+                           int month,
+                           int day,
+                           int hour,
+                           int minute,
+                           int second,
+                           int millisecond,
+                           bool isUtc)
+      : this.isUtc = checkNull(isUtc),
+        millisecondsSinceEpoch = Primitives.valueFromDecomposedDate(
+            year, month, day, hour, minute, second, millisecond, isUtc) {
+    Primitives.lazyAsJsDate(this);
+  }
+
+  patch DateTime._now()
+      : isUtc = false,
+        millisecondsSinceEpoch = Primitives.dateNow() {
+    Primitives.lazyAsJsDate(this);
+  }
+
+  patch static int _brokenDownDateToMillisecondsSinceEpoch(
+      int year, int month, int day, int hour, int minute, int second,
+      int millisecond, bool isUtc) {
+    return Primitives.valueFromDecomposedDate(
+        year, month, day, hour, minute, second, millisecond, isUtc);
+  }
+
+  patch String get timeZoneName {
+    if (isUtc) return "UTC";
+    return Primitives.getTimeZoneName(this);
+  }
+
+  patch Duration get timeZoneOffset {
+    if (isUtc) return new Duration();
+    return new Duration(minutes: Primitives.getTimeZoneOffsetInMinutes(this));
+  }
+
+  patch int get year => Primitives.getYear(this);
+
+  patch int get month => Primitives.getMonth(this);
+
+  patch int get day => Primitives.getDay(this);
+
+  patch int get hour => Primitives.getHours(this);
+
+  patch int get minute => Primitives.getMinutes(this);
+
+  patch int get second => Primitives.getSeconds(this);
+
+  patch int get millisecond => Primitives.getMilliseconds(this);
+
+  patch int get weekday => Primitives.getWeekday(this);
+}
+
+
+// Patch for Stopwatch implementation.
+patch class Stopwatch {
+  patch static int _frequency() => 1000000;
+  patch static int _now() => Primitives.numMicroseconds();
+}
+
+
+// Patch for List implementation.
+patch class List<E> {
+  patch factory List([int length = 0]) {
+    // Explicit type test is necessary to protect Primitives.newGrowableList in
+    // unchecked mode.
+    if ((length is !int) || (length < 0)) {
+      throw new ArgumentError("Length must be a positive integer: $length.");
+    }
+    return Primitives.newGrowableList(length);
+  }
+
+  patch factory List.fixedLength(int length, {E fill: null}) {
+    // Explicit type test is necessary to protect Primitives.newFixedList in
+    // unchecked mode.
+    if ((length is !int) || (length < 0)) {
+      throw new ArgumentError("Length must be a positive integer: $length.");
+    }
+    List result = Primitives.newFixedList(length);
+    if (length != 0 && fill != null) {
+      for (int i = 0; i < result.length; i++) {
+        result[i] = fill;
+      }
+    }
+    return result;
+  }
+}
+
+
+patch class String {
+  patch factory String.fromCharCodes(List<int> charCodes) {
+    if (!isJsArray(charCodes)) {
+      if (charCodes is !List) throw new ArgumentError(charCodes);
+      charCodes = new List.from(charCodes);
+    }
+    return Primitives.stringFromCharCodes(charCodes);
+  }
+}
+
+// Patch for String implementation.
+patch class Strings {
+  patch static String join(Iterable<String> strings, String separator) {
+    checkNull(strings);
+    if (separator is !String) throw new ArgumentError(separator);
+    return stringJoinUnchecked(_toJsStringArray(strings), separator);
+  }
+
+  patch static String concatAll(Iterable<String> strings) {
+    return stringJoinUnchecked(_toJsStringArray(strings), "");
+  }
+
+  static List _toJsStringArray(Iterable<String> strings) {
+    checkNull(strings);
+    var array;
+    if (!isJsArray(strings)) {
+      strings = new List.from(strings);
+    }
+    final length = strings.length;
+    for (int i = 0; i < length; i++) {
+      final string = strings[i];
+      if (string is !String) throw new ArgumentError(string);
+    }
+    return strings;
+  }
+}
+
+patch class RegExp {
+  patch factory RegExp(String pattern,
+                       {bool multiLine: false,
+                        bool caseSensitive: true})
+    => new JSSyntaxRegExp(pattern,
+                          multiLine: multiLine,
+                          caseSensitive: caseSensitive);
+}
+
+// Patch for 'identical' function.
+patch bool identical(Object a, Object b) {
+  return Primitives.identicalImplementation(a, b);
+}
+
+patch class StringBuffer {
+  patch factory StringBuffer([Object content = ""]) {
+    return new JsStringBuffer(content);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart
new file mode 100644
index 0000000..2ad4a1e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart
@@ -0,0 +1,138 @@
+// 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.
+
+library _foreign_helper;
+
+/**
+ * Emits a JavaScript code fragment parameterized by arguments.
+ *
+ * Hash characters `#` in the [codeTemplate] are replaced in left-to-right order
+ * with expressions that contain the values of, or evaluate to, the arguments.
+ * The number of hash marks must match the number or arguments.  Although
+ * declared with arguments [arg0] through [arg2], the form actually has no limit
+ * on the number of arguments.
+ *
+ * The [typeDescription] argument is interpreted as a description of the
+ * behavior of the JavaScript code.  Currently it describes the types that may
+ * be returned by the expression, with the additional behavior that the returned
+ * values may be fresh instances of the types.  The type information must be
+ * correct as it is trusted by the compiler in optimizations, and it must be
+ * precise as possible since it is used for native live type analysis to
+ * tree-shake large parts of the DOM libraries.  If poorly written, the
+ * [typeDescription] will cause unnecessarily bloated programs.  (You can check
+ * for this by compiling with `--verbose`; there is an info message describing
+ * the number of native (DOM) types that can be removed, which usually should be
+ * greater than zero.)
+ *
+ * The [typeDescription] is a [String] which contains a union of types separated
+ * by vertical bar `|` symbols, e.g.  `"num|String"` describes the union of
+ * numbers and Strings.  There is no type in Dart that is this precise.  The
+ * Dart alternative would be `Object` or `dynamic`, but these types imply that
+ * the JS-code might also be creating instances of all the DOM types.  The
+ * [typeDescription] has several extensions to help describe the behavior more
+ * accurately.  In addition to the union type already described:
+ *
+ *  + `=List` is the JavaScript array type.  This is more precise than `List`,
+ *     which includes about fifty DOM types that also implement the List
+ *     interface.
+ *
+ *  + `=Object` is a plain JavaScript object.  Some DOM methods return instances
+ *     that have no corresponing Dart type (e.g. cross-frame documents),
+ *     `=Object` can be used to describe these untyped' values.
+ *
+ *  + `var`.  If the entire [typeDescription] is `var` then the type is
+ *    `dynamic` but the code is known to not create any instances.
+ *
+ * Examples:
+ *
+ *     // Create a JavaScript Array.
+ *     List a = JS('=List', 'new Array(#)', length);
+ *
+ *     // Parent window might be an opaque cross-frame window.
+ *     var thing = JS('=Object|Window', '#.parent', myWindow);
+ *
+ * Guidelines:
+ *
+ *  + Do not use any parameter, local, method or field names in the
+ *    [codeTemplate].  These names are all subject to arbitrary renaming by the
+ *    compiler.  Pass the values in via `#` substition, and test with the
+ *    `--minify` dart2js command-line option.
+ *
+ *  + The substituted expressions are values, not locations.
+ *
+ *        JS('void', '# += "x"', this.field);
+ *
+ *    `this.field` might not be a substituted as a reference to the field.  The
+ *    generated code might accidentally work as intended, but it also might be
+ *
+ *        var t1 = this.field;
+ *        t1 += "x";
+ *
+ *    or
+ *
+ *        this.get$field() += "x";
+ *
+ *    The remedy in this case is to expand the `+=` operator, leaving all
+ *    references to the Dart field as Dart code:
+ *
+ *        this.field = JS('String', '# + "x"', this.field);
+ *
+ *
+ * Additional notes.
+ *
+ * In the future we may extend [typeDescription] to include other aspects of the
+ * behavior, for example, separating the returned types from the instantiated
+ * types, or including effects to allow the compiler to perform more
+ * optimizations around the code.  This might be an extension of [JS] or a new
+ * function similar to [JS] with additional arguments for the new information.
+ */
+// Add additional optional arguments if needed. The method is treated internally
+// as a variable argument method.
+dynamic JS(String typeDescription, String codeTemplate,
+    [var arg0, var arg1, var arg2, var arg3, var arg4, var arg5, var arg6,
+     var arg7, var arg8, var arg9, var arg10, var arg11]) {}
+
+/**
+ * Returns the isolate in which this code is running.
+ */
+dynamic JS_CURRENT_ISOLATE() {}
+
+/**
+ * Invokes [function] in the context of [isolate].
+ */
+dynamic JS_CALL_IN_ISOLATE(var isolate, Function function) {}
+
+/**
+ * Converts the Dart closure [function] into a JavaScript closure.
+ */
+dynamic DART_CLOSURE_TO_JS(Function function) {}
+
+/**
+ * Returns a raw reference to the JavaScript function which implements
+ * [function].
+ *
+ * Warning: this is dangerous, you should probably use
+ * [DART_CLOSURE_TO_JS] instead. The returned object is not a valid
+ * Dart closure, does not store the isolate context or arity.
+ *
+ * A valid example of where this can be used is as the second argument
+ * to V8's Error.captureStackTrace. See
+ * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi.
+ */
+dynamic RAW_DART_FUNCTION_REF(Function function) {}
+
+/**
+ * Sets the current isolate to [isolate].
+ */
+void JS_SET_CURRENT_ISOLATE(var isolate) {}
+
+/**
+ * Creates an isolate and returns it.
+ */
+dynamic JS_CREATE_ISOLATE() {}
+
+/**
+ * Returns the prefix used for generated is checks on classes.
+ */
+String JS_OPERATOR_IS_PREFIX() {}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart
new file mode 100644
index 0000000..6a32f17
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart
@@ -0,0 +1,90 @@
+// 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.
+
+library _interceptors;
+
+import 'dart:collection';
+import 'dart:_collection-dev';
+import 'dart:_js_helper' show allMatchesInStringUnchecked,
+                              Null,
+                              JSSyntaxRegExp,
+                              Primitives,
+                              checkGrowable,
+                              checkMutable,
+                              checkNull,
+                              checkNum,
+                              checkString,
+                              getRuntimeTypeString,
+                              listInsertRange,
+                              regExpGetNative,
+                              stringContainsUnchecked,
+                              stringLastIndexOfUnchecked,
+                              stringReplaceAllFuncUnchecked,
+                              stringReplaceAllUnchecked,
+                              stringReplaceFirstUnchecked,
+                              TypeImpl;
+import 'dart:_foreign_helper' show JS;
+
+part 'js_array.dart';
+part 'js_number.dart';
+part 'js_string.dart';
+
+/**
+ * The interceptor class for all non-primitive objects. All its
+ * members are synthethized by the compiler's emitter.
+ */
+class ObjectInterceptor {
+  const ObjectInterceptor();
+}
+
+/**
+ * Get the interceptor for [object]. Called by the compiler when it needs
+ * to emit a call to an intercepted method, that is a method that is
+ * defined in an interceptor class.
+ */
+getInterceptor(object) {
+  // This is a magic method: the compiler does specialization of it
+  // depending on the uses of intercepted methods and instantiated
+  // primitive types.
+}
+
+/**
+ * The interceptor class for tear-off static methods. Unlike
+ * tear-off instance methods, tear-off static methods are just the JS
+ * function, and methods inherited from Object must therefore be
+ * intercepted.
+ */
+class JSFunction implements Function {
+  const JSFunction();
+  String toString() => 'Closure';
+}
+
+/**
+ * The interceptor class for [bool].
+ */
+class JSBool implements bool {
+  const JSBool();
+
+  // Note: if you change this, also change the function [S].
+  String toString() => JS('String', r'String(#)', this);
+
+  // The values here are SMIs, co-prime and differ about half of the bit
+  // positions, including the low bit, so they are different mod 2^k.
+  int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811);
+
+  Type get runtimeType => bool;
+}
+
+/**
+ * The interceptor class for [Null].
+ */
+class JSNull implements Null {
+  const JSNull();
+
+  // Note: if you change this, also change the function [S].
+  String toString() => 'null';
+
+  int get hashCode => 0;
+  Type get runtimeType => Null;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart
new file mode 100644
index 0000000..0374de5
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart
@@ -0,0 +1,217 @@
+// 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.
+
+patch class _BufferUtils {
+  patch static bool _isBuiltinList(List buffer) {
+    throw new UnsupportedError("_isBuiltinList");
+  }
+}
+
+patch class _Directory {
+  patch static String _current() {
+    throw new UnsupportedError("Directory._current");
+  }
+  patch static _createTemp(String template) {
+    throw new UnsupportedError("Directory._createTemp");
+  }
+  patch static int _exists(String path) {
+    throw new UnsupportedError("Directory._exists");
+  }
+  patch static _create(String path) {
+    throw new UnsupportedError("Directory._create");
+  }
+  patch static _delete(String path, bool recursive) {
+    throw new UnsupportedError("Directory._delete");
+  }
+  patch static _rename(String path, String newPath) {
+    throw new UnsupportedError("Directory._rename");
+  }
+  patch static List _list(String path, bool recursive) {
+    throw new UnsupportedError("Directory._list");
+  }
+  patch static SendPort _newServicePort() {
+    throw new UnsupportedError("Directory._newServicePort");
+  }
+}
+
+patch class _EventHandler {
+  patch static void _start() {
+    throw new UnsupportedError("EventHandler._start");
+  }
+
+  patch static _sendData(Object sender,
+                         ReceivePort receivePort,
+                         int data) {
+    throw new UnsupportedError("EventHandler._sendData");
+  }
+}
+
+patch class _FileUtils {
+  patch static SendPort _newServicePort() {
+    throw new UnsupportedError("FileUtils._newServicePort");
+  }
+}
+
+patch class _File {
+  patch static _exists(String name) {
+    throw new UnsupportedError("File._exists");
+  }
+  patch static _create(String name) {
+    throw new UnsupportedError("File._create");
+  }
+  patch static _delete(String name) {
+    throw new UnsupportedError("File._delete");
+  }
+  patch static _directory(String name) {
+    throw new UnsupportedError("File._directory");
+  }
+  patch static _lengthFromName(String name) {
+    throw new UnsupportedError("File._lengthFromName");
+  }
+  patch static _lastModified(String name) {
+    throw new UnsupportedError("File._lastModified");
+  }
+  patch static _open(String name, int mode) {
+    throw new UnsupportedError("File._open");
+  }
+  patch static int _openStdio(int fd) {
+    throw new UnsupportedError("File._openStdio");
+  }
+  patch static _fullPath(String name) {
+    throw new UnsupportedError("File._fullPath");
+  }
+}
+
+patch class _RandomAccessFile {
+  patch static int _close(int id) {
+    throw new UnsupportedError("RandomAccessFile._close");
+  }
+  patch static _readByte(int id) {
+    throw new UnsupportedError("RandomAccessFile._readByte");
+  }
+  patch static _read(int id, int bytes) {
+    throw new UnsupportedError("RandomAccessFile._read");
+  }
+  patch static _readList(int id, List<int> buffer, int offset, int bytes) {
+    throw new UnsupportedError("RandomAccessFile._readList");
+  }
+  patch static _writeByte(int id, int value) {
+    throw new UnsupportedError("RandomAccessFile._writeByte");
+  }
+  patch static _writeList(int id, List<int> buffer, int offset, int bytes) {
+    throw new UnsupportedError("RandomAccessFile._writeList");
+  }
+  patch static _position(int id) {
+    throw new UnsupportedError("RandomAccessFile._position");
+  }
+  patch static _setPosition(int id, int position) {
+    throw new UnsupportedError("RandomAccessFile._setPosition");
+  }
+  patch static _truncate(int id, int length) {
+    throw new UnsupportedError("RandomAccessFile._truncate");
+  }
+  patch static _length(int id) {
+    throw new UnsupportedError("RandomAccessFile._length");
+  }
+  patch static _flush(int id) {
+    throw new UnsupportedError("RandomAccessFile._flush");
+  }
+}
+
+patch class _HttpSessionManager {
+  patch static Uint8List _getRandomBytes(int count) {
+    throw new UnsupportedError("HttpSessionManager._getRandomBytes");
+  }
+}
+
+patch class _Platform {
+  patch static int _numberOfProcessors() {
+    throw new UnsupportedError("Platform._numberOfProcessors");
+  }
+  patch static String _pathSeparator() {
+    throw new UnsupportedError("Platform._pathSeparator");
+  }
+  patch static String _operatingSystem() {
+    throw new UnsupportedError("Platform._operatingSystem");
+  }
+  patch static _localHostname() {
+    throw new UnsupportedError("Platform._localHostname");
+  }
+  patch static _environment() {
+    throw new UnsupportedError("Platform._environment");
+  }
+}
+
+patch class _ProcessUtils {
+  patch static _exit(int status) {
+    throw new UnsupportedError("ProcessUtils._exit");
+  }
+  patch static _setExitCode(int status) {
+    throw new UnsupportedError("ProcessUtils._setExitCode");
+  }
+}
+
+patch class Process {
+  patch static Future<Process> start(String executable,
+                                     List<String> arguments,
+                                     [ProcessOptions options]) {
+    throw new UnsupportedError("Process.start");
+  }
+
+  patch static Future<ProcessResult> run(String executable,
+                                         List<String> arguments,
+                                         [ProcessOptions options]) {
+    throw new UnsupportedError("Process.run");
+  }
+}
+
+patch class ServerSocket {
+  patch factory ServerSocket(String bindAddress, int port, int backlog) {
+    throw new UnsupportedError("ServerSocket constructor");
+  }
+}
+
+patch class Socket {
+  patch factory Socket(String host, int port) {
+    throw new UnsupportedError("Socket constructor");
+  }
+}
+
+patch class SecureSocket {
+  patch static void initialize({String database,
+                                String password,
+                                bool useBuiltinRoots: true}) {
+    throw new UnsupportedError("SecureSocket.setCertificateDatabase");
+  }
+}
+
+patch class _SecureFilter {
+  patch factory _SecureFilter() {
+    throw new UnsupportedError("_SecureFilter._SecureFilter");
+  }
+}
+
+patch class _StdIOUtils {
+  patch static InputStream _getStdioInputStream() {
+    throw new UnsupportedError("StdIOUtils._getStdioInputStream");
+  }
+  patch static OutputStream _getStdioOutputStream(int fd) {
+    throw new UnsupportedError("StdIOUtils._getStdioOutputStream");
+  }
+  patch static int _socketType(Socket socket) {
+    throw new UnsupportedError("StdIOUtils._socketType");
+  }
+}
+
+patch class _WindowsCodePageDecoder {
+  patch static String _decodeBytes(List<int> bytes) {
+    throw new UnsupportedError("_WindowsCodePageDecoder._decodeBytes");
+  }
+}
+
+patch class _WindowsCodePageEncoder {
+  patch static List<int> _encodeString(String string) {
+    throw new UnsupportedError("_WindowsCodePageEncoder._encodeString");
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart
new file mode 100644
index 0000000..82a80fe
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart
@@ -0,0 +1,1329 @@
+// 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.
+
+library _isolate_helper;
+
+import 'dart:async';
+import 'dart:collection' show Queue, HashMap;
+import 'dart:isolate';
+import 'dart:_js_helper' show convertDartClosureToJS,
+                              Null;
+import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS,
+                                   JS,
+                                   JS_CREATE_ISOLATE,
+                                   JS_SET_CURRENT_ISOLATE;
+
+ReceivePort lazyPort;
+
+/**
+ * Called by the compiler to support switching
+ * between isolates when we get a callback from the DOM.
+ */
+void _callInIsolate(_IsolateContext isolate, Function function) {
+  isolate.eval(function);
+  _globalState.topEventLoop.run();
+}
+
+/**
+ * Called by the compiler to fetch the current isolate context.
+ */
+_IsolateContext _currentIsolate() => _globalState.currentContext;
+
+/**
+ * Wrapper that takes the dart entry point and runs it within an isolate. The
+ * dart2js compiler will inject a call of the form
+ * [: startRootIsolate(main); :] when it determines that this wrapping
+ * is needed. For single-isolate applications (e.g. hello world), this
+ * call is not emitted.
+ */
+void startRootIsolate(entry) {
+  _globalState = new _Manager();
+
+  // Don't start the main loop again, if we are in a worker.
+  if (_globalState.isWorker) return;
+  final rootContext = new _IsolateContext();
+  _globalState.rootContext = rootContext;
+
+  // BUG(5151491): Setting currentContext should not be necessary, but
+  // because closures passed to the DOM as event handlers do not bind their
+  // isolate automatically we try to give them a reasonable context to live in
+  // by having a "default" isolate (the first one created).
+  _globalState.currentContext = rootContext;
+
+  rootContext.eval(entry);
+  _globalState.topEventLoop.run();
+}
+
+/********************************************************
+  Inserted from lib/isolate/dart2js/isolateimpl.dart
+ ********************************************************/
+
+/**
+ * Concepts used here:
+ *
+ * "manager" - A manager contains one or more isolates, schedules their
+ * execution, and performs other plumbing on their behalf.  The isolate
+ * present at the creation of the manager is designated as its "root isolate".
+ * A manager may, for example, be implemented on a web Worker.
+ *
+ * [_Manager] - State present within a manager (exactly once, as a global).
+ *
+ * [_ManagerStub] - A handle held within one manager that allows interaction
+ * with another manager.  A target manager may be addressed by zero or more
+ * [_ManagerStub]s.
+ *
+ */
+
+/**
+ * A native object that is shared across isolates. This object is visible to all
+ * isolates running under the same manager (either UI or background web worker).
+ *
+ * This is code that is intended to 'escape' the isolate boundaries in order to
+ * implement the semantics of isolates in JavaScript. Without this we would have
+ * been forced to implement more code (including the top-level event loop) in
+ * JavaScript itself.
+ */
+// TODO(eub, sigmund): move the "manager" to be entirely in JS.
+// Running any Dart code outside the context of an isolate gives it
+// the chance to break the isolate abstraction.
+_Manager get _globalState => JS("_Manager", r"$globalState");
+
+set _globalState(_Manager val) {
+  JS("void", r"$globalState = #", val);
+}
+
+/** State associated with the current manager. See [globalState]. */
+// TODO(sigmund): split in multiple classes: global, thread, main-worker states?
+class _Manager {
+
+  /** Next available isolate id within this [_Manager]. */
+  int nextIsolateId = 0;
+
+  /** id assigned to this [_Manager]. */
+  int currentManagerId = 0;
+
+  /**
+   * Next available manager id. Only used by the main manager to assign a unique
+   * id to each manager created by it.
+   */
+  int nextManagerId = 1;
+
+  /** Context for the currently running [Isolate]. */
+  _IsolateContext currentContext = null;
+
+  /** Context for the root [Isolate] that first run in this [_Manager]. */
+  _IsolateContext rootContext = null;
+
+  /** The top-level event loop. */
+  _EventLoop topEventLoop;
+
+  /** Whether this program is running from the command line. */
+  bool fromCommandLine;
+
+  /** Whether this [_Manager] is running as a web worker. */
+  bool isWorker;
+
+  /** Whether we support spawning web workers. */
+  bool supportsWorkers;
+
+  /**
+   * Whether to use web workers when implementing isolates. Set to false for
+   * debugging/testing.
+   */
+  bool get useWorkers => supportsWorkers;
+
+  /**
+   * Whether to use the web-worker JSON-based message serialization protocol. By
+   * default this is only used with web workers. For debugging, you can force
+   * using this protocol by changing this field value to [true].
+   */
+  bool get needSerialization => useWorkers;
+
+  /**
+   * Registry of isolates. Isolates must be registered if, and only if, receive
+   * ports are alive.  Normally no open receive-ports means that the isolate is
+   * dead, but DOM callbacks could resurrect it.
+   */
+  Map<int, _IsolateContext> isolates;
+
+  /** Reference to the main [_Manager].  Null in the main [_Manager] itself. */
+  _ManagerStub mainManager;
+
+  /** Registry of active [_ManagerStub]s.  Only used in the main [_Manager]. */
+  Map<int, _ManagerStub> managers;
+
+  _Manager() {
+    _nativeDetectEnvironment();
+    topEventLoop = new _EventLoop();
+    isolates = new Map<int, _IsolateContext>();
+    managers = new Map<int, _ManagerStub>();
+    if (isWorker) {  // "if we are not the main manager ourself" is the intent.
+      mainManager = new _MainManagerStub();
+      _nativeInitWorkerMessageHandler();
+    }
+  }
+
+  void _nativeDetectEnvironment() {
+    bool isWindowDefined = globalWindow != null;
+    bool isWorkerDefined = globalWorker != null;
+
+    isWorker = !isWindowDefined && globalPostMessageDefined;
+    supportsWorkers = isWorker
+       || (isWorkerDefined && IsolateNatives.thisScript != null);
+    fromCommandLine = !isWindowDefined && !isWorker;
+  }
+
+  void _nativeInitWorkerMessageHandler() {
+    var function = JS('',
+                      "function (e) { #(#, e); }",
+                      DART_CLOSURE_TO_JS(IsolateNatives._processWorkerMessage),
+                      mainManager);
+    JS("void", r"#.onmessage = #", globalThis, function);
+    // We define dartPrint so that the implementation of the Dart
+    // print method knows what to call.
+    // TODO(ngeoffray): Should we forward to the main isolate? What if
+    // it exited?
+    JS('void', r'#.dartPrint = function (object) {}', globalThis);
+  }
+
+
+  /**
+   * Close the worker running this code if all isolates are done and
+   * there is no active timer.
+   */
+  void maybeCloseWorker() {
+    if (isWorker
+        && isolates.isEmpty
+        && topEventLoop.activeTimerCount == 0) {
+      mainManager.postMessage(_serializeMessage({'command': 'close'}));
+    }
+  }
+}
+
+/** Context information tracked for each isolate. */
+class _IsolateContext {
+  /** Current isolate id. */
+  int id;
+
+  /** Registry of receive ports currently active on this isolate. */
+  Map<int, ReceivePort> ports;
+
+  /** Holds isolate globals (statics and top-level properties). */
+  var isolateStatics; // native object containing all globals of an isolate.
+
+  _IsolateContext() {
+    id = _globalState.nextIsolateId++;
+    ports = new Map<int, ReceivePort>();
+    isolateStatics = JS_CREATE_ISOLATE();
+  }
+
+  /**
+   * Run [code] in the context of the isolate represented by [this].
+   */
+  dynamic eval(Function code) {
+    var old = _globalState.currentContext;
+    _globalState.currentContext = this;
+    this._setGlobals();
+    var result = null;
+    try {
+      result = code();
+    } finally {
+      _globalState.currentContext = old;
+      if (old != null) old._setGlobals();
+    }
+    return result;
+  }
+
+  void _setGlobals() {
+    JS_SET_CURRENT_ISOLATE(isolateStatics);
+  }
+
+  /** Lookup a port registered for this isolate. */
+  ReceivePort lookup(int portId) => ports[portId];
+
+  /** Register a port on this isolate. */
+  void register(int portId, ReceivePort port)  {
+    if (ports.containsKey(portId)) {
+      throw new Exception("Registry: ports must be registered only once.");
+    }
+    ports[portId] = port;
+    _globalState.isolates[id] = this; // indicate this isolate is active
+  }
+
+  /** Unregister a port on this isolate. */
+  void unregister(int portId) {
+    ports.remove(portId);
+    if (ports.isEmpty) {
+      _globalState.isolates.remove(id); // indicate this isolate is not active
+    }
+  }
+}
+
+/** Represent the event loop on a javascript thread (DOM or worker). */
+class _EventLoop {
+  final Queue<_IsolateEvent> events = new Queue<_IsolateEvent>();
+  int activeTimerCount = 0;
+
+  _EventLoop();
+
+  void enqueue(isolate, fn, msg) {
+    events.addLast(new _IsolateEvent(isolate, fn, msg));
+  }
+
+  _IsolateEvent dequeue() {
+    if (events.isEmpty) return null;
+    return events.removeFirst();
+  }
+
+  void checkOpenReceivePortsFromCommandLine() {
+    if (_globalState.rootContext != null
+        && _globalState.isolates.containsKey(_globalState.rootContext.id)
+        && _globalState.fromCommandLine
+        && _globalState.rootContext.ports.isEmpty) {
+      // We want to reach here only on the main [_Manager] and only
+      // on the command-line.  In the browser the isolate might
+      // still be alive due to DOM callbacks, but the presumption is
+      // that on the command-line, no future events can be injected
+      // into the event queue once it's empty.  Node has setTimeout
+      // so this presumption is incorrect there.  We think(?) that
+      // in d8 this assumption is valid.
+      throw new Exception("Program exited with open ReceivePorts.");
+    }
+  }
+
+  /** Process a single event, if any. */
+  bool runIteration() {
+    final event = dequeue();
+    if (event == null) {
+      checkOpenReceivePortsFromCommandLine();
+      _globalState.maybeCloseWorker();
+      return false;
+    }
+    event.process();
+    return true;
+  }
+
+  /**
+   * Runs multiple iterations of the run-loop. If possible, each iteration is
+   * run asynchronously.
+   */
+  void _runHelper() {
+    if (globalWindow != null) {
+      // Run each iteration from the browser's top event loop.
+      void next() {
+        if (!runIteration()) return;
+        new Timer(0, (_) => next());
+      }
+      next();
+    } else {
+      // Run synchronously until no more iterations are available.
+      while (runIteration()) {}
+    }
+  }
+
+  /**
+   * Call [_runHelper] but ensure that worker exceptions are propragated.
+   */
+  void run() {
+    if (!_globalState.isWorker) {
+      _runHelper();
+    } else {
+      try {
+        _runHelper();
+      } catch (e, trace) {
+        _globalState.mainManager.postMessage(_serializeMessage(
+            {'command': 'error', 'msg': '$e\n$trace' }));
+      }
+    }
+  }
+}
+
+/** An event in the top-level event queue. */
+class _IsolateEvent {
+  _IsolateContext isolate;
+  Function fn;
+  String message;
+
+  _IsolateEvent(this.isolate, this.fn, this.message);
+
+  void process() {
+    isolate.eval(fn);
+  }
+}
+
+/** An interface for a stub used to interact with a manager. */
+abstract class _ManagerStub {
+  get id;
+  void set id(int i);
+  void set onmessage(Function f);
+  void postMessage(msg);
+  void terminate();
+}
+
+/** A stub for interacting with the main manager. */
+class _MainManagerStub implements _ManagerStub {
+  get id => 0;
+  void set id(int i) { throw new UnimplementedError(); }
+  void set onmessage(f) {
+    throw new Exception("onmessage should not be set on MainManagerStub");
+  }
+  void postMessage(msg) {
+    JS("void", r"#.postMessage(#)", globalThis, msg);
+  }
+  void terminate() {}  // Nothing useful to do here.
+}
+
+/**
+ * A stub for interacting with a manager built on a web worker. This
+ * definition uses a 'hidden' type (* prefix on the native name) to
+ * enforce that the type is defined dynamically only when web workers
+ * are actually available.
+ */
+// @Native("*Worker");
+class _WorkerStub implements _ManagerStub {
+  get id => JS("", "#.id", this);
+  void set id(i) { JS("void", "#.id = #", this, i); }
+  void set onmessage(f) { JS("void", "#.onmessage = #", this, f); }
+  void postMessage(msg) { JS("void", "#.postMessage(#)", this, msg); }
+  void terminate() { JS("void", "#.terminate()", this); }
+}
+
+const String _SPAWNED_SIGNAL = "spawned";
+
+var globalThis = IsolateNatives.computeGlobalThis();
+var globalWindow = JS('', "#.window", globalThis);
+var globalWorker = JS('', "#.Worker", globalThis);
+bool globalPostMessageDefined =
+    JS('', "#.postMessage !== (void 0)", globalThis);
+
+class IsolateNatives {
+
+  static String thisScript = computeThisScript();
+
+  /**
+   * The src url for the script tag that loaded this code. Used to create
+   * JavaScript workers.
+   */
+  static String computeThisScript() {
+    // TODO(7369): Find a cross-platform non-brittle way of getting the
+    // currently running script.
+    var scripts = JS('', r"document.getElementsByTagName('script')");
+    // The scripts variable only contains the scripts that have already been
+    // executed. The last one is the currently running script.
+    for (int i = 0, len = JS('int', '#.length', scripts); i < len; i++) {
+      var script = JS('', '#[#]', scripts, i);
+      var src = JS('String|Null', '# && #.src', script, script);
+      // Filter out the test controller script, and the Dart
+      // bootstrap script.
+      if (src != null
+          && !src.endsWith('test_controller.js')
+          && !src.endsWith('dart.js')) {
+        return src;
+      }
+    }
+    return null;
+  }
+
+  static computeGlobalThis() => JS('', 'function() { return this; }()');
+
+  /** Starts a new worker with the given URL. */
+  static _WorkerStub _newWorker(url) => JS("_WorkerStub", r"new Worker(#)", url);
+
+  /**
+   * Assume that [e] is a browser message event and extract its message data.
+   * We don't import the dom explicitly so, when workers are disabled, this
+   * library can also run on top of nodejs.
+   */
+  static _getEventData(e) => JS("", "#.data", e);
+
+  /**
+   * Process messages on a worker, either to control the worker instance or to
+   * pass messages along to the isolate running in the worker.
+   */
+  static void _processWorkerMessage(sender, e) {
+    var msg = _deserializeMessage(_getEventData(e));
+    switch (msg['command']) {
+      case 'start':
+        _globalState.currentManagerId = msg['id'];
+        Function entryPoint = _getJSFunctionFromName(msg['functionName']);
+        var replyTo = _deserializeMessage(msg['replyTo']);
+        var context = new _IsolateContext();
+        _globalState.topEventLoop.enqueue(context, function() {
+          _startIsolate(entryPoint, replyTo);
+        }, 'worker-start');
+        // Make sure we always have a current context in this worker.
+        // TODO(7907): This is currently needed because we're using
+        // Timers to implement Futures, and this isolate library
+        // implementation uses Futures. We should either stop using
+        // Futures in this library, or re-adapt if Futures get a
+        // different implementation.
+        _globalState.currentContext = context;
+        _globalState.topEventLoop.run();
+        break;
+      case 'spawn-worker':
+        _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']);
+        break;
+      case 'message':
+        SendPort port = msg['port'];
+        // If the port has been closed, we ignore the message.
+        if (port != null) {
+          msg['port'].send(msg['msg'], msg['replyTo']);
+        }
+        _globalState.topEventLoop.run();
+        break;
+      case 'close':
+        _log("Closing Worker");
+        _globalState.managers.remove(sender.id);
+        sender.terminate();
+        _globalState.topEventLoop.run();
+        break;
+      case 'log':
+        _log(msg['msg']);
+        break;
+      case 'print':
+        if (_globalState.isWorker) {
+          _globalState.mainManager.postMessage(
+              _serializeMessage({'command': 'print', 'msg': msg}));
+        } else {
+          print(msg['msg']);
+        }
+        break;
+      case 'error':
+        throw msg['msg'];
+    }
+  }
+
+  /** Log a message, forwarding to the main [_Manager] if appropriate. */
+  static _log(msg) {
+    if (_globalState.isWorker) {
+      _globalState.mainManager.postMessage(
+          _serializeMessage({'command': 'log', 'msg': msg }));
+    } else {
+      try {
+        _consoleLog(msg);
+      } catch (e, trace) {
+        throw new Exception(trace);
+      }
+    }
+  }
+
+  static void _consoleLog(msg) {
+    JS("void", r"#.console.log(#)", globalThis, msg);
+  }
+
+  /** Find a constructor given its name. */
+  static dynamic _getJSConstructorFromName(String factoryName) {
+    return JS("", r"$[#]", factoryName);
+  }
+
+  static dynamic _getJSFunctionFromName(String functionName) {
+    return JS("", r"$[#]", functionName);
+  }
+
+  /**
+   * Get a string name for the function, if possible.  The result for
+   * anonymous functions is browser-dependent -- it may be "" or "anonymous"
+   * but you should probably not count on this.
+   */
+  static String _getJSFunctionName(Function f) {
+    return JS("String|Null", r"(#.$name || #)", f, null);
+  }
+
+  /** Create a new JavaScript object instance given its constructor. */
+  static dynamic _allocate(var ctor) {
+    return JS("", "new #()", ctor);
+  }
+
+  static SendPort spawnFunction(void topLevelFunction()) {
+    final name = _getJSFunctionName(topLevelFunction);
+    if (name == null) {
+      throw new UnsupportedError(
+          "only top-level functions can be spawned.");
+    }
+    return spawn(name, null, false);
+  }
+
+  static SendPort spawnDomFunction(void topLevelFunction()) {
+    final name = _getJSFunctionName(topLevelFunction);
+    if (name == null) {
+      throw new UnsupportedError(
+          "only top-level functions can be spawned.");
+    }
+    return spawn(name, null, true);
+  }
+
+  // TODO(sigmund): clean up above, after we make the new API the default:
+
+  static spawn(String functionName, String uri, bool isLight) {
+    Completer<SendPort> completer = new Completer<SendPort>();
+    ReceivePort port = new ReceivePort();
+    port.receive((msg, SendPort replyPort) {
+      port.close();
+      assert(msg == _SPAWNED_SIGNAL);
+      completer.complete(replyPort);
+    });
+
+    SendPort signalReply = port.toSendPort();
+
+    if (_globalState.useWorkers && !isLight) {
+      _startWorker(functionName, uri, signalReply);
+    } else {
+      _startNonWorker(functionName, uri, signalReply);
+    }
+    return new _BufferingSendPort(
+        _globalState.currentContext.id, completer.future);
+  }
+
+  static SendPort _startWorker(
+      String functionName, String uri, SendPort replyPort) {
+    if (_globalState.isWorker) {
+      _globalState.mainManager.postMessage(_serializeMessage({
+          'command': 'spawn-worker',
+          'functionName': functionName,
+          'uri': uri,
+          'replyPort': replyPort}));
+    } else {
+      _spawnWorker(functionName, uri, replyPort);
+    }
+  }
+
+  static SendPort _startNonWorker(
+      String functionName, String uri, SendPort replyPort) {
+    // TODO(eub): support IE9 using an iframe -- Dart issue 1702.
+    if (uri != null) throw new UnsupportedError(
+            "Currently spawnUri is not supported without web workers.");
+    _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
+      final func = _getJSFunctionFromName(functionName);
+      _startIsolate(func, replyPort);
+    }, 'nonworker start');
+  }
+
+  static void _startIsolate(Function topLevel, SendPort replyTo) {
+    lazyPort = new ReceivePort();
+    replyTo.send(_SPAWNED_SIGNAL, port.toSendPort());
+    topLevel();
+  }
+
+  /**
+   * Spawns an isolate in a worker. [factoryName] is the Javascript constructor
+   * name for the isolate entry point class.
+   */
+  static void _spawnWorker(functionName, uri, replyPort) {
+    if (functionName == null) functionName = 'main';
+    if (uri == null) uri = thisScript;
+    final worker = _newWorker(uri);
+    worker.onmessage = JS('',
+                          'function(e) { #(#, e); }',
+                          DART_CLOSURE_TO_JS(_processWorkerMessage),
+                          worker);
+    var workerId = _globalState.nextManagerId++;
+    // We also store the id on the worker itself so that we can unregister it.
+    worker.id = workerId;
+    _globalState.managers[workerId] = worker;
+    worker.postMessage(_serializeMessage({
+      'command': 'start',
+      'id': workerId,
+      // Note: we serialize replyPort twice because the child worker needs to
+      // first deserialize the worker id, before it can correctly deserialize
+      // the port (port deserialization is sensitive to what is the current
+      // workerId).
+      'replyTo': _serializeMessage(replyPort),
+      'functionName': functionName }));
+  }
+}
+
+/********************************************************
+  Inserted from lib/isolate/dart2js/ports.dart
+ ********************************************************/
+
+/** Common functionality to all send ports. */
+class _BaseSendPort implements SendPort {
+  /** Id for the destination isolate. */
+  final int _isolateId;
+
+  const _BaseSendPort(this._isolateId);
+
+  void _checkReplyTo(SendPort replyTo) {
+    if (replyTo != null
+        && replyTo is! _NativeJsSendPort
+        && replyTo is! _WorkerSendPort
+        && replyTo is! _BufferingSendPort) {
+      throw new Exception("SendPort.send: Illegal replyTo port type");
+    }
+  }
+
+  Future call(var message) {
+    final completer = new Completer();
+    final port = new ReceivePortImpl();
+    send(message, port.toSendPort());
+    port.receive((value, ignoreReplyTo) {
+      port.close();
+      if (value is Exception) {
+        completer.completeError(value);
+      } else {
+        completer.complete(value);
+      }
+    });
+    return completer.future;
+  }
+
+  void send(var message, [SendPort replyTo]);
+  bool operator ==(var other);
+  int get hashCode;
+}
+
+/** A send port that delivers messages in-memory via native JavaScript calls. */
+class _NativeJsSendPort extends _BaseSendPort implements SendPort {
+  final ReceivePortImpl _receivePort;
+
+  const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId);
+
+  void send(var message, [SendPort replyTo = null]) {
+    _waitForPendingPorts([message, replyTo], () {
+      _checkReplyTo(replyTo);
+      // Check that the isolate still runs and the port is still open
+      final isolate = _globalState.isolates[_isolateId];
+      if (isolate == null) return;
+      if (_receivePort._callback == null) return;
+
+      // We force serialization/deserialization as a simple way to ensure
+      // isolate communication restrictions are respected between isolates that
+      // live in the same worker. [_NativeJsSendPort] delivers both messages
+      // from the same worker and messages from other workers. In particular,
+      // messages sent from a worker via a [_WorkerSendPort] are received at
+      // [_processWorkerMessage] and forwarded to a native port. In such cases,
+      // here we'll see [_globalState.currentContext == null].
+      final shouldSerialize = _globalState.currentContext != null
+          && _globalState.currentContext.id != _isolateId;
+      var msg = message;
+      var reply = replyTo;
+      if (shouldSerialize) {
+        msg = _serializeMessage(msg);
+        reply = _serializeMessage(reply);
+      }
+      _globalState.topEventLoop.enqueue(isolate, () {
+        if (_receivePort._callback != null) {
+          if (shouldSerialize) {
+            msg = _deserializeMessage(msg);
+            reply = _deserializeMessage(reply);
+          }
+          _receivePort._callback(msg, reply);
+        }
+      }, 'receive $message');
+    });
+  }
+
+  bool operator ==(var other) => (other is _NativeJsSendPort) &&
+      (_receivePort == other._receivePort);
+
+  int get hashCode => _receivePort._id;
+}
+
+/** A send port that delivers messages via worker.postMessage. */
+// TODO(eub): abstract this for iframes.
+class _WorkerSendPort extends _BaseSendPort implements SendPort {
+  final int _workerId;
+  final int _receivePortId;
+
+  const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId)
+      : super(isolateId);
+
+  void send(var message, [SendPort replyTo = null]) {
+    _waitForPendingPorts([message, replyTo], () {
+      _checkReplyTo(replyTo);
+      final workerMessage = _serializeMessage({
+          'command': 'message',
+          'port': this,
+          'msg': message,
+          'replyTo': replyTo});
+
+      if (_globalState.isWorker) {
+        // Communication from one worker to another go through the
+        // main worker.
+        _globalState.mainManager.postMessage(workerMessage);
+      } else {
+        // Deliver the message only if the worker is still alive.
+        _ManagerStub manager = _globalState.managers[_workerId];
+        if (manager != null) {
+          manager.postMessage(workerMessage);
+        }
+      }
+    });
+  }
+
+  bool operator ==(var other) {
+    return (other is _WorkerSendPort) &&
+        (_workerId == other._workerId) &&
+        (_isolateId == other._isolateId) &&
+        (_receivePortId == other._receivePortId);
+  }
+
+  int get hashCode {
+    // TODO(sigmund): use a standard hash when we get one available in corelib.
+    return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId;
+  }
+}
+
+/** A port that buffers messages until an underlying port gets resolved. */
+class _BufferingSendPort extends _BaseSendPort implements SendPort {
+  /** Internal counter to assign unique ids to each port. */
+  static int _idCount = 0;
+
+  /** For implementing equals and hashcode. */
+  final int _id;
+
+  /** Underlying port, when resolved. */
+  SendPort _port;
+
+  /**
+   * Future of the underlying port, so that we can detect when this port can be
+   * sent on messages.
+   */
+  Future<SendPort> _futurePort;
+
+  /** Pending messages (and reply ports). */
+  List pending;
+
+  _BufferingSendPort(isolateId, this._futurePort)
+      : super(isolateId), _id = _idCount, pending = [] {
+    _idCount++;
+    _futurePort.then((p) {
+      _port = p;
+      for (final item in pending) {
+        p.send(item['message'], item['replyTo']);
+      }
+      pending = null;
+    });
+  }
+
+  _BufferingSendPort.fromPort(isolateId, this._port)
+      : super(isolateId), _id = _idCount {
+    _idCount++;
+  }
+
+  void send(var message, [SendPort replyTo]) {
+    if (_port != null) {
+      _port.send(message, replyTo);
+    } else {
+      pending.add({'message': message, 'replyTo': replyTo});
+    }
+  }
+
+  bool operator ==(var other) =>
+      other is _BufferingSendPort && _id == other._id;
+  int get hashCode => _id;
+}
+
+/** Implementation of a multi-use [ReceivePort] on top of JavaScript. */
+class ReceivePortImpl implements ReceivePort {
+  int _id;
+  Function _callback;
+  static int _nextFreeId = 1;
+
+  ReceivePortImpl()
+      : _id = _nextFreeId++ {
+    _globalState.currentContext.register(_id, this);
+  }
+
+  void receive(void onMessage(var message, SendPort replyTo)) {
+    _callback = onMessage;
+  }
+
+  void close() {
+    _callback = null;
+    _globalState.currentContext.unregister(_id);
+  }
+
+  SendPort toSendPort() {
+    return new _NativeJsSendPort(this, _globalState.currentContext.id);
+  }
+}
+
+/** Wait until all ports in a message are resolved. */
+_waitForPendingPorts(var message, void callback()) {
+  final finder = new _PendingSendPortFinder();
+  finder.traverse(message);
+  Future.wait(finder.ports).then((_) => callback());
+}
+
+
+/** Visitor that finds all unresolved [SendPort]s in a message. */
+class _PendingSendPortFinder extends _MessageTraverser {
+  List<Future<SendPort>> ports;
+  _PendingSendPortFinder() : super(), ports = [] {
+    _visited = new _JsVisitedMap();
+  }
+
+  visitPrimitive(x) {}
+
+  visitList(List list) {
+    final seen = _visited[list];
+    if (seen != null) return;
+    _visited[list] = true;
+    // TODO(sigmund): replace with the following: (bug #1660)
+    // list.forEach(_dispatch);
+    list.forEach((e) => _dispatch(e));
+  }
+
+  visitMap(Map map) {
+    final seen = _visited[map];
+    if (seen != null) return;
+
+    _visited[map] = true;
+    // TODO(sigmund): replace with the following: (bug #1660)
+    // map.values.forEach(_dispatch);
+    map.values.forEach((e) => _dispatch(e));
+  }
+
+  visitSendPort(SendPort port) {
+    if (port is _BufferingSendPort && port._port == null) {
+      ports.add(port._futurePort);
+    }
+  }
+}
+
+/********************************************************
+  Inserted from lib/isolate/dart2js/messages.dart
+ ********************************************************/
+
+// Defines message visitors, serialization, and deserialization.
+
+/** Serialize [message] (or simulate serialization). */
+_serializeMessage(message) {
+  if (_globalState.needSerialization) {
+    return new _JsSerializer().traverse(message);
+  } else {
+    return new _JsCopier().traverse(message);
+  }
+}
+
+/** Deserialize [message] (or simulate deserialization). */
+_deserializeMessage(message) {
+  if (_globalState.needSerialization) {
+    return new _JsDeserializer().deserialize(message);
+  } else {
+    // Nothing more to do.
+    return message;
+  }
+}
+
+class _JsSerializer extends _Serializer {
+
+  _JsSerializer() : super() { _visited = new _JsVisitedMap(); }
+
+  visitSendPort(SendPort x) {
+    if (x is _NativeJsSendPort) return visitNativeJsSendPort(x);
+    if (x is _WorkerSendPort) return visitWorkerSendPort(x);
+    if (x is _BufferingSendPort) return visitBufferingSendPort(x);
+    throw "Illegal underlying port $x";
+  }
+
+  visitNativeJsSendPort(_NativeJsSendPort port) {
+    return ['sendport', _globalState.currentManagerId,
+        port._isolateId, port._receivePort._id];
+  }
+
+  visitWorkerSendPort(_WorkerSendPort port) {
+    return ['sendport', port._workerId, port._isolateId, port._receivePortId];
+  }
+
+  visitBufferingSendPort(_BufferingSendPort port) {
+    if (port._port != null) {
+      return visitSendPort(port._port);
+    } else {
+      // TODO(floitsch): Use real exception (which one?).
+      throw
+          "internal error: must call _waitForPendingPorts to ensure all"
+          " ports are resolved at this point.";
+    }
+  }
+
+}
+
+
+class _JsCopier extends _Copier {
+
+  _JsCopier() : super() { _visited = new _JsVisitedMap(); }
+
+  visitSendPort(SendPort x) {
+    if (x is _NativeJsSendPort) return visitNativeJsSendPort(x);
+    if (x is _WorkerSendPort) return visitWorkerSendPort(x);
+    if (x is _BufferingSendPort) return visitBufferingSendPort(x);
+    throw "Illegal underlying port $p";
+  }
+
+  SendPort visitNativeJsSendPort(_NativeJsSendPort port) {
+    return new _NativeJsSendPort(port._receivePort, port._isolateId);
+  }
+
+  SendPort visitWorkerSendPort(_WorkerSendPort port) {
+    return new _WorkerSendPort(
+        port._workerId, port._isolateId, port._receivePortId);
+  }
+
+  SendPort visitBufferingSendPort(_BufferingSendPort port) {
+    if (port._port != null) {
+      return visitSendPort(port._port);
+    } else {
+      // TODO(floitsch): Use real exception (which one?).
+      throw
+          "internal error: must call _waitForPendingPorts to ensure all"
+          " ports are resolved at this point.";
+    }
+  }
+
+}
+
+class _JsDeserializer extends _Deserializer {
+
+  SendPort deserializeSendPort(List x) {
+    int managerId = x[1];
+    int isolateId = x[2];
+    int receivePortId = x[3];
+    // If two isolates are in the same manager, we use NativeJsSendPorts to
+    // deliver messages directly without using postMessage.
+    if (managerId == _globalState.currentManagerId) {
+      var isolate = _globalState.isolates[isolateId];
+      if (isolate == null) return null; // Isolate has been closed.
+      var receivePort = isolate.lookup(receivePortId);
+      if (receivePort == null) return null; // Port has been closed.
+      return new _NativeJsSendPort(receivePort, isolateId);
+    } else {
+      return new _WorkerSendPort(managerId, isolateId, receivePortId);
+    }
+  }
+
+}
+
+class _JsVisitedMap implements _MessageTraverserVisitedMap {
+  List tagged;
+
+  /** Retrieves any information stored in the native object [object]. */
+  operator[](var object) {
+    return _getAttachedInfo(object);
+  }
+
+  /** Injects some information into the native [object]. */
+  void operator[]=(var object, var info) {
+    tagged.add(object);
+    _setAttachedInfo(object, info);
+  }
+
+  /** Get ready to rumble. */
+  void reset() {
+    assert(tagged == null);
+    tagged = new List();
+  }
+
+  /** Remove all information injected in the native objects. */
+  void cleanup() {
+    for (int i = 0, length = tagged.length; i < length; i++) {
+      _clearAttachedInfo(tagged[i]);
+    }
+    tagged = null;
+  }
+
+  void _clearAttachedInfo(var o) {
+    JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null);
+  }
+
+  void _setAttachedInfo(var o, var info) {
+    JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info);
+  }
+
+  _getAttachedInfo(var o) {
+    return JS("", "#['__MessageTraverser__attached_info__']", o);
+  }
+}
+
+// only visible for testing purposes
+// TODO(sigmund): remove once we can disable privacy for testing (bug #1882)
+class TestingOnly {
+  static copy(x) {
+    return new _JsCopier().traverse(x);
+  }
+
+  // only visible for testing purposes
+  static serialize(x) {
+    _Serializer serializer = new _JsSerializer();
+    _Deserializer deserializer = new _JsDeserializer();
+    return deserializer.deserialize(serializer.traverse(x));
+  }
+}
+
+/********************************************************
+  Inserted from lib/isolate/serialization.dart
+ ********************************************************/
+
+class _MessageTraverserVisitedMap {
+
+  operator[](var object) => null;
+  void operator[]=(var object, var info) { }
+
+  void reset() { }
+  void cleanup() { }
+
+}
+
+/** Abstract visitor for dart objects that can be sent as isolate messages. */
+class _MessageTraverser {
+
+  _MessageTraverserVisitedMap _visited;
+  _MessageTraverser() : _visited = new _MessageTraverserVisitedMap();
+
+  /** Visitor's entry point. */
+  traverse(var x) {
+    if (isPrimitive(x)) return visitPrimitive(x);
+    _visited.reset();
+    var result;
+    try {
+      result = _dispatch(x);
+    } finally {
+      _visited.cleanup();
+    }
+    return result;
+  }
+
+  _dispatch(var x) {
+    if (isPrimitive(x)) return visitPrimitive(x);
+    if (x is List) return visitList(x);
+    if (x is Map) return visitMap(x);
+    if (x is SendPort) return visitSendPort(x);
+    if (x is SendPortSync) return visitSendPortSync(x);
+
+    // Overridable fallback.
+    return visitObject(x);
+  }
+
+  visitPrimitive(x);
+  visitList(List x);
+  visitMap(Map x);
+  visitSendPort(SendPort x);
+  visitSendPortSync(SendPortSync x);
+
+  visitObject(Object x) {
+    // TODO(floitsch): make this a real exception. (which one)?
+    throw "Message serialization: Illegal value $x passed";
+  }
+
+  static bool isPrimitive(x) {
+    return (x == null) || (x is String) || (x is num) || (x is bool);
+  }
+}
+
+
+/** A visitor that recursively copies a message. */
+class _Copier extends _MessageTraverser {
+
+  visitPrimitive(x) => x;
+
+  List visitList(List list) {
+    List copy = _visited[list];
+    if (copy != null) return copy;
+
+    int len = list.length;
+
+    // TODO(floitsch): we loose the generic type of the List.
+    copy = new List(len);
+    _visited[list] = copy;
+    for (int i = 0; i < len; i++) {
+      copy[i] = _dispatch(list[i]);
+    }
+    return copy;
+  }
+
+  Map visitMap(Map map) {
+    Map copy = _visited[map];
+    if (copy != null) return copy;
+
+    // TODO(floitsch): we loose the generic type of the map.
+    copy = new Map();
+    _visited[map] = copy;
+    map.forEach((key, val) {
+      copy[_dispatch(key)] = _dispatch(val);
+    });
+    return copy;
+  }
+
+}
+
+/** Visitor that serializes a message as a JSON array. */
+class _Serializer extends _MessageTraverser {
+  int _nextFreeRefId = 0;
+
+  visitPrimitive(x) => x;
+
+  visitList(List list) {
+    int copyId = _visited[list];
+    if (copyId != null) return ['ref', copyId];
+
+    int id = _nextFreeRefId++;
+    _visited[list] = id;
+    var jsArray = _serializeList(list);
+    // TODO(floitsch): we are losing the generic type.
+    return ['list', id, jsArray];
+  }
+
+  visitMap(Map map) {
+    int copyId = _visited[map];
+    if (copyId != null) return ['ref', copyId];
+
+    int id = _nextFreeRefId++;
+    _visited[map] = id;
+    var keys = _serializeList(map.keys.toList());
+    var values = _serializeList(map.values.toList());
+    // TODO(floitsch): we are losing the generic type.
+    return ['map', id, keys, values];
+  }
+
+  _serializeList(List list) {
+    int len = list.length;
+    var result = new List(len);
+    for (int i = 0; i < len; i++) {
+      result[i] = _dispatch(list[i]);
+    }
+    return result;
+  }
+}
+
+/** Deserializes arrays created with [_Serializer]. */
+class _Deserializer {
+  Map<int, dynamic> _deserialized;
+
+  _Deserializer();
+
+  static bool isPrimitive(x) {
+    return (x == null) || (x is String) || (x is num) || (x is bool);
+  }
+
+  deserialize(x) {
+    if (isPrimitive(x)) return x;
+    // TODO(floitsch): this should be new HashMap<int, var|Dynamic>()
+    _deserialized = new HashMap();
+    return _deserializeHelper(x);
+  }
+
+  _deserializeHelper(x) {
+    if (isPrimitive(x)) return x;
+    assert(x is List);
+    switch (x[0]) {
+      case 'ref': return _deserializeRef(x);
+      case 'list': return _deserializeList(x);
+      case 'map': return _deserializeMap(x);
+      case 'sendport': return deserializeSendPort(x);
+      default: return deserializeObject(x);
+    }
+  }
+
+  _deserializeRef(List x) {
+    int id = x[1];
+    var result = _deserialized[id];
+    assert(result != null);
+    return result;
+  }
+
+  List _deserializeList(List x) {
+    int id = x[1];
+    // We rely on the fact that Dart-lists are directly mapped to Js-arrays.
+    List dartList = x[2];
+    _deserialized[id] = dartList;
+    int len = dartList.length;
+    for (int i = 0; i < len; i++) {
+      dartList[i] = _deserializeHelper(dartList[i]);
+    }
+    return dartList;
+  }
+
+  Map _deserializeMap(List x) {
+    Map result = new Map();
+    int id = x[1];
+    _deserialized[id] = result;
+    List keys = x[2];
+    List values = x[3];
+    int len = keys.length;
+    assert(len == values.length);
+    for (int i = 0; i < len; i++) {
+      var key = _deserializeHelper(keys[i]);
+      var value = _deserializeHelper(values[i]);
+      result[key] = value;
+    }
+    return result;
+  }
+
+  deserializeSendPort(List x);
+
+  deserializeObject(List x) {
+    // TODO(floitsch): Use real exception (which one?).
+    throw "Unexpected serialized object";
+  }
+}
+
+class TimerImpl implements Timer {
+  final bool _once;
+  bool _inEventLoop = false;
+  int _handle;
+
+  TimerImpl(int milliseconds, void callback(Timer timer))
+      : _once = true {
+    if (milliseconds == 0 && (!hasTimer() || _globalState.isWorker)) {
+      // This makes a dependency between the async library and the
+      // event loop of the isolate library. The compiler makes sure
+      // that the event loop is compiled if [Timer] is used.
+      // TODO(7907): In case of web workers, we need to use the event
+      // loop instead of setTimeout, to make sure the futures get executed in
+      // order.
+      _globalState.topEventLoop.enqueue(_globalState.currentContext, () {
+        callback(this);
+      }, 'timer');
+      _inEventLoop = true;
+    } else if (hasTimer()) {
+      _globalState.topEventLoop.activeTimerCount++;
+      void internalCallback() {
+        callback(this);
+        _handle = null;
+        _globalState.topEventLoop.activeTimerCount--;
+      }
+      _handle = JS('int', '#.setTimeout(#, #)',
+                   globalThis,
+                   convertDartClosureToJS(internalCallback, 0),
+                   milliseconds);
+    } else {
+      assert(milliseconds > 0);
+      throw new UnsupportedError("Timer greater than 0.");
+    }
+  }
+
+  TimerImpl.repeating(int milliseconds, void callback(Timer timer))
+      : _once = false {
+    if (hasTimer()) {
+      _globalState.topEventLoop.activeTimerCount++;
+      _handle = JS('int', '#.setInterval(#, #)',
+                   globalThis,
+                   convertDartClosureToJS(() { callback(this); }, 0),
+                   milliseconds);
+    } else {
+      throw new UnsupportedError("Repeating timer.");
+    }
+  }
+
+  void cancel() {
+    if (hasTimer()) {
+      if (_inEventLoop) {
+        throw new UnsupportedError("Timer in event loop cannot be canceled.");
+      }
+      if (_handle == null) return;
+      _globalState.topEventLoop.activeTimerCount--;
+      if (_once) {
+        JS('void', '#.clearTimeout(#)', globalThis, _handle);
+      } else {
+        JS('void', '#.clearInterval(#)', globalThis, _handle);
+      }
+      _handle = null;
+    } else {
+      throw new UnsupportedError("Canceling a timer.");
+    }
+  }
+}
+
+bool hasTimer() => JS('', '#.setTimeout', globalThis) != null;
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart
new file mode 100644
index 0000000..aab847a
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart
@@ -0,0 +1,34 @@
+// 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.
+
+// Patch file for the dart:isolate library.
+
+import 'dart:_isolate_helper' show IsolateNatives,
+                                   lazyPort,
+                                   ReceivePortImpl;
+
+patch class _Isolate {
+  patch static ReceivePort get port {
+    if (lazyPort == null) {
+      lazyPort = new ReceivePort();
+    }
+    return lazyPort;
+  }
+
+  patch static SendPort spawnFunction(void topLevelFunction(),
+      [bool UnhandledExceptionCallback(IsolateUnhandledException e)]) {
+    return IsolateNatives.spawnFunction(topLevelFunction);
+  }
+
+  patch static SendPort spawnUri(String uri) {
+    return IsolateNatives.spawn(null, uri, false);
+  }
+}
+
+/** Default factory for receive ports. */
+patch class ReceivePort {
+  patch factory ReceivePort() {
+    return new ReceivePortImpl();
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart
new file mode 100644
index 0000000..e33ae3f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart
@@ -0,0 +1,295 @@
+// 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.
+
+part of _interceptors;
+
+/**
+ * The interceptor class for [List]. The compiler recognizes this
+ * class as an interceptor, and changes references to [:this:] to
+ * actually use the receiver of the method, which is generated as an extra
+ * argument added to each member.
+ */
+class JSArray<E> implements List<E> {
+  const JSArray();
+
+  void add(E value) {
+    checkGrowable(this, 'add');
+    JS('void', r'#.push(#)', this, value);
+  }
+
+  E removeAt(int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index < 0 || index >= length) {
+      throw new RangeError.value(index);
+    }
+    checkGrowable(this, 'removeAt');
+    return JS('var', r'#.splice(#, 1)[0]', this, index);
+  }
+
+  E removeLast() {
+    checkGrowable(this, 'removeLast');
+    if (length == 0) throw new RangeError.value(-1);
+    return JS('var', r'#.pop()', this);
+  }
+
+  void remove(Object element) {
+    checkGrowable(this, 'remove');
+    for (int i = 0; i < this.length; i++) {
+      if (this[i] == element) {
+        JS('var', r'#.splice(#, 1)', this, i);
+        return;
+      }
+    }
+  }
+
+  void removeAll(Iterable elements) {
+    IterableMixinWorkaround.removeAllList(this, elements);
+  }
+
+  void retainAll(Iterable elements) {
+    IterableMixinWorkaround.retainAll(this, elements);
+  }
+
+  void removeMatching(bool test(E element)) {
+    // This could, and should, be optimized.
+    IterableMixinWorkaround.removeMatchingList(this, test);
+  }
+
+  void retainMatching(bool test(E element)) {
+    IterableMixinWorkaround.removeMatchingList(this,
+                                               (E element) => !test(element));
+  }
+
+  Iterable<E> where(bool f(E element)) {
+    return IterableMixinWorkaround.where(this, f);
+  }
+
+  Iterable expand(Iterable f(E element)) {
+    return IterableMixinWorkaround.expand(this, f);
+  }
+
+  void addAll(Collection<E> collection) {
+    for (E e in collection) {
+      this.add(e);
+    }
+  }
+
+  void addLast(E value) {
+    checkGrowable(this, 'addLast');
+    JS('void', r'#.push(#)', this, value);
+  }
+
+  void clear() {
+    length = 0;
+  }
+
+  void forEach(void f(E element)) {
+    return IterableMixinWorkaround.forEach(this, f);
+  }
+
+  Iterable map(f(E element)) {
+    return IterableMixinWorkaround.mapList(this, f);
+  }
+
+  List mappedBy(f(E element)) {
+    return IterableMixinWorkaround.mappedByList(this, f);
+  }
+
+  String join([String separator]) {
+    if (separator == null) separator = "";
+    var list = new List(this.length);
+    for (int i = 0; i < this.length; i++) {
+      list[i] = "${this[i]}";
+    }
+    return JS('String', "#.join(#)", list, separator);
+  }
+
+  Iterable<E> take(int n) {
+    return IterableMixinWorkaround.takeList(this, n);
+  }
+
+  Iterable<E> takeWhile(bool test(E value)) {
+    return IterableMixinWorkaround.takeWhile(this, test);
+  }
+
+  Iterable<E> skip(int n) {
+    return IterableMixinWorkaround.skipList(this, n);
+  }
+
+  Iterable<E> skipWhile(bool test(E value)) {
+    return IterableMixinWorkaround.skipWhile(this, test);
+  }
+
+  reduce(initialValue, combine(previousValue, E element)) {
+    return IterableMixinWorkaround.reduce(this, initialValue, combine);
+  }
+
+  E firstMatching(bool test(E value), {E orElse()}) {
+    return IterableMixinWorkaround.firstMatching(this, test, orElse);
+  }
+
+  E lastMatching(bool test(E value), {E orElse()}) {
+    return IterableMixinWorkaround.lastMatchingInList(this, test, orElse);
+  }
+
+  E singleMatching(bool test(E value)) {
+    return IterableMixinWorkaround.singleMatching(this, test);
+  }
+
+  E elementAt(int index) {
+    return this[index];
+  }
+
+  List<E> getRange(int start, int length) {
+    // TODO(ngeoffray): Parameterize the return value.
+    if (0 == length) return [];
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (length < 0) throw new ArgumentError(length);
+    if (start < 0) throw new RangeError.value(start);
+    int end = start + length;
+    if (end > this.length) {
+      throw new RangeError.value(length);
+    }
+    if (length < 0) throw new ArgumentError(length);
+    return JS('=List', r'#.slice(#, #)', this, start, end);
+  }
+
+  void insertRange(int start, int length, [E initialValue]) {
+    return listInsertRange(this, start, length, initialValue);
+  }
+
+  E get first {
+    if (length > 0) return this[0];
+    throw new StateError("No elements");
+  }
+
+  E get last {
+    if (length > 0) return this[length - 1];
+    throw new StateError("No elements");
+  }
+
+  E get single {
+    if (length == 1) return this[0];
+    if (length == 0) throw new StateError("No elements");
+    throw new StateError("More than one element");
+  }
+
+  E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare);
+
+  E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare);
+
+  void removeRange(int start, int length) {
+    checkGrowable(this, 'removeRange');
+    if (length == 0) {
+      return;
+    }
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (length < 0) throw new ArgumentError(length);
+    var receiverLength = this.length;
+    if (start < 0 || start >= receiverLength) {
+      throw new RangeError.value(start);
+    }
+    if (start + length > receiverLength) {
+      throw new RangeError.value(start + length);
+    }
+    Arrays.copy(this,
+                start + length,
+                this,
+                start,
+                receiverLength - length - start);
+    this.length = receiverLength - length;
+  }
+
+  void setRange(int start, int length, List<E> from, [int startFrom = 0]) {
+    checkMutable(this, 'set range');
+    if (length == 0) return;
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(from); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(startFrom); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (startFrom is !int) throw new ArgumentError(startFrom);
+    if (length < 0) throw new ArgumentError(length);
+    if (start < 0) throw new RangeError.value(start);
+    if (start + length > this.length) {
+      throw new RangeError.value(start + length);
+    }
+
+    Arrays.copy(from, startFrom, this, start, length);
+  }
+
+  bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f);
+
+  bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f);
+
+  List<E> get reversed => IterableMixinWorkaround.reversedList(this);
+
+  void sort([int compare(E a, E b)]) {
+    checkMutable(this, 'sort');
+    IterableMixinWorkaround.sortList(this, compare);
+  }
+
+  int indexOf(E element, [int start = 0]) {
+    if (start is !int) throw new ArgumentError(start);
+    return Arrays.indexOf(this, element, start, length);
+  }
+
+  int lastIndexOf(E element, [int start]) {
+    if (start == null) start = this.length - 1;
+    return Arrays.lastIndexOf(this, element, start);
+  }
+
+  bool contains(E other) {
+    for (int i = 0; i < length; i++) {
+      if (other == this[i]) return true;
+    }
+    return false;
+  }
+
+  bool get isEmpty => length == 0;
+
+  String toString() => Collections.collectionToString(this);
+
+  List<E> toList() => new List<E>.from(this);
+
+  Set<E> toSet() => new Set<E>.from(this);
+
+  Iterator<E> get iterator => new ListIterator<E>(this);
+
+  int get hashCode => Primitives.objectHashCode(this);
+
+  Type get runtimeType {
+    // Call getRuntimeTypeString to get the name including type arguments.
+    return new TypeImpl(getRuntimeTypeString(this));
+  }
+
+  int get length => JS('int', r'#.length', this);
+
+  void set length(int newLength) {
+    if (newLength is !int) throw new ArgumentError(newLength);
+    if (newLength < 0) throw new RangeError.value(newLength);
+    checkGrowable(this, 'set length');
+    JS('void', r'#.length = #', this, newLength);
+  }
+
+  E operator [](int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index >= length || index < 0) throw new RangeError.value(index);
+    return JS('var', '#[#]', this, index);
+  }
+
+  void operator []=(int index, E value) {
+    checkMutable(this, 'indexed set');
+    if (index is !int) throw new ArgumentError(index);
+    if (index >= length || index < 0) throw new RangeError.value(index);
+    JS('void', r'#[#] = #', this, index, value);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart
new file mode 100644
index 0000000..0fbff9c
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart
@@ -0,0 +1,1513 @@
+// 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.
+
+library _js_helper;
+
+import 'dart:collection';
+import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS,
+                                   JS,
+                                   JS_CALL_IN_ISOLATE,
+                                   JS_CURRENT_ISOLATE,
+                                   JS_OPERATOR_IS_PREFIX,
+                                   JS_HAS_EQUALS,
+                                   RAW_DART_FUNCTION_REF,
+                                   UNINTERCEPTED;
+import 'dart:_interceptors' show getInterceptor;
+
+part 'constant_map.dart';
+part 'native_helper.dart';
+part 'regexp_helper.dart';
+part 'string_helper.dart';
+
+bool isJsArray(var value) {
+  return value != null && JS('bool', r'#.constructor === Array', value);
+}
+
+checkMutable(list, reason) {
+  if (JS('bool', r'!!(#.immutable$list)', list)) {
+    throw new UnsupportedError(reason);
+  }
+}
+
+checkGrowable(list, reason) {
+  if (JS('bool', r'!!(#.fixed$length)', list)) {
+    throw new UnsupportedError(reason);
+  }
+}
+
+String S(value) {
+  if (value is String) return value;
+  if ((value is num && value != 0) || value is bool) {
+    return JS('String', r'String(#)', value);
+  }
+  if (value == null) return 'null';
+  var res = value.toString();
+  if (res is !String) throw new ArgumentError(value);
+  return res;
+}
+
+createInvocationMirror(name, internalName, type, arguments, argumentNames) =>
+    new JSInvocationMirror(name, internalName, type, arguments, argumentNames);
+
+class JSInvocationMirror implements InvocationMirror {
+  static const METHOD = 0;
+  static const GETTER = 1;
+  static const SETTER = 2;
+
+  final String memberName;
+  final String _internalName;
+  final int _kind;
+  final List _arguments;
+  final List _namedArgumentNames;
+  /** Map from argument name to index in _arguments. */
+  Map<String,dynamic> _namedIndices = null;
+
+  JSInvocationMirror(this.memberName,
+                     this._internalName,
+                     this._kind,
+                     this._arguments,
+                     this._namedArgumentNames);
+
+  bool get isMethod => _kind == METHOD;
+  bool get isGetter => _kind == GETTER;
+  bool get isSetter => _kind == SETTER;
+  bool get isAccessor => _kind != METHOD;
+
+  List get positionalArguments {
+    if (isGetter) return null;
+    var list = [];
+    var argumentCount =
+        _arguments.length - _namedArgumentNames.length;
+    for (var index = 0 ; index < argumentCount ; index++) {
+      list.add(_arguments[index]);
+    }
+    return list;
+  }
+
+  Map<String,dynamic> get namedArguments {
+    if (isAccessor) return null;
+    var map = <String,dynamic>{};
+    int namedArgumentCount = _namedArgumentNames.length;
+    int namedArgumentsStartIndex = _arguments.length - namedArgumentCount;
+    for (int i = 0; i < namedArgumentCount; i++) {
+      map[_namedArgumentNames[i]] = _arguments[namedArgumentsStartIndex + i];
+    }
+    return map;
+  }
+
+  static final _objectInterceptor = getInterceptor(new Object());
+  invokeOn(Object object) {
+    var interceptor = getInterceptor(object);
+    var receiver = object;
+    var name = _internalName;
+    var arguments = _arguments;
+    if (identical(interceptor, _objectInterceptor)) {
+      if (!isJsArray(arguments)) arguments = new List.from(arguments);
+    } else {
+      arguments = [object]..addAll(arguments);
+      receiver = interceptor;
+    }
+    return JS("var", "#[#].apply(#, #)", receiver, name, receiver, arguments);
+  }
+}
+
+class Primitives {
+  static int hashCodeSeed = 0;
+
+  static int objectHashCode(object) {
+    int hash = JS('var', r'#.$identityHash', object);
+    if (hash == null) {
+      // TOOD(ahe): We should probably randomize this somehow.
+      hash = ++hashCodeSeed;
+      JS('void', r'#.$identityHash = #', object, hash);
+    }
+    return hash;
+  }
+
+  /**
+   * This is the low-level method that is used to implement
+   * [print]. It is possible to override this function from JavaScript
+   * by defining a function in JavaScript called "dartPrint".
+   */
+  static void printString(String string) {
+    if (JS('bool', r'typeof dartPrint == "function"')) {
+      // Support overriding print from JavaScript.
+      JS('void', r'dartPrint(#)', string);
+      return;
+    }
+
+    // Inside browser.
+    if (JS('bool', r'typeof window == "object"')) {
+      // On IE, the console is only defined if dev tools is open.
+      if (JS('bool', r'typeof console == "object"')) {
+        JS('void', r'console.log(#)', string);
+      }
+      return;
+    }
+
+    // Running in d8, the V8 developer shell, or in Firefox' js-shell.
+    if (JS('bool', r'typeof print == "function"')) {
+      JS('void', r'print(#)', string);
+      return;
+    }
+
+    // This is somewhat nasty, but we don't want to drag in a bunch of
+    // dependencies to handle a situation that cannot happen. So we
+    // avoid using Dart [:throw:] and Dart [toString].
+    JS('void', "throw 'Unable to print message: ' + String(#)", string);
+  }
+
+  static void _throwFormatException(String string) {
+    throw new FormatException(string);
+  }
+
+  static int parseInt(String source,
+                      int radix,
+                      int handleError(String source)) {
+    if (handleError == null) handleError = _throwFormatException;
+
+    checkString(source);
+    var match = JS('=List|Null',
+        r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(#)',
+        source);
+    int digitsIndex = 1;
+    int hexIndex = 2;
+    int decimalIndex = 3;
+    int nonDecimalHexIndex = 4;
+    if (radix == null) {
+      radix = 10;
+      if (match != null) {
+        if (match[hexIndex] != null) {
+          // Cannot fail because we know that the digits are all hex.
+          return JS('num', r'parseInt(#, 16)', source);
+        }
+        if (match[decimalIndex] != null) {
+          // Cannot fail because we know that the digits are all decimal.
+          return JS('num', r'parseInt(#, 10)', source);
+        }
+        return handleError(source);
+      }
+    } else {
+      if (radix is! int) throw new ArgumentError("Radix is not an integer");
+      if (radix < 2 || radix > 36) {
+        throw new RangeError("Radix $radix not in range 2..36");
+      }
+      if (match != null) {
+        if (radix == 10 && match[decimalIndex] != null) {
+          // Cannot fail because we know that the digits are all decimal.
+          return JS('num', r'parseInt(#, 10)', source);
+        }
+        if (radix < 10 || match[decimalIndex] == null) {
+          // We know that the characters must be ASCII as otherwise the
+          // regexp wouldn't have matched. Calling toLowerCase is thus
+          // guaranteed to be a safe operation. If it wasn't ASCII, then
+          // "İ" would become "i", and we would accept it for radices greater
+          // than 18.
+          int maxCharCode;
+          if (radix <= 10) {
+            // Allow all digits less than the radix. For example 0, 1, 2 for
+            // radix 3.
+            // "0".charCodeAt(0) + radix - 1;
+            maxCharCode = 0x30 + radix - 1;
+          } else {
+            // Characters are located after the digits in ASCII. Therefore we
+            // only check for the character code. The regexp above made already
+            // sure that the string does not contain anything but digits or
+            // characters.
+            // "0".charCodeAt(0) + radix - 1;
+            maxCharCode = 0x61 + radix - 10 - 1;
+          }
+          String digitsPart = match[digitsIndex].toLowerCase();
+          for (int i = 0; i < digitsPart.length; i++) {
+            if (digitsPart.charCodeAt(i) > maxCharCode) {
+              return handleError(source);
+            }
+          }
+        }
+      }
+    }
+    if (match == null) return handleError(source);
+    return JS('num', r'parseInt(#, #)', source, radix);
+  }
+
+  static double parseDouble(String source, int handleError(String source)) {
+    checkString(source);
+    if (handleError == null) handleError = _throwFormatException;
+    // Notice that JS parseFloat accepts garbage at the end of the string.
+    // Accept only:
+    // - NaN
+    // - [+/-]Infinity
+    // - a Dart double literal
+    // We do not allow leading or trailing whitespace.
+    if (!JS('bool',
+            r'/^\s*(?:NaN|[+-]?(?:Infinity|'
+                r'(?:\.\d+|\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))\s*$/.test(#)',
+            source)) {
+      return handleError(source);
+    }
+    var result = JS('num', r'parseFloat(#)', source);
+    if (result.isNaN && source != 'NaN') {
+      return handleError(source);
+    }
+    return result;
+  }
+
+  /** [: r"$".charCodeAt(0) :] */
+  static const int DOLLAR_CHAR_VALUE = 36;
+
+  static String objectTypeName(Object object) {
+    String name = constructorNameFallback(object);
+    if (name == 'Object') {
+      // Try to decompile the constructor by turning it into a string
+      // and get the name out of that. If the decompiled name is a
+      // string, we use that instead of the very generic 'Object'.
+      var decompiled = JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]',
+                          JS('var', r'String(#.constructor)', object));
+      if (decompiled is String) name = decompiled;
+    }
+    // TODO(kasperl): If the namer gave us a fresh global name, we may
+    // want to remove the numeric suffix that makes it unique too.
+    if (identical(name.charCodeAt(0), DOLLAR_CHAR_VALUE)) name = name.substring(1);
+    return name;
+  }
+
+  static String objectToString(Object object) {
+    String name = objectTypeName(object);
+    return "Instance of '$name'";
+  }
+
+  static List newGrowableList(length) {
+    return JS('=List', r'new Array(#)', length);
+  }
+
+  static List newFixedList(length) {
+    var result = JS('=List', r'new Array(#)', length);
+    JS('void', r'#.fixed$length = #', result, true);
+    return result;
+  }
+
+  static num dateNow() => JS('num', r'Date.now()');
+
+  static num numMicroseconds() {
+    if (JS('bool', 'typeof window != "undefined" && window !== null')) {
+      var performance = JS('var', 'window.performance');
+      if (performance != null &&
+          JS('bool', 'typeof #.webkitNow == "function"', performance)) {
+        return (1000 * JS('num', '#.webkitNow()', performance)).floor();
+      }
+    }
+    return 1000 * dateNow();
+  }
+
+  // This is to avoid stack overflows due to very large argument arrays in
+  // apply().  It fixes http://dartbug.com/6919
+  static String _fromCharCodeApply(List<int> array) {
+    String result = "";
+    const kMaxApply = 500;
+    int end = array.length;
+    for (var i = 0; i < end; i += kMaxApply) {
+      var subarray;
+      if (end <= kMaxApply) {
+        subarray = array;
+      } else {
+        subarray = JS('=List', r'#.slice(#, #)', array,
+                      i, i + kMaxApply < end ? i + kMaxApply : end);
+      }
+      result = JS('String', '# + String.fromCharCode.apply(#, #)',
+                  result, null, subarray);
+    }
+    return result;
+  }
+
+  static String stringFromCodePoints(codePoints) {
+    List<int> a = <int>[];
+    for (var i in codePoints) {
+      if (i is !int) throw new ArgumentError(i);
+      if (i <= 0xffff) {
+        a.add(i);
+      } else if (i <= 0x10ffff) {
+        a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
+        a.add(0xdc00 + (i & 0x3ff));
+      } else {
+        throw new ArgumentError(i);
+      }
+    }
+    return _fromCharCodeApply(a);
+  }
+
+  static String stringFromCharCodes(charCodes) {
+    for (var i in charCodes) {
+      if (i is !int) throw new ArgumentError(i);
+      if (i < 0) throw new ArgumentError(i);
+      if (i > 0xffff) return stringFromCodePoints(charCodes);
+    }
+    return _fromCharCodeApply(charCodes);
+  }
+
+  static String getTimeZoneName(receiver) {
+    // When calling toString on a Date it will emit the timezone in parenthesis.
+    // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
+    // We extract this name using a regexp.
+    var d = lazyAsJsDate(receiver);
+    return JS('String', r'/\((.*)\)/.exec(#.toString())[1]', d);
+  }
+
+  static int getTimeZoneOffsetInMinutes(receiver) {
+    // Note that JS and Dart disagree on the sign of the offset.
+    return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
+  }
+
+  static valueFromDecomposedDate(years, month, day, hours, minutes, seconds,
+                                 milliseconds, isUtc) {
+    final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
+    checkInt(years);
+    checkInt(month);
+    checkInt(day);
+    checkInt(hours);
+    checkInt(minutes);
+    checkInt(seconds);
+    checkInt(milliseconds);
+    checkBool(isUtc);
+    var jsMonth = month - 1;
+    var value;
+    if (isUtc) {
+      value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)',
+                 years, jsMonth, day, hours, minutes, seconds, milliseconds);
+    } else {
+      value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()',
+                 years, jsMonth, day, hours, minutes, seconds, milliseconds);
+    }
+    if (value.isNaN ||
+        value < -MAX_MILLISECONDS_SINCE_EPOCH ||
+        value > MAX_MILLISECONDS_SINCE_EPOCH) {
+      throw new ArgumentError();
+    }
+    if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc);
+    return value;
+  }
+
+  static patchUpY2K(value, years, isUtc) {
+    var date = JS('', r'new Date(#)', value);
+    if (isUtc) {
+      JS('num', r'#.setUTCFullYear(#)', date, years);
+    } else {
+      JS('num', r'#.setFullYear(#)', date, years);
+    }
+    return JS('num', r'#.valueOf()', date);
+  }
+
+  // Lazily keep a JS Date stored in the JS object.
+  static lazyAsJsDate(receiver) {
+    if (JS('bool', r'#.date === (void 0)', receiver)) {
+      JS('void', r'#.date = new Date(#)', receiver,
+         receiver.millisecondsSinceEpoch);
+    }
+    return JS('var', r'#.date', receiver);
+  }
+
+  // The getters for date and time parts below add a positive integer to ensure
+  // that the result is really an integer, because the JavaScript implementation
+  // may return -0.0 instead of 0.
+
+  static getYear(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getMonth(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
+      : JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver));
+  }
+
+  static getDay(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getHours(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getMinutes(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getSeconds(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getMilliseconds(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getWeekday(receiver) {
+    int weekday = (receiver.isUtc)
+      ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
+      : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver));
+    // Adjust by one because JS weeks start on Sunday.
+    return (weekday + 6) % 7 + 1;
+  }
+
+  static valueFromDateString(str) {
+    if (str is !String) throw new ArgumentError(str);
+    var value = JS('num', r'Date.parse(#)', str);
+    if (value.isNaN) throw new ArgumentError(str);
+    return value;
+  }
+
+  static getProperty(object, key) {
+    if (object == null || object is bool || object is num || object is String) {
+      throw new ArgumentError(object);
+    }
+    return JS('var', '#[#]', object, key);
+  }
+
+  static void setProperty(object, key, value) {
+    if (object == null || object is bool || object is num || object is String) {
+      throw new ArgumentError(object);
+    }
+    JS('void', '#[#] = #', object, key, value);
+  }
+
+  static applyFunction(Function function,
+                       List positionalArguments,
+                       Map<String, dynamic> namedArguments) {
+    int argumentCount = 0;
+    StringBuffer buffer = new StringBuffer();
+    List arguments = [];
+
+    if (positionalArguments != null) {
+      argumentCount += positionalArguments.length;
+      arguments.addAll(positionalArguments);
+    }
+
+    // Sort the named arguments to get the right selector name and
+    // arguments order.
+    if (namedArguments != null && !namedArguments.isEmpty) {
+      // Call new List.from to make sure we get a JavaScript array.
+      List<String> listOfNamedArguments =
+          new List<String>.from(namedArguments.keys);
+      argumentCount += namedArguments.length;
+      // We're sorting on strings, and the behavior is the same between
+      // Dart string sort and JS string sort. To avoid needing the Dart
+      // sort implementation, we use the JavaScript one instead.
+      JS('void', '#.sort()', listOfNamedArguments);
+      listOfNamedArguments.forEach((String name) {
+        buffer.add('\$$name');
+        arguments.add(namedArguments[name]);
+      });
+    }
+
+    String selectorName = 'call\$$argumentCount$buffer';
+    var jsFunction = JS('var', '#[#]', function, selectorName);
+    if (jsFunction == null) {
+      throw new NoSuchMethodError(function, selectorName, arguments, {});
+    }
+    // We bound 'this' to [function] because of how we compile
+    // closures: escaped local variables are stored and accessed through
+    // [function].
+    return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
+  }
+
+  static getConstructor(String className) {
+    // TODO(ahe): How to safely access $?
+    return JS('var', r'$[#]', className);
+  }
+
+  static bool identicalImplementation(a, b) {
+    return JS('bool', '# == null', a)
+      ? JS('bool', '# == null', b)
+      : JS('bool', '# === #', a, b);
+  }
+}
+
+/**
+ * Called by generated code to throw an illegal-argument exception,
+ * for example, if a non-integer index is given to an optimized
+ * indexed access.
+ */
+iae(argument) {
+  throw new ArgumentError(argument);
+}
+
+/**
+ * Called by generated code to throw an index-out-of-range exception,
+ * for example, if a bounds check fails in an optimized indexed
+ * access.
+ */
+ioore(index) {
+  throw new RangeError.value(index);
+}
+
+listInsertRange(receiver, start, length, initialValue) {
+  if (length == 0) {
+    return;
+  }
+  if (length is !int) throw new ArgumentError(length);
+  if (length < 0) throw new ArgumentError(length);
+  if (start is !int) throw new ArgumentError(start);
+
+  var receiverLength = JS('num', r'#.length', receiver);
+  if (start < 0 || start > receiverLength) {
+    throw new RangeError.value(start);
+  }
+  receiver.length = receiverLength + length;
+  Arrays.copy(receiver,
+              start,
+              receiver,
+              start + length,
+              receiverLength - start);
+  if (initialValue != null) {
+    for (int i = start; i < start + length; i++) {
+      receiver[i] = initialValue;
+    }
+  }
+  receiver.length = receiverLength + length;
+}
+
+stringLastIndexOfUnchecked(receiver, element, start)
+  => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
+
+
+checkNull(object) {
+  if (object == null) throw new ArgumentError(null);
+  return object;
+}
+
+checkNum(value) {
+  if (value is !num) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+checkInt(value) {
+  if (value is !int) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+checkBool(value) {
+  if (value is !bool) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+checkString(value) {
+  if (value is !String) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+class MathNatives {
+  static double sqrt(num value)
+    => JS('double', r'Math.sqrt(#)', checkNum(value));
+
+  static double sin(num value)
+    => JS('double', r'Math.sin(#)', checkNum(value));
+
+  static double cos(num value)
+    => JS('double', r'Math.cos(#)', checkNum(value));
+
+  static double tan(num value)
+    => JS('double', r'Math.tan(#)', checkNum(value));
+
+  static double acos(num value)
+    => JS('double', r'Math.acos(#)', checkNum(value));
+
+  static double asin(num value)
+    => JS('double', r'Math.asin(#)', checkNum(value));
+
+  static double atan(num value)
+    => JS('double', r'Math.atan(#)', checkNum(value));
+
+  static double atan2(num a, num b)
+    => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b));
+
+  static double exp(num value)
+    => JS('double', r'Math.exp(#)', checkNum(value));
+
+  static double log(num value)
+    => JS('double', r'Math.log(#)', checkNum(value));
+
+  static num pow(num value, num exponent) {
+    checkNum(value);
+    checkNum(exponent);
+    return JS('num', r'Math.pow(#, #)', value, exponent);
+  }
+
+  static double random() => JS('double', r'Math.random()');
+}
+
+/**
+ * Wrap the given Dart object and record a stack trace.
+ *
+ * The code in [unwrapException] deals with getting the original Dart
+ * object out of the wrapper again.
+ */
+$throw(ex) {
+  if (ex == null) ex = const NullThrownError();
+  var wrapper = new DartError(ex);
+
+  if (JS('bool', '!!Error.captureStackTrace')) {
+    // Use V8 API for recording a "fast" stack trace (this installs a
+    // "stack" property getter on [wrapper]).
+    JS('void', r'Error.captureStackTrace(#, #)',
+       wrapper, RAW_DART_FUNCTION_REF($throw));
+  } else {
+    // Otherwise, produce a stack trace and record it in the wrapper.
+    // This is a slower way to create a stack trace which works on
+    // some browsers, but may simply evaluate to null.
+    String stackTrace = JS('', 'new Error().stack');
+    JS('void', '#.stack = #', wrapper, stackTrace);
+  }
+  return wrapper;
+}
+
+/**
+ * Wrapper class for throwing exceptions.
+ */
+class DartError {
+  /// The Dart object (or primitive JavaScript value) which was thrown is
+  /// attached to this object as a field named 'dartException'.  We do this
+  /// only in raw JS so that we can use the 'in' operator and so that the
+  /// minifier does not rename the field.  Therefore it is not declared as a
+  /// real field.
+
+  DartError(var dartException) {
+    JS('void', '#.dartException = #', this, dartException);
+    // Install a toString method that the JavaScript system will call
+    // to format uncaught exceptions.
+    JS('void', '#.toString = #', this, DART_CLOSURE_TO_JS(toStringWrapper));
+  }
+
+  /**
+   * V8/Chrome installs a property getter, "stack", when calling
+   * Error.captureStackTrace (see [$throw]). In [$throw], we make sure
+   * that this property is always set.
+   */
+  String get stack => JS('', '#.stack', this);
+
+  /**
+   * This method can be invoked by calling toString from
+   * JavaScript. See the constructor of this class.
+   *
+   * We only expect this method to be called (indirectly) by the
+   * browser when an uncaught exception occurs. Instance of this class
+   * should never escape into Dart code (except for [$throw] above).
+   */
+  String toString() {
+    // If Error.captureStackTrace is available, accessing stack from
+    // this method would cause recursion because the stack property
+    // (on this object) is actually a getter which calls toString on
+    // this object (via the wrapper installed in this class'
+    // constructor). Fortunately, both Chrome and d8 prints the stack
+    // trace and Chrome even applies source maps to the stack
+    // trace. Remeber, this method is only ever invoked by the browser
+    // when an uncaught exception occurs.
+    var dartException = JS('var', r'#.dartException', this);
+    if (JS('bool', '!!Error.captureStackTrace') || (stack == null)) {
+      return dartException.toString();
+    } else {
+      return '$dartException\n$stack';
+    }
+  }
+
+  /**
+   * This method is installed as JavaScript toString method on
+   * [DartError].  So JavaScript 'this' binds to an instance of
+   * DartError.
+   */
+  static toStringWrapper() => JS('', r'this').toString();
+}
+
+makeLiteralListConst(list) {
+  JS('bool', r'#.immutable$list = #', list, true);
+  JS('bool', r'#.fixed$length = #', list, true);
+  return list;
+}
+
+throwRuntimeError(message) {
+  throw new RuntimeError(message);
+}
+
+/**
+ * The SSA builder generates a call to this method when a malformed type is used
+ * in a subtype test.
+ */
+throwMalformedSubtypeError(value, type, reasons) {
+  throw new TypeErrorImplementation.malformedSubtype(value, type, reasons);
+}
+
+throwAbstractClassInstantiationError(className) {
+  throw new AbstractClassInstantiationError(className);
+}
+
+/**
+ * Called from catch blocks in generated code to extract the Dart
+ * exception from the thrown value. The thrown value may have been
+ * created by [$throw] or it may be a 'native' JS exception.
+ *
+ * Some native exceptions are mapped to new Dart instances, others are
+ * returned unmodified.
+ */
+unwrapException(ex) {
+  // Note that we are checking if the object has the property. If it
+  // has, it could be set to null if the thrown value is null.
+  if (JS('bool', r'"dartException" in #', ex)) {
+    return JS('', r'#.dartException', ex);
+  }
+
+  // Grab hold of the exception message. This field is available on
+  // all supported browsers.
+  var message = JS('var', r'#.message', ex);
+
+  if (JS('bool', r'# instanceof TypeError', ex)) {
+    // The type and arguments fields are Chrome specific but they
+    // allow us to get very detailed information about what kind of
+    // exception occurred.
+    var type = JS('var', r'#.type', ex);
+    var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex);
+    if (contains(message, 'JSNull') ||
+        type == 'property_not_function' ||
+        type == 'called_non_callable' ||
+        type == 'non_object_property_call' ||
+        type == 'non_object_property_load') {
+      return new NoSuchMethodError(null, name, [], {});
+    } else if (type == 'undefined_method') {
+      return new NoSuchMethodError('', name, [], {});
+    }
+
+    var ieErrorCode = JS('int', '#.number & 0xffff', ex);
+    var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex);
+    // If we cannot use [type] to determine what kind of exception
+    // we're dealing with we fall back on looking at the exception
+    // message if it is available and a string.
+    if (message is String) {
+      if (message.endsWith('is null') ||
+          message.endsWith('is undefined') ||
+          message.endsWith('is null or undefined') ||
+          message.endsWith('of undefined') ||
+          message.endsWith('of null')) {
+        return new NoSuchMethodError(null, '<unknown>', [], {});
+      } else if (contains(message, ' has no method ') ||
+                 contains(message, ' is not a function') ||
+                 (ieErrorCode == 438 && ieFacilityNumber == 10)) {
+        // Examples:
+        //  x.foo is not a function
+        //  'undefined' is not a function (evaluating 'x.foo(1,2,3)')
+        // Object doesn't support property or method 'foo' which sets the error
+        // code 438 in IE.
+        // TODO(kasperl): Compute the right name if possible.
+        return new NoSuchMethodError('', '<unknown>', [], {});
+      }
+    }
+
+    // If we cannot determine what kind of error this is, we fall back
+    // to reporting this as a generic exception. It's probably better
+    // than nothing.
+    return new Exception(message is String ? message : '');
+  }
+
+  if (JS('bool', r'# instanceof RangeError', ex)) {
+    if (message is String && contains(message, 'call stack')) {
+      return new StackOverflowError();
+    }
+
+    // In general, a RangeError is thrown when trying to pass a number
+    // as an argument to a function that does not allow a range that
+    // includes that number.
+    return new ArgumentError();
+  }
+
+  // Check for the Firefox specific stack overflow signal.
+  if (JS('bool',
+         r"typeof InternalError == 'function' && # instanceof InternalError",
+         ex)) {
+    if (message is String && message == 'too much recursion') {
+      return new StackOverflowError();
+    }
+  }
+
+  // Just return the exception. We should not wrap it because in case
+  // the exception comes from the DOM, it is a JavaScript
+  // object backed by a native Dart class.
+  return ex;
+}
+
+/**
+ * Called by generated code to fetch the stack trace from an
+ * exception.
+ */
+StackTrace getTraceFromException(exception) {
+  return new StackTrace(JS("var", r"#.stack", exception));
+}
+
+class StackTrace {
+  var stack;
+  StackTrace(this.stack);
+  String toString() => stack != null ? stack : '';
+}
+
+
+/**
+ * Called by generated code to build a map literal. [keyValuePairs] is
+ * a list of key, value, key, value, ..., etc.
+ */
+makeLiteralMap(List keyValuePairs) {
+  Iterator iterator = keyValuePairs.iterator;
+  Map result = new LinkedHashMap();
+  while (iterator.moveNext()) {
+    String key = iterator.current;
+    iterator.moveNext();
+    var value = iterator.current;
+    result[key] = value;
+  }
+  return result;
+}
+
+invokeClosure(Function closure,
+              var isolate,
+              int numberOfArguments,
+              var arg1,
+              var arg2) {
+  if (numberOfArguments == 0) {
+    return JS_CALL_IN_ISOLATE(isolate, () => closure());
+  } else if (numberOfArguments == 1) {
+    return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1));
+  } else if (numberOfArguments == 2) {
+    return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2));
+  } else {
+    throw new Exception(
+        'Unsupported number of arguments for wrapped closure');
+  }
+}
+
+/**
+ * Called by generated code to convert a Dart closure to a JS
+ * closure when the Dart closure is passed to the DOM.
+ */
+convertDartClosureToJS(closure, int arity) {
+  if (closure == null) return null;
+  var function = JS('var', r'#.$identity', closure);
+  if (JS('bool', r'!!#', function)) return function;
+  // By fetching the current isolate before creating the JavaScript
+  // function, we prevent the compiler from inlining its use in
+  // the JavaScript function below (the compiler generates code for
+  // fetching the isolate before creating the JavaScript function).
+  // If it was inlined, the JavaScript function would not get the
+  // current isolate, but the one that is active when the callback
+  // executes.
+  var currentIsolate = JS_CURRENT_ISOLATE();
+
+  // We use $0 and $1 to not clash with variable names used by the
+  // compiler and/or minifier.
+  function = JS("var",
+                r"""function($0, $1) { return #(#, #, #, $0, $1); }""",
+                DART_CLOSURE_TO_JS(invokeClosure),
+                closure,
+                JS_CURRENT_ISOLATE(),
+                arity);
+
+  JS('void', r'#.$identity = #', closure, function);
+  return function;
+}
+
+/**
+ * Super class for Dart closures.
+ */
+class Closure implements Function {
+  String toString() => "Closure";
+}
+
+bool jsHasOwnProperty(var jsObject, String property) {
+  return JS('bool', r'#.hasOwnProperty(#)', jsObject, property);
+}
+
+jsPropertyAccess(var jsObject, String property) {
+  return JS('var', r'#[#]', jsObject, property);
+}
+
+/**
+ * Called at the end of unaborted switch cases to get the singleton
+ * FallThroughError exception that will be thrown.
+ */
+getFallThroughError() => const FallThroughErrorImplementation();
+
+/**
+ * Represents the type Dynamic. The compiler treats this specially.
+ */
+abstract class Dynamic_ {
+}
+
+/**
+ * A metadata annotation describing the types instantiated by a native element.
+ *
+ * The annotation is valid on a native method and a field of a native class.
+ *
+ * By default, a field of a native class is seen as an instantiation point for
+ * all native classes that are a subtype of the field's type, and a native
+ * method is seen as an instantiation point fo all native classes that are a
+ * subtype of the method's return type, or the argument types of the declared
+ * type of the method's callback parameter.
+ *
+ * An @[Creates] annotation overrides the default set of instantiated types.  If
+ * one or more @[Creates] annotations are present, the type of the native
+ * element is ignored, and the union of @[Creates] annotations is used instead.
+ * The names in the strings are resolved and the program will fail to compile
+ * with dart2js if they do not name types.
+ *
+ * The argument to [Creates] is a string.  The string is parsed as the names of
+ * one or more types, separated by vertical bars `|`.  There are some special
+ * names:
+ *
+ * * `=List`. This means 'exactly List', which is the JavaScript Array
+ *   implementation of [List] and no other implementation.
+ *
+ * * `=Object`. This means 'exactly Object', which is a plain JavaScript object
+ *   with properties and none of the subtypes of Object.
+ *
+ * Example: we may know that a method always returns a specific implementation:
+ *
+ *     @Creates('_NodeList')
+ *     List<Node> getElementsByTagName(String tag) native;
+ *
+ * Useful trick: A method can be marked as not instantiating any native classes
+ * with the annotation `@Creates('Null')`.  This is useful for fields on native
+ * classes that are used only in Dart code.
+ *
+ *     @Creates('Null')
+ *     var _cachedFoo;
+ */
+class Creates {
+  final String types;
+  const Creates(this.types);
+}
+
+/**
+ * A metadata annotation describing the types returned or yielded by a native
+ * element.
+ *
+ * The annotation is valid on a native method and a field of a native class.
+ *
+ * By default, a native method or field is seen as returning or yielding all
+ * subtypes if the method return type or field type.  This annotation allows a
+ * more precise set of types to be specified.
+ *
+ * See [Creates] for the syntax of the argument.
+ *
+ * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys.
+ *
+ *     @Returns('String|num|=List')
+ *     dynamic key;
+ *
+ *     // Equivalent:
+ *     @Returns('String') @Returns('num') @Returns('=List')
+ *     dynamic key;
+ */
+class Returns {
+  final String types;
+  const Returns(this.types);
+}
+
+/**
+ * A metadata annotation placed on native methods and fields of native classes
+ * to specify the JavaScript name.
+ *
+ * This example declares a Dart field + getter + setter called `$dom_title` that
+ * corresponds to the JavaScript property `title`.
+ *
+ *     class Docmument native "*Foo" {
+ *       @JSName('title')
+ *       String $dom_title;
+ *     }
+ */
+class JSName {
+  final String name;
+  const JSName(this.name);
+}
+
+/**
+ * Represents the type of Null. The compiler treats this specially.
+ * TODO(lrn): Null should be defined in core. It's a class, like int.
+ * It just happens to act differently in assignability tests and,
+ * like int, can't be extended or implemented.
+ */
+class Null {
+  factory Null() {
+    throw new UnsupportedError('new Null()');
+  }
+}
+
+setRuntimeTypeInfo(target, typeInfo) {
+  assert(typeInfo == null || isJsArray(typeInfo));
+  // We have to check for null because factories may return null.
+  if (target != null) JS('var', r'#.$builtinTypeInfo = #', target, typeInfo);
+}
+
+getRuntimeTypeInfo(target) {
+  if (target == null) return null;
+  var res = JS('var', r'#.$builtinTypeInfo', target);
+  // If the object does not have runtime type information, return an
+  // empty literal, to avoid null checks.
+  // TODO(ngeoffray): Make the object a top-level field to avoid
+  // allocating a new object every single time.
+  return (res == null) ? JS('var', '{}') : res;
+}
+
+/**
+ * The following methods are called by the runtime to implement
+ * checked mode and casts. We specialize each primitive type (eg int, bool), and
+ * use the compiler's convention to do is-checks on regular objects.
+ */
+boolConversionCheck(value) {
+  boolTypeCheck(value);
+  assert(value != null);
+  return value;
+}
+
+stringTypeCheck(value) {
+  if (value == null) return value;
+  if (value is String) return value;
+  throw new TypeErrorImplementation(value, 'String');
+}
+
+stringTypeCast(value) {
+  if (value is String || value == null) return value;
+  // TODO(lrn): When reified types are available, pass value.class and String.
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'String');
+}
+
+doubleTypeCheck(value) {
+  if (value == null) return value;
+  if (value is double) return value;
+  throw new TypeErrorImplementation(value, 'double');
+}
+
+doubleTypeCast(value) {
+  if (value is double || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'double');
+}
+
+numTypeCheck(value) {
+  if (value == null) return value;
+  if (value is num) return value;
+  throw new TypeErrorImplementation(value, 'num');
+}
+
+numTypeCast(value) {
+  if (value is num || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'num');
+}
+
+boolTypeCheck(value) {
+  if (value == null) return value;
+  if (value is bool) return value;
+  throw new TypeErrorImplementation(value, 'bool');
+}
+
+boolTypeCast(value) {
+  if (value is bool || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'bool');
+}
+
+functionTypeCheck(value) {
+  if (value == null) return value;
+  if (value is Function) return value;
+  throw new TypeErrorImplementation(value, 'Function');
+}
+
+functionTypeCast(value) {
+  if (value is Function || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'Function');
+}
+
+intTypeCheck(value) {
+  if (value == null) return value;
+  if (value is int) return value;
+  throw new TypeErrorImplementation(value, 'int');
+}
+
+intTypeCast(value) {
+  if (value is int || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'int');
+}
+
+void propertyTypeError(value, property) {
+  // Cuts the property name to the class name.
+  String name = property.substring(3, property.length);
+  throw new TypeErrorImplementation(value, name);
+}
+
+void propertyTypeCastError(value, property) {
+  // Cuts the property name to the class name.
+  String actualType = Primitives.objectTypeName(value);
+  String expectedType = property.substring(3, property.length);
+  throw new CastErrorImplementation(actualType, expectedType);
+}
+
+/**
+ * For types that are not supertypes of native (eg DOM) types,
+ * we emit a simple property check to check that an object implements
+ * that type.
+ */
+propertyTypeCheck(value, property) {
+  if (value == null) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+/**
+ * For types that are not supertypes of native (eg DOM) types,
+ * we emit a simple property check to check that an object implements
+ * that type.
+ */
+propertyTypeCast(value, property) {
+  if (value == null || JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * For types that are supertypes of native (eg DOM) types, we emit a
+ * call because we cannot add a JS property to their prototype at load
+ * time.
+ */
+callTypeCheck(value, property) {
+  if (value == null) return value;
+  if ((identical(JS('String', 'typeof #', value), 'object'))
+      && JS('bool', '#[#]()', value, property)) {
+    return value;
+  }
+  propertyTypeError(value, property);
+}
+
+/**
+ * For types that are supertypes of native (eg DOM) types, we emit a
+ * call because we cannot add a JS property to their prototype at load
+ * time.
+ */
+callTypeCast(value, property) {
+  if (value == null
+      || ((JS('bool', 'typeof # === "object"', value))
+          && JS('bool', '#[#]()', value, property))) {
+    return value;
+  }
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * Specialization of the type check for num and String and their
+ * supertype since [value] can be a JS primitive.
+ */
+numberOrStringSuperTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (value is num) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+numberOrStringSuperTypeCast(value, property) {
+  if (value is String) return value;
+  if (value is num) return value;
+  return propertyTypeCast(value, property);
+}
+
+numberOrStringSuperNativeTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (value is num) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+numberOrStringSuperNativeTypeCast(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (value is num) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * Specialization of the type check for String and its supertype
+ * since [value] can be a JS primitive.
+ */
+stringSuperTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+stringSuperTypeCast(value, property) {
+  if (value is String) return value;
+  return propertyTypeCast(value, property);
+}
+
+stringSuperNativeTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+stringSuperNativeTypeCast(value, property) {
+  if (value is String || value == null) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * Specialization of the type check for List and its supertypes,
+ * since [value] can be a JS array.
+ */
+listTypeCheck(value) {
+  if (value == null) return value;
+  if (value is List) return value;
+  throw new TypeErrorImplementation(value, 'List');
+}
+
+listTypeCast(value) {
+  if (value is List || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'List');
+}
+
+listSuperTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is List) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+listSuperTypeCast(value, property) {
+  if (value is List) return value;
+  return propertyTypeCast(value, property);
+}
+
+listSuperNativeTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is List) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+listSuperNativeTypeCast(value, property) {
+  if (value is List || value == null) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+voidTypeCheck(value) {
+  if (value == null) return value;
+  throw new TypeErrorImplementation(value, 'void');
+}
+
+malformedTypeCheck(value, type, reasons) {
+  if (value == null) return value;
+  throwMalformedSubtypeError(value, type, reasons);
+}
+
+/**
+ * Special interface recognized by the compiler and implemented by DOM
+ * objects that support integer indexing. This interface is not
+ * visible to anyone, and is only injected into special libraries.
+ */
+abstract class JavaScriptIndexingBehavior {
+}
+
+// TODO(lrn): These exceptions should be implemented in core.
+// When they are, remove the 'Implementation' here.
+
+/** Thrown by type assertions that fail. */
+class TypeErrorImplementation implements TypeError {
+  final String message;
+
+  /**
+   * Normal type error caused by a failed subtype test.
+   */
+  TypeErrorImplementation(Object value, String type)
+      : message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
+                  "of type '$type'";
+
+  /**
+   * Type error caused by a subtype test on a malformed type.
+   */
+  TypeErrorImplementation.malformedSubtype(Object value,
+                                           String type, String reasons)
+      : message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
+                  "of type '$type' because '$type' is malformed: $reasons.";
+
+  String toString() => message;
+}
+
+/** Thrown by the 'as' operator if the cast isn't valid. */
+class CastErrorImplementation implements CastError {
+  // TODO(lrn): Rename to CastError (and move implementation into core).
+  // TODO(lrn): Change actualType and expectedType to "Type" when reified
+  // types are available.
+  final Object actualType;
+  final Object expectedType;
+
+  CastErrorImplementation(this.actualType, this.expectedType);
+
+  String toString() {
+    return "CastError: Casting value of type $actualType to"
+           " incompatible type $expectedType";
+  }
+}
+
+class FallThroughErrorImplementation implements FallThroughError {
+  const FallThroughErrorImplementation();
+  String toString() => "Switch case fall-through.";
+}
+
+/**
+ * Helper function for implementing asserts. The compiler treats this specially.
+ */
+void assertHelper(condition) {
+  if (condition is Function) condition = condition();
+  if (condition is !bool) {
+    throw new TypeErrorImplementation(condition, 'bool');
+  }
+  // Compare to true to avoid boolean conversion check in checked
+  // mode.
+  if (!identical(condition, true)) throw new AssertionError();
+}
+
+/**
+ * Called by generated code when a method that must be statically
+ * resolved cannot be found.
+ */
+void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) {
+  throw new NoSuchMethodError(obj, name, arguments, const {},
+                              expectedArgumentNames);
+}
+
+/**
+ * Called by generated code when a static field's initializer references the
+ * field that is currently being initialized.
+ */
+void throwCyclicInit(String staticName) {
+  throw new RuntimeError("Cyclic initialization for static $staticName");
+}
+
+class TypeImpl implements Type {
+  final String typeName;
+  TypeImpl(this.typeName);
+  toString() => typeName;
+  int get hashCode => typeName.hashCode;
+  bool operator ==(other) {
+    if (other is !TypeImpl) return false;
+    return typeName == other.typeName;
+  }
+}
+
+String getClassName(var object) {
+  return JS('String', r'#.constructor.builtin$cls', object);
+}
+
+String getTypeArgumentAsString(List runtimeType) {
+  String className = getConstructorName(runtimeType[0]);
+  if (runtimeType.length == 1) return className;
+  return '$className<${joinArguments(runtimeType, 1)}>';
+}
+
+String getConstructorName(type) => JS('String', r'#.builtin$cls', type);
+
+String runtimeTypeToString(type) {
+  if (type == null) {
+    return 'dynamic';
+  } else if (isJsArray(type)) {
+    // A list representing a type with arguments.
+    return getTypeArgumentAsString(type);
+  } else {
+    // A reference to the constructor.
+    return getConstructorName(type);
+  }
+}
+
+String joinArguments(var types, int startIndex) {
+  bool firstArgument = true;
+  StringBuffer buffer = new StringBuffer();
+  for (int index = startIndex; index < types.length; index++) {
+    if (firstArgument) {
+      firstArgument = false;
+    } else {
+      buffer. add(', ');
+    }
+    var argument = types[index];
+    buffer.add(runtimeTypeToString(argument));
+  }
+  return buffer.toString();
+}
+
+String getRuntimeTypeString(var object) {
+  String className = isJsArray(object) ? 'List' : getClassName(object);
+  var typeInfo = JS('var', r'#.$builtinTypeInfo', object);
+  if (typeInfo == null) return className;
+  return "$className<${joinArguments(typeInfo, 0)}>";
+}
+
+/**
+ * Check whether the type represented by [s] is a subtype of the type
+ * represented by [t].
+ *
+ * Type representations can be:
+ *  1) a JavaScript constructor for a class C: the represented type is the raw
+ *     type C.
+ *  2) a JavaScript object: this represents a class for which there is no
+ *     JavaScript constructor, because it is only used in type arguments or it
+ *     is native. The represented type is the raw type of this class.
+ *  3) a JavaScript array: the first entry is of type 1 or 2 and identifies the
+ *     class of the type and the rest of the array are the type arguments.
+ *  4) [:null:]: the dynamic type.
+ */
+bool isSubtype(var s, var t) {
+  // If either type is dynamic, [s] is a subtype of [t].
+  if (JS('bool', '# == null', s) || JS('bool', '# == null', t)) return true;
+  // Subtyping is reflexive.
+  if (JS('bool', '# === #', s, t)) return true;
+  // Get the object describing the class and check for the subtyping flag
+  // constructed from the type of [t].
+  var typeOfS = isJsArray(s) ? s[0] : s;
+  var typeOfT = isJsArray(t) ? t[0] : t;
+  var test = '${JS_OPERATOR_IS_PREFIX()}${runtimeTypeToString(typeOfT)}';
+  if (JS('var', r'#[#]', typeOfS, test) == null) return false;
+  // The class of [s] is a subclass of the class of [t]. If either of the types
+  // is raw, [s] is a subtype of [t].
+  if (!isJsArray(s) || !isJsArray(t)) return true;
+  // Recursively check the type arguments.
+  int len = s.length;
+  if (len != t.length) return false;
+  for (int i = 1; i < len; i++) {
+    if (!isSubtype(s[i], t[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+createRuntimeType(String name) => new TypeImpl(name);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart
new file mode 100644
index 0000000..fa5677f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart
@@ -0,0 +1,272 @@
+// 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.
+
+part of _interceptors;
+
+/**
+ * The super interceptor class for [JSInt] and [JSDouble]. The compiler
+ * recognizes this class as an interceptor, and changes references to
+ * [:this:] to actually use the receiver of the method, which is
+ * generated as an extra argument added to each member.
+ */
+class JSNumber implements num {
+  const JSNumber();
+
+  int compareTo(num b) {
+    if (b is! num) throw new ArgumentError(b);
+    if (this < b) {
+      return -1;
+    } else if (this > b) {
+      return 1;
+    } else if (this == b) {
+      if (this == 0) {
+        bool bIsNegative = b.isNegative;
+        if (isNegative == bIsNegative) return 0;
+        if (isNegative) return -1;
+        return 1;
+      }
+      return 0;
+    } else if (isNaN) {
+      if (b.isNaN) {
+        return 0;
+      }
+      return 1;
+    } else {
+      return -1;
+    }
+  }
+
+  bool get isNegative => (this == 0) ? (1 / this) < 0 : this < 0;
+
+  bool get isNaN => JS('bool', r'isNaN(#)', this);
+
+  num remainder(num b) {
+    checkNull(b); // TODO(ngeoffray): This is not specified but co19 tests it.
+    if (b is! num) throw new ArgumentError(b);
+    return JS('num', r'# % #', this, b);
+  }
+
+  num abs() => JS('num', r'Math.abs(#)', this);
+
+  int toInt() {
+    if (isNaN) throw new UnsupportedError('NaN');
+    if (isInfinite) throw new UnsupportedError('Infinity');
+    num truncated = truncate();
+    return JS('bool', r'# == -0.0', truncated) ? 0 : truncated;
+  }
+
+  num ceil() => JS('num', r'Math.ceil(#)', this);
+
+  num floor() => JS('num', r'Math.floor(#)', this);
+
+  bool get isInfinite {
+    return JS('bool', r'# == Infinity', this)
+      || JS('bool', r'# == -Infinity', this);
+  }
+
+  num round() {
+    if (this < 0) {
+      return JS('num', r'-Math.round(-#)', this);
+    } else {
+      return JS('num', r'Math.round(#)', this);
+    }
+  }
+
+  num clamp(lowerLimit, upperLimit) {
+    if (lowerLimit is! num) throw new ArgumentError(lowerLimit);
+    if (upperLimit is! num) throw new ArgumentError(upperLimit);
+    if (lowerLimit.compareTo(upperLimit) > 0) {
+      throw new ArgumentError(lowerLimit);
+    }
+    if (this.compareTo(lowerLimit) < 0) return lowerLimit;
+    if (this.compareTo(upperLimit) > 0) return upperLimit;
+    return this;
+  }
+
+  double toDouble() => this;
+
+  num truncate() => this < 0 ? ceil() : floor();
+
+  String toStringAsFixed(int fractionDigits) {
+    checkNum(fractionDigits);
+    // TODO(floitsch): fractionDigits must be an integer.
+    if (fractionDigits < 0 || fractionDigits > 20) {
+      throw new RangeError(fractionDigits);
+    }
+    String result = JS('String', r'#.toFixed(#)', this, fractionDigits);
+    if (this == 0 && isNegative) return "-$result";
+    return result;
+  }
+
+  String toStringAsExponential([int fractionDigits]) {
+    String result;
+    if (fractionDigits != null) {
+      // TODO(floitsch): fractionDigits must be an integer.
+      checkNum(fractionDigits);
+      if (fractionDigits < 0 || fractionDigits > 20) {
+        throw new RangeError(fractionDigits);
+      }
+      result = JS('String', r'#.toExponential(#)', this, fractionDigits);
+    } else {
+      result = JS('String', r'#.toExponential()', this);
+    }
+    if (this == 0 && isNegative) return "-$result";
+    return result;
+  }
+
+  String toStringAsPrecision(int precision) {
+    // TODO(floitsch): precision must be an integer.
+    checkNum(precision);
+    if (precision < 1 || precision > 21) {
+      throw new RangeError(precision);
+    }
+    String result = JS('String', r'#.toPrecision(#)',
+                       this, precision);
+    if (this == 0 && isNegative) return "-$result";
+    return result;
+  }
+
+  String toRadixString(int radix) {
+    checkNum(radix);
+    if (radix < 2 || radix > 36) throw new RangeError(radix);
+    return JS('String', r'#.toString(#)', this, radix);
+  }
+
+  // Note: if you change this, also change the function [S].
+  String toString() {
+    if (this == 0 && JS('bool', '(1 / #) < 0', this)) {
+      return '-0.0';
+    } else {
+      return JS('String', r'String(#)', this);
+    }
+  }
+
+  int get hashCode => JS('int', '# & 0x1FFFFFFF', this);
+
+  num operator -() => JS('num', r'-#', this);
+
+  num operator +(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# + #', this, other);
+  }
+
+  num operator -(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# - #', this, other);
+  }
+
+  num operator /(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# / #', this, other);
+  }
+
+  num operator *(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# * #', this, other);
+  }
+
+  num operator %(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    // Euclidean Modulo.
+    num result = JS('num', r'# % #', this, other);
+    if (result == 0) return 0;  // Make sure we don't return -0.0.
+    if (result > 0) return result;
+    if (JS('num', '#', other) < 0) {
+      return result - JS('num', '#', other);
+    } else {
+      return result + JS('num', '#', other);
+    }
+  }
+
+  num operator ~/(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return (JS('num', r'# / #', this, other)).truncate();
+  }
+
+  // TODO(ngeoffray): Move the bit operations below to [JSInt] and
+  // make them take an int. Because this will make operations slower,
+  // we define these methods on number for now but we need to decide
+  // the grain at which we do the type checks.
+
+  num operator <<(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    if (JS('num', '#', other) < 0) throw new ArgumentError(other);
+    // JavaScript only looks at the last 5 bits of the shift-amount. Shifting
+    // by 33 is hence equivalent to a shift by 1.
+    if (JS('bool', r'# > 31', other)) return 0;
+    return JS('num', r'(# << #) >>> 0', this, other);
+  }
+
+  num operator >>(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    if (JS('num', '#', other) < 0) throw new ArgumentError(other);
+    if (JS('num', '#', this) > 0) {
+      // JavaScript only looks at the last 5 bits of the shift-amount. In JS
+      // shifting by 33 is hence equivalent to a shift by 1. Shortcut the
+      // computation when that happens.
+      if (JS('bool', r'# > 31', other)) return 0;
+      // Given that 'a' is positive we must not use '>>'. Otherwise a number
+      // that has the 31st bit set would be treated as negative and shift in
+      // ones.
+      return JS('num', r'# >>> #', this, other);
+    }
+    // For negative numbers we just clamp the shift-by amount. 'a' could be
+    // negative but not have its 31st bit set. The ">>" would then shift in
+    // 0s instead of 1s. Therefore we cannot simply return 0xFFFFFFFF.
+    if (JS('num', '#', other) > 31) other = 31;
+    return JS('num', r'(# >> #) >>> 0', this, other);
+  }
+
+  num operator &(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', r'(# & #) >>> 0', this, other);    
+  }
+
+  num operator |(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', r'(# | #) >>> 0', this, other);    
+  }
+
+  num operator ^(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', r'(# ^ #) >>> 0', this, other);    
+  }
+
+  bool operator <(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# < #', this, other);
+  }
+
+  bool operator >(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# > #', this, other);
+  }
+
+  bool operator <=(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# <= #', this, other);
+  }
+
+  bool operator >=(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# >= #', this, other);
+  }
+}
+
+class JSInt extends JSNumber implements int {
+  const JSInt();
+
+  bool get isEven => (this & 1) == 0;
+
+  bool get isOdd => (this & 1) == 1;
+
+  Type get runtimeType => int;
+
+  int operator ~() => JS('int', r'(~#) >>> 0', this);
+}
+
+class JSDouble extends JSNumber implements double {
+  const JSDouble();
+  Type get runtimeType => double;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart
new file mode 100644
index 0000000..381a690
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart
@@ -0,0 +1,229 @@
+// 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.
+
+part of _interceptors;
+
+/**
+ * The interceptor class for [String]. The compiler recognizes this
+ * class as an interceptor, and changes references to [:this:] to
+ * actually use the receiver of the method, which is generated as an extra
+ * argument added to each member.
+ */
+class JSString implements String {
+  const JSString();
+
+  int charCodeAt(index) => codeUnitAt(index);
+
+  int codeUnitAt(int index) {
+    if (index is !num) throw new ArgumentError(index);
+    if (index < 0) throw new RangeError.value(index);
+    if (index >= length) throw new RangeError.value(index);
+    return JS('int', r'#.charCodeAt(#)', this, index);
+  }
+
+  Iterable<Match> allMatches(String str) {
+    checkString(str);
+    return allMatchesInStringUnchecked(this, str);
+  }
+
+  String concat(String other) {
+    if (other is !String) throw new ArgumentError(other);
+    return JS('String', r'# + #', this, other);
+  }
+
+  bool endsWith(String other) {
+    checkString(other);
+    int otherLength = other.length;
+    if (otherLength > length) return false;
+    return other == substring(length - otherLength);
+  }
+
+  String replaceAll(Pattern from, String to) {
+    checkString(to);
+    return stringReplaceAllUnchecked(this, from, to);
+  }
+
+  String replaceAllMapped(Pattern from, String convert(Match match)) {
+    return this.splitMapJoin(from, onMatch: convert);
+  }
+
+  String splitMapJoin(Pattern from,
+                      {String onMatch(Match match),
+                       String onNonMatch(String nonMatch)}) {
+    return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch);
+  }
+
+  String replaceFirst(Pattern from, String to) {
+    checkString(to);
+    return stringReplaceFirstUnchecked(this, from, to);
+  }
+
+  List<String> split(Pattern pattern) {
+    checkNull(pattern);
+    if (pattern is String) {
+      return JS('=List', r'#.split(#)', this, pattern);
+    } else if (pattern is JSSyntaxRegExp) {
+      var re = regExpGetNative(pattern);
+      return JS('=List', r'#.split(#)', this, re);
+    } else {
+      throw "String.split(Pattern) UNIMPLEMENTED";
+    }
+  }
+
+  List<String> splitChars() {
+    return JS('=List', r'#.split("")', this);
+  }
+
+  bool startsWith(String other) {
+    checkString(other);
+    int otherLength = other.length;
+    if (otherLength > length) return false;
+    return JS('bool', r'# == #', other,
+              JS('String', r'#.substring(0, #)', this, otherLength));
+  }
+
+  String substring(int startIndex, [int endIndex]) {
+    checkNum(startIndex);
+    if (endIndex == null) endIndex = length;
+    checkNum(endIndex);
+    if (startIndex < 0 ) throw new RangeError.value(startIndex);
+    if (startIndex > endIndex) throw new RangeError.value(startIndex);
+    if (endIndex > length) throw new RangeError.value(endIndex);
+    return JS('String', r'#.substring(#, #)', this, startIndex, endIndex);
+  }
+
+  String slice([int startIndex, int endIndex]) {
+    int start, end;
+    if (startIndex == null) {
+      start = 0;
+    } else if (startIndex is! int) {
+      throw new ArgumentError("startIndex is not int");
+    } else if (startIndex >= 0) {
+      start = startIndex;
+    } else {
+      start = this.length + startIndex;
+    }
+    if (start < 0 || start > this.length) {
+      throw new RangeError(
+          "startIndex out of range: $startIndex (length: $length)");
+    }
+    if (endIndex == null) {
+      end = this.length;
+    } else if (endIndex is! int) {
+      throw new ArgumentError("endIndex is not int");
+    } else if (endIndex >= 0) {
+      end = endIndex;
+    } else {
+      end = this.length + endIndex;
+    }
+    if (end < 0 || end > this.length) {
+      throw new RangeError(
+          "endIndex out of range: $endIndex (length: $length)");
+    }
+    if (end < start) {
+      throw new ArgumentError(
+          "End before start: $endIndex < $startIndex (length: $length)");
+    }
+    return JS('String', '#.substring(#, #)', this, start, end);
+  }
+
+
+  String toLowerCase() {
+    return JS('String', r'#.toLowerCase()', this);
+  }
+
+  String toUpperCase() {
+    return JS('String', r'#.toUpperCase()', this);
+  }
+
+  String trim() {
+    return JS('String', r'#.trim()', this);
+  }
+
+  List<int> get charCodes  {
+    List<int> result = new List<int>.fixedLength(length);
+    for (int i = 0; i < length; i++) {
+      result[i] = JS('int', '#.charCodeAt(#)', this, i);
+    }
+    return result;
+  }
+
+  Iterable<int> get codeUnits {
+    throw new UnimplementedError("String.codeUnits");
+  }
+
+  Iterable<int> get runes {
+    throw new UnimplementedError("String.runes");
+  }
+
+  int indexOf(String other, [int start = 0]) {
+    checkNull(other);
+    if (start is !int) throw new ArgumentError(start);
+    if (other is !String) throw new ArgumentError(other);
+    if (start < 0) return -1;
+    return JS('int', r'#.indexOf(#, #)', this, other, start);
+  }
+
+  int lastIndexOf(String other, [int start]) {
+    checkNull(other);
+    if (other is !String) throw new ArgumentError(other);
+    if (start != null) {
+      if (start is !num) throw new ArgumentError(start);
+      if (start < 0) return -1;
+      if (start >= length) {
+        if (other == "") return length;
+        start = length - 1;
+      }
+    } else {
+      start = length - 1;
+    }
+    return stringLastIndexOfUnchecked(this, other, start);
+  }
+
+  bool contains(String other, [int startIndex = 0]) {
+    checkNull(other);
+    return stringContainsUnchecked(this, other, startIndex);
+  }
+
+  bool get isEmpty => length == 0;
+
+  int compareTo(String other) {
+    if (other is !String) throw new ArgumentError(other);
+    return this == other ? 0
+      : JS('bool', r'# < #', this, other) ? -1 : 1;
+  }
+
+  // Note: if you change this, also change the function [S].
+  String toString() => this;
+
+  /**
+   * This is the [Jenkins hash function][1] but using masking to keep
+   * values in SMI range.
+   *
+   * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function
+   */
+  int get hashCode {
+    // TODO(ahe): This method shouldn't have to use JS. Update when our
+    // optimizations are smarter.
+    int hash = 0;
+    for (int i = 0; i < length; i++) {
+      hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i));
+      hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+      hash = JS('int', '# ^ (# >> 6)', hash, hash);
+    }
+    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) <<  3));
+    hash = JS('int', '# ^ (# >> 11)', hash, hash);
+    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+  }
+
+  Type get runtimeType => String;
+
+  int get length => JS('int', r'#.length', this);
+
+  String operator [](int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index >= length || index < 0) throw new RangeError.value(index);
+    return JS('String', '#[#]', this, index);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart
new file mode 100644
index 0000000..3bccb2c
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart
@@ -0,0 +1,68 @@
+// 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.
+
+// Patch file for dart:math library.
+import 'dart:_foreign_helper' show JS;
+
+patch double sqrt(num x)
+  => JS('double', r'Math.sqrt(#)', checkNum(x));
+
+patch double sin(num x)
+  => JS('double', r'Math.sin(#)', checkNum(x));
+
+patch double cos(num x)
+  => JS('double', r'Math.cos(#)', checkNum(x));
+
+patch double tan(num x)
+  => JS('double', r'Math.tan(#)', checkNum(x));
+
+patch double acos(num x)
+  => JS('double', r'Math.acos(#)', checkNum(x));
+
+patch double asin(num x)
+  => JS('double', r'Math.asin(#)', checkNum(x));
+
+patch double atan(num x)
+  => JS('double', r'Math.atan(#)', checkNum(x));
+
+patch double atan2(num a, num b)
+  => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b));
+
+patch double exp(num x)
+  => JS('double', r'Math.exp(#)', checkNum(x));
+
+patch double log(num x)
+  => JS('double', r'Math.log(#)', checkNum(x));
+
+patch num pow(num x, num exponent) {
+  checkNum(x);
+  checkNum(exponent);
+  return JS('num', r'Math.pow(#, #)', x, exponent);
+}
+
+patch class Random {
+  patch factory Random([int seed]) => const _Random();
+}
+
+class _Random implements Random {
+  // The Dart2JS implementation of Random doesn't use a seed.
+  const _Random();
+
+  int nextInt(int max) {
+    if (max < 0) throw new ArgumentError("negative max: $max");
+    if (max > 0xFFFFFFFF) max = 0xFFFFFFFF;
+    return JS("int", "(Math.random() * #) >>> 0", max);
+  }
+
+  /**
+   * Generates a positive random floating point value uniformly distributed on
+   * the range from 0.0, inclusive, to 1.0, exclusive.
+   */
+  double nextDouble() => JS("double", "Math.random()");
+
+  /**
+   * Generates a random boolean value.
+   */
+  bool nextBool() => JS("bool", "Math.random() < 0.5");
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart
new file mode 100644
index 0000000..1236507
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart
@@ -0,0 +1,114 @@
+// 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.
+
+// Patch library for dart:mirrors.
+
+import 'dart:_foreign_helper' show JS;
+
+// Yeah, seriously: mirrors in dart2js are experimental...
+const String _MIRROR_OPT_IN_MESSAGE = """
+
+This program is using an experimental feature called \"mirrors\".  As
+currently implemented, mirrors do not work with minification, and will
+cause spurious errors depending on how code was optimized.
+
+The authors of this program are aware of these problems and have
+decided the thrill of using an experimental feature is outweighing the
+risks.  Furthermore, the authors of this program understand that
+long-term, to fix the problems mentioned above, mirrors may have
+negative impact on size and performance of Dart programs compiled to
+JavaScript.
+""";
+
+bool _mirrorsEnabled = false;
+
+/**
+ * Stub class for the mirror system.
+ */
+patch MirrorSystem currentMirrorSystem() {
+  _ensureEnabled();
+  throw new UnsupportedError("MirrorSystem not implemented");
+}
+
+patch Future<MirrorSystem> mirrorSystemOf(SendPort port) {
+  _ensureEnabled();
+  throw new UnsupportedError("MirrorSystem not implemented");
+}
+
+patch InstanceMirror reflect(Object reflectee) {
+  if (!_mirrorsEnabled && (_MIRROR_OPT_IN_MESSAGE == reflectee)) {
+    // Turn on mirrors and warn that it is an experimental feature.
+    _mirrorsEnabled = true;
+    print(reflectee);
+  }
+  _ensureEnabled();
+  return new _InstanceMirror(reflectee);
+}
+
+class _InstanceMirror extends InstanceMirror {
+  static final Expando<ClassMirror> classMirrors = new Expando<ClassMirror>();
+
+  final reflectee;
+
+  _InstanceMirror(this.reflectee) {
+    _ensureEnabled();
+  }
+
+  bool get hasReflectee => true;
+
+  ClassMirror get type {
+    String className = Primitives.objectTypeName(reflectee);
+    var constructor = Primitives.getConstructor(className);
+    var mirror = classMirrors[constructor];
+    if (mirror == null) {
+      mirror = new _ClassMirror(className, constructor);
+      classMirrors[constructor] = mirror;
+    }
+    return mirror;
+  }
+
+  Future<InstanceMirror> invoke(String memberName,
+                                List<Object> positionalArguments,
+                                [Map<String,Object> namedArguments]) {
+    if (namedArguments != null && !namedArguments.isEmpty) {
+      throw new UnsupportedError('Named arguments are not implemented');
+    }
+    // Copy the list to ensure that it can safely be passed to
+    // JavaScript.
+    var jsList = new List.from(positionalArguments);
+    var mangledName = '${memberName}\$${positionalArguments.length}';
+    var method = JS('var', '#[#]', reflectee, mangledName);
+    var completer = new Completer<InstanceMirror>();
+    // TODO(ahe): [Completer] or [Future] should have API to create a
+    // delayed action.  Simulating with a [Timer].
+    new Timer(0, (timer) {
+      if (JS('String', 'typeof #', method) == 'function') {
+        var result =
+            JS('var', '#.apply(#, #)', method, reflectee, jsList);
+        completer.complete(new _InstanceMirror(result));
+      } else {
+        completer.completeError('not a method $memberName');
+      }
+    });
+    return completer.future;
+  }
+
+  String toString() => 'InstanceMirror($reflectee)';
+}
+
+class _ClassMirror extends ClassMirror {
+  final String _name;
+  final _jsConstructor;
+
+  _ClassMirror(this._name, this._jsConstructor) {
+    _ensureEnabled();
+  }
+
+  String toString() => 'ClassMirror($_name)';
+}
+
+_ensureEnabled() {
+  if (_mirrorsEnabled) return;
+  throw new UnsupportedError('dart:mirrors is an experimental feature');
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart
new file mode 100644
index 0000000..c75e554
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart
@@ -0,0 +1,431 @@
+// 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.
+
+part of _js_helper;
+
+String typeNameInChrome(obj) {
+  String name = JS('String', "#.constructor.name", obj);
+  return typeNameInWebKitCommon(name);
+}
+
+String typeNameInSafari(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  // Safari is very similar to Chrome.
+  return typeNameInWebKitCommon(name);
+}
+
+String typeNameInWebKitCommon(tag) {
+  String name = JS('String', '#', tag);
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'CanvasPixelArray') return 'Uint8ClampedArray';
+  if (name == 'WebKitMutationObserver') return 'MutationObserver';
+  if (name == 'AudioChannelMerger') return 'ChannelMergerNode';
+  if (name == 'AudioChannelSplitter') return 'ChannelSplitterNode';
+  if (name == 'AudioGainNode') return 'GainNode';
+  if (name == 'AudioPannerNode') return 'PannerNode';
+  if (name == 'JavaScriptAudioNode') return 'ScriptProcessorNode';
+  if (name == 'Oscillator') return 'OscillatorNode';
+  if (name == 'RealtimeAnalyserNode') return 'AnalyserNode';
+  if (name == 'IDBVersionChangeRequest') return 'IDBOpenDBRequest';
+  return name;
+}
+
+String typeNameInOpera(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'ApplicationCache') return 'DOMApplicationCache';
+  return name;
+}
+
+String typeNameInFirefox(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'CSS2Properties') return 'CSSStyleDeclaration';
+  if (name == 'DataTransfer') return 'Clipboard';
+  if (name == 'DragEvent') return 'MouseEvent';
+  if (name == 'GeoGeolocation') return 'Geolocation';
+  if (name == 'MouseScrollEvent') return 'WheelEvent';
+  if (name == 'OfflineResourceList') return 'DOMApplicationCache';
+  if (name == 'WorkerMessageEvent') return 'MessageEvent';
+  if (name == 'XMLDocument') return 'Document';
+  return name;
+}
+
+String typeNameInIE(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'Document') {
+    // IE calls both HTML and XML documents 'Document', so we check for the
+    // xmlVersion property, which is the empty string on HTML documents.
+    if (JS('bool', '!!#.xmlVersion', obj)) return 'Document';
+    return 'HTMLDocument';
+  }
+  if (name == 'ApplicationCache') return 'DOMApplicationCache';
+  if (name == 'CanvasPixelArray') return 'Uint8ClampedArray';
+  if (name == 'DataTransfer') return 'Clipboard';
+  if (name == 'DragEvent') return 'MouseEvent';
+  if (name == 'HTMLDDElement') return 'HTMLElement';
+  if (name == 'HTMLDTElement') return 'HTMLElement';
+  if (name == 'HTMLTableDataCellElement') return 'HTMLTableCellElement';
+  if (name == 'HTMLTableHeaderCellElement') return 'HTMLTableCellElement';
+  if (name == 'HTMLPhraseElement') return 'HTMLElement';
+  if (name == 'MSStyleCSSProperties') return 'CSSStyleDeclaration';
+  if (name == 'MouseWheelEvent') return 'WheelEvent';
+  if (name == 'Position') return 'Geoposition';
+
+  // Patches for types which report themselves as Objects.
+  if (name == 'Object') {
+    if (JS('bool', 'window.DataView && (# instanceof window.DataView)', obj)) {
+      return 'DataView';
+    }
+  }
+  return name;
+}
+
+String constructorNameFallback(object) {
+  if (object == null) return 'Null';
+  var constructor = JS('var', "#.constructor", object);
+  if (identical(JS('String', "typeof(#)", constructor), 'function')) {
+    // The constructor isn't null or undefined at this point. Try
+    // to grab hold of its name.
+    var name = JS('var', '#.name', constructor);
+    // If the name is a non-empty string, we use that as the type
+    // name of this object. On Firefox, we often get 'Object' as
+    // the constructor name even for more specialized objects so
+    // we have to fall through to the toString() based implementation
+    // below in that case.
+    if (name is String
+        && !identical(name, '')
+        && !identical(name, 'Object')
+        && !identical(name, 'Function.prototype')) {  // Can happen in Opera.
+      return name;
+    }
+  }
+  String string = JS('String', 'Object.prototype.toString.call(#)', object);
+  return JS('String', '#.substring(8, # - 1)', string, string.length);
+}
+
+/**
+ * If a lookup on an object [object] that has [tag] fails, this function is
+ * called to provide an alternate tag.  This allows us to fail gracefully if we
+ * can make a good guess, for example, when browsers add novel kinds of
+ * HTMLElement that we have never heard of.
+ */
+String alternateTag(object, String tag) {
+  // Does it smell like some kind of HTML element?
+  if (JS('bool', r'!!/^HTML[A-Z].*Element$/.test(#)', tag)) {
+    // Check that it is not a simple JavaScript object.
+    String string = JS('String', 'Object.prototype.toString.call(#)', object);
+    if (string == '[object Object]') return null;
+    return 'HTMLElement';
+  }
+  return null;
+}
+
+// TODO(ngeoffray): stop using this method once our optimizers can
+// change str1.contains(str2) into str1.indexOf(str2) != -1.
+bool contains(String userAgent, String name) {
+  return JS('int', '#.indexOf(#)', userAgent, name) != -1;
+}
+
+int arrayLength(List array) {
+  return JS('int', '#.length', array);
+}
+
+arrayGet(List array, int index) {
+  return JS('var', '#[#]', array, index);
+}
+
+void arraySet(List array, int index, var value) {
+  JS('var', '#[#] = #', array, index, value);
+}
+
+propertyGet(var object, String property) {
+  return JS('var', '#[#]', object, property);
+}
+
+bool callHasOwnProperty(var function, var object, String property) {
+  return JS('bool', '#.call(#, #)', function, object, property);
+}
+
+void propertySet(var object, String property, var value) {
+  JS('var', '#[#] = #', object, property, value);
+}
+
+getPropertyFromPrototype(var object, String name) {
+  return JS('var', 'Object.getPrototypeOf(#)[#]', object, name);
+}
+
+newJsObject() {
+  return JS('var', '{}');
+}
+
+/**
+ * Returns the function to use to get the type name of an object.
+ */
+Function getFunctionForTypeNameOf() {
+  // If we're not in the browser, we're almost certainly running on v8.
+  if (!identical(JS('String', 'typeof(navigator)'), 'object')) return typeNameInChrome;
+
+  String userAgent = JS('String', "navigator.userAgent");
+  if (contains(userAgent, 'Chrome') || contains(userAgent, 'DumpRenderTree')) {
+    return typeNameInChrome;
+  } else if (contains(userAgent, 'Firefox')) {
+    return typeNameInFirefox;
+  } else if (contains(userAgent, 'MSIE')) {
+    return typeNameInIE;
+  } else if (contains(userAgent, 'Opera')) {
+    return typeNameInOpera;
+  } else if (contains(userAgent, 'AppleWebKit')) {
+    // Chrome matches 'AppleWebKit' too, but we test for Chrome first, so this
+    // is not a problem.
+    // Note: Just testing for "Safari" doesn't work when the page is embedded
+    // in a UIWebView on iOS 6.
+    return typeNameInSafari;
+  } else {
+    return constructorNameFallback;
+  }
+}
+
+
+/**
+ * Cached value for the function to use to get the type name of an
+ * object.
+ */
+Function _getTypeNameOf;
+
+/**
+ * Returns the type name of [obj].
+ */
+String getTypeNameOf(var obj) {
+  if (_getTypeNameOf == null) _getTypeNameOf = getFunctionForTypeNameOf();
+  return _getTypeNameOf(obj);
+}
+
+String toStringForNativeObject(var obj) {
+  String name = JS('String', '#', getTypeNameOf(obj));
+  return 'Instance of $name';
+}
+
+int hashCodeForNativeObject(object) => Primitives.objectHashCode(object);
+
+/**
+ * Sets a JavaScript property on an object.
+ */
+void defineProperty(var obj, String property, var value) {
+  JS('void',
+      'Object.defineProperty(#, #, '
+          '{value: #, enumerable: false, writable: true, configurable: true})',
+      obj,
+      property,
+      value);
+}
+
+/**
+ * This method looks up the type name of [obj] in [methods]. [methods]
+ * is a Javascript object. If it cannot find it, it looks into the
+ * [_dynamicMetadata] array. If the method can still not be found, it
+ * creates a method that will throw a [NoSuchMethodError].
+ *
+ * Once it has a method, the prototype of [obj] is patched with that
+ * method, on the property [name]. The method is then invoked.
+ *
+ * This method returns the result of invoking the found method.
+ */
+dynamicBind(var obj,
+            String name,
+            var methods,
+            List arguments) {
+  // The tag is related to the class name.  E.g. the dart:html class
+  // '_ButtonElement' has the tag 'HTMLButtonElement'.  TODO(erikcorry): rename
+  // getTypeNameOf to getTypeTag.
+  String tag = getTypeNameOf(obj);
+  var hasOwnPropertyFunction = JS('var', 'Object.prototype.hasOwnProperty');
+
+  var method = dynamicBindLookup(hasOwnPropertyFunction, tag, methods);
+  if (method == null) {
+    String secondTag = alternateTag(obj, tag);
+    if (secondTag != null) {
+      method = dynamicBindLookup(hasOwnPropertyFunction, secondTag, methods);
+    }
+  }
+
+  // If we didn't find the method then look up in the Dart Object class, using
+  // getTypeNameOf in case the minifier has renamed Object.
+  if (method == null) {
+    String nameOfObjectClass = getTypeNameOf(const Object());
+    method =
+        lookupDynamicClass(hasOwnPropertyFunction, methods, nameOfObjectClass);
+  }
+
+  var proto = JS('var', 'Object.getPrototypeOf(#)', obj);
+  if (method == null) {
+    // If the method cannot be found, we use a trampoline method that
+    // will throw a [NoSuchMethodError] if the object is of the
+    // exact prototype, or will call [dynamicBind] again if the object
+    // is a subclass.
+    method = JS('var',
+        'function () {'
+          'if (Object.getPrototypeOf(this) === #) {'
+            'throw new TypeError(# + " is not a function");'
+          '} else {'
+            'return Object.prototype[#].apply(this, arguments);'
+          '}'
+        '}',
+      proto, name, name);
+  }
+
+  if (!callHasOwnProperty(hasOwnPropertyFunction, proto, name)) {
+    defineProperty(proto, name, method);
+  }
+
+  return JS('var', '#.apply(#, #)', method, obj, arguments);
+}
+
+dynamicBindLookup(var hasOwnPropertyFunction, String tag, var methods) {
+  var method = lookupDynamicClass(hasOwnPropertyFunction, methods, tag);
+  // Look at the inheritance data, getting the class tags and using them
+  // to check the methods table for this method name.
+  if (method == null && _dynamicMetadata != null) {
+    for (int i = 0; i < arrayLength(_dynamicMetadata); i++) {
+      MetaInfo entry = arrayGet(_dynamicMetadata, i);
+      if (callHasOwnProperty(hasOwnPropertyFunction, entry._set, tag)) {
+        method =
+            lookupDynamicClass(hasOwnPropertyFunction, methods, entry._tag);
+        // Stop if we found it in the methods array.
+        if (method != null) break;
+      }
+    }
+  }
+  return method;
+}
+
+// For each method name and class inheritance subtree, we use an ordinary JS
+// object as a hash map to store the method for each class.  Entries are added
+// in native_emitter.dart (see dynamicName).  In order to avoid the class names
+// clashing with the method names on Object.prototype (needed for native
+// objects) we must always use hasOwnProperty.
+var lookupDynamicClass(var hasOwnPropertyFunction,
+                       var methods,
+                       String className) {
+  return callHasOwnProperty(hasOwnPropertyFunction, methods, className) ?
+         propertyGet(methods, className) :
+         null;
+}
+
+/**
+ * Code for doing the dynamic dispatch on JavaScript prototypes that are not
+ * available at compile-time. Each property of a native Dart class
+ * is registered through this function, which is called with the
+ * following pattern:
+ *
+ * dynamicFunction('propertyName').prototypeName = // JS code
+ *
+ * What this function does is:
+ * - Creates a map of { prototypeName: JS code }.
+ * - Attaches 'propertyName' to the JS Object prototype that will
+ *   intercept at runtime all calls to propertyName.
+ * - Sets the value of 'propertyName' to the returned method from
+ *   [dynamicBind].
+ *
+ */
+dynamicFunction(name) {
+  var f = JS('var', 'Object.prototype[#]', name);
+  if (f != null && JS('bool', '!!#.methods', f)) {
+    return JS('var', '#.methods', f);
+  }
+
+  // TODO(ngeoffray): We could make this a map if the code we
+  // generate plays well with a Dart map.
+  var methods = JS('var', '{}');
+  // If there is a method attached to the Dart Object class, use it as
+  // the method to call in case no method is registered for that type.
+  var dartMethod = getPropertyFromPrototype(const Object(), name);
+  // Take the method from the Dart Object class if we didn't find it yet and it
+  // is there.
+  if (dartMethod != null) propertySet(methods, 'Object', dartMethod);
+
+  var bind = JS('var',
+      'function() {'
+        'return #(this, #, #, Array.prototype.slice.call(arguments));'
+      '}',
+    DART_CLOSURE_TO_JS(dynamicBind), name, methods);
+
+  JS('void', '#.methods = #', bind, methods);
+  defineProperty(JS('var', 'Object.prototype'), name, bind);
+  return methods;
+}
+
+/**
+ * This class encodes the class hierarchy when we need it for dynamic
+ * dispatch.
+ */
+class MetaInfo {
+  /**
+   * The type name this [MetaInfo] relates to.
+   */
+  String _tag;
+
+  /**
+   * A string containing the names of subtypes of [tag], separated by
+   * '|'.
+   */
+  String _tags;
+
+  /**
+   * A list of names of subtypes of [tag].
+   */
+  Object _set;
+
+  MetaInfo(this._tag, this._tags, this._set);
+}
+
+List<MetaInfo> get _dynamicMetadata {
+  // Because [dynamicMetadata] has to be shared with multiple isolates
+  // that access native classes (eg multiple DOM isolates),
+  // [_dynamicMetadata] cannot be a field, otherwise all non-main
+  // isolates would not have any value for it.
+  if (identical(JS('var', 'typeof(\$dynamicMetadata)'), 'undefined')) {
+    _dynamicMetadata = <MetaInfo>[];
+  }
+  return JS('var', '\$dynamicMetadata');
+}
+
+void set _dynamicMetadata(List<MetaInfo> table) {
+  JS('void', '\$dynamicMetadata = #', table);
+}
+
+/**
+ * Builds the metadata used for encoding the class hierarchy of native
+ * classes. The following example:
+ *
+ * class A native "*A" {}
+ * class B extends A native "*B" {}
+ *
+ * Will generate:
+ * ['A', 'A|B']
+ *
+ * This method returns a list of [MetaInfo] objects.
+ */
+List <MetaInfo> buildDynamicMetadata(List<List<String>> inputTable) {
+  List<MetaInfo> result = <MetaInfo>[];
+  for (int i = 0; i < arrayLength(inputTable); i++) {
+    String tag = JS('String', '#', arrayGet(arrayGet(inputTable, i), 0));
+    String tags = JS('String', '#', arrayGet(arrayGet(inputTable, i), 1));
+    var set = newJsObject();
+    List<String> tagNames = tags.split('|');
+    for (int j = 0; j < arrayLength(tagNames); j++) {
+      propertySet(set, arrayGet(tagNames, j), true);
+    }
+    result.add(new MetaInfo(tag, tags, set));
+  }
+  return result;
+}
+
+/**
+ * Called by the compiler to setup [_dynamicMetadata].
+ */
+void dynamicSetMetadata(List<List<String>> inputTable) {
+  _dynamicMetadata = buildDynamicMetadata(inputTable);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart
new file mode 100644
index 0000000..b62b0b5
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart
@@ -0,0 +1,151 @@
+// 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.
+
+part of _js_helper;
+
+List regExpExec(JSSyntaxRegExp regExp, String str) {
+  var nativeRegExp = regExpGetNative(regExp);
+  var result = JS('=List', r'#.exec(#)', nativeRegExp, str);
+  if (JS('bool', r'# == null', result)) return null;
+  return result;
+}
+
+bool regExpTest(JSSyntaxRegExp regExp, String str) {
+  var nativeRegExp = regExpGetNative(regExp);
+  return JS('bool', r'#.test(#)', nativeRegExp, str);
+}
+
+regExpGetNative(JSSyntaxRegExp regExp) {
+  var r = JS('var', r'#._re', regExp);
+  if (r == null) {
+    r = JS('var', r'#._re = #', regExp, regExpMakeNative(regExp));
+  }
+  return r;
+}
+
+regExpAttachGlobalNative(JSSyntaxRegExp regExp) {
+  JS('void', r'#._re = #', regExp, regExpMakeNative(regExp, global: true));
+}
+
+regExpMakeNative(JSSyntaxRegExp regExp, {bool global: false}) {
+  String pattern = regExp.pattern;
+  bool isMultiLine = regExp.isMultiLine;
+  bool isCaseSensitive = regExp.isCaseSensitive;
+  checkString(pattern);
+  StringBuffer sb = new StringBuffer();
+  if (isMultiLine) sb.add('m');
+  if (!isCaseSensitive) sb.add('i');
+  if (global) sb.add('g');
+  try {
+    return JS('var', r'new RegExp(#, #)', pattern, sb.toString());
+  } catch (e) {
+    throw new IllegalJSRegExpException(pattern,
+                                       JS('String', r'String(#)', e));
+  }
+}
+
+int regExpMatchStart(m) => JS('int', r'#.index', m);
+
+class JSSyntaxRegExp implements RegExp {
+  final String _pattern;
+  final bool _isMultiLine;
+  final bool _isCaseSensitive;
+
+  const JSSyntaxRegExp(String pattern,
+                       {bool multiLine: false,
+                        bool caseSensitive: true})
+      : _pattern = pattern,
+        _isMultiLine = multiLine,
+        _isCaseSensitive = caseSensitive;
+
+  Match firstMatch(String str) {
+    List<String> m = regExpExec(this, checkString(str));
+    if (m == null) return null;
+    var matchStart = regExpMatchStart(m);
+    // m.lastIndex only works with flag 'g'.
+    var matchEnd = matchStart + m[0].length;
+    return new _MatchImplementation(pattern, str, matchStart, matchEnd, m);
+  }
+
+  bool hasMatch(String str) => regExpTest(this, checkString(str));
+
+  String stringMatch(String str) {
+    var match = firstMatch(str);
+    return match == null ? null : match.group(0);
+  }
+
+  Iterable<Match> allMatches(String str) {
+    checkString(str);
+    return new _AllMatchesIterable(this, str);
+  }
+
+  String get pattern => _pattern;
+  bool get isMultiLine => _isMultiLine;
+  bool get isCaseSensitive => _isCaseSensitive;
+
+  static JSSyntaxRegExp _globalVersionOf(JSSyntaxRegExp other) {
+    JSSyntaxRegExp re =
+        new JSSyntaxRegExp(other.pattern,
+                           multiLine: other.isMultiLine,
+                           caseSensitive: other.isCaseSensitive);
+    regExpAttachGlobalNative(re);
+    return re;
+  }
+
+  _getNative() => regExpGetNative(this);
+}
+
+class _MatchImplementation implements Match {
+  final String pattern;
+  final String str;
+  final int start;
+  final int end;
+  final List<String> _groups;
+
+  const _MatchImplementation(
+      String this.pattern,
+      String this.str,
+      int this.start,
+      int this.end,
+      List<String> this._groups);
+
+  String group(int index) => _groups[index];
+  String operator [](int index) => group(index);
+  int get groupCount => _groups.length - 1;
+
+  List<String> groups(List<int> groups) {
+    List<String> out = [];
+    for (int i in groups) {
+      out.add(group(i));
+    }
+    return out;
+  }
+}
+
+class _AllMatchesIterable extends Iterable<Match> {
+  final JSSyntaxRegExp _re;
+  final String _str;
+
+  const _AllMatchesIterable(this._re, this._str);
+
+  Iterator<Match> get iterator => new _AllMatchesIterator(_re, _str);
+}
+
+class _AllMatchesIterator implements Iterator<Match> {
+  final RegExp _re;
+  final String _str;
+  Match _current;
+
+  _AllMatchesIterator(JSSyntaxRegExp re, String this._str)
+    : _re = JSSyntaxRegExp._globalVersionOf(re);
+
+  Match get current => _current;
+
+  bool moveNext() {
+    // firstMatch actually acts as nextMatch because of
+    // hidden global flag.
+    _current = _re.firstMatch(_str);
+    return _current != null;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart
new file mode 100644
index 0000000..326b892
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart
@@ -0,0 +1,130 @@
+// 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.
+
+// This is an empty dummy patch file for the VM dart:scalarlist library.
+// This is needed in order to be able to generate documentation for the
+// scalarlist library.
+
+patch class Int8List {
+  patch factory Int8List(int length) {
+    throw new UnsupportedError('Int8List');
+  }
+
+  patch factory Int8List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int8List.view');
+  }
+}
+
+
+patch class Uint8List {
+  patch factory Uint8List(int length) {
+    throw new UnsupportedError('Uint8List');
+  }
+
+  patch factory Uint8List.view(ByteArray array,
+                               [int start = 0, int length]) {
+    throw new UnsupportedError('Uint8List.view');
+  }
+}
+
+
+patch class Uint8ClampedList {
+  patch factory Uint8ClampedList(int length) {
+    throw new UnsupportedError('Uint8ClampedList');
+  }
+
+  patch factory Uint8ClampedList.view(ByteArray array,
+                                      [int start = 0, int length]) {
+    throw new UnsupportedError('Uint8ClampedList.view');
+  }
+}
+
+
+patch class Int16List {
+  patch factory Int16List(int length) {
+    throw new UnsupportedError('Int16List');
+
+  }
+
+  patch factory Int16List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int16List.view');
+  }
+}
+
+
+patch class Uint16List {
+  patch factory Uint16List(int length) {
+    throw new UnsupportedError('Uint16List');
+  }
+
+  patch factory Uint16List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Uint16List.view');
+  }
+}
+
+
+patch class Int32List {
+  patch factory Int32List(int length) {
+    throw new UnsupportedError('Int32List');
+  }
+
+  patch factory Int32List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int32List.view');
+  }
+}
+
+
+patch class Uint32List {
+  patch factory Uint32List(int length) {
+    throw new UnsupportedError('Uint32List');
+  }
+
+  patch factory Uint32List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Uint32List.view');
+  }
+}
+
+
+patch class Int64List {
+  patch factory Int64List(int length) {
+    throw new UnsupportedError('Int64List');
+  }
+
+  patch factory Int64List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int64List.view');
+  }
+}
+
+
+patch class Uint64List {
+  patch factory Uint64List(int length) {
+    throw new UnsupportedError('Uint64List');
+  }
+
+  patch factory Uint64List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Uint64List.view');
+  }
+}
+
+
+patch class Float32List {
+  patch factory Float32List(int length) {
+    throw new UnsupportedError('Float32List');
+  }
+
+  patch factory Float32List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Float32List.view');
+  }
+}
+
+
+patch class Float64List {
+  patch factory Float64List(int length) {
+    throw new UnsupportedError('Float64List');
+  }
+
+  patch factory Float64List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Float64List.view');
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart
new file mode 100644
index 0000000..8ac1fa4
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart
@@ -0,0 +1,234 @@
+// 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.
+
+part of _js_helper;
+
+class StringMatch implements Match {
+  const StringMatch(int this.start,
+                    String this.str,
+                    String this.pattern);
+
+  int get end => start + pattern.length;
+  String operator[](int g) => group(g);
+  int get groupCount => 0;
+
+  String group(int group_) {
+    if (group_ != 0) {
+      throw new RangeError.value(group_);
+    }
+    return pattern;
+  }
+
+  List<String> groups(List<int> groups_) {
+    List<String> result = new List<String>();
+    for (int g in groups_) {
+      result.add(group(g));
+    }
+    return result;
+  }
+
+  final int start;
+  final String str;
+  final String pattern;
+}
+
+List<Match> allMatchesInStringUnchecked(String needle, String haystack) {
+  // Copied from StringBase.allMatches in
+  // /runtime/lib/string_base.dart
+  List<Match> result = new List<Match>();
+  int length = haystack.length;
+  int patternLength = needle.length;
+  int startIndex = 0;
+  while (true) {
+    int position = haystack.indexOf(needle, startIndex);
+    if (position == -1) {
+      break;
+    }
+    result.add(new StringMatch(position, haystack, needle));
+    int endIndex = position + patternLength;
+    if (endIndex == length) {
+      break;
+    } else if (position == endIndex) {
+      ++startIndex;  // empty match, advance and restart
+    } else {
+      startIndex = endIndex;
+    }
+  }
+  return result;
+}
+
+stringContainsUnchecked(receiver, other, startIndex) {
+  if (other is String) {
+    return receiver.indexOf(other, startIndex) != -1;
+  } else if (other is JSSyntaxRegExp) {
+    return other.hasMatch(receiver.substring(startIndex));
+  } else {
+    var substr = receiver.substring(startIndex);
+    return other.allMatches(substr).iterator.moveNext();
+  }
+}
+
+stringReplaceJS(receiver, replacer, to) {
+  // The JavaScript String.replace method recognizes replacement
+  // patterns in the replacement string. Dart does not have that
+  // behavior.
+  to = JS('String', r"#.replace('$', '$$$$')", to);
+  return JS('String', r'#.replace(#, #)', receiver, replacer, to);
+}
+
+final RegExp quoteRegExp = new JSSyntaxRegExp(r'[-[\]{}()*+?.,\\^$|#\s]');
+
+stringReplaceAllUnchecked(receiver, from, to) {
+  checkString(to);
+  if (from is String) {
+    if (from == "") {
+      if (receiver == "") {
+        return to;
+      } else {
+        StringBuffer result = new StringBuffer();
+        int length = receiver.length;
+        result.add(to);
+        for (int i = 0; i < length; i++) {
+          result.add(receiver[i]);
+          result.add(to);
+        }
+        return result.toString();
+      }
+    } else {
+      var quoter = regExpMakeNative(quoteRegExp, global: true);
+      var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter);
+      RegExp replaceRegExp = new JSSyntaxRegExp(quoted);
+      var replacer = regExpMakeNative(replaceRegExp, global: true);
+      return stringReplaceJS(receiver, replacer, to);
+    }
+  } else if (from is JSSyntaxRegExp) {
+    var re = regExpMakeNative(from, global: true);
+    return stringReplaceJS(receiver, re, to);
+  } else {
+    checkNull(from);
+    // TODO(floitsch): implement generic String.replace (with patterns).
+    throw "String.replaceAll(Pattern) UNIMPLEMENTED";
+  }
+}
+
+String _matchString(Match match) => match[0];
+String _stringIdentity(String string) => string;
+
+stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) {
+  if (pattern is! Pattern) {
+    throw new ArgumentError("${pattern} is not a Pattern");
+  }
+  if (onMatch == null) onMatch = _matchString;
+  if (onNonMatch == null) onNonMatch = _stringIdentity;
+  if (pattern is String) {
+    return stringReplaceAllStringFuncUnchecked(receiver, pattern,
+                                               onMatch, onNonMatch);
+  }
+  StringBuffer buffer = new StringBuffer();
+  int startIndex = 0;
+  for (Match match in pattern.allMatches(receiver)) {
+    buffer.add(onNonMatch(receiver.substring(startIndex, match.start)));
+    buffer.add(onMatch(match));
+    startIndex = match.end;
+  }
+  buffer.add(onNonMatch(receiver.substring(startIndex)));
+  return buffer.toString();
+}
+
+stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) {
+  // Pattern is the empty string.
+  StringBuffer buffer = new StringBuffer();
+  int length = receiver.length;
+  int i = 0;
+  buffer.add(onNonMatch(""));
+  while (i < length) {
+    buffer.add(onMatch(new StringMatch(i, receiver, "")));
+    // Special case to avoid splitting a surrogate pair.
+    int code = receiver.charCodeAt(i);
+    if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
+      // Leading surrogate;
+      code = receiver.charCodeAt(i + 1);
+      if ((code & ~0x3FF) == 0xDC00) {
+        // Matching trailing surrogate.
+        buffer.add(onNonMatch(receiver.substring(i, i + 2)));
+        i += 2;
+        continue;
+      }
+    }
+    buffer.add(onNonMatch(receiver[i]));
+    i++;
+  }
+  buffer.add(onMatch(new StringMatch(i, receiver, "")));
+  buffer.add(onNonMatch(""));
+  return buffer.toString();
+}
+
+stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) {
+  int patternLength = pattern.length;
+  if (patternLength == 0) {
+    return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch);
+  }
+  int length = receiver.length;
+  StringBuffer buffer = new StringBuffer();
+  int startIndex = 0;
+  while (startIndex < length) {
+    int position = receiver.indexOf(pattern, startIndex);
+    if (position == -1) {
+      break;
+    }
+    buffer.add(onNonMatch(receiver.substring(startIndex, position)));
+    buffer.add(onMatch(new StringMatch(position, receiver, pattern)));
+    startIndex = position + patternLength;
+  }
+  buffer.add(onNonMatch(receiver.substring(startIndex)));
+  return buffer.toString();
+}
+
+
+stringReplaceFirstUnchecked(receiver, from, to) {
+  if (from is String) {
+    return stringReplaceJS(receiver, from, to);
+  } else if (from is JSSyntaxRegExp) {
+    var re = regExpGetNative(from);
+    return stringReplaceJS(receiver, re, to);
+  } else {
+    checkNull(from);
+    // TODO(floitsch): implement generic String.replace (with patterns).
+    throw "String.replace(Pattern) UNIMPLEMENTED";
+  }
+}
+
+stringJoinUnchecked(array, separator) {
+  return JS('String', r'#.join(#)', array, separator);
+}
+
+class JsStringBuffer implements StringBuffer {
+  String _contents;
+
+  JsStringBuffer(content)
+      : _contents = (content is String) ? content : '$content';
+
+  int get length => _contents.length;
+
+  bool get isEmpty => length == 0;
+
+  void add(Object obj) {
+    _contents = JS('String', '# + #', _contents,
+                   (obj is String) ? obj : '$obj');
+  }
+
+  void addAll(Iterable objects) {
+    for (Object obj in objects) add(obj);
+  }
+
+  void addCharCode(int charCode) {
+    add(new String.fromCharCodes([charCode]));
+  }
+
+  void clear() {
+    _contents = "";
+  }
+
+  String toString() => _contents;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/library_loader.dart b/pkgs/markdown/lib/src/compiler/implementation/library_loader.dart
new file mode 100644
index 0000000..074cece
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/library_loader.dart
@@ -0,0 +1,837 @@
+// 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.
+
+part of dart2js;
+
+/**
+ * [CompilerTask] for loading libraries and setting up the import/export scopes.
+ *
+ * The library loader uses four different kinds of URIs in different parts of
+ * the loading process.
+ *
+ * ## User URI ##
+ *
+ * A 'user URI' is a URI provided by the user in code and as the main entry URI
+ * at the command line. These generally come in 3 versions:
+ *
+ *   * A relative URI such as 'foo.dart', '../bar.dart', and 'baz/boz.dart'.
+ *
+ *   * A dart URI such as 'dart:core' and 'dart:_js_helper'.
+ *
+ *   * A package URI such as 'package:foo.dart' and 'package:bar/baz.dart'.
+ *
+ * A user URI can also be absolute, like 'file:///foo.dart' or
+ * 'http://example.com/bar.dart', but such URIs cannot necessarily be used for
+ * locating source files, since the scheme must be supported by the input
+ * provider. The standard input provider for dart2js only supports the 'file'
+ * scheme.
+ *
+ * ## Resolved URI ##
+ *
+ * A 'resolved URI' is a (user) URI that has been resolved to an absolute URI
+ * based on the readable URI (see below) from which it was loaded. A URI with an
+ * explicit scheme (such as 'dart:', 'package:' or 'file:') is already resolved.
+ * A relative URI like for instance '../foo/bar.dart' is translated into an
+ * resolved URI in one of three ways:
+ *
+ *  * If provided as the main entry URI at the command line, the URI is resolved
+ *    relative to the current working directory, say
+ *    'file:///current/working/dir/', and the resolved URI is therefore
+ *    'file:///current/working/foo/bar.dart'.
+ *
+ *  * If the relative URI is provided in an import, export or part tag, and the
+ *    readable URI of the enclosing compilation unit is a file URI,
+ *    'file://some/path/baz.dart', then the resolved URI is
+ *    'file://some/foo/bar.dart'.
+ *
+ *  * If the relative URI is provided in an import, export or part tag, and the
+ *    readable URI of the enclosing compilation unit is a package URI,
+ *    'package:some/path/baz.dart', then the resolved URI is
+ *    'package:some/foo/bar.dart'.
+ *
+ * The resolved URI thus preserves the scheme through resolution: A readable
+ * file URI results in an resolved file URI and a readable package URI results
+ * in an resolved package URI. Note that since a dart URI is not a readable URI,
+ * import, export or part tags within platform libraries are not interpreted as
+ * dart URIs but instead relative to the library source file location.
+ *
+ * The resolved URI of a library is also used as the canonical URI
+ * ([LibraryElement.canonicalUri]) by which we identify which libraries are
+ * identical. This means that libraries loaded through the 'package' scheme will
+ * resolve to the same library when loaded from within using relative URIs (see
+ * for instance the test 'standalone/package/package1_test.dart'). But loading a
+ * platform library using a relative URI will _not_ result in the same library
+ * as when loaded through the dart URI.
+ *
+ * ## Readable URI ##
+ *
+ * A 'readable URI' is an absolute URI whose scheme is either 'package' or
+ * something supported by the input provider, normally 'file'. Dart URIs such as
+ * 'dart:core' and 'dart:_js_helper' are not readable themselves but are instead
+ * resolved into a readable URI using the library root URI provided from the
+ * command line and the list of platform libraries found in
+ * 'sdk/lib/_internal/libraries.dart'. This is done through the
+ * [Compiler.translateResolvedUri] method which checks whether a library by that
+ * name exists and in case of internal libraries whether access is granted.
+ *
+ * ## Resource URI ##
+ *
+ * A 'resource URI' is an absolute URI with a scheme supported by the input
+ * provider. For the standard implementation this means a URI with the 'file'
+ * scheme. Readable URIs are converted into resource URIs as part of the
+ * [Compiler.readScript] method. In the standard implementation the package URIs
+ * are converted to file URIs using the package root URI provided on the
+ * command line as base. If the package root URI is
+ * 'file:///current/working/dir/' then the package URI 'package:foo/bar.dart'
+ * will be resolved to the resource URI
+ * 'file:///current/working/dir/foo/bar.dart'.
+ *
+ * The distinction between readable URI and resource URI is necessary to ensure
+ * that these imports
+ *
+ *     import 'package:foo.dart' as a;
+ *     import 'packages/foo.dart' as b;
+ *
+ * do _not_ resolve to the same library when the package root URI happens to
+ * point to the 'packages' folder.
+ *
+ */
+abstract class LibraryLoader extends CompilerTask {
+  LibraryLoader(Compiler compiler) : super(compiler);
+
+  /**
+   * Loads the library specified by the [resolvedUri] 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].
+   */
+  // TODO(johnniwinther): Remove [canonicalUri] together with
+  // [Compiler.scanBuiltinLibrary].
+  LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri);
+
+  // TODO(johnniwinther): Remove this when patches don't need special parsing.
+  void registerLibraryFromTag(LibraryDependencyHandler handler,
+                              LibraryElement library,
+                              LibraryDependency tag);
+
+  /**
+   * Adds the elements in the export scope of [importedLibrary] to the import
+   * scope of [importingLibrary].
+   */
+  // TODO(johnniwinther): Move handling of 'js_helper' to the library loader
+  // to remove this method from the [LibraryLoader] interface.
+  void importLibrary(LibraryElement importingLibrary,
+                     LibraryElement importedLibrary,
+                     Import tag);
+}
+
+/**
+ * [CombinatorFilter] is a succinct representation of a list of combinators from
+ * a library dependency tag.
+ */
+class CombinatorFilter {
+  const CombinatorFilter();
+
+  /**
+   * Returns [:true:] if [element] is excluded by this filter.
+   */
+  bool exclude(Element element) => false;
+
+  /**
+   * Creates a filter based on the combinators of [tag].
+   */
+  factory CombinatorFilter.fromTag(LibraryDependency tag) {
+    if (tag == null || tag.combinators == null) {
+      return const CombinatorFilter();
+    }
+
+    // If the list of combinators contain at least one [:show:] we can create
+    // a positive list of elements to include, otherwise we create a negative
+    // list of elements to exclude.
+    bool show = false;
+    Set<SourceString> nameSet;
+    for (Combinator combinator in tag.combinators) {
+      if (combinator.isShow) {
+        show = true;
+        var set = new Set<SourceString>();
+        for (Identifier identifier in combinator.identifiers) {
+          set.add(identifier.source);
+        }
+        if (nameSet == null) {
+          nameSet = set;
+        } else {
+          nameSet = nameSet.intersection(set);
+        }
+      }
+    }
+    if (nameSet == null) {
+      nameSet = new Set<SourceString>();
+    }
+    for (Combinator combinator in tag.combinators) {
+      if (combinator.isHide) {
+        for (Identifier identifier in combinator.identifiers) {
+          if (show) {
+            // We have a positive list => Remove hidden elements.
+            nameSet.remove(identifier.source);
+          } else {
+            // We have no positive list => Accumulate hidden elements.
+            nameSet.add(identifier.source);
+          }
+        }
+      }
+    }
+    return show ? new ShowFilter(nameSet) : new HideFilter(nameSet);
+  }
+}
+
+/**
+ * A list of combinators represented as a list of element names to include.
+ */
+class ShowFilter extends CombinatorFilter {
+  final Set<SourceString> includedNames;
+
+  ShowFilter(this.includedNames);
+
+  bool exclude(Element element) => !includedNames.contains(element.name);
+}
+
+/**
+ * A list of combinators represented as a list of element names to exclude.
+ */
+class HideFilter extends CombinatorFilter {
+  final Set<SourceString> excludedNames;
+
+  HideFilter(this.excludedNames);
+
+  bool exclude(Element element) => excludedNames.contains(element.name);
+}
+
+/**
+ * Implementation class for [LibraryLoader]. The distinction between
+ * [LibraryLoader] and [LibraryLoaderTask] is made to hide internal members from
+ * the [LibraryLoader] interface.
+ */
+class LibraryLoaderTask extends LibraryLoader {
+  LibraryLoaderTask(Compiler compiler) : super(compiler);
+  String get name => 'LibraryLoader';
+
+  final Map<String, LibraryElement> libraryNames =
+      new LinkedHashMap<String, LibraryElement>();
+
+  LibraryDependencyHandler currentHandler;
+
+  LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri) {
+    return measure(() {
+      assert(currentHandler == null);
+      currentHandler = new LibraryDependencyHandler(compiler);
+      LibraryElement library =
+          createLibrary(currentHandler, null, resolvedUri, 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) {
+    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) {
+        Import import = tag;
+        tagState = checkTag(TagState.IMPORT_OR_EXPORT, import);
+        if (import.uri.dartString.slowToString() == 'dart:core') {
+          importsDartCore = true;
+        }
+        libraryDependencies.addLast(import);
+      } 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) {
+        Part part = tag;
+        StringNode uri = part.uri;
+        Uri resolvedUri = base.resolve(uri.dartString.slowToString());
+        tagState = checkTag(TagState.SOURCE, part);
+        scanPart(part, resolvedUri, library);
+      } else {
+        compiler.internalError("Unhandled library tag.", node: tag);
+      }
+    }
+
+    // Apply patch, if any.
+    if (library.isPlatformLibrary) {
+      patchDartLibrary(handler, library, library.canonicalUri.path);
+    }
+
+    // Import dart:core if not already imported.
+    if (!importsDartCore && !isDartCore(library.canonicalUri)) {
+      handler.registerDependency(library, null, loadCoreLibrary(handler));
+    }
+
+    for (LibraryDependency tag in libraryDependencies.toLink()) {
+      registerLibraryFromTag(handler, library, tag);
+    }
+  }
+
+  void checkDuplicatedLibraryName(LibraryElement library) {
+    LibraryName tag = library.libraryTag;
+    if (tag != null) {
+      String name = library.getLibraryOrScriptName();
+      LibraryElement existing =
+          libraryNames.putIfAbsent(name, () => library);
+      if (!identical(existing, library)) {
+        Uri uri = library.entryCompilationUnit.script.uri;
+        compiler.reportMessage(
+            compiler.spanFromSpannable(tag.name, uri),
+            MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': name}),
+            api.Diagnostic.WARNING);
+        Uri existingUri = existing.entryCompilationUnit.script.uri;
+        compiler.reportMessage(
+            compiler.spanFromSpannable(existing.libraryTag.name, existingUri),
+            MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': 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
+          = createLibrary(handler, null, coreUri, null, coreUri);
+    }
+    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 [resolvedUri] given is
+   * used as is, any URI resolution should be done beforehand.
+   */
+  void scanPart(Part part, Uri resolvedUri, LibraryElement library) {
+    if (!resolvedUri.isAbsolute()) throw new ArgumentError(resolvedUri);
+    Uri readableUri = compiler.translateResolvedUri(library, resolvedUri, part);
+    Script sourceScript = compiler.readScript(readableUri, part);
+    CompilationUnitElement unit =
+        new CompilationUnitElementX(sourceScript, library);
+    compiler.withCurrentElement(unit, () {
+      compiler.scanner.scan(unit);
+      if (unit.partTag == null) {
+        bool wasDiagnosticEmitted = false;
+        compiler.withCurrentElement(library, () {
+          wasDiagnosticEmitted =
+              compiler.onDeprecatedFeature(part, 'missing part-of tag');
+        });
+        if (wasDiagnosticEmitted) {
+          compiler.reportMessage(
+              compiler.spanFromElement(unit),
+              MessageKind.MISSING_PART_OF_TAG.error(),
+              api.Diagnostic.INFO);
+        }
+      }
+    });
+  }
+
+  /**
+   * 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 registerLibraryFromTag(LibraryDependencyHandler handler,
+                              LibraryElement library,
+                              LibraryDependency tag) {
+    Uri base = library.entryCompilationUnit.script.uri;
+    Uri resolvedUri = base.resolve(tag.uri.dartString.slowToString());
+    LibraryElement loadedLibrary =
+        createLibrary(handler, library, resolvedUri, tag.uri, resolvedUri);
+    handler.registerDependency(library, tag, loadedLibrary);
+
+    if (!loadedLibrary.hasLibraryName()) {
+      compiler.withCurrentElement(library, () {
+        compiler.reportError(tag == null ? null : tag.uri,
+            'no library name found in ${loadedLibrary.canonicalUri}');
+      });
+    }
+  }
+
+  /**
+   * Create (or reuse) a library element for the library specified by the
+   * [resolvedUri].
+   *
+   * If a new library is created, the [handler] is notified.
+   */
+  // TODO(johnniwinther): Remove [canonicalUri] and make [resolvedUri] the
+  // canonical uri when [Compiler.scanBuiltinLibrary] is removed.
+  LibraryElement createLibrary(LibraryDependencyHandler handler,
+                               LibraryElement importingLibrary,
+                               Uri resolvedUri, Node node, Uri canonicalUri) {
+    bool newLibrary = false;
+    Uri readableUri =
+        compiler.translateResolvedUri(importingLibrary, resolvedUri, node);
+    if (readableUri == null) return null;
+    LibraryElement createLibrary() {
+      newLibrary = true;
+      Script script = compiler.readScript(readableUri, node);
+      LibraryElement element = new LibraryElementX(script, canonicalUri);
+      handler.registerNewLibrary(element);
+      native.maybeEnableNative(compiler, element);
+      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.onLibraryScanned(library, resolvedUri);
+      });
+    }
+    return library;
+  }
+
+  // TODO(johnniwinther): Remove this method when 'js_helper' is handled by
+  // [LibraryLoaderTask].
+  void importLibrary(LibraryElement importingLibrary,
+                     LibraryElement importedLibrary,
+                     Import tag) {
+    new ImportLink(tag, importedLibrary).importLibrary(compiler,
+                                                       importingLibrary);
+  }
+}
+
+
+/**
+ * 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 [importedLibrary] imported through [import].
+ */
+class ImportLink {
+  final Import import;
+  final LibraryElement importedLibrary;
+
+  ImportLink(this.import, this.importedLibrary);
+
+  /**
+   * Imports the library into the [importingLibrary].
+   */
+  void importLibrary(Compiler compiler, LibraryElement importingLibrary) {
+    assert(invariant(importingLibrary,
+                     importedLibrary.exportsHandled,
+                     message: 'Exports not handled on $importedLibrary'));
+    var combinatorFilter = new CombinatorFilter.fromTag(import);
+    if (import != null && import.prefix != null) {
+      SourceString prefix = import.prefix.source;
+      Element e = importingLibrary.find(prefix);
+      if (e == null) {
+        e = new PrefixElementX(prefix, importingLibrary.entryCompilationUnit,
+                               import.getBeginToken());
+        importingLibrary.addToScope(e, compiler);
+      }
+      if (!identical(e.kind, ElementKind.PREFIX)) {
+        compiler.withCurrentElement(e, () {
+          compiler.reportWarning(new Identifier(e.position()),
+          'duplicated definition');
+        });
+        compiler.reportError(import.prefix, 'duplicate definition');
+      }
+      PrefixElement prefixElement = e;
+      importedLibrary.forEachExport((Element element) {
+        if (combinatorFilter.exclude(element)) return;
+        // TODO(johnniwinther): Clean-up like [checkDuplicateLibraryName].
+        Element existing =
+            prefixElement.imported.putIfAbsent(element.name, () => element);
+        if (!identical(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, () {
+          if (combinatorFilter.exclude(element)) return;
+          importingLibrary.addImport(element, compiler);
+        });
+      });
+    }
+  }
+}
+
+/**
+ * The combinator filter computed from an export tag and the library dependency
+ * node for the library that declared the export tag. This represents an edge in
+ * the library dependency graph.
+ */
+class ExportLink {
+  final CombinatorFilter combinatorFilter;
+  final LibraryDependencyNode exportNode;
+
+  ExportLink(Export export, LibraryDependencyNode this.exportNode)
+      : this.combinatorFilter = new CombinatorFilter.fromTag(export);
+
+  /**
+   * Exports [element] to the dependent library unless [element] is filtered by
+   * the export combinators. Returns [:true:] if the set pending exports of the
+   * dependent library was modified.
+   */
+  bool exportElement(Element element) {
+    if (combinatorFilter.exclude(element)) return false;
+    return exportNode.addElementToPendingExports(element);
+  }
+}
+
+/**
+ * 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;
+
+  // TODO(ahe): Remove [hashCodeCounter] and [hashCode] when
+  // VM implementation of Object.hashCode is not slow.
+  final int hashCode = ++hashCodeCounter;
+  static int hashCodeCounter = 0;
+
+
+  /**
+   * 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 Link<ImportLink>();
+
+  /**
+   * A linked list of the export tags the dependent upon this node library.
+   * This is used to propagate exports during the computation of export scopes.
+   */
+  Link<ExportLink> dependencies = const Link<ExportLink>();
+
+  /**
+   * The export scope for [library] which is gradually computed by the work-list
+   * computation in [LibraryDependencyHandler.computeExports].
+   */
+  Map<SourceString, Element> exportScope =
+      new LinkedHashMap<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) {
+    dependencies =
+        dependencies.prepend(new ExportLink(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.getNonPrivateElementsInScope());
+  }
+
+  void registerHandledExports(LibraryElement exportedLibraryElement,
+                              CombinatorFilter filter) {
+    assert(invariant(library, exportedLibraryElement.exportsHandled));
+    for (Element exportedElement in exportedLibraryElement.exports) {
+      if (!filter.exclude(exportedElement)) {
+        pendingExportSet.add(exportedElement);
+      }
+    }
+  }
+
+  /**
+   * Registers the compute export scope with the node library.
+   */
+  void registerExports() {
+    library.setExports(exportScope.values.toList());
+  }
+
+  /**
+   * Registers the imports of the node library.
+   */
+  void registerImports(Compiler compiler) {
+    for (ImportLink link in imports) {
+      link.importLibrary(compiler, library);
+    }
+  }
+
+  /**
+   * 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 export scope.
+   */
+  Element addElementToExportScope(Compiler compiler, Element element) {
+    SourceString name = element.name;
+    Element existingElement = exportScope[name];
+    if (existingElement != null) {
+      if (existingElement.isErroneous()) {
+        compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT,
+                                 {'name': name});
+        element = existingElement;
+      } else if (existingElement.getLibrary() != library) {
+        // Declared elements hide exported elements.
+        compiler.reportErrorCode(existingElement, MessageKind.DUPLICATE_EXPORT,
+                                 {'name': name});
+        compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT,
+                                 {'name': name});
+        element = exportScope[name] = new ErroneousElementX(
+            MessageKind.DUPLICATE_EXPORT, {'name': name}, name, library);
+      }
+    } else {
+      exportScope[name] = element;
+    }
+    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;
+    for (ExportLink link in dependencies) {
+      if (link.exportElement(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(Element element) {
+    if (!identical(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 LinkedHashMap<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;
+      Map<LibraryDependencyNode, List<Element>> tasks =
+          new LinkedHashMap<LibraryDependencyNode, List<Element>>();
+
+      // Locally defined elements take precedence over exported
+      // elements.  So we must propagate local elements first.  We
+      // ensure this by pulling the pending exports before
+      // propagating.  This enforces that we handle exports
+      // breadth-first, with locally defined elements being level 0.
+      nodeMap.forEach((_, LibraryDependencyNode node) {
+        List<Element> pendingExports = node.pullPendingExports();
+        tasks[node] = pendingExports;
+      });
+      tasks.forEach((LibraryDependencyNode node, List<Element> pendingExports) {
+        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].
+      LibraryDependencyNode exportingNode = nodeMap[library];
+      if (loadedLibrary.exportsHandled) {
+        // Export scope already computed on [loadedLibrary].
+        var combinatorFilter = new CombinatorFilter.fromTag(tag);
+        exportingNode.registerHandledExports(loadedLibrary, combinatorFilter);
+        return;
+      }
+      LibraryDependencyNode exportedNode = nodeMap[loadedLibrary];
+      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();
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart
new file mode 100644
index 0000000..13a15f8
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart
@@ -0,0 +1,1742 @@
+// 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.
+
+library mirrors_dart2js;
+
+import 'dart:async';
+import 'dart:collection' show LinkedHashMap;
+import 'dart:io';
+import 'dart:uri';
+
+import '../../compiler.dart' as diagnostics;
+import '../elements/elements.dart';
+import '../resolution/resolution.dart' show ResolverTask, ResolverVisitor;
+import '../apiimpl.dart' show Compiler;
+import '../scanner/scannerlib.dart' hide SourceString;
+import '../ssa/ssa.dart';
+import '../dart2jslib.dart' hide Compiler;
+import '../dart_types.dart';
+import '../filenames.dart';
+import '../source_file.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../util/uri_extras.dart';
+import '../dart2js.dart';
+import '../util/characters.dart';
+import '../source_file_provider.dart';
+
+import 'mirrors.dart';
+import 'mirrors_util.dart';
+import 'util.dart';
+
+//------------------------------------------------------------------------------
+// Utility types and functions for the dart2js mirror system
+//------------------------------------------------------------------------------
+
+bool _isPrivate(String name) {
+  return name.startsWith('_');
+}
+
+List<ParameterMirror> _parametersFromFunctionSignature(
+    Dart2JsMirrorSystem system,
+    Dart2JsMethodMirror method,
+    FunctionSignature signature) {
+  var parameters = <ParameterMirror>[];
+  Link<Element> link = signature.requiredParameters;
+  while (!link.isEmpty) {
+    parameters.add(new Dart2JsParameterMirror(
+        system, method, link.head, false, false));
+    link = link.tail;
+  }
+  link = signature.optionalParameters;
+  bool isNamed = signature.optionalParametersAreNamed;
+  while (!link.isEmpty) {
+    parameters.add(new Dart2JsParameterMirror(
+        system, method, link.head, true, isNamed));
+    link = link.tail;
+  }
+  return parameters;
+}
+
+Dart2JsTypeMirror _convertTypeToTypeMirror(
+    Dart2JsMirrorSystem system,
+    DartType type,
+    InterfaceType defaultType,
+    [FunctionSignature functionSignature]) {
+  if (type == null) {
+    return new Dart2JsInterfaceTypeMirror(system, defaultType);
+  } else if (type is InterfaceType) {
+    if (type == system.compiler.types.dynamicType) {
+      return new Dart2JsDynamicMirror(system, type);
+    } else {
+      return new Dart2JsInterfaceTypeMirror(system, type);
+    }
+  } else if (type is TypeVariableType) {
+    return new Dart2JsTypeVariableMirror(system, type);
+  } else if (type is FunctionType) {
+    return new Dart2JsFunctionTypeMirror(system, type, functionSignature);
+  } else if (type is VoidType) {
+    return new Dart2JsVoidMirror(system, type);
+  } else if (type is TypedefType) {
+    return new Dart2JsTypedefMirror(system, type);
+  } else if (type is MalformedType) {
+    // TODO(johnniwinther): We need a mirror on malformed types.
+    return system.dynamicType;
+  }
+  _diagnosticListener.internalError(
+      "Unexpected type $type of kind ${type.kind}");
+  system.compiler.internalError("Unexpected type $type of kind ${type.kind}");
+}
+
+Collection<Dart2JsMemberMirror> _convertElementMemberToMemberMirrors(
+    Dart2JsContainerMirror library, Element element) {
+  if (element.isSynthesized) {
+    return const <Dart2JsMemberMirror>[];
+  } else if (element is VariableElement) {
+    return <Dart2JsMemberMirror>[new Dart2JsFieldMirror(library, element)];
+  } else if (element is FunctionElement) {
+    return <Dart2JsMemberMirror>[new Dart2JsMethodMirror(library, element)];
+  } else if (element is AbstractFieldElement) {
+    var members = <Dart2JsMemberMirror>[];
+    if (element.getter != null) {
+      members.add(new Dart2JsMethodMirror(library, element.getter));
+    }
+    if (element.setter != null) {
+      members.add(new Dart2JsMethodMirror(library, element.setter));
+    }
+    return members;
+  }
+  library.mirrors.compiler.internalError(
+      "Unexpected member type $element ${element.kind}");
+}
+
+MethodMirror _convertElementMethodToMethodMirror(Dart2JsContainerMirror library,
+                                                 Element element) {
+  if (element is FunctionElement) {
+    return new Dart2JsMethodMirror(library, element);
+  } else {
+    return null;
+  }
+}
+
+InstanceMirror _convertConstantToInstanceMirror(Dart2JsMirrorSystem mirrors,
+                                                Constant constant) {
+  if (constant is BoolConstant) {
+    return new Dart2JsBoolConstantMirror(mirrors, constant);
+  } else if (constant is NumConstant) {
+    return new Dart2JsNumConstantMirror(mirrors, constant);
+  } else if (constant is StringConstant) {
+    return new Dart2JsStringConstantMirror(mirrors, constant);
+  } else if (constant is ListConstant) {
+    return new Dart2JsListConstantMirror(mirrors, constant);
+  } else if (constant is MapConstant) {
+    return new Dart2JsMapConstantMirror(mirrors, constant);
+  } else if (constant is TypeConstant) {
+    return new Dart2JsTypeConstantMirror(mirrors, constant);
+  } else if (constant is FunctionConstant) {
+    return new Dart2JsConstantMirror(mirrors, constant);
+  } else if (constant is NullConstant) {
+    return new Dart2JsNullConstantMirror(mirrors, constant);
+  } else if (constant is ConstructedConstant) {
+    return new Dart2JsConstructedConstantMirror(mirrors, constant);
+  }
+  mirrors.compiler.internalError("Unexpected constant $constant");
+}
+
+class Dart2JsMethodKind {
+  static const Dart2JsMethodKind REGULAR = const Dart2JsMethodKind("regular");
+  static const Dart2JsMethodKind GENERATIVE =
+      const Dart2JsMethodKind("generative");
+  static const Dart2JsMethodKind REDIRECTING =
+      const Dart2JsMethodKind("redirecting");
+  static const Dart2JsMethodKind CONST = const Dart2JsMethodKind("const");
+  static const Dart2JsMethodKind FACTORY = const Dart2JsMethodKind("factory");
+  static const Dart2JsMethodKind GETTER = const Dart2JsMethodKind("getter");
+  static const Dart2JsMethodKind SETTER = const Dart2JsMethodKind("setter");
+  static const Dart2JsMethodKind OPERATOR = const Dart2JsMethodKind("operator");
+
+  final String text;
+
+  const Dart2JsMethodKind(this.text);
+
+  String toString() => text;
+}
+
+
+String _getOperatorFromOperatorName(String name) {
+  Map<String, String> mapping = const {
+    'eq': '==',
+    'not': '~',
+    'index': '[]',
+    'indexSet': '[]=',
+    'mul': '*',
+    'div': '/',
+    'mod': '%',
+    'tdiv': '~/',
+    'add': '+',
+    'sub': '-',
+    'shl': '<<',
+    'shr': '>>',
+    'ge': '>=',
+    'gt': '>',
+    'le': '<=',
+    'lt': '<',
+    'and': '&',
+    'xor': '^',
+    'or': '|',
+  };
+  String newName = mapping[name];
+  if (newName == null) {
+    throw new Exception('Unhandled operator name: $name');
+  }
+  return newName;
+}
+
+DiagnosticListener get _diagnosticListener {
+  return const Dart2JsDiagnosticListener();
+}
+
+class Dart2JsDiagnosticListener implements DiagnosticListener {
+  const Dart2JsDiagnosticListener();
+
+  void cancel(String reason, {node, token, instruction, element}) {
+    print(reason);
+  }
+
+  void log(message) {
+    print(message);
+  }
+
+  void internalError(String message,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element}) {
+    cancel('Internal error: $message', node: node, token: token,
+           instruction: instruction, element: element);
+  }
+
+  void internalErrorOnElement(Element element, String message) {
+    internalError(message, element: element);
+  }
+
+  SourceSpan spanFromSpannable(Node node, [Uri uri]) {
+    // TODO(johnniwinther): implement this.
+    throw 'unimplemented';
+  }
+
+  void reportMessage(SourceSpan span, Diagnostic message,
+                     diagnostics.Diagnostic kind) {
+    // TODO(johnniwinther): implement this.
+    throw 'unimplemented';
+  }
+
+  bool onDeprecatedFeature(Spannable span, String feature) {
+    // TODO(johnniwinther): implement this?
+    throw 'unimplemented';
+  }
+}
+
+//------------------------------------------------------------------------------
+// Compilation implementation
+//------------------------------------------------------------------------------
+
+// TODO(johnniwinther): Support client configurable handlers/providers.
+class Dart2JsCompilation implements Compilation {
+  Compiler _compiler;
+  final Uri cwd;
+  final SourceFileProvider provider;
+
+  Dart2JsCompilation(Path script, Path libraryRoot,
+                     [Path packageRoot, List<String> opts = const <String>[]])
+      : cwd = getCurrentDirectory(),
+        provider = new SourceFileProvider() {
+    var handler = new FormattingDiagnosticHandler(provider);
+    var libraryUri = cwd.resolve(libraryRoot.toString());
+    var packageUri;
+    if (packageRoot != null) {
+      packageUri = cwd.resolve(packageRoot.toString());
+    } else {
+      packageUri = libraryUri;
+    }
+    _compiler = new Compiler(provider.readStringFromUri,
+                                 null,
+                                 handler.diagnosticHandler,
+                                 libraryUri, packageUri, opts);
+    var scriptUri = cwd.resolve(script.toString());
+    // TODO(johnniwinther): Detect file not found
+    _compiler.run(scriptUri);
+  }
+
+  Dart2JsCompilation.library(List<Path> libraries, Path libraryRoot,
+                     [Path packageRoot, List<String> opts = const <String>[]])
+      : cwd = getCurrentDirectory(),
+        provider = new SourceFileProvider() {
+    var libraryUri = cwd.resolve(libraryRoot.toString());
+    var packageUri;
+    if (packageRoot != null) {
+      packageUri = cwd.resolve(packageRoot.toString());
+    } else {
+      packageUri = libraryUri;
+    }
+    opts = new List<String>.from(opts);
+    opts.add('--analyze-only');
+    opts.add('--analyze-all');
+    _compiler = new Compiler(provider.readStringFromUri,
+                                 null,
+                                 silentDiagnosticHandler,
+                                 libraryUri, packageUri, opts);
+    var librariesUri = <Uri>[];
+    for (Path library in libraries) {
+      librariesUri.add(cwd.resolve(library.toString()));
+      // TODO(johnniwinther): Detect file not found
+    }
+    _compiler.librariesToAnalyzeWhenRun = librariesUri;
+    _compiler.run(null);
+  }
+
+  MirrorSystem get mirrors => new Dart2JsMirrorSystem(_compiler);
+
+  Future<String> compileToJavaScript() =>
+      new Future<String>.immediate(_compiler.assembledCode);
+}
+
+
+//------------------------------------------------------------------------------
+// Dart2Js specific extensions of mirror interfaces
+//------------------------------------------------------------------------------
+
+abstract class Dart2JsMirror implements Mirror {
+  Dart2JsMirrorSystem get mirrors;
+}
+
+abstract class Dart2JsDeclarationMirror extends Dart2JsMirror
+    implements DeclarationMirror {
+
+  bool get isTopLevel => owner != null && owner is LibraryMirror;
+
+  bool get isPrivate => _isPrivate(simpleName);
+
+  /**
+   * Returns the first token for the source of this declaration, not including
+   * metadata annotations.
+   */
+  Token getBeginToken();
+
+  /**
+   * Returns the last token for the source of this declaration.
+   */
+  Token getEndToken();
+
+  /**
+   * Returns the script for the source of this declaration.
+   */
+  Script getScript();
+}
+
+abstract class Dart2JsTypeMirror extends Dart2JsDeclarationMirror
+    implements TypeMirror {
+}
+
+abstract class Dart2JsElementMirror extends Dart2JsDeclarationMirror {
+  final Dart2JsMirrorSystem mirrors;
+  final Element _element;
+  List<InstanceMirror> _metadata;
+
+  Dart2JsElementMirror(this.mirrors, this._element) {
+    assert (mirrors != null);
+    assert (_element != null);
+  }
+
+  String get simpleName => _element.name.slowToString();
+
+  String get displayName => simpleName;
+
+  /**
+   * Computes the first token for this declaration using the begin token of the
+   * element node or element position as indicator.
+   */
+  Token getBeginToken() {
+    // TODO(johnniwinther): Avoid calling [parseNode].
+    Node node = _element.parseNode(mirrors.compiler);
+    if (node == null) {
+      return _element.position();
+    }
+    return node.getBeginToken();
+  }
+
+  /**
+   * Computes the last token for this declaration using the end token of the
+   * element node or element position as indicator.
+   */
+  Token getEndToken() {
+    // TODO(johnniwinther): Avoid calling [parseNode].
+    Node node = _element.parseNode(mirrors.compiler);
+    if (node == null) {
+      return _element.position();
+    }
+    return node.getEndToken();
+  }
+
+  /**
+   * Returns the first token for the source of this declaration, including
+   * metadata annotations.
+   */
+  Token getFirstToken() {
+    if (!_element.metadata.isEmpty) {
+      for (MetadataAnnotation metadata in _element.metadata) {
+        if (metadata.beginToken != null) {
+          return metadata.beginToken;
+        }
+      }
+    }
+    return getBeginToken();
+  }
+
+  Script getScript() => _element.getCompilationUnit().script;
+
+  SourceLocation get location {
+    Token beginToken = getFirstToken();
+    Script script = getScript();
+    SourceSpan span;
+    if (beginToken == null) {
+      span = new SourceSpan(script.uri, 0, 0);
+    } else {
+      Token endToken = getEndToken();
+      span = mirrors.compiler.spanFromTokens(beginToken, endToken, script.uri);
+    }
+    return new Dart2JsSourceLocation(script, span);
+  }
+
+  String toString() => _element.toString();
+
+  int get hashCode => qualifiedName.hashCode;
+
+  void _appendCommentTokens(Token commentToken) {
+    while (commentToken != null && commentToken.kind == COMMENT_TOKEN) {
+      _metadata.add(new Dart2JsCommentInstanceMirror(
+          mirrors, commentToken.slowToString()));
+      commentToken = commentToken.next;
+    }
+  }
+
+  List<InstanceMirror> get metadata {
+    if (_metadata == null) {
+      _metadata = <InstanceMirror>[];
+      for (MetadataAnnotation metadata in _element.metadata) {
+        _appendCommentTokens(mirrors.compiler.commentMap[metadata.beginToken]);
+        metadata.ensureResolved(mirrors.compiler);
+        _metadata.add(
+            _convertConstantToInstanceMirror(mirrors, metadata.value));
+      }
+      _appendCommentTokens(mirrors.compiler.commentMap[getBeginToken()]);
+    }
+    // TODO(johnniwinther): Return an unmodifiable list instead.
+    return new List<InstanceMirror>.from(_metadata);
+  }
+}
+
+abstract class Dart2JsMemberMirror extends Dart2JsElementMirror
+    implements MemberMirror {
+
+  Dart2JsMemberMirror(Dart2JsMirrorSystem system, Element element)
+      : super(system, element);
+
+  bool get isConstructor => false;
+
+  bool get isVariable => false;
+
+  bool get isMethod => false;
+
+  bool get isStatic => false;
+
+  bool get isParameter => false;
+}
+
+//------------------------------------------------------------------------------
+// Mirror system implementation.
+//------------------------------------------------------------------------------
+
+class Dart2JsMirrorSystem implements MirrorSystem {
+  final Compiler compiler;
+  Map<String, Dart2JsLibraryMirror> _libraries;
+  Map<LibraryElement, Dart2JsLibraryMirror> _libraryMap;
+
+  Dart2JsMirrorSystem(this.compiler)
+    : _libraryMap = new Map<LibraryElement, Dart2JsLibraryMirror>();
+
+  void _ensureLibraries() {
+    if (_libraries == null) {
+      _libraries = <String, Dart2JsLibraryMirror>{};
+      compiler.libraries.forEach((_, LibraryElement v) {
+        var mirror = new Dart2JsLibraryMirror(mirrors, v);
+        _libraries[mirror.simpleName] = mirror;
+        _libraryMap[v] = mirror;
+      });
+    }
+  }
+
+  Map<String, LibraryMirror> get libraries {
+    _ensureLibraries();
+    return new ImmutableMapWrapper<String, LibraryMirror>(_libraries);
+  }
+
+  Dart2JsLibraryMirror _getLibrary(LibraryElement element) =>
+      _libraryMap[element];
+
+  Dart2JsMirrorSystem get mirrors => this;
+
+  TypeMirror get dynamicType =>
+      _convertTypeToTypeMirror(this, compiler.types.dynamicType, null);
+
+  TypeMirror get voidType =>
+      _convertTypeToTypeMirror(this, compiler.types.voidType, null);
+}
+
+abstract class Dart2JsContainerMirror extends Dart2JsElementMirror
+    implements ContainerMirror {
+  Map<String, MemberMirror> _members;
+
+  Dart2JsContainerMirror(Dart2JsMirrorSystem system, Element element)
+      : super(system, element);
+
+  void _ensureMembers();
+
+  Map<String, MemberMirror> get members {
+    _ensureMembers();
+    return new ImmutableMapWrapper<String, MemberMirror>(_members);
+  }
+
+  Map<String, MethodMirror> get functions {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members,
+        (MemberMirror member) => member is MethodMirror ? member : null);
+  }
+
+  Map<String, MethodMirror> get getters {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members,
+        (MemberMirror member) =>
+            member is MethodMirror && (member as MethodMirror).isGetter ?
+                member : null);
+  }
+
+  Map<String, MethodMirror> get setters {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members,
+        (MemberMirror member) =>
+            member is MethodMirror && (member as MethodMirror).isSetter ?
+                member : null);
+  }
+
+  Map<String, VariableMirror> get variables {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, VariableMirror>(
+        _members,
+        (MemberMirror member) => member is VariableMirror ? member : null);
+  }
+}
+
+class Dart2JsLibraryMirror extends Dart2JsContainerMirror
+    implements LibraryMirror {
+  Map<String, ClassMirror> _classes;
+
+  Dart2JsLibraryMirror(Dart2JsMirrorSystem system, LibraryElement library)
+      : super(system, library);
+
+  LibraryElement get _library => _element;
+
+  Uri get uri => _library.canonicalUri;
+
+  DeclarationMirror get owner => null;
+
+  bool get isPrivate => false;
+
+  LibraryMirror library() => this;
+
+  /**
+   * Returns the library name (for libraries with a #library tag) or the script
+   * file name (for scripts without a #library tag). The latter case is used to
+   * provide a 'library name' for scripts, to use for instance in dartdoc.
+   */
+  String get simpleName {
+    if (_library.libraryTag != null) {
+      // TODO(ahe): Remove StringNode check when old syntax is removed.
+      StringNode name = _library.libraryTag.name.asStringNode();
+      if (name != null) {
+        return name.dartString.slowToString();
+      } else {
+        return _library.libraryTag.name.toString();
+      }
+    } else {
+      // Use the file name as script name.
+      String path = _library.canonicalUri.path;
+      return path.substring(path.lastIndexOf('/') + 1);
+    }
+  }
+
+  String get qualifiedName => simpleName;
+
+  void _ensureClasses() {
+    if (_classes == null) {
+      _classes = <String, ClassMirror>{};
+      _library.forEachLocalMember((Element e) {
+        if (e.isClass()) {
+          ClassElement classElement = e;
+          classElement.ensureResolved(mirrors.compiler);
+          var type = new Dart2JsClassMirror.fromLibrary(this, classElement);
+          assert(invariant(_library, !_classes.containsKey(type.simpleName),
+              message: "Type name '${type.simpleName}' "
+                       "is not unique in $_library."));
+          _classes[type.simpleName] = type;
+        } else if (e.isTypedef()) {
+          var type = new Dart2JsTypedefMirror.fromLibrary(this,
+              e.computeType(mirrors.compiler));
+          assert(invariant(_library, !_classes.containsKey(type.simpleName),
+              message: "Type name '${type.simpleName}' "
+                       "is not unique in $_library."));
+          _classes[type.simpleName] = type;
+        }
+      });
+    }
+  }
+
+  void _ensureMembers() {
+    if (_members == null) {
+      _members = <String, MemberMirror>{};
+      _library.forEachLocalMember((Element e) {
+        if (!e.isClass() && !e.isTypedef()) {
+          for (var member in _convertElementMemberToMemberMirrors(this, e)) {
+            assert(!_members.containsKey(member.simpleName));
+            _members[member.simpleName] = member;
+          }
+        }
+      });
+    }
+  }
+
+  Map<String, ClassMirror> get classes {
+    _ensureClasses();
+    return new ImmutableMapWrapper<String, ClassMirror>(_classes);
+  }
+
+  /**
+   * Computes the first token of this library using the first library tag as
+   * indicator.
+   */
+  Token getBeginToken() {
+    if (_library.libraryTag != null) {
+      return _library.libraryTag.getBeginToken();
+    } else if (!_library.tags.isEmpty) {
+      return _library.tags.reverse().head.getBeginToken();
+    }
+    return null;
+  }
+
+  /**
+   * Computes the first token of this library using the last library tag as
+   * indicator.
+   */
+  Token getEndToken() {
+    if (!_library.tags.isEmpty) {
+      return _library.tags.head.getEndToken();
+    }
+    return null;
+  }
+}
+
+class Dart2JsSourceLocation implements SourceLocation {
+  final Script _script;
+  final SourceSpan _span;
+  int _line;
+  int _column;
+
+  Dart2JsSourceLocation(this._script, this._span);
+
+  int _computeLine() {
+    var sourceFile = _script.file as SourceFile;
+    if (sourceFile != null) {
+      return sourceFile.getLine(offset) + 1;
+    }
+    var index = 0;
+    var lineNumber = 0;
+    while (index <= offset && index < sourceText.length) {
+      index = sourceText.indexOf('\n', index) + 1;
+      if (index <= 0) break;
+      lineNumber++;
+    }
+    return lineNumber;
+  }
+
+  int get line {
+    if (_line == null) {
+      _line = _computeLine();
+    }
+    return _line;
+  }
+
+  int _computeColumn() {
+    if (length == 0) return 0;
+
+    var sourceFile = _script.file as SourceFile;
+    if (sourceFile != null) {
+      return sourceFile.getColumn(sourceFile.getLine(offset), offset) + 1;
+    }
+    int index = offset - 1;
+    var columnNumber = 0;
+    while (0 <= index && index < sourceText.length) {
+      columnNumber++;
+      var charCode = sourceText.charCodeAt(index);
+      if (charCode == $CR || charCode == $LF) {
+        break;
+      }
+      index--;
+    }
+    return columnNumber;
+  }
+
+  int get column {
+    if (_column == null) {
+      _column = _computeColumn();
+    }
+    return _column;
+  }
+
+  int get offset => _span.begin;
+
+  int get length => _span.end - _span.begin;
+
+  String get text => _script.text.substring(_span.begin, _span.end);
+
+  Uri get sourceUri => _script.uri;
+
+  String get sourceText => _script.text;
+}
+
+class Dart2JsParameterMirror extends Dart2JsMemberMirror
+    implements ParameterMirror {
+  final MethodMirror _method;
+  final bool isOptional;
+  final bool isNamed;
+
+  factory Dart2JsParameterMirror(Dart2JsMirrorSystem system,
+                                 MethodMirror method,
+                                 VariableElement element,
+                                 bool isOptional,
+                                 bool isNamed) {
+    if (element is FieldParameterElement) {
+      return new Dart2JsFieldParameterMirror(system,
+          method, element, isOptional, isNamed);
+    }
+    return new Dart2JsParameterMirror._normal(system,
+        method, element, isOptional, isNamed);
+  }
+
+  Dart2JsParameterMirror._normal(Dart2JsMirrorSystem system,
+                         this._method,
+                         VariableElement element,
+                         this.isOptional,
+                         this.isNamed)
+    : super(system, element);
+
+  DeclarationMirror get owner => _method;
+
+  VariableElement get _variableElement => _element;
+
+  String get qualifiedName => '${_method.qualifiedName}#${simpleName}';
+
+  TypeMirror get type => _convertTypeToTypeMirror(mirrors,
+      _variableElement.computeType(mirrors.compiler),
+      mirrors.compiler.types.dynamicType,
+      _variableElement.variables.functionSignature);
+
+
+  bool get isFinal => false;
+
+  bool get isConst => false;
+
+  String get defaultValue {
+    if (hasDefaultValue) {
+      SendSet expression = _variableElement.cachedNode.asSendSet();
+      return unparse(expression.arguments.head);
+    }
+    return null;
+  }
+
+  bool get hasDefaultValue {
+    return _variableElement.cachedNode != null &&
+        _variableElement.cachedNode is SendSet;
+  }
+
+  bool get isInitializingFormal => false;
+
+  VariableMirror get initializedField => null;
+}
+
+class Dart2JsFieldParameterMirror extends Dart2JsParameterMirror {
+
+  Dart2JsFieldParameterMirror(Dart2JsMirrorSystem system,
+                              MethodMirror method,
+                              FieldParameterElement element,
+                              bool isOptional,
+                              bool isNamed)
+      : super._normal(system, method, element, isOptional, isNamed);
+
+  FieldParameterElement get _fieldParameterElement => _element;
+
+  TypeMirror get type {
+    if (_fieldParameterElement.variables.cachedNode.type != null) {
+      return super.type;
+    }
+    return _convertTypeToTypeMirror(mirrors,
+      _fieldParameterElement.fieldElement.computeType(mirrors.compiler),
+      mirrors.compiler.types.dynamicType,
+      _variableElement.variables.functionSignature);
+  }
+
+  bool get isInitializingFormal => true;
+
+  VariableMirror get initializedField => new Dart2JsFieldMirror(
+      _method.owner, _fieldParameterElement.fieldElement);
+}
+
+//------------------------------------------------------------------------------
+// Declarations
+//------------------------------------------------------------------------------
+class Dart2JsClassMirror extends Dart2JsContainerMirror
+    implements Dart2JsTypeMirror, ClassMirror {
+  final Dart2JsLibraryMirror library;
+  List<TypeVariableMirror> _typeVariables;
+
+  Dart2JsClassMirror(Dart2JsMirrorSystem system, ClassElement _class)
+      : this.library = system._getLibrary(_class.getLibrary()),
+        super(system, _class);
+
+  ClassElement get _class => _element;
+
+  Dart2JsClassMirror.fromLibrary(Dart2JsLibraryMirror library,
+                                 ClassElement _class)
+      : this.library = library,
+        super(library.mirrors, _class);
+
+  DeclarationMirror get owner => library;
+
+  String get qualifiedName => '${library.qualifiedName}.${simpleName}';
+
+  void _ensureMembers() {
+    if (_members == null) {
+      _members = <String, Dart2JsMemberMirror>{};
+      _class.forEachMember((_, e) {
+        for (var member in _convertElementMemberToMemberMirrors(this, e)) {
+          assert(!_members.containsKey(member.simpleName));
+          _members[member.simpleName] = member;
+        }
+      });
+    }
+  }
+
+  Map<String, MethodMirror> get methods => functions;
+
+  Map<String, MethodMirror> get constructors {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members, (m) => m.isConstructor ? m : null);
+  }
+
+  bool get isObject => _class == mirrors.compiler.objectClass;
+
+  bool get isDynamic => false;
+
+  bool get isVoid => false;
+
+  bool get isTypeVariable => false;
+
+  bool get isTypedef => false;
+
+  bool get isFunction => false;
+
+  ClassMirror get originalDeclaration => this;
+
+  ClassMirror get superclass {
+    if (_class.supertype != null) {
+      return new Dart2JsInterfaceTypeMirror(mirrors, _class.supertype);
+    }
+    return null;
+  }
+
+  List<ClassMirror> get superinterfaces {
+    var list = <ClassMirror>[];
+    Link<DartType> link = _class.interfaces;
+    while (!link.isEmpty) {
+      var type = _convertTypeToTypeMirror(mirrors, link.head,
+                                          mirrors.compiler.types.dynamicType);
+      list.add(type);
+      link = link.tail;
+    }
+    return list;
+  }
+
+  bool get isClass => !_class.isInterface();
+
+  bool get isInterface => _class.isInterface();
+
+  bool get isAbstract => _class.modifiers.isAbstract();
+
+  bool get isOriginalDeclaration => true;
+
+  List<TypeMirror> get typeArguments {
+    throw new UnsupportedError(
+        'Declarations do not have type arguments');
+  }
+
+  List<TypeVariableMirror> get typeVariables {
+    if (_typeVariables == null) {
+      _typeVariables = <TypeVariableMirror>[];
+      _class.ensureResolved(mirrors.compiler);
+      for (TypeVariableType typeVariable in _class.typeVariables) {
+        _typeVariables.add(
+            new Dart2JsTypeVariableMirror(mirrors, typeVariable));
+      }
+    }
+    return _typeVariables;
+  }
+
+  /**
+   * Returns the default type for this interface.
+   */
+  ClassMirror get defaultFactory {
+    if (_class.defaultClass != null) {
+      return new Dart2JsInterfaceTypeMirror(mirrors, _class.defaultClass);
+    }
+    return null;
+  }
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! ClassMirror) {
+      return false;
+    }
+    if (library != other.library) {
+      return false;
+    }
+    if (!identical(isOriginalDeclaration, other.isOriginalDeclaration)) {
+      return false;
+    }
+    return qualifiedName == other.qualifiedName;
+  }
+}
+
+class Dart2JsTypedefMirror extends Dart2JsTypeElementMirror
+    implements Dart2JsTypeMirror, TypedefMirror {
+  final Dart2JsLibraryMirror _library;
+  List<TypeVariableMirror> _typeVariables;
+  TypeMirror _definition;
+
+  Dart2JsTypedefMirror(Dart2JsMirrorSystem system, TypedefType _typedef)
+      : this._library = system._getLibrary(_typedef.element.getLibrary()),
+        super(system, _typedef);
+
+  Dart2JsTypedefMirror.fromLibrary(Dart2JsLibraryMirror library,
+                                   TypedefType _typedef)
+      : this._library = library,
+        super(library.mirrors, _typedef);
+
+  TypedefType get _typedef => _type;
+
+  String get qualifiedName => '${library.qualifiedName}.${simpleName}';
+
+  LibraryMirror get library => _library;
+
+  bool get isTypedef => true;
+
+  List<TypeMirror> get typeArguments {
+    throw new UnsupportedError(
+        'Declarations do not have type arguments');
+  }
+
+  List<TypeVariableMirror> get typeVariables {
+    if (_typeVariables == null) {
+      _typeVariables = <TypeVariableMirror>[];
+      for (TypeVariableType typeVariable in _typedef.typeArguments) {
+        _typeVariables.add(
+            new Dart2JsTypeVariableMirror(mirrors, typeVariable));
+      }
+    }
+    return _typeVariables;
+  }
+
+  TypeMirror get value {
+    if (_definition == null) {
+      // TODO(johnniwinther): Should be [ensureResolved].
+      mirrors.compiler.resolveTypedef(_typedef.element);
+      _definition = _convertTypeToTypeMirror(
+          mirrors,
+          _typedef.element.alias,
+          mirrors.compiler.types.dynamicType,
+          _typedef.element.functionSignature);
+    }
+    return _definition;
+  }
+
+  ClassMirror get originalDeclaration => this;
+
+  // TODO(johnniwinther): How should a typedef respond to these?
+  ClassMirror get superclass => null;
+
+  List<ClassMirror> get superinterfaces => const <ClassMirror>[];
+
+  bool get isClass => false;
+
+  bool get isInterface => false;
+
+  bool get isOriginalDeclaration => true;
+
+  bool get isAbstract => false;
+}
+
+class Dart2JsTypeVariableMirror extends Dart2JsTypeElementMirror
+    implements TypeVariableMirror {
+  final TypeVariableType _typeVariableType;
+  ClassMirror _declarer;
+
+  Dart2JsTypeVariableMirror(Dart2JsMirrorSystem system,
+                            TypeVariableType typeVariableType)
+    : this._typeVariableType = typeVariableType,
+      super(system, typeVariableType) {
+      assert(_typeVariableType != null);
+  }
+
+
+  String get qualifiedName => '${declarer.qualifiedName}.${simpleName}';
+
+  ClassMirror get declarer {
+    if (_declarer == null) {
+      if (_typeVariableType.element.enclosingElement.isClass()) {
+        _declarer = new Dart2JsClassMirror(mirrors,
+            _typeVariableType.element.enclosingElement);
+      } else if (_typeVariableType.element.enclosingElement.isTypedef()) {
+        _declarer = new Dart2JsTypedefMirror(mirrors,
+            _typeVariableType.element.enclosingElement.computeType(
+                mirrors.compiler));
+      }
+    }
+    return _declarer;
+  }
+
+  LibraryMirror get library => declarer.library;
+
+  DeclarationMirror get owner => declarer;
+
+  bool get isTypeVariable => true;
+
+  TypeMirror get upperBound => _convertTypeToTypeMirror(
+      mirrors,
+      _typeVariableType.element.bound,
+      mirrors.compiler.objectClass.computeType(mirrors.compiler));
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! TypeVariableMirror) {
+      return false;
+    }
+    if (declarer != other.declarer) {
+      return false;
+    }
+    return qualifiedName == other.qualifiedName;
+  }
+}
+
+
+//------------------------------------------------------------------------------
+// Types
+//------------------------------------------------------------------------------
+
+abstract class Dart2JsTypeElementMirror extends Dart2JsElementMirror
+    implements Dart2JsTypeMirror {
+  final DartType _type;
+
+  Dart2JsTypeElementMirror(Dart2JsMirrorSystem system, DartType type)
+    : super(system, type.element),
+      this._type = type;
+
+  String get simpleName => _type.name.slowToString();
+
+  DeclarationMirror get owner => library;
+
+  LibraryMirror get library {
+    return mirrors._getLibrary(_type.element.getLibrary());
+  }
+
+  bool get isObject => false;
+
+  bool get isVoid => false;
+
+  bool get isDynamic => false;
+
+  bool get isTypeVariable => false;
+
+  bool get isTypedef => false;
+
+  bool get isFunction => false;
+
+  String toString() => _type.toString();
+
+  Map<String, MemberMirror> get members => const <String, MemberMirror>{};
+
+  Map<String, MethodMirror> get constructors => const <String, MethodMirror>{};
+
+  Map<String, MethodMirror> get methods => const <String, MethodMirror>{};
+
+  Map<String, MethodMirror> get getters => const <String, MethodMirror>{};
+
+  Map<String, MethodMirror> get setters => const <String, MethodMirror>{};
+
+  Map<String, VariableMirror> get variables => const <String, VariableMirror>{};
+
+  ClassMirror get defaultFactory => null;
+}
+
+class Dart2JsInterfaceTypeMirror extends Dart2JsTypeElementMirror
+    implements ClassMirror {
+  List<TypeMirror> _typeArguments;
+
+  Dart2JsInterfaceTypeMirror(Dart2JsMirrorSystem system,
+                             InterfaceType interfaceType)
+      : super(system, interfaceType);
+
+  InterfaceType get _interfaceType => _type;
+
+  String get qualifiedName => originalDeclaration.qualifiedName;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MemberMirror> get members => originalDeclaration.members;
+
+  bool get isObject => mirrors.compiler.objectClass == _type.element;
+
+  bool get isDynamic => mirrors.compiler.dynamicClass == _type.element;
+
+  ClassMirror get originalDeclaration
+      => new Dart2JsClassMirror(mirrors, _type.element);
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  ClassMirror get superclass => originalDeclaration.superclass;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces;
+
+  bool get isClass => originalDeclaration.isClass;
+
+  bool get isInterface => originalDeclaration.isInterface;
+
+  bool get isAbstract => originalDeclaration.isAbstract;
+
+  bool get isPrivate => originalDeclaration.isPrivate;
+
+  bool get isOriginalDeclaration => false;
+
+  List<TypeMirror> get typeArguments {
+    if (_typeArguments == null) {
+      _typeArguments = <TypeMirror>[];
+      if (!_interfaceType.isRaw) {
+        Link<DartType> type = _interfaceType.typeArguments;
+        while (type != null && type.head != null) {
+          _typeArguments.add(_convertTypeToTypeMirror(mirrors, type.head,
+              mirrors.compiler.types.dynamicType));
+          type = type.tail;
+        }
+      }
+    }
+    return _typeArguments;
+  }
+
+  List<TypeVariableMirror> get typeVariables =>
+      originalDeclaration.typeVariables;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get constructors =>
+      originalDeclaration.constructors;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get methods => originalDeclaration.methods;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get setters => originalDeclaration.setters;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get getters => originalDeclaration.getters;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, VariableMirror> get variables => originalDeclaration.variables;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables?
+  ClassMirror get defaultFactory => originalDeclaration.defaultFactory;
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! ClassMirror) {
+      return false;
+    }
+    if (other.isOriginalDeclaration) {
+      return false;
+    }
+    if (originalDeclaration != other.originalDeclaration) {
+      return false;
+    }
+    var thisTypeArguments = typeArguments.iterator;
+    var otherTypeArguments = other.typeArguments.iterator;
+    while (thisTypeArguments.moveNext()) {
+      if (!otherTypeArguments.moveNext()) return false;
+      if (thisTypeArguments.current != otherTypeArguments.current) {
+        return false;
+      }
+    }
+    return !otherTypeArguments.moveNext();
+  }
+}
+
+
+class Dart2JsFunctionTypeMirror extends Dart2JsTypeElementMirror
+    implements FunctionTypeMirror {
+  final FunctionSignature _functionSignature;
+  List<ParameterMirror> _parameters;
+
+  Dart2JsFunctionTypeMirror(Dart2JsMirrorSystem system,
+                             FunctionType functionType, this._functionSignature)
+      : super(system, functionType) {
+    assert (_functionSignature != null);
+  }
+
+  FunctionType get _functionType => _type;
+
+  // TODO(johnniwinther): Is this the qualified name of a function type?
+  String get qualifiedName => originalDeclaration.qualifiedName;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MemberMirror> get members {
+    var method = callMethod;
+    if (method != null) {
+      var map = new Map<String, MemberMirror>.from(
+          originalDeclaration.members);
+      var name = method.qualifiedName;
+      assert(!map.containsKey(name));
+      map[name] = method;
+      return new ImmutableMapWrapper<String, MemberMirror>(map);
+    }
+    return originalDeclaration.members;
+  }
+
+  bool get isFunction => true;
+
+  MethodMirror get callMethod => _convertElementMethodToMethodMirror(
+      mirrors._getLibrary(_functionType.element.getLibrary()),
+      _functionType.element);
+
+  ClassMirror get originalDeclaration
+      => new Dart2JsClassMirror(mirrors, mirrors.compiler.functionClass);
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  ClassMirror get superclass => originalDeclaration.superclass;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces;
+
+  bool get isClass => originalDeclaration.isClass;
+
+  bool get isInterface => originalDeclaration.isInterface;
+
+  bool get isPrivate => originalDeclaration.isPrivate;
+
+  bool get isOriginalDeclaration => false;
+
+  bool get isAbstract => false;
+
+  List<TypeMirror> get typeArguments => const <TypeMirror>[];
+
+  List<TypeVariableMirror> get typeVariables =>
+      originalDeclaration.typeVariables;
+
+  TypeMirror get returnType {
+    return _convertTypeToTypeMirror(mirrors, _functionType.returnType,
+                                    mirrors.compiler.types.dynamicType);
+  }
+
+  List<ParameterMirror> get parameters {
+    if (_parameters == null) {
+      _parameters = _parametersFromFunctionSignature(mirrors, callMethod,
+                                                     _functionSignature);
+    }
+    return _parameters;
+  }
+}
+
+class Dart2JsVoidMirror extends Dart2JsTypeElementMirror {
+
+  Dart2JsVoidMirror(Dart2JsMirrorSystem system, VoidType voidType)
+      : super(system, voidType);
+
+  VoidType get _voidType => _type;
+
+  String get qualifiedName => simpleName;
+
+  /**
+   * The void type has no location.
+   */
+  SourceLocation get location => null;
+
+  /**
+   * The void type has no library.
+   */
+  LibraryMirror get library => null;
+
+  bool get isVoid => true;
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! TypeMirror) {
+      return false;
+    }
+    return other.isVoid;
+  }
+}
+
+
+class Dart2JsDynamicMirror extends Dart2JsTypeElementMirror {
+  Dart2JsDynamicMirror(Dart2JsMirrorSystem system, InterfaceType voidType)
+      : super(system, voidType);
+
+  InterfaceType get _dynamicType => _type;
+
+  String get qualifiedName => simpleName;
+
+  /**
+   * The dynamic type has no location.
+   */
+  SourceLocation get location => null;
+
+  /**
+   * The dynamic type has no library.
+   */
+  LibraryMirror get library => null;
+
+  bool get isDynamic => true;
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! TypeMirror) {
+      return false;
+    }
+    return other.isDynamic;
+  }
+}
+
+//------------------------------------------------------------------------------
+// Member mirrors implementation.
+//------------------------------------------------------------------------------
+
+class Dart2JsMethodMirror extends Dart2JsMemberMirror
+    implements MethodMirror {
+  final Dart2JsContainerMirror _objectMirror;
+  final String simpleName;
+  final String displayName;
+  final String constructorName;
+  final String operatorName;
+  final Dart2JsMethodKind _kind;
+
+  Dart2JsMethodMirror._internal(Dart2JsContainerMirror objectMirror,
+      FunctionElement function,
+      String this.simpleName,
+      String this.displayName,
+      String this.constructorName,
+      String this.operatorName,
+      Dart2JsMethodKind this._kind)
+      : this._objectMirror = objectMirror,
+        super(objectMirror.mirrors, function);
+
+  factory Dart2JsMethodMirror(Dart2JsContainerMirror objectMirror,
+                              FunctionElement function) {
+    String realName = function.name.slowToString();
+    // TODO(ahe): This method should not be calling
+    // Elements.operatorNameToIdentifier.
+    String simpleName =
+        Elements.operatorNameToIdentifier(function.name).slowToString();
+    String displayName;
+    String constructorName = null;
+    String operatorName = null;
+    Dart2JsMethodKind kind;
+    if (function.kind == ElementKind.GETTER) {
+      kind = Dart2JsMethodKind.GETTER;
+      displayName = simpleName;
+    } else if (function.kind == ElementKind.SETTER) {
+      kind = Dart2JsMethodKind.SETTER;
+      displayName = simpleName;
+      simpleName = '$simpleName=';
+    } else if (function.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
+      // TODO(johnniwinther): Support detection of redirecting constructors.
+      constructorName = '';
+      int dollarPos = simpleName.indexOf('\$');
+      if (dollarPos != -1) {
+        constructorName = simpleName.substring(dollarPos + 1);
+        simpleName = simpleName.substring(0, dollarPos);
+        // Simple name is TypeName.constructorName.
+        simpleName = '$simpleName.$constructorName';
+      } else {
+        // Simple name is TypeName.
+      }
+      if (function.modifiers.isConst()) {
+        kind = Dart2JsMethodKind.CONST;
+      } else {
+        kind = Dart2JsMethodKind.GENERATIVE;
+      }
+      displayName = simpleName;
+    } else if (function.modifiers.isFactory()) {
+      kind = Dart2JsMethodKind.FACTORY;
+      constructorName = '';
+      int dollarPos = simpleName.indexOf('\$');
+      if (dollarPos != -1) {
+        constructorName = simpleName.substring(dollarPos+1);
+        simpleName = simpleName.substring(0, dollarPos);
+        simpleName = '$simpleName.$constructorName';
+      }
+      // Simple name is TypeName.constructorName.
+      displayName = simpleName;
+    } else if (realName == 'unary-') {
+      kind = Dart2JsMethodKind.OPERATOR;
+      operatorName = '-';
+      // Simple name is 'unary-'.
+      simpleName = Mirror.UNARY_MINUS;
+      // Display name is 'operator operatorName'.
+      displayName = 'operator -';
+    } else if (simpleName.startsWith('operator\$')) {
+      String str = simpleName.substring(9);
+      simpleName = 'operator';
+      kind = Dart2JsMethodKind.OPERATOR;
+      operatorName = _getOperatorFromOperatorName(str);
+      // Simple name is 'operator operatorName'.
+      simpleName = operatorName;
+      // Display name is 'operator operatorName'.
+      displayName = 'operator $operatorName';
+    } else {
+      kind = Dart2JsMethodKind.REGULAR;
+      displayName = simpleName;
+    }
+    return new Dart2JsMethodMirror._internal(objectMirror, function,
+        simpleName, displayName, constructorName, operatorName, kind);
+  }
+
+  FunctionElement get _function => _element;
+
+  String get qualifiedName
+      => '${owner.qualifiedName}.$simpleName';
+
+  DeclarationMirror get owner => _objectMirror;
+
+  bool get isTopLevel => _objectMirror is LibraryMirror;
+
+  bool get isConstructor
+      => isGenerativeConstructor || isConstConstructor ||
+         isFactoryConstructor || isRedirectingConstructor;
+
+  bool get isMethod => !isConstructor;
+
+  bool get isPrivate =>
+      isConstructor ? _isPrivate(constructorName) : _isPrivate(simpleName);
+
+  bool get isStatic => _function.modifiers.isStatic();
+
+  List<ParameterMirror> get parameters {
+    return _parametersFromFunctionSignature(mirrors, this,
+        _function.computeSignature(mirrors.compiler));
+  }
+
+  TypeMirror get returnType => _convertTypeToTypeMirror(
+      mirrors, _function.computeSignature(mirrors.compiler).returnType,
+      mirrors.compiler.types.dynamicType);
+
+  bool get isAbstract => _function.isAbstract(mirrors.compiler);
+
+  bool get isRegularMethod => !(isGetter || isSetter || isConstructor);
+
+  bool get isConstConstructor => _kind == Dart2JsMethodKind.CONST;
+
+  bool get isGenerativeConstructor => _kind == Dart2JsMethodKind.GENERATIVE;
+
+  bool get isRedirectingConstructor => _kind == Dart2JsMethodKind.REDIRECTING;
+
+  bool get isFactoryConstructor => _kind == Dart2JsMethodKind.FACTORY;
+
+  bool get isGetter => _kind == Dart2JsMethodKind.GETTER;
+
+  bool get isSetter => _kind == Dart2JsMethodKind.SETTER;
+
+  bool get isOperator => _kind == Dart2JsMethodKind.OPERATOR;
+}
+
+class Dart2JsFieldMirror extends Dart2JsMemberMirror implements VariableMirror {
+  Dart2JsContainerMirror _objectMirror;
+  VariableElement _variable;
+
+  Dart2JsFieldMirror(Dart2JsContainerMirror objectMirror,
+                     VariableElement variable)
+      : this._objectMirror = objectMirror,
+        this._variable = variable,
+        super(objectMirror.mirrors, variable);
+
+  String get qualifiedName
+      => '${owner.qualifiedName}.$simpleName';
+
+  DeclarationMirror get owner => _objectMirror;
+
+  bool get isTopLevel => _objectMirror is LibraryMirror;
+
+  bool get isVariable => true;
+
+  bool get isStatic => _variable.modifiers.isStatic();
+
+  bool get isFinal => _variable.modifiers.isFinal();
+
+  bool get isConst => _variable.modifiers.isConst();
+
+  TypeMirror get type => _convertTypeToTypeMirror(mirrors,
+      _variable.computeType(mirrors.compiler),
+      mirrors.compiler.types.dynamicType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Mirrors on constant values used for metadata.
+////////////////////////////////////////////////////////////////////////////////
+
+class Dart2JsConstantMirror extends InstanceMirror {
+  final Dart2JsMirrorSystem mirrors;
+  final Constant _constant;
+
+  Dart2JsConstantMirror(this.mirrors, this._constant);
+
+  ClassMirror get type {
+    return new Dart2JsClassMirror(mirrors,
+        _constant.computeType(mirrors.compiler).element);
+  }
+
+  bool get hasReflectee => false;
+
+  get reflectee {
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+
+  Future<InstanceMirror> getField(String fieldName) {
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+}
+
+class Dart2JsNullConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsNullConstantMirror(Dart2JsMirrorSystem mirrors, NullConstant constant)
+      : super(mirrors, constant);
+
+  NullConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => null;
+}
+
+class Dart2JsBoolConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsBoolConstantMirror(Dart2JsMirrorSystem mirrors, BoolConstant constant)
+      : super(mirrors, constant);
+
+  Dart2JsBoolConstantMirror.fromBool(Dart2JsMirrorSystem mirrors, bool value)
+      : super(mirrors, value ? new TrueConstant() : new FalseConstant());
+
+  BoolConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => _constant is TrueConstant;
+}
+
+class Dart2JsStringConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsStringConstantMirror(Dart2JsMirrorSystem mirrors,
+                              StringConstant constant)
+      : super(mirrors, constant);
+
+  Dart2JsStringConstantMirror.fromString(Dart2JsMirrorSystem mirrors,
+                                         String text)
+      : super(mirrors,
+              new StringConstant(new DartString.literal(text), null));
+
+  StringConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => _constant.value.slowToString();
+}
+
+class Dart2JsNumConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsNumConstantMirror(Dart2JsMirrorSystem mirrors,
+                           NumConstant constant)
+      : super(mirrors, constant);
+
+  NumConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => _constant.value;
+}
+
+class Dart2JsListConstantMirror extends Dart2JsConstantMirror
+    implements ListInstanceMirror {
+  Dart2JsListConstantMirror(Dart2JsMirrorSystem mirrors,
+                            ListConstant constant)
+      : super(mirrors, constant);
+
+  ListConstant get _constant => super._constant;
+
+  int get length => _constant.length;
+
+  Future<InstanceMirror> operator[](int index) {
+    if (index < 0) throw new RangeError('Negative index');
+    if (index >= _constant.length) throw new RangeError('Index out of bounds');
+    return new Future<InstanceMirror>.immediate(
+        _convertConstantToInstanceMirror(mirrors, _constant.entries[index]));
+  }
+}
+
+class Dart2JsMapConstantMirror extends Dart2JsConstantMirror
+    implements MapInstanceMirror {
+  List<String> _listCache;
+
+  Dart2JsMapConstantMirror(Dart2JsMirrorSystem mirrors,
+                           MapConstant constant)
+      : super(mirrors, constant);
+
+  MapConstant get _constant => super._constant;
+
+  List<String> get _list {
+    if (_listCache == null) {
+      _listCache = new List<String>(_constant.keys.entries.length);
+      int index = 0;
+      for (StringConstant keyConstant in _constant.keys.entries) {
+        _listCache[index] = keyConstant.value.slowToString();
+        index++;
+      }
+    }
+    return _listCache;
+  }
+
+  int get length => _constant.length;
+
+  Collection<String> get keys {
+    // TODO(johnniwinther): Return an unmodifiable list instead.
+    return new List<String>.from(_list);
+  }
+
+  Future<InstanceMirror> operator[](String key) {
+    int index = _list.indexOf(key);
+    if (index == -1) return null;
+    return new Future<InstanceMirror>.immediate(
+        _convertConstantToInstanceMirror(mirrors, _constant.values[index]));
+  }
+}
+
+class Dart2JsTypeConstantMirror extends Dart2JsConstantMirror
+    implements TypeInstanceMirror {
+
+  Dart2JsTypeConstantMirror(Dart2JsMirrorSystem mirrors,
+                            TypeConstant constant)
+      : super(mirrors, constant);
+
+  TypeConstant get _constant => super._constant;
+
+  TypeMirror get representedType => _convertTypeToTypeMirror(
+      mirrors, _constant.representedType, mirrors.compiler.types.dynamicType);
+}
+
+class Dart2JsConstructedConstantMirror extends Dart2JsConstantMirror {
+  Map<String,Constant> _fieldMapCache;
+
+  Dart2JsConstructedConstantMirror(Dart2JsMirrorSystem mirrors,
+                                   ConstructedConstant constant)
+      : super(mirrors, constant);
+
+  ConstructedConstant get _constant => super._constant;
+
+  Map<String,Constant> get _fieldMap {
+    if (_fieldMapCache == null) {
+      _fieldMapCache = new LinkedHashMap<String,Constant>();
+      if (identical(_constant.type.element.kind, ElementKind.CLASS)) {
+        var index = 0;
+        ClassElement element = _constant.type.element;
+        element.forEachInstanceField((_, Element field) {
+          String fieldName = field.name.slowToString();
+          _fieldMapCache.putIfAbsent(fieldName, () => _constant.fields[index]);
+          index++;
+        }, includeBackendMembers: true, includeSuperMembers: true);
+      }
+    }
+    return _fieldMapCache;
+  }
+
+  Future<InstanceMirror> getField(String fieldName) {
+    Constant fieldConstant = _fieldMap[fieldName];
+    if (fieldConstant != null) {
+      return new Future<InstanceMirror>.immediate(
+          _convertConstantToInstanceMirror(mirrors, fieldConstant));
+    }
+    return super.getField(fieldName);
+  }
+}
+
+class Dart2JsCommentInstanceMirror implements CommentInstanceMirror {
+  final Dart2JsMirrorSystem mirrors;
+  final String text;
+  String _trimmedText;
+
+  Dart2JsCommentInstanceMirror(this.mirrors, this.text);
+
+  ClassMirror get type {
+    return new Dart2JsClassMirror(mirrors, mirrors.compiler.documentClass);
+  }
+
+  bool get isDocComment => text.startsWith('/**') || text.startsWith('///');
+
+  String get trimmedText {
+    if (_trimmedText == null) {
+      _trimmedText = stripComment(text);
+    }
+    return _trimmedText;
+  }
+
+  bool get hasReflectee => false;
+
+  get reflectee {
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+
+  Future<InstanceMirror> getField(String fieldName) {
+    if (fieldName == 'isDocComment') {
+      return new Future.immediate(
+          new Dart2JsBoolConstantMirror.fromBool(mirrors, isDocComment));
+    } else if (fieldName == 'text') {
+      return new Future.immediate(
+          new Dart2JsStringConstantMirror.fromString(mirrors, text));
+    } else if (fieldName == 'trimmedText') {
+      return new Future.immediate(
+          new Dart2JsStringConstantMirror.fromString(mirrors, trimmedText));
+    }
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart
new file mode 100644
index 0000000..6f5189e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart
@@ -0,0 +1,738 @@
+// 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.
+
+library mirrors;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:uri';
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+import 'dart2js_mirror.dart';
+
+/**
+ * [Compilation] encapsulates the compilation of a program.
+ */
+abstract class Compilation {
+  /**
+   * Creates a new compilation which has [script] as its entry point.
+   */
+  factory Compilation(Path script,
+                      Path libraryRoot,
+                      [Path packageRoot,
+                       List<String> opts = const <String>[]]) {
+    return new Dart2JsCompilation(script, libraryRoot, packageRoot, opts);
+  }
+
+  /**
+   * Creates a new compilation which consists of a set of libraries, but which
+   * has no entry point. This compilation cannot generate output but can only
+   * be used for static inspection of the source code.
+   */
+  factory Compilation.library(List<Path> libraries,
+                              Path libraryRoot,
+                              [Path packageRoot,
+                               List<String> opts = const []]) {
+    return new Dart2JsCompilation.library(libraries, libraryRoot,
+                                          packageRoot, opts);
+  }
+
+  /**
+   * Returns the mirror system for this compilation.
+   */
+  final MirrorSystem mirrors;
+
+  /**
+   * Returns a future for the compiled JavaScript code.
+   */
+  Future<String> compileToJavaScript();
+}
+
+/**
+ * The main interface for the whole mirror system.
+ */
+abstract class MirrorSystem {
+  /**
+   * Returns an unmodifiable map of all libraries in this mirror system.
+   */
+  Map<String, LibraryMirror> get libraries;
+
+  /**
+   * A mirror on the [:dynamic:] type.
+   */
+  TypeMirror get dynamicType;
+
+  /**
+   * A mirror on the [:void:] type.
+   */
+  TypeMirror get voidType;
+}
+
+
+/**
+ * An entity in the mirror system.
+ */
+abstract class Mirror {
+  static const String UNARY_MINUS = 'unary-';
+
+  // TODO(johnniwinther): Do we need this on all mirrors?
+  /**
+   * Returns the mirror system which contains this mirror.
+   */
+  MirrorSystem get mirrors;
+}
+
+abstract class DeclarationMirror implements Mirror {
+  /**
+   * The simple name of the entity. The simple name is unique within the
+   * scope of the entity declaration.
+   *
+   * The simple name is in most cases the declared single identifier name of
+   * the entity, such as 'method' for a method [:void method() {...}:]. For an
+   * unnamed constructor for [:class Foo:] the simple name is 'Foo'. For a
+   * constructor for [:class Foo:] named 'named' the simple name is 'Foo.named'.
+   * For a property [:foo:] the simple name of the getter method is 'foo' and
+   * the simple name of the setter is 'foo='. For operators the simple name is
+   * the operator itself, for example '+' for [:operator +:].
+   *
+   * The simple name for the unary minus operator is [UNARY_MINUS].
+   */
+  String get simpleName;
+
+  /**
+   * The display name is the normal representation of the entity name. In most
+   * cases the display name is the simple name, but for a setter 'foo=' the
+   * display name is simply 'foo' and for the unary minus operator the display
+   * name is 'operator -'. The display name is not unique.
+   */
+  String get displayName;
+
+  /**
+   * Returns the name of this entity qualified by is enclosing context. For
+   * instance, the qualified name of a method 'method' in class 'Class' in
+   * library 'library' is 'library.Class.method'.
+   */
+  String get qualifiedName;
+
+  /**
+   * The source location of this Dart language entity.
+   */
+  SourceLocation get location;
+
+  /**
+   * A mirror on the owner of this function. This is the declaration immediately
+   * surrounding the reflectee.
+   *
+   * Note that for libraries, the owner will be [:null:].
+   */
+  DeclarationMirror get owner;
+
+  /**
+   * Is this declaration private?
+   *
+   * Note that for libraries, this will be [:false:].
+   */
+  bool get isPrivate;
+
+  /**
+   * Is this declaration top-level?
+   *
+   * This is defined to be equivalent to:
+   *    [:mirror.owner != null && mirror.owner is LibraryMirror:]
+   */
+  bool get isTopLevel;
+
+  /**
+   * A list of the metadata associated with this declaration.
+   */
+  List<InstanceMirror> get metadata;
+}
+
+abstract class ObjectMirror implements Mirror {
+  /**
+   * Invokes a getter and returns a mirror on the result. The getter
+   * can be the implicit getter for a field or a user-defined getter
+   * method.
+   */
+  Future<InstanceMirror> getField(String fieldName);
+}
+
+/**
+ * An [InstanceMirror] reflects an instance of a Dart language object.
+ */
+abstract class InstanceMirror implements ObjectMirror {
+  /**
+   * A mirror on the type of the reflectee.
+   */
+  ClassMirror get type;
+
+  /**
+   * Does [reflectee] contain the instance reflected by this mirror?
+   * This will always be true in the local case (reflecting instances
+   * in the same isolate), but only true in the remote case if this
+   * mirror reflects a simple value.
+   *
+   * A value is simple if one of the following holds:
+   *  - the value is null
+   *  - the value is of type [num]
+   *  - the value is of type [bool]
+   *  - the value is of type [String]
+   */
+  bool get hasReflectee;
+
+  /**
+   * If the [InstanceMirror] reflects an instance it is meaningful to
+   * have a local reference to, we provide access to the actual
+   * instance here.
+   *
+   * If you access [reflectee] when [hasReflectee] is false, an
+   * exception is thrown.
+   */
+  get reflectee;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on constant lists.
+ */
+abstract class ListInstanceMirror implements InstanceMirror {
+  Future<InstanceMirror> operator[](int index);
+  int get length;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on constant maps.
+ */
+abstract class MapInstanceMirror implements InstanceMirror {
+  /**
+   * Returns a collection containing all the keys in the map.
+   */
+  Collection<String> get keys;
+
+  /**
+   * Returns a future on the instance mirror of the value for the given key or
+   * null if key is not in the map.
+   */
+  Future<InstanceMirror> operator[](String key);
+
+  /**
+   * The number of {key, value} pairs in the map.
+   */
+  int get length;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on type constants.
+ */
+abstract class TypeInstanceMirror implements InstanceMirror {
+  /**
+   * Returns the type mirror for the type represented by the reflected type
+   * constant.
+   */
+  TypeMirror get representedType;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on comments as metadata.
+ */
+abstract class CommentInstanceMirror implements InstanceMirror {
+  /**
+   * The comment text as written in the source text.
+   */
+  String get text;
+
+  /**
+   * The comment text without the start, end, and padding text.
+   *
+   * For example, if [text] is [: /** Comment text. */ :] then the [trimmedText]
+   * is [: Comment text. :].
+   */
+  String get trimmedText;
+
+  /**
+   * Is [:true:] if this comment is a documentation comment.
+   *
+   * That is, that the comment is either enclosed in [: /** ... */ :] or starts
+   * with [: /// :].
+   */
+  bool get isDocComment;
+}
+
+/**
+ * Common interface for classes and libraries.
+ */
+abstract class ContainerMirror implements Mirror {
+
+  /**
+   * An immutable map from from names to mirrors for all members in this
+   * container.
+   */
+  Map<String, MemberMirror> get members;
+}
+
+/**
+ * A library.
+ */
+abstract class LibraryMirror implements ContainerMirror, DeclarationMirror {
+  /**
+   * An immutable map from from names to mirrors for all members in this
+   * library.
+   *
+   * The members of a library are its top-level classes, functions, variables,
+   * getters, and setters.
+   */
+  Map<String, MemberMirror> get members;
+
+  /**
+   * An immutable map from names to mirrors for all class
+   * declarations in this library.
+   */
+  Map<String, ClassMirror> get classes;
+
+  /**
+   * An immutable map from names to mirrors for all function, getter,
+   * and setter declarations in this library.
+   */
+  Map<String, MethodMirror> get functions;
+
+  /**
+   * An immutable map from names to mirrors for all getter
+   * declarations in this library.
+   */
+  Map<String, MethodMirror> get getters;
+
+  /**
+   * An immutable map from names to mirrors for all setter
+   * declarations in this library.
+   */
+  Map<String, MethodMirror> get setters;
+
+  /**
+   * An immutable map from names to mirrors for all variable
+   * declarations in this library.
+   */
+  Map<String, VariableMirror> get variables;
+
+  /**
+   * Returns the canonical URI for this library.
+   */
+  Uri get uri;
+}
+
+/**
+ * Common interface for classes, interfaces, typedefs and type variables.
+ */
+abstract class TypeMirror implements DeclarationMirror {
+  /**
+   * Returns the library in which this member resides.
+   */
+  LibraryMirror get library;
+
+  /**
+   * Is [:true:] iff this type is the [:Object:] type.
+   */
+  bool get isObject;
+
+  /**
+   * Is [:true:] iff this type is the [:dynamic:] type.
+   */
+  bool get isDynamic;
+
+  /**
+   * Is [:true:] iff this type is the void type.
+   */
+  bool get isVoid;
+
+  /**
+   * Is [:true:] iff this type is a type variable.
+   */
+  bool get isTypeVariable;
+
+  /**
+   * Is [:true:] iff this type is a typedef.
+   */
+  bool get isTypedef;
+
+  /**
+   * Is [:true:] iff this type is a function type.
+   */
+  bool get isFunction;
+}
+
+/**
+ * A class or interface type.
+ */
+abstract class ClassMirror implements TypeMirror, ContainerMirror {
+  /**
+   * A mirror on the original declaration of this type.
+   *
+   * For most classes, they are their own original declaration.  For
+   * generic classes, however, there is a distinction between the
+   * original class declaration, which has unbound type variables, and
+   * the instantiations of generic classes, which have bound type
+   * variables.
+   */
+  ClassMirror get originalDeclaration;
+
+  /**
+   * Returns the super class of this type, or null if this type is [Object] or a
+   * typedef.
+   */
+  ClassMirror get superclass;
+
+  /**
+   * Returns a list of the interfaces directly implemented by this type.
+   */
+  List<ClassMirror> get superinterfaces;
+
+  /**
+   * Is [:true:] iff this type is a class.
+   */
+  bool get isClass;
+
+  /**
+   * Is [:true:] iff this type is an interface.
+   */
+  bool get isInterface;
+
+  /**
+   * Is this the original declaration of this type?
+   *
+   * For most classes, they are their own original declaration.  For
+   * generic classes, however, there is a distinction between the
+   * original class declaration, which has unbound type variables, and
+   * the instantiations of generic classes, which have bound type
+   * variables.
+   */
+  bool get isOriginalDeclaration;
+
+  /**
+   * Is [:true:] if this class is declared abstract.
+   */
+  bool get isAbstract;
+
+  /**
+   * Returns a list of the type arguments for this type.
+   */
+  List<TypeMirror> get typeArguments;
+
+  /**
+   * Returns the list of type variables for this type.
+   */
+  List<TypeVariableMirror> get typeVariables;
+
+  /**
+   * An immutable map from from names to mirrors for all members of
+   * this type.
+   *
+   * The members of a type are its methods, fields, getters, and
+   * setters.  Note that constructors and type variables are not
+   * considered to be members of a type.
+   *
+   * This does not include inherited members.
+   */
+  Map<String, MemberMirror> get members;
+
+  /**
+   * An immutable map from names to mirrors for all method,
+   * declarations for this type.  This does not include getters and
+   * setters.
+   */
+  Map<String, MethodMirror> get methods;
+
+  /**
+   * An immutable map from names to mirrors for all getter
+   * declarations for this type.
+   */
+  Map<String, MethodMirror> get getters;
+
+  /**
+   * An immutable map from names to mirrors for all setter
+   * declarations for this type.
+   */
+  Map<String, MethodMirror> get setters;
+
+  /**
+   * An immutable map from names to mirrors for all variable
+   * declarations for this type.
+   */
+  Map<String, VariableMirror> get variables;
+
+  /**
+   * An immutable map from names to mirrors for all constructor
+   * declarations for this type.
+   */
+  Map<String, MethodMirror> get constructors;
+
+  /**
+   * Returns the default type for this interface.
+   */
+  ClassMirror get defaultFactory;
+}
+
+/**
+ * A type parameter as declared on a generic type.
+ */
+abstract class TypeVariableMirror implements TypeMirror {
+  /**
+   * Returns the bound of the type parameter.
+   */
+  TypeMirror get upperBound;
+}
+
+/**
+ * A function type.
+ */
+abstract class FunctionTypeMirror implements ClassMirror {
+  /**
+   * Returns the return type of this function type.
+   */
+  TypeMirror get returnType;
+
+  /**
+   * Returns the parameters for this function type.
+   */
+  List<ParameterMirror> get parameters;
+
+  /**
+   * Returns the call method for this function type.
+   */
+  MethodMirror get callMethod;
+}
+
+/**
+ * A typedef.
+ */
+abstract class TypedefMirror implements ClassMirror {
+  /**
+   * The defining type for this typedef.
+   *
+   * For instance [:void f(int):] for a [:typedef void f(int):].
+   */
+  TypeMirror get value;
+}
+
+/**
+ * A member of a type, i.e. a field, method or constructor.
+ */
+abstract class MemberMirror implements DeclarationMirror {
+  /**
+   * Is this member a constructor?
+   */
+  bool get isConstructor;
+
+  /**
+   * Is this member a variable?
+   *
+   * This is [:false:] for locals.
+   */
+  bool get isVariable;
+
+  /**
+   * Is this member a method?.
+   *
+   * This is [:false:] for constructors.
+   */
+  bool get isMethod;
+
+  /**
+   * Is this member declared static?
+   */
+  bool get isStatic;
+
+  /**
+   * Is this member a parameter?
+   */
+  bool get isParameter;
+}
+
+/**
+ * A field.
+ */
+abstract class VariableMirror implements MemberMirror {
+
+  /**
+   * Returns true if this field is final.
+   */
+  bool get isFinal;
+
+  /**
+   * Returns true if this field is const.
+   */
+  bool get isConst;
+
+  /**
+   * Returns the type of this field.
+   */
+  TypeMirror get type;
+}
+
+/**
+ * Common interface constructors and methods, including factories, getters and
+ * setters.
+ */
+abstract class MethodMirror implements MemberMirror {
+  /**
+   * Returns the list of parameters for this method.
+   */
+  List<ParameterMirror> get parameters;
+
+  /**
+   * Returns the return type of this method.
+   */
+  TypeMirror get returnType;
+
+  /**
+   * Is the reflectee abstract?
+   */
+  bool get isAbstract;
+
+  /**
+   * Is the reflectee a regular function or method?
+   *
+   * A function or method is regular if it is not a getter, setter, or
+   * constructor.  Note that operators, by this definition, are
+   * regular methods.
+   */
+  bool get isRegularMethod;
+
+  /**
+   * Is the reflectee a const constructor?
+   */
+  bool get isConstConstructor;
+
+  /**
+   * Is the reflectee a generative constructor?
+   */
+  bool get isGenerativeConstructor;
+
+  /**
+   * Is the reflectee a redirecting constructor?
+   */
+  bool get isRedirectingConstructor;
+
+  /**
+   * Is the reflectee a factory constructor?
+   */
+  bool get isFactoryConstructor;
+
+  /**
+   * Returns the constructor name for named constructors and factory methods,
+   * e.g. [:'bar':] for constructor [:Foo.bar:] of type [:Foo:].
+   */
+  String get constructorName;
+
+  /**
+   * Is [:true:] if this method is a getter method.
+   */
+  bool get isGetter;
+
+  /**
+   * Is [:true:] if this method is a setter method.
+   */
+  bool get isSetter;
+
+  /**
+   * Is [:true:] if this method is an operator method.
+   */
+  bool get isOperator;
+
+  /**
+   * Returns the operator name for operator methods, e.g. [:'<':] for
+   * [:operator <:]
+   */
+  String get operatorName;
+}
+
+/**
+ * A formal parameter.
+ */
+abstract class ParameterMirror implements VariableMirror {
+  /**
+   * Returns the type of this parameter.
+   */
+  TypeMirror get type;
+
+  /**
+   * Returns the default value for this parameter.
+   */
+  String get defaultValue;
+
+  /**
+   * Does this parameter have a default value?
+   */
+  bool get hasDefaultValue;
+
+  /**
+   * Is this parameter optional?
+   */
+  bool get isOptional;
+
+  /**
+   * Is this parameter named?
+   */
+  bool get isNamed;
+
+  /**
+   * Returns [:true:] iff this parameter is an initializing formal of a
+   * constructor. That is, if it is of the form [:this.x:] where [:x:] is a
+   * field.
+   */
+  bool get isInitializingFormal;
+
+  /**
+   * Returns the initialized field, if this parameter is an initializing formal.
+   */
+  VariableMirror get initializedField;
+}
+
+/**
+ * A [SourceLocation] describes the span of an entity in Dart source code.
+ * A [SourceLocation] with a non-zero [length] should be the minimum span that
+ * encloses the declaration of the mirrored entity.
+ */
+abstract class SourceLocation {
+  /**
+   * The 1-based line number for this source location.
+   *
+   * A value of 0 means that the line number is unknown.
+   */
+  int get line;
+
+  /**
+   * The 1-based column number for this source location.
+   *
+   * A value of 0 means that the column number is unknown.
+   */
+  int get column;
+
+  /**
+   * The 0-based character offset into the [sourceText] where this source
+   * location begins.
+   *
+   * A value of -1 means that the offset is unknown.
+   */
+  int get offset;
+
+  /**
+   * The number of characters in this source location.
+   *
+   * A value of 0 means that the [offset] is approximate.
+   */
+  int get length;
+
+  /**
+   * The text of the location span.
+   */
+  String get text;
+
+  /**
+   * Returns the URI where the source originated.
+   */
+  Uri get sourceUri;
+
+  /**
+   * Returns the text of this source.
+   */
+  String get sourceText;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart
new file mode 100644
index 0000000..d904089
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart
@@ -0,0 +1,161 @@
+// 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.
+
+library mirrors_util;
+
+import 'dart:collection' show Queue;
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+import 'mirrors.dart';
+
+//------------------------------------------------------------------------------
+// Utility functions for using the Mirror API
+//------------------------------------------------------------------------------
+
+/**
+ * Returns an iterable over the type declarations directly inheriting from
+ * the declaration of this type.
+ */
+Iterable<ClassMirror> computeSubdeclarations(ClassMirror type) {
+  type = type.originalDeclaration;
+  var subtypes = <ClassMirror>[];
+  type.mirrors.libraries.forEach((_, library) {
+    for (ClassMirror otherType in library.classes.values) {
+      var superClass = otherType.superclass;
+      if (superClass != null) {
+        superClass = superClass.originalDeclaration;
+        if (type.library == superClass.library) {
+          if (superClass == type) {
+             subtypes.add(otherType);
+          }
+        }
+      }
+      final superInterfaces = otherType.superinterfaces;
+      for (ClassMirror superInterface in superInterfaces) {
+        superInterface = superInterface.originalDeclaration;
+        if (type.library == superInterface.library) {
+          if (superInterface == type) {
+            subtypes.add(otherType);
+          }
+        }
+      }
+    }
+  });
+  return subtypes;
+}
+
+LibraryMirror findLibrary(MemberMirror member) {
+  DeclarationMirror owner = member.owner;
+  if (owner is LibraryMirror) {
+    return owner;
+  } else if (owner is TypeMirror) {
+    return owner.library;
+  }
+  throw new Exception('Unexpected owner: ${owner}');
+}
+
+class HierarchyIterable extends Iterable<ClassMirror> {
+  final bool includeType;
+  final ClassMirror type;
+
+  HierarchyIterable(this.type, {bool includeType})
+      : this.includeType = includeType;
+
+  Iterator<ClassMirror> get iterator =>
+      new HierarchyIterator(type, includeType: includeType);
+}
+
+/**
+ * [HierarchyIterator] iterates through the class hierarchy of the provided
+ * type.
+ *
+ * First the superclass relation is traversed, skipping [Object], next the
+ * superinterface relation and finally is [Object] visited. The supertypes are
+ * visited in breadth first order and a superinterface is visited more than once
+ * if implemented through multiple supertypes.
+ */
+class HierarchyIterator implements Iterator<ClassMirror> {
+  final Queue<ClassMirror> queue = new Queue<ClassMirror>();
+  ClassMirror object;
+  ClassMirror _current;
+
+  HierarchyIterator(ClassMirror type, {bool includeType}) {
+    if (includeType) {
+      queue.add(type);
+    } else {
+      push(type);
+    }
+  }
+
+  ClassMirror push(ClassMirror type) {
+    if (type.superclass != null) {
+      if (type.superclass.isObject) {
+        object = type.superclass;
+      } else {
+        queue.addFirst(type.superclass);
+      }
+    }
+    queue.addAll(type.superinterfaces);
+    return type;
+  }
+
+  ClassMirror get current => _current;
+
+  bool moveNext() {
+    _current = null;
+    if (queue.isEmpty) {
+      if (object == null) return false;
+      _current = object;
+      object = null;
+      return true;
+    } else {
+      _current = push(queue.removeFirst());
+      return true;
+    }
+  }
+}
+
+final RegExp _singleLineCommentStart = new RegExp(r'^///? ?(.*)');
+final RegExp _multiLineCommentStartEnd =
+    new RegExp(r'^/\*\*? ?([\s\S]*)\*/$', multiLine: true);
+final RegExp _multiLineCommentLineStart = new RegExp(r'^[ \t]*\* ?(.*)');
+
+/**
+ * Pulls the raw text out of a comment (i.e. removes the comment
+ * characters).
+ */
+String stripComment(String comment) {
+  Match match = _singleLineCommentStart.firstMatch(comment);
+  if (match != null) {
+    return match[1];
+  }
+  match = _multiLineCommentStartEnd.firstMatch(comment);
+  if (match != null) {
+    comment = match[1];
+    var sb = new StringBuffer();
+    List<String> lines = comment.split('\n');
+    for (int index = 0 ; index < lines.length ; index++) {
+      String line = lines[index];
+      if (index == 0) {
+        sb.add(line); // Add the first line unprocessed.
+        continue;
+      }
+      sb.add('\n');
+      match = _multiLineCommentLineStart.firstMatch(line);
+      if (match != null) {
+        sb.add(match[1]);
+      } else if (index < lines.length-1 || !line.trim().isEmpty) {
+        // Do not add the last line if it only contains white space.
+        // This interprets cases like
+        //     /*
+        //      * Foo
+        //      */
+        // as "\nFoo\n" and not as "\nFoo\n     ".
+        sb.add(line);
+      }
+    }
+    return sb.toString();
+  }
+  throw new ArgumentError('Invalid comment $comment');
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart
new file mode 100644
index 0000000..3470211
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart
@@ -0,0 +1,173 @@
+// 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.
+
+library util;
+
+/**
+ * An abstract map implementation. This class can be used as a superclass for
+ * implementing maps, requiring only the further implementation of the
+ * [:operator []:], [:forEach:] and [:length:] methods to provide a fully
+ * implemented immutable map.
+ */
+abstract class AbstractMap<K,V> implements Map<K,V> {
+  AbstractMap();
+
+  AbstractMap.from(Map<K,V> other) {
+    other.forEach((k,v) => this[k] = v);
+  }
+
+  void operator []=(K key, value) {
+    throw new UnsupportedError('[]= is not supported');
+  }
+
+  void clear() {
+    throw new UnsupportedError('clear() is not supported');
+  }
+
+  bool containsKey(K key) {
+    var found = false;
+    forEach((k,_) {
+      if (k == key) {
+        found = true;
+      }
+    });
+    return found;
+  }
+
+  bool containsValue(V value) {
+    var found = false;
+    forEach((_,v) {
+      if (v == value) {
+        found = true;
+      }
+    });
+    return found;
+  }
+
+  Collection<K> get keys {
+    var keys = <K>[];
+    forEach((k,_) => keys.add(k));
+    return keys;
+  }
+
+  Collection<V> get values {
+    var values = <V>[];
+    forEach((_,v) => values.add(v));
+    return values;
+  }
+
+  bool get isEmpty => length == 0;
+  V putIfAbsent(K key, V ifAbsent()) {
+    if (!containsKey(key)) {
+      V value = this[key];
+      this[key] = ifAbsent();
+      return value;
+    }
+    return null;
+  }
+
+  V remove(K key) {
+    throw new UnsupportedError('V remove(K key) is not supported');
+  }
+}
+
+/**
+ * [ImmutableMapWrapper] wraps a (mutable) map as an immutable map where all
+ * mutating operations throw [UnsupportedError] upon invocation.
+ */
+class ImmutableMapWrapper<K,V> extends AbstractMap<K,V> {
+  final Map<K,V> _map;
+
+  ImmutableMapWrapper(this._map);
+
+  int get length => _map.length;
+
+  V operator [](K key) {
+    if (key is K) {
+      return _map[key];
+    }
+    return null;
+  }
+
+  void forEach(void f(K key, V value)) {
+    _map.forEach(f);
+  }
+}
+
+/**
+ * A [Filter] function returns [:true:] iff [value] should be included.
+ */
+typedef bool Filter<V>(V value);
+
+/**
+ * An immutable map wrapper capable of filtering the input map.
+ */
+class FilteredImmutableMap<K,V> extends ImmutableMapWrapper<K,V> {
+  final Filter<V> _filter;
+
+  FilteredImmutableMap(Map<K,V> map, this._filter) : super(map);
+
+  int get length {
+    var count = 0;
+    forEach((k,v) {
+      count++;
+    });
+    return count;
+  }
+
+  void forEach(void f(K key, V value)) {
+    _map.forEach((K k, V v) {
+      if (_filter(v)) {
+        f(k, v);
+      }
+    });
+  }
+}
+
+/**
+ * An [AsFilter] takes a [value] of type [V1] and returns [value] iff it is of
+ * type [V2] or [:null:] otherwise. An [AsFilter] therefore behaves like the
+ * [:as:] expression.
+ */
+typedef V2 AsFilter<V1, V2>(V1 value);
+
+/**
+ * An immutable map wrapper capable of filtering the input map based on types.
+ * It takes an [AsFilter] function which converts the original values of type
+ * [Vin] into values of type [Vout], or returns [:null:] if the value should
+ * not be included in the filtered map.
+ */
+class AsFilteredImmutableMap<K, Vin, Vout> extends AbstractMap<K, Vout> {
+  final Map<K, Vin> _map;
+  final AsFilter<Vin, Vout> _filter;
+
+  AsFilteredImmutableMap(this._map, this._filter);
+
+  int get length {
+    var count = 0;
+    forEach((k,v) {
+      count++;
+    });
+    return count;
+  }
+
+  Vout operator [](K key) {
+    if (key is K) {
+      Vin value = _map[key];
+      if (value != null) {
+        return _filter(value);
+      }
+    }
+    return null;
+  }
+
+  void forEach(void f(K key, Vout value)) {
+    _map.forEach((K k, Vin v) {
+      var value = _filter(v);
+      if (value != null) {
+        f(k, value);
+      }
+    });
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/native_handler.dart b/pkgs/markdown/lib/src/compiler/implementation/native_handler.dart
new file mode 100644
index 0000000..373f16e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/native_handler.dart
@@ -0,0 +1,902 @@
+// 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.
+
+library native;
+
+import 'dart:collection' show Queue;
+import 'dart:uri';
+import 'dart2jslib.dart' hide SourceString;
+import 'dart_types.dart';
+import 'elements/elements.dart';
+import 'js_backend/js_backend.dart';
+import 'resolution/resolution.dart' show ResolverVisitor;
+import 'scanner/scannerlib.dart';
+import 'ssa/ssa.dart';
+import 'tree/tree.dart';
+import 'util/util.dart';
+
+
+/// This class is a temporary work-around until we get a more powerful DartType.
+class SpecialType {
+  final String name;
+  const SpecialType._(this.name);
+
+  /// The type Object, but no subtypes:
+  static const JsObject = const SpecialType._('=Object');
+
+  /// The specific implementation of List that is JavaScript Array:
+  static const JsArray = const SpecialType._('=List');
+}
+
+
+/**
+ * This could be an abstract class but we use it as a stub for the dart_backend.
+ */
+class NativeEnqueuer {
+  /// Initial entry point to native enqueuer.
+  void processNativeClasses(Iterable<LibraryElement> libraries) {}
+
+  /// Notification of a main Enqueuer worklist element.  For methods, adds
+  /// information from metadata attributes, and computes types instantiated due
+  /// to calling the method.
+  void registerElement(Element element) {}
+
+  /// Notification of native field.  Adds information from metadata attributes.
+  void handleFieldAnnotations(Element field) {}
+
+  /// Computes types instantiated due to getting a native field.
+  void registerFieldLoad(Element field) {}
+
+  /// Computes types instantiated due to setting a native field.
+  void registerFieldStore(Element field) {}
+
+  NativeBehavior getNativeBehaviorOf(Send node) => null;
+
+  /**
+   * Handles JS-calls, which can be an instantiation point for types.
+   *
+   * For example, the following code instantiates and returns native classes
+   * that are `_DOMWindowImpl` or a subtype.
+   *
+   *     JS('_DOMWindowImpl', 'window')
+   *
+   */
+  // TODO(sra): The entry from codegen will not have a resolver.
+  void registerJsCall(Send node, ResolverVisitor resolver) {}
+
+  /// Emits a summary information using the [log] function.
+  void logSummary(log(message)) {}
+}
+
+
+abstract class NativeEnqueuerBase implements NativeEnqueuer {
+
+  /**
+   * The set of all native classes.  Each native class is in [nativeClasses] and
+   * exactly one of [unusedClasses], [pendingClasses] and [registeredClasses].
+   */
+  final Set<ClassElement> nativeClasses = new Set<ClassElement>();
+
+  final Set<ClassElement> registeredClasses = new Set<ClassElement>();
+  final Set<ClassElement> pendingClasses = new Set<ClassElement>();
+  final Set<ClassElement> unusedClasses = new Set<ClassElement>();
+
+  /**
+   * Records matched constraints ([SpecialType] or [DartType]).  Once a type
+   * constraint has been matched, there is no need to match it again.
+   */
+  final Set matchedTypeConstraints = new Set();
+
+  /// Pending actions.  Classes in [pendingClasses] have action thunks in
+  /// [queue] to register the class.
+  final queue = new Queue();
+  bool flushing = false;
+
+  /// Maps JS foreign calls to their computed native behavior.
+  final Map<Node, NativeBehavior> nativeBehaviors =
+      new Map<Node, NativeBehavior>();
+
+  final Enqueuer world;
+  final Compiler compiler;
+  final bool enableLiveTypeAnalysis;
+
+  ClassElement _annotationCreatesClass;
+  ClassElement _annotationReturnsClass;
+  ClassElement _annotationJsNameClass;
+
+  /// Subclasses of [NativeEnqueuerBase] are constructed by the backend.
+  NativeEnqueuerBase(this.world, this.compiler, this.enableLiveTypeAnalysis);
+
+  void processNativeClasses(Iterable<LibraryElement> libraries) {
+    libraries.forEach(processNativeClassesInLibrary);
+    processNativeClassesInLibrary(compiler.isolateHelperLibrary);
+    if (!enableLiveTypeAnalysis) {
+      nativeClasses.forEach((c) => enqueueClass(c, 'forced'));
+      flushQueue();
+    }
+  }
+
+  void processNativeClassesInLibrary(LibraryElement library) {
+    // Use implementation to ensure the inclusion of injected members.
+    library.implementation.forEachLocalMember((Element element) {
+      if (element.isClass() && element.isNative()) {
+        processNativeClass(element);
+      }
+    });
+  }
+
+  void processNativeClass(ClassElement classElement) {
+    nativeClasses.add(classElement);
+    unusedClasses.add(classElement);
+    // Resolve class to ensure the class has valid inheritance info.
+    classElement.ensureResolved(compiler);
+  }
+
+  ClassElement get annotationCreatesClass {
+    findAnnotationClasses();
+    return _annotationCreatesClass;
+  }
+
+  ClassElement get annotationReturnsClass {
+    findAnnotationClasses();
+    return _annotationReturnsClass;
+  }
+
+  ClassElement get annotationJsNameClass {
+    findAnnotationClasses();
+    return _annotationJsNameClass;
+  }
+
+  void findAnnotationClasses() {
+    if (_annotationCreatesClass != null) return;
+    ClassElement find(name) {
+      Element e = compiler.findHelper(name);
+      if (e == null || e is! ClassElement) {
+        compiler.cancel("Could not find implementation class '${name}'");
+      }
+      return e;
+    }
+    _annotationCreatesClass = find(const SourceString('Creates'));
+    _annotationReturnsClass = find(const SourceString('Returns'));
+    _annotationJsNameClass = find(const SourceString('JSName'));
+  }
+
+  /// Returns the JSName annotation string or `null` if no JSName annotation is
+  /// present.
+  String findJsNameFromAnnotation(Element element) {
+    String name = null;
+    ClassElement annotationClass = annotationJsNameClass;
+    for (Link<MetadataAnnotation> link = element.metadata;
+         !link.isEmpty;
+         link = link.tail) {
+      MetadataAnnotation annotation = link.head.ensureResolved(compiler);
+      var value = annotation.value;
+      if (value is! ConstructedConstant) continue;
+      if (value.type is! InterfaceType) continue;
+      if (!identical(value.type.element, annotationClass)) continue;
+
+      var fields = value.fields;
+      // TODO(sra): Better validation of the constant.
+      if (fields.length != 1 || fields[0] is! StringConstant) {
+        PartialMetadataAnnotation partial = annotation;
+        compiler.cancel(
+            'Annotations needs one string: ${partial.parseNode(compiler)}');
+      }
+      String specString = fields[0].toDartString().slowToString();
+      if (name == null) {
+        name = specString;
+      } else {
+        PartialMetadataAnnotation partial = annotation;
+        compiler.cancel(
+            'Too many JSName annotations: ${partial.parseNode(compiler)}');
+      }
+    }
+    return name;
+  }
+
+  enqueueClass(ClassElement classElement, cause) {
+    assert(unusedClasses.contains(classElement));
+    unusedClasses.remove(classElement);
+    pendingClasses.add(classElement);
+    queue.add(() { processClass(classElement, cause); });
+  }
+
+  void flushQueue() {
+    if (flushing) return;
+    flushing = true;
+    while (!queue.isEmpty) {
+      (queue.removeFirst())();
+    }
+    flushing = false;
+  }
+
+  processClass(ClassElement classElement, cause) {
+    assert(!registeredClasses.contains(classElement));
+
+    bool firstTime = registeredClasses.isEmpty;
+    pendingClasses.remove(classElement);
+    registeredClasses.add(classElement);
+
+    world.registerInstantiatedClass(classElement);
+
+    // Also parse the node to know all its methods because otherwise it will
+    // only be parsed if there is a call to one of its constructors.
+    classElement.parseNode(compiler);
+
+    if (firstTime) {
+      queue.add(onFirstNativeClass);
+    }
+  }
+
+  registerElement(Element element) {
+    compiler.withCurrentElement(element, () {
+      if (element.isFunction() || element.isGetter() || element.isSetter()) {
+        handleMethodAnnotations(element);
+        if (element.isNative()) {
+          registerMethodUsed(element);
+        }
+      } else if (element.isField()) {
+        handleFieldAnnotations(element);
+        if (element.isNative()) {
+          registerFieldLoad(element);
+          registerFieldStore(element);
+        }
+      }
+    });
+  }
+
+  handleFieldAnnotations(Element element) {
+    if (element.enclosingElement.isNative()) {
+      // Exclude non-instance (static) fields - they not really native and are
+      // compiled as isolate globals.  Access of a property of a constructor
+      // function or a non-method property in the prototype chain, must be coded
+      // using a JS-call.
+      if (element.isInstanceMember()) {
+        setNativeName(element);
+      }
+    }
+  }
+
+  handleMethodAnnotations(Element method) {
+    if (isNativeMethod(method)) {
+      setNativeName(method);
+    }
+  }
+
+  /// Sets the native name of [element], either from an annotation, or
+  /// defaulting to the Dart name.
+  void setNativeName(Element element) {
+    String name = findJsNameFromAnnotation(element);
+    if (name == null) name = element.name.slowToString();
+    element.setNative(name);
+  }
+
+  bool isNativeMethod(Element element) {
+    if (!element.getLibrary().canUseNative) return false;
+    // Native method?
+    return compiler.withCurrentElement(element, () {
+      Node node = element.parseNode(compiler);
+      if (node is! FunctionExpression) return false;
+      node = node.body;
+      Token token = node.getBeginToken();
+      if (identical(token.stringValue, 'native')) return true;
+      return false;
+    });
+  }
+
+  void registerMethodUsed(Element method) {
+    processNativeBehavior(
+        NativeBehavior.ofMethod(method, compiler),
+        method);
+      flushQueue();
+  }
+
+  void registerFieldLoad(Element field) {
+    processNativeBehavior(
+        NativeBehavior.ofFieldLoad(field, compiler),
+        field);
+    flushQueue();
+  }
+
+  void registerFieldStore(Element field) {
+    processNativeBehavior(
+        NativeBehavior.ofFieldStore(field, compiler),
+        field);
+    flushQueue();
+  }
+
+  void registerJsCall(Send node, ResolverVisitor resolver) {
+    NativeBehavior behavior = NativeBehavior.ofJsCall(node, compiler, resolver);
+    processNativeBehavior(behavior, node);
+    nativeBehaviors[node] = behavior;
+    flushQueue();
+  }
+
+  NativeBehavior getNativeBehaviorOf(Send node) => nativeBehaviors[node];
+
+  processNativeBehavior(NativeBehavior behavior, cause) {
+    bool allUsedBefore = unusedClasses.isEmpty;
+    for (var type in behavior.typesInstantiated) {
+      if (matchedTypeConstraints.contains(type)) continue;
+      matchedTypeConstraints.add(type);
+      if (type is SpecialType) {
+        if (type == SpecialType.JsArray) {
+          world.registerInstantiatedClass(compiler.listClass);
+        } else if (type == SpecialType.JsObject) {
+          world.registerInstantiatedClass(compiler.objectClass);
+        }
+        continue;
+      }
+      if (type is InterfaceType) {
+        if (type.element == compiler.intClass) {
+          world.registerInstantiatedClass(compiler.intClass);
+        } else if (type.element == compiler.doubleClass) {
+          world.registerInstantiatedClass(compiler.doubleClass);
+        } else if (type.element == compiler.numClass) {
+          world.registerInstantiatedClass(compiler.doubleClass);
+          world.registerInstantiatedClass(compiler.intClass);
+        } else if (type.element == compiler.stringClass) {
+          world.registerInstantiatedClass(compiler.stringClass);
+        } else if (type.element == compiler.nullClass) {
+          world.registerInstantiatedClass(compiler.nullClass);
+        } else if (type.element == compiler.boolClass) {
+          world.registerInstantiatedClass(compiler.boolClass);
+        }
+      }
+      assert(type is DartType);
+      enqueueUnusedClassesMatching(
+          (nativeClass) => compiler.types.isSubtype(nativeClass.thisType, type),
+          cause,
+          'subtypeof($type)');
+    }
+
+    // Give an info so that library developers can compile with -v to find why
+    // all the native classes are included.
+    if (unusedClasses.isEmpty && !allUsedBefore) {
+      compiler.log('All native types marked as used due to $cause.');
+    }
+  }
+
+  enqueueUnusedClassesMatching(bool predicate(classElement),
+                               cause,
+                               [String reason]) {
+    Iterable matches = unusedClasses.where(predicate);
+    matches.forEach((c) => enqueueClass(c, cause));
+  }
+
+  onFirstNativeClass() {
+    staticUse(name) => world.registerStaticUse(compiler.findHelper(name));
+
+    staticUse(const SourceString('dynamicFunction'));
+    staticUse(const SourceString('dynamicSetMetadata'));
+    staticUse(const SourceString('defineProperty'));
+    staticUse(const SourceString('toStringForNativeObject'));
+    staticUse(const SourceString('hashCodeForNativeObject'));
+
+    addNativeExceptions();
+  }
+
+  addNativeExceptions() {
+    enqueueUnusedClassesMatching((classElement) {
+        // TODO(sra): Annotate exception classes in dart:html.
+        String name = classElement.name.slowToString();
+        if (name.contains('Exception')) return true;
+        if (name.contains('Error')) return true;
+        return false;
+      },
+      'native exception');
+  }
+}
+
+
+class NativeResolutionEnqueuer extends NativeEnqueuerBase {
+
+  NativeResolutionEnqueuer(Enqueuer world, Compiler compiler)
+    : super(world, compiler, compiler.enableNativeLiveTypeAnalysis);
+
+  void logSummary(log(message)) {
+    log('Resolved ${registeredClasses.length} native elements used, '
+        '${unusedClasses.length} native elements dead.');
+  }
+}
+
+
+class NativeCodegenEnqueuer extends NativeEnqueuerBase {
+
+  final CodeEmitterTask emitter;
+
+  final Set<ClassElement> doneAddSubtypes = new Set<ClassElement>();
+
+  NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter)
+    : super(world, compiler, compiler.enableNativeLiveTypeAnalysis);
+
+  void processNativeClasses(Iterable<LibraryElement> libraries) {
+    super.processNativeClasses(libraries);
+
+    // HACK HACK - add all the resolved classes.
+    NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer;
+    for (final classElement in enqueuer.registeredClasses) {
+      if (unusedClasses.contains(classElement)) {
+        enqueueClass(classElement, 'was resolved');
+      }
+    }
+    flushQueue();
+  }
+
+  processClass(ClassElement classElement, cause) {
+    super.processClass(classElement, cause);
+    // Add the information that this class is a subtype of its supertypes.  The
+    // code emitter and the ssa builder use that information.
+    addSubtypes(classElement, emitter.nativeEmitter);
+  }
+
+  void addSubtypes(ClassElement cls, NativeEmitter emitter) {
+    if (!cls.isNative()) return;
+    if (doneAddSubtypes.contains(cls)) return;
+    doneAddSubtypes.add(cls);
+
+    // Walk the superclass chain since classes on the superclass chain might not
+    // be instantiated (abstract or simply unused).
+    addSubtypes(cls.superclass, emitter);
+
+    for (DartType type in cls.allSupertypes) {
+      List<Element> subtypes = emitter.subtypes.putIfAbsent(
+          type.element,
+          () => <ClassElement>[]);
+      subtypes.add(cls);
+    }
+
+    // Skip through all the mixin applications in the super class
+    // chain. That way, the direct subtypes set only contain the
+    // natives classes.
+    ClassElement superclass = cls.superclass;
+    while (superclass != null && superclass.isMixinApplication) {
+      assert(!superclass.isNative());
+      superclass = superclass.superclass;
+    }
+
+    List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent(
+        superclass,
+        () => <ClassElement>[]);
+    directSubtypes.add(cls);
+  }
+
+  void logSummary(log(message)) {
+    log('Compiled ${registeredClasses.length} native classes, '
+        '${unusedClasses.length} native classes omitted.');
+  }
+}
+
+void maybeEnableNative(Compiler compiler,
+                       LibraryElement library) {
+  String libraryName = library.canonicalUri.toString();
+  if (library.entryCompilationUnit.script.name.contains(
+          'dart/tests/compiler/dart2js_native')
+      || libraryName == 'dart:async'
+      || libraryName == 'dart:html'
+      || libraryName == 'dart:html_common'
+      || libraryName == 'dart:indexed_db'
+      || libraryName == 'dart:svg'
+      || libraryName == 'dart:web_audio') {
+    library.canUseNative = true;
+  }
+}
+
+/**
+ * A summary of the behavior of a native element.
+ *
+ * Native code can return values of one type and cause native subtypes of
+ * another type to be instantiated.  By default, we compute both from the
+ * declared type.
+ *
+ * A field might yield any native type that 'is' the field type.
+ *
+ * A method might create and return instances of native subclasses of its
+ * declared return type, and a callback argument may be called with instances of
+ * the callback parameter type (e.g. Event).
+ *
+ * If there is one or more @Creates annotations, the union of the named types
+ * replaces the inferred instantiated type, and the return type is ignored for
+ * the purpose of inferring instantiated types.
+ *
+ *     @Creates(IDBCursor)    // Created asynchronously.
+ *     @Creates(IDBRequest)   // Created synchronously (for return value).
+ *     IDBRequest request = objectStore.openCursor();
+ *
+ * If there is one or more @Returns annotations, the union of the named types
+ * replaces the declared return type.
+ *
+ *     @Returns(IDBRequest)
+ *     IDBRequest request = objectStore.openCursor();
+ */
+class NativeBehavior {
+
+  /// [DartType]s or [SpecialType]s returned or yielded by the native element.
+  final List typesReturned = [];
+
+  /// [DartType]s or [SpecialType]s instantiated by the native element.
+  final List typesInstantiated = [];
+
+  static final NativeBehavior NONE = new NativeBehavior();
+
+  //NativeBehavior();
+
+  static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) {
+    // The first argument of a JS-call is a string encoding various attributes
+    // of the code.
+    //
+    //  'Type1|Type2'.  A union type.
+    //  '=Object'.      A JavaScript Object, no subtype.
+    //  '=List'.        A JavaScript Array, no subtype.
+
+    var argNodes = jsCall.arguments;
+    if (argNodes.isEmpty) {
+      compiler.cancel("JS expression has no type", node: jsCall);
+    }
+
+    var firstArg = argNodes.head;
+    LiteralString specLiteral = firstArg.asLiteralString();
+    if (specLiteral != null) {
+      String specString = specLiteral.dartString.slowToString();
+      // Various things that are not in fact types.
+      if (specString == 'void') return NativeBehavior.NONE;
+      if (specString == '' || specString == 'var') {
+        var behavior = new NativeBehavior();
+        behavior.typesReturned.add(compiler.objectClass.computeType(compiler));
+        return behavior;
+      }
+      var behavior = new NativeBehavior();
+      for (final typeString in specString.split('|')) {
+        var type = _parseType(typeString, compiler,
+            (name) => resolver.resolveTypeFromString(name),
+            jsCall);
+        behavior.typesInstantiated.add(type);
+        behavior.typesReturned.add(type);
+      }
+      return behavior;
+    }
+
+    // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2').  It
+    // is not very satisfactory because it does not work for void, dynamic.
+
+    compiler.cancel("Unexpected JS first argument", node: firstArg);
+  }
+
+  static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) {
+    FunctionType type = method.computeType(compiler);
+    var behavior = new NativeBehavior();
+    behavior.typesReturned.add(type.returnType);
+    behavior._capture(type, compiler);
+
+    // TODO(sra): Optional arguments are currently missing from the
+    // DartType. This should be fixed so the following work-around can be
+    // removed.
+    method.computeSignature(compiler).forEachOptionalParameter(
+        (Element parameter) {
+          behavior._escape(parameter.computeType(compiler), compiler);
+        });
+
+    behavior._overrideWithAnnotations(method, compiler);
+    return behavior;
+  }
+
+  static NativeBehavior ofFieldLoad(Element field, Compiler compiler) {
+    DartType type = field.computeType(compiler);
+    var behavior = new NativeBehavior();
+    behavior.typesReturned.add(type);
+    behavior._capture(type, compiler);
+    behavior._overrideWithAnnotations(field, compiler);
+    return behavior;
+  }
+
+  static NativeBehavior ofFieldStore(Element field, Compiler compiler) {
+    DartType type = field.computeType(compiler);
+    var behavior = new NativeBehavior();
+    behavior._escape(type, compiler);
+    // We don't override the default behaviour - the annotations apply to
+    // loading the field.
+    return behavior;
+  }
+
+  void _overrideWithAnnotations(Element element, Compiler compiler) {
+    if (element.metadata.isEmpty) return;
+
+    DartType lookup(String name) {
+      Element e = element.buildScope().lookup(new SourceString(name));
+      if (e == null) return null;
+      if (e is! ClassElement) return null;
+      e.ensureResolved(compiler);
+      return e.computeType(compiler);
+    }
+
+    NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer;
+    var creates = _collect(element, compiler, enqueuer.annotationCreatesClass,
+                           lookup);
+    var returns = _collect(element, compiler, enqueuer.annotationReturnsClass,
+                           lookup);
+
+    if (creates != null) {
+      typesInstantiated..clear()..addAll(creates);
+    }
+    if (returns != null) {
+      typesReturned..clear()..addAll(returns);
+    }
+  }
+
+  /**
+   * Returns a list of type constraints from the annotations of
+   * [annotationClass].
+   * Returns `null` if no constraints.
+   */
+  static _collect(Element element, Compiler compiler, Element annotationClass,
+                  lookup(str)) {
+    var types = null;
+    for (Link<MetadataAnnotation> link = element.metadata;
+         !link.isEmpty;
+         link = link.tail) {
+      MetadataAnnotation annotation = link.head.ensureResolved(compiler);
+      var value = annotation.value;
+      if (value is! ConstructedConstant) continue;
+      if (value.type is! InterfaceType) continue;
+      if (!identical(value.type.element, annotationClass)) continue;
+
+      var fields = value.fields;
+      // TODO(sra): Better validation of the constant.
+      if (fields.length != 1 || fields[0] is! StringConstant) {
+        PartialMetadataAnnotation partial = annotation;
+        compiler.cancel(
+            'Annotations needs one string: ${partial.parseNode(compiler)}');
+      }
+      String specString = fields[0].toDartString().slowToString();
+      for (final typeString in specString.split('|')) {
+        var type = _parseType(typeString, compiler, lookup, annotation);
+        if (types == null) types = [];
+        types.add(type);
+      }
+    }
+    return types;
+  }
+
+  /// Models the behavior of having intances of [type] escape from Dart code
+  /// into native code.
+  void _escape(DartType type, Compiler compiler) {
+    type = type.unalias(compiler);
+    if (type is FunctionType) {
+      // A function might be called from native code, passing us novel
+      // parameters.
+      _escape(type.returnType, compiler);
+      for (Link<DartType> parameters = type.parameterTypes;
+           !parameters.isEmpty;
+           parameters = parameters.tail) {
+        _capture(parameters.head, compiler);
+      }
+    }
+  }
+
+  /// Models the behavior of Dart code receiving instances and methods of [type]
+  /// from native code.  We usually start the analysis by capturing a native
+  /// method that has been used.
+  void _capture(DartType type, Compiler compiler) {
+    type = type.unalias(compiler);
+    if (type is FunctionType) {
+      _capture(type.returnType, compiler);
+      for (Link<DartType> parameters = type.parameterTypes;
+           !parameters.isEmpty;
+           parameters = parameters.tail) {
+        _escape(parameters.head, compiler);
+      }
+    } else {
+      typesInstantiated.add(type);
+    }
+  }
+
+  static _parseType(String typeString, Compiler compiler,
+      lookup(name), locationNodeOrElement) {
+    if (typeString == '=Object') return SpecialType.JsObject;
+    if (typeString == '=List') return SpecialType.JsArray;
+    if (typeString == 'dynamic') {
+      return  compiler.dynamicClass.computeType(compiler);
+    }
+    DartType type = lookup(typeString);
+    if (type != null) return type;
+
+    int index = typeString.indexOf('<');
+    if (index < 1) {
+      compiler.cancel("Type '$typeString' not found",
+          node: _errorNode(locationNodeOrElement, compiler));
+    }
+    type = lookup(typeString.substring(0, index));
+    if (type != null)  {
+      // TODO(sra): Parse type parameters.
+      return type;
+    }
+    compiler.cancel("Type '$typeString' not found",
+        node: _errorNode(locationNodeOrElement, compiler));
+  }
+
+  static _errorNode(locationNodeOrElement, compiler) {
+    if (locationNodeOrElement is Node) return locationNodeOrElement;
+    return locationNodeOrElement.parseNode(compiler);
+  }
+}
+
+void checkAllowedLibrary(ElementListener listener, Token token) {
+  LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary();
+  if (!currentLibrary.canUseNative) {
+    listener.recoverableError("Unexpected token", token: token);
+  }
+}
+
+Token handleNativeBlockToSkip(Listener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  token = token.next;
+  if (identical(token.kind, STRING_TOKEN)) {
+    token = token.next;
+  }
+  if (identical(token.stringValue, '{')) {
+    BeginGroupToken beginGroupToken = token;
+    token = beginGroupToken.endGroup;
+  }
+  return token;
+}
+
+Token handleNativeClassBodyToSkip(Listener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  listener.handleIdentifier(token);
+  token = token.next;
+  if (!identical(token.kind, STRING_TOKEN)) {
+    return listener.unexpected(token);
+  }
+  token = token.next;
+  if (!identical(token.stringValue, '{')) {
+    return listener.unexpected(token);
+  }
+  BeginGroupToken beginGroupToken = token;
+  token = beginGroupToken.endGroup;
+  return token;
+}
+
+Token handleNativeClassBody(Listener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  token = token.next;
+  if (!identical(token.kind, STRING_TOKEN)) {
+    listener.unexpected(token);
+  } else {
+    token = token.next;
+  }
+  return token;
+}
+
+Token handleNativeFunctionBody(ElementListener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  Token begin = token;
+  listener.beginReturnStatement(token);
+  token = token.next;
+  bool hasExpression = false;
+  if (identical(token.kind, STRING_TOKEN)) {
+    hasExpression = true;
+    listener.beginLiteralString(token);
+    listener.endLiteralString(0);
+    token = token.next;
+  }
+  listener.endReturnStatement(hasExpression, begin, token);
+  // TODO(ngeoffray): expect a ';'.
+  // Currently there are method with both native marker and Dart body.
+  return token.next;
+}
+
+SourceString checkForNativeClass(ElementListener listener) {
+  SourceString nativeTagInfo;
+  Node node = listener.nodes.head;
+  if (node != null
+      && node.asIdentifier() != null
+      && node.asIdentifier().source.stringValue == 'native') {
+    nativeTagInfo = node.asIdentifier().token.next.value;
+    listener.popNode();
+  }
+  return nativeTagInfo;
+}
+
+bool isOverriddenMethod(FunctionElement element,
+                        ClassElement cls,
+                        NativeEmitter nativeEmitter) {
+  List<ClassElement> subtypes = nativeEmitter.subtypes[cls];
+  if (subtypes == null) return false;
+  for (ClassElement subtype in subtypes) {
+    if (subtype.lookupLocalMember(element.name) != null) return true;
+  }
+  return false;
+}
+
+final RegExp nativeRedirectionRegExp = new RegExp(r'^[a-zA-Z][a-zA-Z_$0-9]*$');
+
+void handleSsaNative(SsaBuilder builder, Expression nativeBody) {
+  Compiler compiler = builder.compiler;
+  FunctionElement element = builder.work.element;
+  NativeEmitter nativeEmitter = builder.emitter.nativeEmitter;
+
+  HInstruction convertDartClosure(Element parameter, FunctionType type) {
+    HInstruction local = builder.localsHandler.readLocal(parameter);
+    Constant arityConstant =
+        builder.constantSystem.createInt(type.computeArity());
+    HInstruction arity = builder.graph.addConstant(arityConstant);
+    // TODO(ngeoffray): For static methods, we could pass a method with a
+    // defined arity.
+    Element helper = builder.backend.getClosureConverter();
+    builder.pushInvokeHelper2(helper, local, arity, HType.UNKNOWN);
+    HInstruction closure = builder.pop();
+    return closure;
+  }
+
+  // Check which pattern this native method follows:
+  // 1) foo() native;
+  //      hasBody = false
+  // 2) foo() native "bar";
+  //      No longer supported, this is now done with @JSName('foo') and case 1.
+  // 3) foo() native "return 42";
+  //      hasBody = true
+  bool hasBody = false;
+  assert(element.isNative());
+  String nativeMethodName = element.fixedBackendName();
+  if (nativeBody != null) {
+    LiteralString jsCode = nativeBody.asLiteralString();
+    String str = jsCode.dartString.slowToString();
+    if (nativeRedirectionRegExp.hasMatch(str)) {
+      compiler.cancel("Deprecated syntax, use @JSName('name') instead.",
+                      node: nativeBody);
+    }
+    hasBody = true;
+  }
+
+  if (!hasBody) {
+    nativeEmitter.nativeMethods.add(element);
+  }
+
+  FunctionSignature parameters = element.computeSignature(builder.compiler);
+  if (!hasBody) {
+    List<String> arguments = <String>[];
+    List<HInstruction> inputs = <HInstruction>[];
+    String receiver = '';
+    if (element.isInstanceMember()) {
+      receiver = '#.';
+      inputs.add(builder.localsHandler.readThis());
+    }
+    parameters.forEachParameter((Element parameter) {
+      DartType type = parameter.computeType(compiler).unalias(compiler);
+      HInstruction input = builder.localsHandler.readLocal(parameter);
+      if (type is FunctionType) {
+        // The parameter type is a function type either directly or through
+        // typedef(s).
+        input = convertDartClosure(parameter, type);
+      }
+      inputs.add(input);
+      arguments.add('#');
+    });
+
+    String foreignParameters = Strings.join(arguments, ',');
+    String nativeMethodCall;
+    if (element.kind == ElementKind.FUNCTION) {
+      nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)';
+    } else if (element.kind == ElementKind.GETTER) {
+      nativeMethodCall = '$receiver$nativeMethodName';
+    } else if (element.kind == ElementKind.SETTER) {
+      nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters';
+    } else {
+      builder.compiler.internalError('unexpected kind: "${element.kind}"',
+                                     element: element);
+    }
+
+    DartString jsCode = new DartString.literal(nativeMethodCall);
+    builder.push(new HForeign(jsCode, HType.UNKNOWN, inputs));
+    builder.close(new HReturn(builder.pop())).addSuccessor(builder.graph.exit);
+  } else {
+    if (parameters.parameterCount != 0) {
+      compiler.cancel(
+          'native "..." syntax is restricted to functions with zero parameters',
+          node: nativeBody);
+    }
+    LiteralString jsCode = nativeBody.asLiteralString();
+    builder.push(new HForeign.statement(jsCode.dartString, <HInstruction>[]));
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart
new file mode 100644
index 0000000..5a0da01
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart
@@ -0,0 +1,596 @@
+// 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.
+
+/**
+ * This library contains the infrastructure to parse and integrate patch files.
+ *
+ * Three types of elements can be patched: [LibraryElement], [ClassElement],
+ * [FunctionElement]. Patches are introduced in patch libraries which are loaded
+ * together with the corresponding origin library. Which libraries that are
+ * patched is determined by the [dart2jsPatchPath] field of [LibraryInfo] found
+ * in [:lib/_internal/libraries.dart:].
+ *
+ * Patch libraries are parsed like regular library and thus provided with their
+ * own elements. These elements which are distinct from the elements from the
+ * patched library and the relation between patched and patch elements is
+ * established through the [:patch:] and [:origin:] fields found on
+ * [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields
+ * are set on the patched elements to point to their corresponding patch
+ * element, and the [:origin:] elements are set on the patch elements to point
+ * their corresponding patched elements.
+ *
+ * The fields [Element.isPatched] and [Element.isPatch] can be used to determine
+ * whether the [:patch:] or [:origin:] field, respectively, has been set on an
+ * element, regardless of whether the element is one of the three patchable
+ * element types or not.
+ *
+ * ## Variants of Classes and Functions ##
+ *
+ * With patches there are four variants of classes and function:
+ *
+ * Regular: A class or function which is not declared in a patch library and
+ *   which has no corresponding patch.
+ * Origin: A class or function which is not declared in a patch library and
+ *   which has a corresponding patch. Origin functions must use the [:external:]
+ *   modifier and can have no body. Origin classes and functions are also
+ *   called 'patched'.
+ * Patch: A class or function which is declared in a patch library and which
+ *   has a corresponding origin. Both patch classes and patch functions must use
+ *   the [:patch:] modifier.
+ * Injected: A class or function (or even field) which is declared in a
+ *   patch library and which has no corresponding origin. An injected element
+ *   cannot use the [:patch:] modifier. Injected elements are never visible from
+ *   outside the patch library in which they have been declared. For this
+ *   reason, injected elements are often declared private and therefore called
+ *   also called 'patch private'.
+ *
+ * Examples of the variants is shown in the code below:
+ *
+ *     // In the origin library:
+ *     class RegularClass { // A regular class.
+ *       void regularMethod() {} // A regular method.
+ *     }
+ *     class PatchedClass { // An origin class.
+ *       int regularField; // A regular field.
+ *       void regularMethod() {} // A regular method.
+ *       external void patchedMethod(); // An origin method.
+ *     }
+ *
+ *     // In the patch library:
+ *     class _InjectedClass { // An injected class.
+ *       void _injectedMethod() {} // An injected method.
+ *     }
+ *     patch class PatchedClass { // A patch class.
+ *       int _injectedField; { // An injected field.
+ *       patch void patchedMethod() {} // A patch method.
+ *     }
+ *
+ *
+ * ## Declaration and Implementation ##
+ *
+ * With patches we have two views on elements: as the 'declaration' which
+ * introduces the entity and defines its interface, and as the 'implementation'
+ * which defines the actual implementation of the entity.
+ *
+ * Every element has a 'declaration' and an 'implementation' element. For
+ * regular and injected elements these are the same. For origin elements the
+ * declaration is the element itself and the implementation is the patch element
+ * found through its [:patch:] field. For patch elements the implementation is
+ * the element itself and the declaration is the origin element found through
+ * its [:origin:] field. The declaration and implementation of any element is
+ * conveniently available through the [Element.declaration] and
+ * [Element.implementation] getters.
+ *
+ * Most patch-related invariants enforced through-out the compiler are defined
+ * in terms of 'declaration' and 'implementation', and tested through the
+ * predicate getters [Element.isDeclaration] and [Element.isImplementation].
+ * Patch invariants are stated both in comments and as assertions.
+ *
+ *
+ * ## General invariant guidelines ##
+ *
+ * For [LibraryElement] we always use declarations. This means the
+ * [Element.getLibrary] method will only return library declarations. Patch
+ * library implementations are only accessed through calls to
+ * [Element.getImplementationLibrary] which is used to setup the correct
+ * [Element.enclosingElement] relation between patch/injected elements and the
+ * patch library.
+ *
+ * For [ClassElement] and [FunctionElement] we use declarations for determining
+ * identity and implementations for work based on the AST nodes, such as
+ * resolution, type-checking, type inference, building SSA graphs, etc.
+ * - Worklist only contain declaration elements.
+ * - Most maps and sets use declarations exclusively, and their individual
+ *   invariants are stated in the field comments.
+ * - [TreeElements] only map to patch elements from inside a patch library.
+ *   TODO(johnniwinther): Simplify this invariant to use only declarations in
+ *   [TreeElements].
+ * - Builders shift between declaration and implementation depending on usages.
+ * - Compile-time constants use constructor implementation exclusively.
+ * - Work on function parameters is performed on the declaration of the function
+ *   element.
+ */
+
+library patchparser;
+
+import "dart:uri";
+import "tree/tree.dart" as tree;
+import "dart2jslib.dart" as leg;  // CompilerTask, Compiler.
+import "apiimpl.dart";
+import "../compiler.dart" as api;
+import "scanner/scannerlib.dart";  // Scanner, Parsers, Listeners
+import "elements/elements.dart";
+import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX;
+import 'util/util.dart';
+
+class PatchParserTask extends leg.CompilerTask {
+  PatchParserTask(leg.Compiler compiler): super(compiler);
+  final String name = "Patching Parser";
+
+  /**
+   * Scans a library patch file, applies the method patches and
+   * injections to the library, and returns a list of class
+   * patches.
+   */
+  void patchLibrary(leg.LibraryDependencyHandler handler,
+                    Uri patchUri, LibraryElement originLibrary) {
+
+    leg.Script script = compiler.readScript(patchUri, null);
+    var patchLibrary = new LibraryElementX(script, null, originLibrary);
+    compiler.withCurrentElement(patchLibrary, () {
+      handler.registerNewLibrary(patchLibrary);
+      LinkBuilder<tree.LibraryTag> imports = new LinkBuilder<tree.LibraryTag>();
+      compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () {
+        // This patches the elements of the patch library into [library].
+        // Injected elements are added directly under the compilation unit.
+        // Patch elements are stored on the patched functions or classes.
+        scanLibraryElements(patchLibrary.entryCompilationUnit, imports);
+      });
+      // After scanning declarations, we handle the import tags in the patch.
+      // TODO(lrn): These imports end up in the original library and are in
+      // scope for the original methods too. This should be fixed.
+      compiler.importHelperLibrary(originLibrary);
+      for (tree.LibraryTag tag in imports.toLink()) {
+        compiler.libraryLoader.registerLibraryFromTag(
+            handler, patchLibrary, tag);
+      }
+    });
+  }
+
+  void scanLibraryElements(
+        CompilationUnitElement compilationUnit,
+        LinkBuilder<tree.LibraryTag> imports) {
+    measure(() {
+      // TODO(lrn): Possibly recursively handle #source directives in patch.
+      leg.Script script = compilationUnit.script;
+      Token tokens = new StringScanner(script.text).tokenize();
+      Function idGenerator = compiler.getNextFreeClassId;
+      PatchListener patchListener =
+          new PatchElementListener(compiler,
+                                   compilationUnit,
+                                   idGenerator,
+                                   imports);
+      new PatchParser(patchListener).parseUnit(tokens);
+    });
+  }
+
+  void parsePatchClassNode(PartialClassElement element) {
+    // Parse [PartialClassElement] using a "patch"-aware parser instead
+    // of calling its [parseNode] method.
+    if (element.cachedNode != null) return;
+
+    return measure(() => compiler.withCurrentElement(element, () {
+      PatchMemberListener listener = new PatchMemberListener(compiler, element);
+      Parser parser = new PatchClassElementParser(listener);
+      Token token = parser.parseTopLevelDeclaration(element.beginToken);
+      assert(identical(token, element.endToken.next));
+      element.cachedNode = listener.popNode();
+      assert(listener.nodes.isEmpty);
+
+      Link<Element> patches = element.localMembers;
+      applyContainerPatch(element.origin, patches);
+    }));
+  }
+
+  void applyContainerPatch(ClassElement originClass,
+                           Link<Element> patches) {
+    for (Element patch in patches) {
+      if (!isPatchElement(patch)) continue;
+
+      Element origin = originClass.localLookup(patch.name);
+      patchElement(compiler, origin, patch);
+    }
+  }
+}
+
+/**
+ * Extension of the [Listener] interface to handle the extra "patch" pseudo-
+ * keyword in patch files.
+ * Patch files shouldn't have a type named "patch".
+ */
+abstract class PatchListener extends Listener {
+  void beginPatch(Token patch);
+  void endPatch(Token patch);
+}
+
+/**
+ * Partial parser that extends the top-level and class grammars to allow the
+ * word "patch" in front of some declarations.
+ */
+class PatchParser extends PartialParser {
+  PatchParser(PatchListener listener) : super(listener);
+
+  PatchListener get patchListener => listener;
+
+  bool isPatch(Token token) {
+    return token.stringValue == null &&
+           token.slowToString() == "patch";
+  }
+
+  /**
+   * Parse top-level declarations, and allow "patch" in front of functions
+   * and classes.
+   */
+  Token parseTopLevelDeclaration(Token token) {
+    if (!isPatch(token)) {
+      return super.parseTopLevelDeclaration(token);
+    }
+    Token patch = token;
+    token = token.next;
+    String value = token.stringValue;
+    if (identical(value, 'interface')
+        || identical(value, 'typedef')
+        || identical(value, '#')
+        || identical(value, 'abstract')) {
+      // At the top level, you can only patch functions and classes.
+      // Patch classes and functions can't be marked abstract.
+      return listener.unexpected(patch);
+    }
+    patchListener.beginPatch(patch);
+    token = super.parseTopLevelDeclaration(token);
+    patchListener.endPatch(patch);
+    return token;
+  }
+
+  /**
+   * Parse a class member.
+   * If the member starts with "patch", it's a member override.
+   * Only methods can be overridden, including constructors, getters and
+   * setters, but not fields. If "patch" occurs in front of a field, the error
+   * is caught elsewhere.
+   */
+  Token parseMember(Token token) {
+    if (!isPatch(token)) {
+      return super.parseMember(token);
+    }
+    Token patch = token;
+    patchListener.beginPatch(patch);
+    token = super.parseMember(token.next);
+    patchListener.endPatch(patch);
+    return token;
+  }
+}
+
+/**
+ * Partial parser for patch files that also handles the members of class
+ * declarations.
+ */
+class PatchClassElementParser extends PatchParser {
+  PatchClassElementParser(PatchListener listener) : super(listener);
+
+  Token parseClassBody(Token token) => fullParseClassBody(token);
+}
+
+/**
+ * Extension of [ElementListener] for parsing patch files.
+ */
+class PatchElementListener extends ElementListener implements PatchListener {
+  final LinkBuilder<tree.LibraryTag> imports;
+  bool isMemberPatch = false;
+  bool isClassPatch = false;
+
+  PatchElementListener(leg.DiagnosticListener listener,
+                       CompilationUnitElement patchElement,
+                       int idGenerator(),
+                       this.imports)
+    : super(listener, patchElement, idGenerator);
+
+  MetadataAnnotation popMetadataHack() {
+    // TODO(ahe): Remove this method.
+    popNode(); // Discard null.
+    return new PatchMetadataAnnotation();
+  }
+
+  void beginPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = true;
+    } else {
+      isMemberPatch = true;
+    }
+    handleIdentifier(token);
+  }
+
+  void endPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = false;
+    } else {
+      isMemberPatch = false;
+    }
+  }
+
+  /**
+    * Allow script tags (import only, the parser rejects the rest for now) in
+    * patch files. The import tags will be added to the library.
+    */
+  bool allowLibraryTags() => true;
+
+  void addLibraryTag(tree.LibraryTag tag) {
+    super.addLibraryTag(tag);
+    imports.addLast(tag);
+  }
+
+  void pushElement(Element patch) {
+    if (isMemberPatch || (isClassPatch && patch is ClassElement)) {
+      // Apply patch.
+      patch.addMetadata(popMetadataHack());
+      LibraryElement originLibrary = compilationUnitElement.getLibrary();
+      assert(originLibrary.isPatched);
+      Element origin = originLibrary.localLookup(patch.name);
+      patchElement(listener, origin, patch);
+    }
+    super.pushElement(patch);
+  }
+}
+
+/**
+ * Extension of [MemberListener] for parsing patch class bodies.
+ */
+class PatchMemberListener extends MemberListener implements PatchListener {
+  bool isMemberPatch = false;
+  bool isClassPatch = false;
+  PatchMemberListener(leg.DiagnosticListener listener,
+                      Element enclosingElement)
+    : super(listener, enclosingElement);
+
+  MetadataAnnotation popMetadataHack() {
+    // TODO(ahe): Remove this method.
+    popNode(); // Discard null.
+    return new PatchMetadataAnnotation();
+  }
+
+  void beginPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = true;
+    } else {
+      isMemberPatch = true;
+    }
+    handleIdentifier(token);
+  }
+
+  void endPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = false;
+    } else {
+      isMemberPatch = false;
+    }
+  }
+
+  void addMember(Element element) {
+    if (isMemberPatch || (isClassPatch && element is ClassElement)) {
+      element.addMetadata(popMetadataHack());
+    }
+    super.addMember(element);
+  }
+}
+
+// TODO(ahe): Get rid of this class.
+class PatchMetadataAnnotation extends MetadataAnnotationX {
+  final leg.Constant value = null;
+
+  PatchMetadataAnnotation() : super(STATE_DONE);
+
+  Token get beginToken => null;
+  Token get endToken => null;
+}
+
+void patchElement(leg.DiagnosticListener listener,
+                   Element origin,
+                   Element patch) {
+  if (origin == null) {
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_NON_EXISTING.error({'name': patch.name}),
+        api.Diagnostic.ERROR);
+    return;
+  }
+  if (!(origin.isClass() ||
+        origin.isConstructor() ||
+        origin.isFunction() ||
+        origin.isAbstractField())) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NONPATCHABLE.error(),
+        api.Diagnostic.ERROR);
+    return;
+  }
+  if (patch.isClass()) {
+    tryPatchClass(listener, origin, patch);
+  } else if (patch.isGetter()) {
+    tryPatchGetter(listener, origin, patch);
+  } else if (patch.isSetter()) {
+    tryPatchSetter(listener, origin, patch);
+  } else if (patch.isConstructor()) {
+    tryPatchConstructor(listener, origin, patch);
+  } else if(patch.isFunction()) {
+    tryPatchFunction(listener, origin, patch);
+  } else {
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_NONPATCHABLE.error(),
+        api.Diagnostic.ERROR);
+  }
+}
+
+void tryPatchClass(leg.DiagnosticListener listener,
+                    Element origin,
+                    ClassElement patch) {
+  if (!origin.isClass()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_CLASS.error({'className': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_CLASS.error({'className': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchClass(listener, origin, patch);
+}
+
+void patchClass(leg.DiagnosticListener listener,
+                 ClassElement origin,
+                 ClassElement patch) {
+  if (origin.isPatched) {
+    listener.internalErrorOnElement(
+        origin, "Patching the same class more than once.");
+  }
+  // TODO(johnniwinther): Change to functions on the ElementX class.
+  origin.patch = patch;
+  patch.origin = origin;
+}
+
+void tryPatchGetter(leg.DiagnosticListener listener,
+                     Element origin,
+                     FunctionElement patch) {
+  if (!origin.isAbstractField()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_GETTER.error({'name': origin.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  AbstractFieldElement originField = origin;
+  if (originField.getter == null) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NO_GETTER.error({'getterName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, originField.getter, patch);
+}
+
+void tryPatchSetter(leg.DiagnosticListener listener,
+                     Element origin,
+                     FunctionElement patch) {
+  if (!origin.isAbstractField()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_SETTER.error({'name': origin.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  AbstractFieldElement originField = origin;
+  if (originField.setter == null) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NO_SETTER.error({'setterName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, originField.setter, patch);
+}
+
+void tryPatchConstructor(leg.DiagnosticListener listener,
+                          Element origin,
+                          FunctionElement patch) {
+  if (!origin.isConstructor()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_CONSTRUCTOR.error(
+            {'constructorName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR.error(
+            {'constructorName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, origin, patch);
+}
+
+void tryPatchFunction(leg.DiagnosticListener listener,
+                       Element origin,
+                       FunctionElement patch) {
+  if (!origin.isFunction()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_FUNCTION.error({'functionName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_FUNCTION.error(
+            {'functionName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, origin, patch);
+}
+
+void patchFunction(leg.DiagnosticListener listener,
+                    FunctionElement origin,
+                    FunctionElement patch) {
+  if (!origin.modifiers.isExternal()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_EXTERNAL.error(),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_FUNCTION.error(
+            {'functionName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  if (origin.isPatched) {
+    listener.internalErrorOnElement(origin,
+        "Trying to patch a function more than once.");
+  }
+  if (origin.cachedNode != null) {
+    listener.internalErrorOnElement(origin,
+        "Trying to patch an already compiled function.");
+  }
+  // Don't just assign the patch field. This also updates the cachedNode.
+  // TODO(johnniwinther): Change to functions on the ElementX class.
+  origin.setPatch(patch);
+  patch.origin = origin;
+}
+
+// TODO(johnniwinther): Add unittest when patch is (real) metadata.
+bool isPatchElement(Element element) {
+  // TODO(lrn): More checks needed if we introduce metadata for real.
+  // In that case, it must have the identifier "native" as metadata.
+  for (Link link = element.metadata; !link.isEmpty; link = link.tail) {
+    if (link.head is PatchMetadataAnnotation) return true;
+  }
+  return false;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart
new file mode 100644
index 0000000..e62aa70
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart
@@ -0,0 +1,3663 @@
+// 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.
+
+part of resolution;
+
+abstract class TreeElements {
+  Element operator[](Node node);
+  Selector getSelector(Send send);
+  DartType getType(Node node);
+  bool isParameterChecked(Element element);
+  Set<Node> get superUses;
+}
+
+class TreeElementMapping implements TreeElements {
+  final Element currentElement;
+  final Map<Node, Selector> selectors = new LinkedHashMap<Node, Selector>();
+  final Map<Node, DartType> types = new LinkedHashMap<Node, DartType>();
+  final Set<Element> checkedParameters = new Set<Element>();
+  final Set<Node> superUses = new Set<Node>();
+
+  TreeElementMapping(this.currentElement);
+
+  operator []=(Node node, Element element) {
+    assert(invariant(node, () {
+      if (node is FunctionExpression) {
+        return !node.modifiers.isExternal();
+      }
+      return true;
+    }));
+    // TODO(johnniwinther): Simplify this invariant to use only declarations in
+    // [TreeElements].
+    assert(invariant(node, () {
+      if (!element.isErroneous() && currentElement != null && element.isPatch) {
+        return currentElement.getImplementationLibrary().isPatch;
+      }
+      return true;
+    }));
+    // TODO(ahe): Investigate why the invariant below doesn't hold.
+    // assert(invariant(node,
+    //                  getTreeElement(node) == element ||
+    //                  getTreeElement(node) == null,
+    //                  message: '${getTreeElement(node)}; $element'));
+
+    setTreeElement(node, element);
+  }
+
+  operator [](Node node) => getTreeElement(node);
+
+  void remove(Node node) {
+    setTreeElement(node, null);
+  }
+
+  void setType(Node node, DartType type) {
+    types[node] = type;
+  }
+
+  DartType getType(Node node) => types[node];
+
+  void setSelector(Node node, Selector selector) {
+    selectors[node] = selector;
+  }
+
+  Selector getSelector(Node node) => selectors[node];
+
+  bool isParameterChecked(Element element) {
+    return checkedParameters.contains(element);
+  }
+}
+
+class ResolverTask extends CompilerTask {
+  ResolverTask(Compiler compiler) : super(compiler);
+
+  String get name => 'Resolver';
+
+  TreeElements resolve(Element element) {
+    return measure(() {
+      if (Elements.isErroneousElement(element)) return null;
+
+      for (MetadataAnnotation metadata in element.metadata) {
+        metadata.ensureResolved(compiler);
+      }
+
+      ElementKind kind = element.kind;
+      if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR) ||
+          identical(kind, ElementKind.FUNCTION) ||
+          identical(kind, ElementKind.GETTER) ||
+          identical(kind, ElementKind.SETTER)) {
+        return resolveMethodElement(element);
+      }
+
+      if (identical(kind, ElementKind.FIELD)) return resolveField(element);
+
+      if (identical(kind, ElementKind.PARAMETER) ||
+          identical(kind, ElementKind.FIELD_PARAMETER)) {
+        return resolveParameter(element);
+      }
+      if (element.isClass()) {
+        ClassElement cls = element;
+        cls.ensureResolved(compiler);
+        return null;
+      } else if (element.isTypedef()) {
+        TypedefElement typdef = element;
+        resolveTypedef(typdef);
+        return null;
+      } else if (element.isTypeVariable()) {
+        element.computeType(compiler);
+        return null;
+      }
+
+      compiler.unimplemented("resolve($element)",
+                             node: element.parseNode(compiler));
+    });
+  }
+
+  String constructorNameForDiagnostics(SourceString className,
+                                   SourceString constructorName) {
+    String classNameString = className.slowToString();
+    String constructorNameString = constructorName.slowToString();
+    return (constructorName == const SourceString(''))
+        ? classNameString
+        : "$classNameString.$constructorNameString";
+   }
+
+  void resolveRedirectingConstructor(InitializerResolver resolver,
+                                     Node node,
+                                     FunctionElement constructor,
+                                     FunctionElement redirection) {
+    Set<FunctionElement> seen = new Set<FunctionElement>();
+    seen.add(constructor);
+    while (redirection != null) {
+      if (seen.contains(redirection)) {
+        resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE);
+        return;
+      }
+      seen.add(redirection);
+
+      if (redirection.isPatched) {
+        checkMatchingPatchSignatures(constructor, redirection.patch);
+        redirection = redirection.patch;
+      }
+      redirection = resolver.visitor.resolveConstructorRedirection(redirection);
+    }
+  }
+
+  void checkMatchingPatchParameters(FunctionElement origin,
+                                    Link<Element> originParameters,
+                                    Link<Element> patchParameters) {
+    while (!originParameters.isEmpty) {
+      Element originParameter = originParameters.head;
+      Element patchParameter = patchParameters.head;
+      // Hack: Use unparser to test parameter equality. This only works because
+      // we are restricting patch uses and the approach cannot be used
+      // elsewhere.
+      String originParameterText =
+          originParameter.parseNode(compiler).toString();
+      String patchParameterText =
+          patchParameter.parseNode(compiler).toString();
+      if (originParameterText != patchParameterText) {
+        error(originParameter.parseNode(compiler),
+              MessageKind.PATCH_PARAMETER_MISMATCH,
+              {'methodName': origin.name,
+               'originParameter': originParameterText,
+               'patchParameter': patchParameterText});
+      }
+
+      originParameters = originParameters.tail;
+      patchParameters = patchParameters.tail;
+    }
+  }
+
+  void checkMatchingPatchSignatures(FunctionElement origin,
+                                    FunctionElement patch) {
+    // TODO(johnniwinther): Show both origin and patch locations on errors.
+    FunctionExpression originTree = compiler.withCurrentElement(origin, () {
+      return origin.parseNode(compiler);
+    });
+    FunctionSignature originSignature = compiler.withCurrentElement(origin, () {
+      return origin.computeSignature(compiler);
+    });
+    FunctionExpression patchTree = compiler.withCurrentElement(patch, () {
+      return patch.parseNode(compiler);
+    });
+    FunctionSignature patchSignature = compiler.withCurrentElement(patch, () {
+      return patch.computeSignature(compiler);
+    });
+
+    if (originSignature.returnType != patchSignature.returnType) {
+      compiler.withCurrentElement(patch, () {
+        Node errorNode =
+            patchTree.returnType != null ? patchTree.returnType : patchTree;
+        error(errorNode, MessageKind.PATCH_RETURN_TYPE_MISMATCH,
+              {'methodName': origin.name,
+               'originReturnType': originSignature.returnType,
+               'patchReturnType': patchSignature.returnType});
+      });
+    }
+    if (originSignature.requiredParameterCount !=
+        patchSignature.requiredParameterCount) {
+      compiler.withCurrentElement(patch, () {
+        error(patchTree,
+              MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH,
+              {'methodName': origin.name,
+               'originParameterCount': originSignature.requiredParameterCount,
+               'patchParameterCount': patchSignature.requiredParameterCount});
+      });
+    } else {
+      checkMatchingPatchParameters(origin,
+                                   originSignature.requiredParameters,
+                                   patchSignature.requiredParameters);
+    }
+    if (originSignature.optionalParameterCount != 0 &&
+        patchSignature.optionalParameterCount != 0) {
+      if (originSignature.optionalParametersAreNamed !=
+          patchSignature.optionalParametersAreNamed) {
+        compiler.withCurrentElement(patch, () {
+          error(patchTree,
+                MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH,
+                {'methodName': origin.name});
+        });
+      }
+    }
+    if (originSignature.optionalParameterCount !=
+        patchSignature.optionalParameterCount) {
+      compiler.withCurrentElement(patch, () {
+        error(patchTree,
+              MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH,
+              {'methodName': origin.name,
+               'originParameterCount': originSignature.optionalParameterCount,
+               'patchParameterCount': patchSignature.optionalParameterCount});
+      });
+    } else {
+      checkMatchingPatchParameters(origin,
+                                   originSignature.optionalParameters,
+                                   patchSignature.optionalParameters);
+    }
+  }
+
+  TreeElements resolveMethodElement(FunctionElement element) {
+    assert(invariant(element, element.isDeclaration));
+    return compiler.withCurrentElement(element, () {
+      bool isConstructor =
+          identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR);
+      TreeElements elements =
+          compiler.enqueuer.resolution.getCachedElements(element);
+      if (elements != null) {
+        assert(isConstructor);
+        return elements;
+      }
+      if (element.isPatched) {
+        checkMatchingPatchSignatures(element, element.patch);
+        element = element.patch;
+      }
+      return compiler.withCurrentElement(element, () {
+        FunctionExpression tree = element.parseNode(compiler);
+        if (tree.modifiers.isExternal()) {
+          error(tree, MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION);
+          return;
+        }
+        if (isConstructor) {
+          if (tree.returnType != null) {
+            error(tree, MessageKind.CONSTRUCTOR_WITH_RETURN_TYPE);
+          }
+          resolveConstructorImplementation(element, tree);
+        }
+        ResolverVisitor visitor = visitorFor(element);
+        visitor.useElement(tree, element);
+        visitor.setupFunction(tree, element);
+
+        if (isConstructor) {
+          // Even if there is no initializer list we still have to do the
+          // resolution in case there is an implicit super constructor call.
+          InitializerResolver resolver = new InitializerResolver(visitor);
+          FunctionElement redirection =
+              resolver.resolveInitializers(element, tree);
+          if (redirection != null) {
+            resolveRedirectingConstructor(resolver, tree, element, redirection);
+          }
+        } else if (tree.initializers != null) {
+          error(tree, MessageKind.FUNCTION_WITH_INITIALIZER);
+        }
+        visitBody(visitor, tree.body);
+
+        // Get the resolution tree and check that the resolved
+        // function doesn't use 'super' if it is mixed into another
+        // class. This is the part of the 'super' mixin check that
+        // happens when a function is resolved after the mixin
+        // application has been performed.
+        TreeElements resolutionTree = visitor.mapping;
+        ClassElement enclosingClass = element.getEnclosingClass();
+        if (enclosingClass != null) {
+          Set<MixinApplicationElement> mixinUses =
+              compiler.world.mixinUses[enclosingClass];
+          if (mixinUses != null) {
+            ClassElement mixin = enclosingClass;
+            for (MixinApplicationElement mixinApplication in mixinUses) {
+              checkMixinSuperUses(resolutionTree, mixinApplication, mixin);
+            }
+          }
+        }
+        return resolutionTree;
+      });
+    });
+  }
+
+  /// This method should only be used by this library (or tests of
+  /// this library).
+  ResolverVisitor visitorFor(Element element) {
+    var mapping = new TreeElementMapping(element);
+    return new ResolverVisitor(compiler, element, mapping);
+  }
+
+  void visitBody(ResolverVisitor visitor, Statement body) {
+    visitor.visit(body);
+  }
+
+  void resolveConstructorImplementation(FunctionElement constructor,
+                                        FunctionExpression node) {
+    if (!identical(constructor.defaultImplementation, constructor)) return;
+    ClassElement intrface = constructor.getEnclosingClass();
+    if (!intrface.isInterface()) return;
+    DartType defaultType = intrface.defaultClass;
+    if (defaultType == null) {
+      error(node, MessageKind.NO_DEFAULT_CLASS,
+            {'interfaceName': intrface.name});
+    }
+    ClassElement defaultClass = defaultType.element;
+    defaultClass.ensureResolved(compiler);
+    assert(defaultClass.resolutionState == STATE_DONE);
+    assert(defaultClass.supertypeLoadState == STATE_DONE);
+    if (defaultClass.isInterface()) {
+      error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE,
+            {'interfaceName': defaultClass.name});
+    }
+    // We have now established the following:
+    // [intrface] is an interface, let's say "MyInterface".
+    // [defaultClass] is a class, let's say "MyClass".
+
+    Selector selector;
+    // If the default class implements the interface then we must use the
+    // default class' name. Otherwise we look for a factory with the name
+    // of the interface.
+    if (defaultClass.implementsInterface(intrface)) {
+      var constructorNameString = constructor.name.slowToString();
+      // Create selector based on constructor.name but where interface
+      // is replaced with default class name.
+      // TODO(ahe): Don't use string manipulations here.
+      int classNameSeparatorIndex = constructorNameString.indexOf('\$');
+      if (classNameSeparatorIndex < 0) {
+        selector = new Selector.callDefaultConstructor(
+            defaultClass.getLibrary());
+      } else {
+        selector = new Selector.callConstructor(
+            new SourceString(
+                constructorNameString.substring(classNameSeparatorIndex + 1)),
+            defaultClass.getLibrary());
+      }
+      constructor.defaultImplementation =
+          defaultClass.lookupConstructor(selector);
+    } else {
+      selector =
+          new Selector.callConstructor(constructor.name,
+                                       defaultClass.getLibrary());
+      constructor.defaultImplementation =
+          defaultClass.lookupFactoryConstructor(selector);
+    }
+    if (constructor.defaultImplementation == null) {
+      // We failed to find a constructor named either
+      // "MyInterface.name" or "MyClass.name".
+      // TODO(aprelev@gmail.com): Use constructorNameForDiagnostics in
+      // the error message below.
+      error(node,
+            MessageKind.CANNOT_FIND_CONSTRUCTOR2,
+            {'constructorName': selector.name, 'className': defaultClass.name});
+    }
+  }
+
+  TreeElements resolveField(VariableElement element) {
+    Node tree = element.parseNode(compiler);
+    if(element.modifiers.isStatic() && element.variables.isTopLevel()) {
+      error(element.modifiers.getStatic(),
+            MessageKind.TOP_LEVEL_VARIABLE_DECLARED_STATIC);
+    }
+    ResolverVisitor visitor = visitorFor(element);
+    initializerDo(tree, visitor.visit);
+
+    // Perform various checks as side effect of "computing" the type.
+    element.computeType(compiler);
+
+    return visitor.mapping;
+  }
+
+  TreeElements resolveParameter(Element element) {
+    Node tree = element.parseNode(compiler);
+    ResolverVisitor visitor = visitorFor(element.enclosingElement);
+    initializerDo(tree, visitor.visit);
+    return visitor.mapping;
+  }
+
+  DartType resolveTypeAnnotation(Element element, TypeAnnotation annotation) {
+    DartType type = resolveReturnType(element, annotation);
+    if (type == compiler.types.voidType) {
+      error(annotation, MessageKind.VOID_NOT_ALLOWED);
+    }
+    return type;
+  }
+
+  DartType resolveReturnType(Element element, TypeAnnotation annotation) {
+    if (annotation == null) return compiler.types.dynamicType;
+    DartType result = visitorFor(element).resolveTypeAnnotation(annotation);
+    if (result == null) {
+      // TODO(karklose): warning.
+      return compiler.types.dynamicType;
+    }
+    return result;
+  }
+
+  /**
+   * Load and resolve the supertypes of [cls].
+   *
+   * Warning: do not call this method directly. It should only be
+   * called by [resolveClass] and [ClassSupertypeResolver].
+   */
+  void loadSupertypes(ClassElement cls, Spannable from) {
+    compiler.withCurrentElement(cls, () => measure(() {
+      if (cls.supertypeLoadState == STATE_DONE) return;
+      if (cls.supertypeLoadState == STATE_STARTED) {
+        compiler.reportErrorCode(from, MessageKind.CYCLIC_CLASS_HIERARCHY,
+                                 {'className': cls.name});
+        cls.supertypeLoadState = STATE_DONE;
+        cls.allSupertypes = const Link<DartType>().prepend(
+            compiler.objectClass.computeType(compiler));
+        // TODO(ahe): We should also set cls.supertype here to avoid
+        // creating a malformed class hierarchy.
+        return;
+      }
+      cls.supertypeLoadState = STATE_STARTED;
+      compiler.withCurrentElement(cls, () {
+        // TODO(ahe): Cache the node in cls.
+        cls.parseNode(compiler).accept(
+            new ClassSupertypeResolver(compiler, cls));
+        if (cls.supertypeLoadState != STATE_DONE) {
+          cls.supertypeLoadState = STATE_DONE;
+        }
+      });
+    }));
+  }
+
+  // TODO(johnniwinther): Remove this queue when resolution has been split into
+  // syntax and semantic resolution.
+  ClassElement currentlyResolvedClass;
+  Queue<ClassElement> pendingClassesToBeResolved = new Queue<ClassElement>();
+
+  /**
+   * Resolve the class [element].
+   *
+   * Before calling this method, [element] was constructed by the
+   * scanner and most fields are null or empty. This method fills in
+   * these fields and also ensure that the supertypes of [element] are
+   * resolved.
+   *
+   * Warning: Do not call this method directly. Instead use
+   * [:element.ensureResolved(compiler):].
+   */
+  void resolveClass(ClassElement element) {
+    ClassElement previousResolvedClass = currentlyResolvedClass;
+    currentlyResolvedClass = element;
+    resolveClassInternal(element);
+    if (previousResolvedClass == null) {
+      while (!pendingClassesToBeResolved.isEmpty) {
+        pendingClassesToBeResolved.removeFirst().ensureResolved(compiler);
+      }
+    }
+    currentlyResolvedClass = previousResolvedClass;
+  }
+
+  void _ensureClassWillBeResolved(ClassElement element) {
+    if (currentlyResolvedClass == null) {
+      element.ensureResolved(compiler);
+    } else {
+      pendingClassesToBeResolved.add(element);
+    }
+  }
+
+  void resolveClassInternal(ClassElement element) {
+    if (!element.isPatch) {
+      compiler.withCurrentElement(element, () => measure(() {
+        assert(element.resolutionState == STATE_NOT_STARTED);
+        element.resolutionState = STATE_STARTED;
+        Node tree = element.parseNode(compiler);
+        loadSupertypes(element, tree);
+
+        ClassResolverVisitor visitor =
+            new ClassResolverVisitor(compiler, element);
+        visitor.visit(tree);
+        element.resolutionState = STATE_DONE;
+      }));
+      if (element.isPatched) {
+        // Ensure handling patch after origin.
+        element.patch.ensureResolved(compiler);
+      }
+    } else { // Handle patch classes:
+      element.resolutionState = STATE_STARTED;
+      // Ensure handling origin before patch.
+      element.origin.ensureResolved(compiler);
+      // Ensure that the type is computed.
+      element.computeType(compiler);
+      // Copy class hiearchy from origin.
+      element.supertype = element.origin.supertype;
+      element.defaultClass = element.origin.defaultClass;
+      element.interfaces = element.origin.interfaces;
+      element.allSupertypes = element.origin.allSupertypes;
+      // Stepwise assignment to ensure invariant.
+      element.supertypeLoadState = STATE_STARTED;
+      element.supertypeLoadState = STATE_DONE;
+      element.resolutionState = STATE_DONE;
+      // TODO(johnniwinther): Check matching type variables and
+      // empty extends/implements clauses.
+    }
+    for (MetadataAnnotation metadata in element.metadata) {
+      metadata.ensureResolved(compiler);
+    }
+  }
+
+  void checkClass(ClassElement element) {
+    if (element.isMixinApplication) {
+      checkMixinApplication(element);
+    } else {
+      checkClassMembers(element);
+    }
+  }
+
+  void checkMixinApplication(MixinApplicationElement mixinApplication) {
+    Modifiers modifiers = mixinApplication.modifiers;
+    int illegalFlags = modifiers.flags & ~Modifiers.FLAG_ABSTRACT;
+    if (illegalFlags != 0) {
+      Modifiers illegalModifiers = new Modifiers.withFlags(null, illegalFlags);
+      compiler.reportErrorCode(
+          modifiers,
+          MessageKind.ILLEGAL_MIXIN_APPLICATION_MODIFIERS,
+          {'modifiers': illegalModifiers});
+    }
+
+    // In case of cyclic mixin applications, the mixin chain will have
+    // been cut. If so, we have already reported the error to the
+    // user so we just return from here.
+    ClassElement mixin = mixinApplication.mixin;
+    if (mixin == null) return;
+
+    // Check that the mixed in class has Object as its superclass.
+    if (!mixin.superclass.isObject(compiler)) {
+      compiler.reportErrorCode(mixin, MessageKind.ILLEGAL_MIXIN_SUPERCLASS);
+    }
+
+    // Check that the mixed in class doesn't have any constructors and
+    // make sure we aren't mixing in methods that use 'super'.
+    mixin.forEachLocalMember((Element member) {
+      if (member.isGenerativeConstructor() && !member.isSynthesized) {
+        compiler.reportErrorCode(member, MessageKind.ILLEGAL_MIXIN_CONSTRUCTOR);
+      } else {
+        // Get the resolution tree and check that the resolved member
+        // doesn't use 'super'. This is the part of the 'super' mixin
+        // check that happens when a function is resolved before the
+        // mixin application has been performed.
+        checkMixinSuperUses(
+            compiler.enqueuer.resolution.resolvedElements[member],
+            mixinApplication,
+            mixin);
+      }
+    });
+  }
+
+  void checkMixinSuperUses(TreeElements resolutionTree,
+                           MixinApplicationElement mixinApplication,
+                           ClassElement mixin) {
+    if (resolutionTree == null) return;
+    Set<Node> superUses = resolutionTree.superUses;
+    if (superUses.isEmpty) return;
+    compiler.reportErrorCode(mixinApplication,
+                             MessageKind.ILLEGAL_MIXIN_WITH_SUPER,
+                             {'className': mixin.name});
+    // Show the user the problematic uses of 'super' in the mixin.
+    for (Node use in superUses) {
+      CompilationError error = MessageKind.ILLEGAL_MIXIN_SUPER_USE.error();
+      compiler.reportMessage(compiler.spanFromNode(use),
+                             error, Diagnostic.INFO);
+    }
+  }
+
+  void checkClassMembers(ClassElement cls) {
+    assert(invariant(cls, cls.isDeclaration));
+    if (cls.isObject(compiler)) return;
+    // TODO(johnniwinther): Should this be done on the implementation element as
+    // well?
+    cls.forEachMember((holder, member) {
+      compiler.withCurrentElement(member, () {
+        // Perform various checks as side effect of "computing" the type.
+        member.computeType(compiler);
+
+        // Check modifiers.
+        if (member.isFunction() && member.modifiers.isFinal()) {
+          compiler.reportErrorCode(
+              member, MessageKind.ILLEGAL_FINAL_METHOD_MODIFIER);
+        }
+        if (member.isConstructor()) {
+          final mismatchedFlagsBits =
+              member.modifiers.flags &
+              (Modifiers.FLAG_STATIC | Modifiers.FLAG_ABSTRACT);
+          if (mismatchedFlagsBits != 0) {
+            final mismatchedFlags =
+                new Modifiers.withFlags(null, mismatchedFlagsBits);
+            compiler.reportErrorCode(
+                member,
+                MessageKind.ILLEGAL_CONSTRUCTOR_MODIFIERS,
+                {'modifiers': mismatchedFlags});
+          }
+          checkConstructorNameHack(holder, member);
+        }
+        checkAbstractField(member);
+        checkValidOverride(member, cls.lookupSuperMember(member.name));
+        checkUserDefinableOperator(member);
+      });
+    });
+  }
+
+  // TODO(ahe): Remove this method.  It is only needed while we store
+  // constructor names as ClassName$id.  Once we start storing
+  // constructors as just id, this will be caught by the general
+  // mechanism for duplicate members.
+  /// Check that a constructor name does not conflict with a member.
+  void checkConstructorNameHack(ClassElement holder, FunctionElement member) {
+    // If the name of the constructor is the same as the name of the
+    // class, there cannot be a problem.
+    if (member.name == holder.name) return;
+
+    SourceString name =
+      Elements.deconstructConstructorName(member.name, holder);
+
+    // If the name could not be deconstructed, this is is from a
+    // factory method from a deprecated interface implementation.
+    if (name == null) return;
+
+    Element otherMember = holder.lookupLocalMember(name);
+    if (otherMember != null) {
+      if (compiler.onDeprecatedFeature(member, 'conflicting constructor')) {
+        compiler.reportMessage(
+            compiler.spanFromElement(otherMember),
+            MessageKind.GENERIC.error({'text': 'This member conflicts with a'
+                                               ' constructor.'}),
+            Diagnostic.INFO);
+      }
+    }
+  }
+
+  void checkAbstractField(Element member) {
+    // Only check for getters. The test can only fail if there is both a setter
+    // and a getter with the same name, and we only need to check each abstract
+    // field once, so we just ignore setters.
+    if (!member.isGetter()) return;
+
+    // Find the associated abstract field.
+    ClassElement classElement = member.getEnclosingClass();
+    Element lookupElement = classElement.lookupLocalMember(member.name);
+    if (lookupElement == null) {
+      compiler.internalErrorOnElement(member,
+                                      "No abstract field for accessor");
+    } else if (!identical(lookupElement.kind, ElementKind.ABSTRACT_FIELD)) {
+       compiler.internalErrorOnElement(
+           member, "Inaccessible abstract field for accessor");
+    }
+    AbstractFieldElement field = lookupElement;
+
+    if (field.getter == null) return;
+    if (field.setter == null) return;
+    int getterFlags = field.getter.modifiers.flags | Modifiers.FLAG_ABSTRACT;
+    int setterFlags = field.setter.modifiers.flags | Modifiers.FLAG_ABSTRACT;
+    if (!identical(getterFlags, setterFlags)) {
+      final mismatchedFlags =
+        new Modifiers.withFlags(null, getterFlags ^ setterFlags);
+      compiler.reportErrorCode(
+          field.getter,
+          MessageKind.GETTER_MISMATCH,
+          {'modifiers': mismatchedFlags});
+      compiler.reportErrorCode(
+          field.setter,
+          MessageKind.SETTER_MISMATCH,
+          {'modifiers': mismatchedFlags});
+    }
+  }
+
+  void checkUserDefinableOperator(Element member) {
+    FunctionElement function = member.asFunctionElement();
+    if (function == null) return;
+    String value = member.name.stringValue;
+    if (value == null) return;
+    if (!(isUserDefinableOperator(value) || identical(value, 'unary-'))) return;
+
+    bool isMinus = false;
+    int requiredParameterCount;
+    MessageKind messageKind;
+    FunctionSignature signature = function.computeSignature(compiler);
+    if (identical(value, 'unary-')) {
+      isMinus = true;
+      messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 0;
+    } else if (isMinusOperator(value)) {
+      isMinus = true;
+      messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 1;
+    } else if (isUnaryOperator(value)) {
+      messageKind = MessageKind.UNARY_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 0;
+    } else if (isBinaryOperator(value)) {
+      messageKind = MessageKind.BINARY_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 1;
+    } else if (isTernaryOperator(value)) {
+      messageKind = MessageKind.TERNARY_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 2;
+    } else {
+      compiler.internalErrorOnElement(function,
+          'Unexpected user defined operator $value');
+    }
+    checkArity(function, requiredParameterCount, messageKind, isMinus);
+  }
+
+  void checkArity(FunctionElement function,
+                  int requiredParameterCount, MessageKind messageKind,
+                  bool isMinus) {
+    FunctionExpression node = function.parseNode(compiler);
+    FunctionSignature signature = function.computeSignature(compiler);
+    if (signature.requiredParameterCount != requiredParameterCount) {
+      Node errorNode = node;
+      if (node.parameters != null) {
+        if (isMinus ||
+            signature.requiredParameterCount < requiredParameterCount) {
+          // If there are too few parameters, point to the whole parameter list.
+          // For instance
+          //
+          //     int operator +() {}
+          //                   ^^
+          //
+          //     int operator []=(value) {}
+          //                     ^^^^^^^
+          //
+          // For operator -, always point the whole parameter list, like
+          //
+          //     int operator -(a, b) {}
+          //                   ^^^^^^
+          //
+          // instead of
+          //
+          //     int operator -(a, b) {}
+          //                       ^
+          //
+          // since the correction might not be to remove 'b' but instead to
+          // remove 'a, b'.
+          errorNode = node.parameters;
+        } else {
+          errorNode = node.parameters.nodes.skip(requiredParameterCount).head;
+        }
+      }
+      compiler.reportErrorCode(
+          errorNode, messageKind, {'operatorName': function.name});
+    }
+    if (signature.optionalParameterCount != 0) {
+      Node errorNode =
+          node.parameters.nodes.skip(signature.requiredParameterCount).head;
+      if (signature.optionalParametersAreNamed) {
+        compiler.reportErrorCode(
+            errorNode,
+            MessageKind.OPERATOR_NAMED_PARAMETERS,
+            {'operatorName': function.name});
+      } else {
+        compiler.reportErrorCode(
+            errorNode,
+            MessageKind.OPERATOR_OPTIONAL_PARAMETERS,
+            {'operatorName': function.name});
+      }
+    }
+  }
+
+  reportErrorWithContext(Element errorneousElement,
+                         MessageKind errorMessage,
+                         Element contextElement,
+                         MessageKind contextMessage) {
+    compiler.reportErrorCode(
+        errorneousElement,
+        errorMessage,
+        {'memberName': contextElement.name,
+         'className': contextElement.getEnclosingClass().name});
+    compiler.reportMessage(
+        compiler.spanFromElement(contextElement),
+        contextMessage.error(),
+        Diagnostic.INFO);
+  }
+
+  void checkValidOverride(Element member, Element superMember) {
+    if (superMember == null) return;
+    if (member.modifiers.isStatic()) {
+      reportErrorWithContext(
+          member, MessageKind.NO_STATIC_OVERRIDE,
+          superMember, MessageKind.NO_STATIC_OVERRIDE_CONT);
+    } else {
+      FunctionElement superFunction = superMember.asFunctionElement();
+      FunctionElement function = member.asFunctionElement();
+      if (superFunction == null || superFunction.isAccessor()) {
+        // Field or accessor in super.
+        if (function != null && !function.isAccessor()) {
+          // But a plain method in this class.
+          reportErrorWithContext(
+              member, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD,
+              superMember, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT);
+        }
+      } else {
+        // Instance method in super.
+        if (function == null || function.isAccessor()) {
+          // But a field (or accessor) in this class.
+          reportErrorWithContext(
+              member, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD,
+              superMember, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT);
+        } else {
+          // Both are plain instance methods.
+          if (superFunction.requiredParameterCount(compiler) !=
+              function.requiredParameterCount(compiler)) {
+          reportErrorWithContext(
+              member,
+              MessageKind.BAD_ARITY_OVERRIDE,
+              superMember,
+              MessageKind.BAD_ARITY_OVERRIDE_CONT);
+          }
+          // TODO(ahe): Check optional parameters.
+        }
+      }
+    }
+  }
+
+  FunctionSignature resolveSignature(FunctionElement element) {
+    return compiler.withCurrentElement(element, () {
+      FunctionExpression node =
+          compiler.parser.measure(() => element.parseNode(compiler));
+      return measure(() => SignatureResolver.analyze(
+          compiler, node.parameters, node.returnType, element));
+    });
+  }
+
+  FunctionSignature resolveFunctionExpression(Element element,
+                                              FunctionExpression node) {
+    return measure(() => SignatureResolver.analyze(
+      compiler, node.parameters, node.returnType, element));
+  }
+
+  void resolveTypedef(TypedefElement element) {
+    if (element.isResolved || element.isBeingResolved) return;
+    element.isBeingResolved = true;
+    return compiler.withCurrentElement(element, () {
+      measure(() {
+        Typedef node =
+          compiler.parser.measure(() => element.parseNode(compiler));
+        TypedefResolverVisitor visitor =
+          new TypedefResolverVisitor(compiler, element);
+        visitor.visit(node);
+
+        element.isBeingResolved = false;
+        element.isResolved = true;
+      });
+    });
+  }
+
+  FunctionType computeFunctionType(Element element,
+                                   FunctionSignature signature) {
+    var parameterTypes = new LinkBuilder<DartType>();
+    for (Element parameter in signature.requiredParameters) {
+       parameterTypes.addLast(parameter.computeType(compiler));
+    }
+    var optionalParameterTypes = const Link<DartType>();
+    var namedParameters = const Link<SourceString>();
+    var namedParameterTypes = const Link<DartType>();
+    if (signature.optionalParametersAreNamed) {
+      var namedParametersBuilder = new LinkBuilder<SourceString>();
+      var namedParameterTypesBuilder = new LinkBuilder<DartType>();
+      for (Element parameter in signature.orderedOptionalParameters) {
+        namedParametersBuilder.addLast(parameter.name);
+        namedParameterTypesBuilder.addLast(parameter.computeType(compiler));
+      }
+      namedParameters = namedParametersBuilder.toLink();
+      namedParameterTypes = namedParameterTypesBuilder.toLink();
+    } else {
+      var optionalParameterTypesBuilder = new LinkBuilder<DartType>();
+      for (Element parameter in signature.optionalParameters) {
+        optionalParameterTypesBuilder.addLast(parameter.computeType(compiler));
+      }
+      optionalParameterTypes = optionalParameterTypesBuilder.toLink();
+    }
+    return new FunctionType(element,
+        signature.returnType,
+        parameterTypes.toLink(),
+        optionalParameterTypes,
+        namedParameters,
+        namedParameterTypes);
+  }
+
+  void resolveMetadataAnnotation(PartialMetadataAnnotation annotation) {
+    compiler.withCurrentElement(annotation.annotatedElement, () => measure(() {
+      assert(annotation.resolutionState == STATE_NOT_STARTED);
+      annotation.resolutionState = STATE_STARTED;
+
+      Node node = annotation.parseNode(compiler);
+      ResolverVisitor visitor =
+          visitorFor(annotation.annotatedElement.enclosingElement);
+      node.accept(visitor);
+      annotation.value = compiler.metadataHandler.compileNodeWithDefinitions(
+          node, visitor.mapping, isConst: true);
+
+      annotation.resolutionState = STATE_DONE;
+    }));
+  }
+
+  error(Node node, MessageKind kind, [arguments = const {}]) {
+    ResolutionError message = new ResolutionError(kind, arguments);
+    compiler.reportError(node, message);
+  }
+}
+
+class InitializerResolver {
+  final ResolverVisitor visitor;
+  final Map<SourceString, Node> initialized;
+  Link<Node> initializers;
+  bool hasSuper;
+
+  InitializerResolver(this.visitor)
+    : initialized = new Map<SourceString, Node>(), hasSuper = false;
+
+  error(Node node, MessageKind kind, [arguments = const {}]) {
+    visitor.error(node, kind, arguments);
+  }
+
+  warning(Node node, MessageKind kind, [arguments = const {}]) {
+    visitor.warning(node, kind, arguments);
+  }
+
+  bool isFieldInitializer(SendSet node) {
+    if (node.selector.asIdentifier() == null) return false;
+    if (node.receiver == null) return true;
+    if (node.receiver.asIdentifier() == null) return false;
+    return node.receiver.asIdentifier().isThis();
+  }
+
+  void checkForDuplicateInitializers(SourceString name, Node init) {
+    if (initialized.containsKey(name)) {
+      error(init, MessageKind.DUPLICATE_INITIALIZER, {'fieldName': name});
+      warning(initialized[name], MessageKind.ALREADY_INITIALIZED,
+              {'fieldName': name});
+    }
+    initialized[name] = init;
+  }
+
+  void resolveFieldInitializer(FunctionElement constructor, SendSet init) {
+    // init is of the form [this.]field = value.
+    final Node selector = init.selector;
+    final SourceString name = selector.asIdentifier().source;
+    // Lookup target field.
+    Element target;
+    if (isFieldInitializer(init)) {
+      target = constructor.getEnclosingClass().lookupLocalMember(name);
+      if (target == null) {
+        error(selector, MessageKind.CANNOT_RESOLVE, {'name': name});
+      } else if (target.kind != ElementKind.FIELD) {
+        error(selector, MessageKind.NOT_A_FIELD, {'fieldName': name});
+      } else if (!target.isInstanceMember()) {
+        error(selector, MessageKind.INIT_STATIC_FIELD, {'fieldName': name});
+      }
+    } else {
+      error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER);
+    }
+    visitor.useElement(init, target);
+    visitor.world.registerStaticUse(target);
+    checkForDuplicateInitializers(name, init);
+    // Resolve initializing value.
+    visitor.visitInStaticContext(init.arguments.head);
+  }
+
+  ClassElement getSuperOrThisLookupTarget(FunctionElement constructor,
+                                          bool isSuperCall,
+                                          Node diagnosticNode) {
+    ClassElement lookupTarget = constructor.getEnclosingClass();
+    if (isSuperCall) {
+      // Calculate correct lookup target and constructor name.
+      if (identical(lookupTarget, visitor.compiler.objectClass)) {
+        error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT);
+      } else {
+        return lookupTarget.supertype.element;
+      }
+    }
+    return lookupTarget;
+  }
+
+  Element resolveSuperOrThisForSend(FunctionElement constructor,
+                                    FunctionExpression functionNode,
+                                    Send call) {
+    // Resolve the selector and the arguments.
+    ResolverTask resolver = visitor.compiler.resolver;
+    visitor.inStaticContext(() {
+      visitor.resolveSelector(call);
+      visitor.resolveArguments(call.argumentsNode);
+    });
+    Selector selector = visitor.mapping.getSelector(call);
+    bool isSuperCall = Initializers.isSuperConstructorCall(call);
+
+    ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor,
+                                                           isSuperCall,
+                                                           call);
+    Selector constructorSelector =
+        visitor.getRedirectingThisOrSuperConstructorSelector(call);
+    FunctionElement calledConstructor =
+        lookupTarget.lookupConstructor(constructorSelector);
+
+    final bool isImplicitSuperCall = false;
+    final SourceString className = lookupTarget.name;
+    verifyThatConstructorMatchesCall(calledConstructor,
+                                     selector,
+                                     isImplicitSuperCall,
+                                     call,
+                                     className,
+                                     constructorSelector);
+
+    visitor.useElement(call, calledConstructor);
+    visitor.world.registerStaticUse(calledConstructor);
+    return calledConstructor;
+  }
+
+  void resolveImplicitSuperConstructorSend(FunctionElement constructor,
+                                           FunctionExpression functionNode) {
+    // If the class has a super resolve the implicit super call.
+    ClassElement classElement = constructor.getEnclosingClass();
+    ClassElement superClass = classElement.superclass;
+    if (classElement != visitor.compiler.objectClass) {
+      assert(superClass != null);
+      assert(superClass.resolutionState == STATE_DONE);
+      SourceString constructorName = const SourceString('');
+      Selector callToMatch = new Selector.call(
+          constructorName,
+          classElement.getLibrary(),
+          0);
+
+      final bool isSuperCall = true;
+      ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor,
+                                                             isSuperCall,
+                                                             functionNode);
+      Selector constructorSelector = new Selector.callDefaultConstructor(
+          visitor.enclosingElement.getLibrary());
+      Element calledConstructor = lookupTarget.lookupConstructor(
+          constructorSelector);
+
+      final SourceString className = lookupTarget.name;
+      final bool isImplicitSuperCall = true;
+      verifyThatConstructorMatchesCall(calledConstructor,
+                                       callToMatch,
+                                       isImplicitSuperCall,
+                                       functionNode,
+                                       className,
+                                       constructorSelector);
+
+      visitor.world.registerStaticUse(calledConstructor);
+    }
+  }
+
+  void verifyThatConstructorMatchesCall(
+      FunctionElement lookedupConstructor,
+      Selector call,
+      bool isImplicitSuperCall,
+      Node diagnosticNode,
+      SourceString className,
+      Selector constructorSelector) {
+    if (lookedupConstructor == null
+        || !lookedupConstructor.isGenerativeConstructor()) {
+      var fullConstructorName =
+          visitor.compiler.resolver.constructorNameForDiagnostics(
+              className,
+              constructorSelector.name);
+      MessageKind kind = isImplicitSuperCall
+          ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT
+          : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR;
+      error(diagnosticNode, kind, {'constructorName': fullConstructorName});
+    } else {
+      if (!call.applies(lookedupConstructor, visitor.compiler)) {
+        MessageKind kind = isImplicitSuperCall
+                           ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT
+                           : MessageKind.NO_MATCHING_CONSTRUCTOR;
+        error(diagnosticNode, kind);
+      }
+    }
+  }
+
+  FunctionElement resolveRedirection(FunctionElement constructor,
+                                     FunctionExpression functionNode) {
+    if (functionNode.initializers == null) return null;
+    Link<Node> link = functionNode.initializers.nodes;
+    if (!link.isEmpty && Initializers.isConstructorRedirect(link.head)) {
+      return resolveSuperOrThisForSend(constructor, functionNode, link.head);
+    }
+    return null;
+  }
+
+  /**
+   * Resolve all initializers of this constructor. In the case of a redirecting
+   * constructor, the resolved constructor's function element is returned.
+   */
+  FunctionElement resolveInitializers(FunctionElement constructor,
+                                      FunctionExpression functionNode) {
+    // Keep track of all "this.param" parameters specified for constructor so
+    // that we can ensure that fields are initialized only once.
+    FunctionSignature functionParameters =
+        constructor.computeSignature(visitor.compiler);
+    functionParameters.forEachParameter((Element element) {
+      if (identical(element.kind, ElementKind.FIELD_PARAMETER)) {
+        checkForDuplicateInitializers(element.name,
+                                      element.parseNode(visitor.compiler));
+      }
+    });
+
+    if (functionNode.initializers == null) {
+      initializers = const Link<Node>();
+    } else {
+      initializers = functionNode.initializers.nodes;
+    }
+    FunctionElement result;
+    bool resolvedSuper = false;
+    for (Link<Node> link = initializers;
+         !link.isEmpty;
+         link = link.tail) {
+      if (link.head.asSendSet() != null) {
+        final SendSet init = link.head.asSendSet();
+        resolveFieldInitializer(constructor, init);
+      } else if (link.head.asSend() != null) {
+        final Send call = link.head.asSend();
+        if (Initializers.isSuperConstructorCall(call)) {
+          if (resolvedSuper) {
+            error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER);
+          }
+          resolveSuperOrThisForSend(constructor, functionNode, call);
+          resolvedSuper = true;
+        } else if (Initializers.isConstructorRedirect(call)) {
+          // Check that there is no body (Language specification 7.5.1).
+          if (functionNode.hasBody()) {
+            error(functionNode, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_BODY);
+          }
+          // Check that there are no other initializers.
+          if (!initializers.tail.isEmpty) {
+            error(call, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER);
+          }
+          return resolveSuperOrThisForSend(constructor, functionNode, call);
+        } else {
+          visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED);
+          return null;
+        }
+      } else {
+        error(link.head, MessageKind.INVALID_INITIALIZER);
+      }
+    }
+    if (!resolvedSuper) {
+      resolveImplicitSuperConstructorSend(constructor, functionNode);
+    }
+    return null;  // If there was no redirection always return null.
+  }
+}
+
+class CommonResolverVisitor<R> extends Visitor<R> {
+  final Compiler compiler;
+
+  CommonResolverVisitor(Compiler this.compiler);
+
+  R visitNode(Node node) {
+    cancel(node,
+           'internal error: Unhandled node: ${node.getObjectDescription()}');
+  }
+
+  R visitEmptyStatement(Node node) => null;
+
+  /** Convenience method for visiting nodes that may be null. */
+  R visit(Node node) => (node == null) ? null : node.accept(this);
+
+  void error(Node node, MessageKind kind, [Map arguments = const {}]) {
+    ResolutionError message  = new ResolutionError(kind, arguments);
+    compiler.reportError(node, message);
+  }
+
+  void warning(Node node, MessageKind kind, [Map arguments = const {}]) {
+    ResolutionWarning message  = new ResolutionWarning(kind, arguments);
+    compiler.reportWarning(node, message);
+  }
+
+  void cancel(Node node, String message) {
+    compiler.cancel(message, node: node);
+  }
+
+  void internalError(Node node, String message) {
+    compiler.internalError(message, node: node);
+  }
+
+  void unimplemented(Node node, String message) {
+    compiler.unimplemented(message, node: node);
+  }
+}
+
+abstract class LabelScope {
+  LabelScope get outer;
+  LabelElement lookup(String label);
+}
+
+class LabeledStatementLabelScope implements LabelScope {
+  final LabelScope outer;
+  final Map<String, LabelElement> labels;
+  LabeledStatementLabelScope(this.outer, this.labels);
+  LabelElement lookup(String labelName) {
+    LabelElement label = labels[labelName];
+    if (label != null) return label;
+    return outer.lookup(labelName);
+  }
+}
+
+class SwitchLabelScope implements LabelScope {
+  final LabelScope outer;
+  final Map<String, LabelElement> caseLabels;
+
+  SwitchLabelScope(this.outer, this.caseLabels);
+
+  LabelElement lookup(String labelName) {
+    LabelElement result = caseLabels[labelName];
+    if (result != null) return result;
+    return outer.lookup(labelName);
+  }
+}
+
+class EmptyLabelScope implements LabelScope {
+  const EmptyLabelScope();
+  LabelElement lookup(String label) => null;
+  LabelScope get outer {
+    throw 'internal error: empty label scope has no outer';
+  }
+}
+
+class StatementScope {
+  LabelScope labels;
+  Link<TargetElement> breakTargetStack;
+  Link<TargetElement> continueTargetStack;
+  // Used to provide different numbers to statements if one is inside the other.
+  // Can be used to make otherwise duplicate labels unique.
+  int nestingLevel = 0;
+
+  StatementScope()
+      : labels = const EmptyLabelScope(),
+        breakTargetStack = const Link<TargetElement>(),
+        continueTargetStack = const Link<TargetElement>();
+
+  LabelElement lookupLabel(String label) {
+    return labels.lookup(label);
+  }
+
+  TargetElement currentBreakTarget() =>
+    breakTargetStack.isEmpty ? null : breakTargetStack.head;
+
+  TargetElement currentContinueTarget() =>
+    continueTargetStack.isEmpty ? null : continueTargetStack.head;
+
+  void enterLabelScope(Map<String, LabelElement> elements) {
+    labels = new LabeledStatementLabelScope(labels, elements);
+    nestingLevel++;
+  }
+
+  void exitLabelScope() {
+    nestingLevel--;
+    labels = labels.outer;
+  }
+
+  void enterLoop(TargetElement element) {
+    breakTargetStack = breakTargetStack.prepend(element);
+    continueTargetStack = continueTargetStack.prepend(element);
+    nestingLevel++;
+  }
+
+  void exitLoop() {
+    nestingLevel--;
+    breakTargetStack = breakTargetStack.tail;
+    continueTargetStack = continueTargetStack.tail;
+  }
+
+  void enterSwitch(TargetElement breakElement,
+                   Map<String, LabelElement> continueElements) {
+    breakTargetStack = breakTargetStack.prepend(breakElement);
+    labels = new SwitchLabelScope(labels, continueElements);
+    nestingLevel++;
+  }
+
+  void exitSwitch() {
+    nestingLevel--;
+    breakTargetStack = breakTargetStack.tail;
+    labels = labels.outer;
+  }
+}
+
+class TypeResolver {
+  final Compiler compiler;
+
+  TypeResolver(this.compiler);
+
+  Element resolveTypeName(Scope scope,
+                          SourceString prefixName,
+                          Identifier typeName) {
+    if (prefixName != null) {
+      Element e = scope.lookup(prefixName);
+      if (e != null) {
+        if (identical(e.kind, ElementKind.PREFIX)) {
+          // The receiver is a prefix. Lookup in the imported members.
+          PrefixElement prefix = e;
+          return prefix.lookupLocalMember(typeName.source);
+        } else if (identical(e.kind, ElementKind.CLASS)) {
+          // TODO(johnniwinther): Remove this case.
+          // The receiver is the class part of a named constructor.
+          return e;
+        }
+      } else {
+        // The caller creates the ErroneousElement for the MalformedType.
+        return null;
+      }
+    } else {
+      String stringValue = typeName.source.stringValue;
+      if (identical(stringValue, 'void')) {
+        return compiler.types.voidType.element;
+      } else if (identical(stringValue, 'Dynamic')) {
+        // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+        compiler.onDeprecatedFeature(typeName, 'Dynamic');
+        return compiler.dynamicClass;
+      } else if (identical(stringValue, 'dynamic')) {
+        return compiler.dynamicClass;
+      } else {
+        return scope.lookup(typeName.source);
+      }
+    }
+  }
+
+  // TODO(johnniwinther): Change  [onFailure] and [whenResolved] to use boolean
+  // flags instead of closures.
+  DartType resolveTypeAnnotation(
+      TypeAnnotation node,
+      Scope scope,
+      Element enclosingElement,
+      {onFailure(Node node, MessageKind kind, [Map arguments]),
+       whenResolved(Node node, DartType type)}) {
+    if (onFailure == null) {
+      onFailure = (n, k, [arguments]) {};
+    }
+    if (whenResolved == null) {
+      whenResolved = (n, t) {};
+    }
+    if (scope == null) {
+      compiler.internalError('resolveTypeAnnotation: no scope specified');
+    }
+    return resolveTypeAnnotationInContext(scope, node, enclosingElement,
+        onFailure, whenResolved);
+  }
+
+  DartType resolveTypeAnnotationInContext(Scope scope, TypeAnnotation node,
+                                          Element enclosingElement,
+                                          onFailure, whenResolved) {
+    Identifier typeName;
+    SourceString prefixName;
+    Send send = node.typeName.asSend();
+    if (send != null) {
+      // The type name is of the form [: prefix . identifier :].
+      prefixName = send.receiver.asIdentifier().source;
+      typeName = send.selector.asIdentifier();
+    } else {
+      typeName = node.typeName.asIdentifier();
+    }
+
+    Element element = resolveTypeName(scope, prefixName, typeName);
+    DartType type;
+
+    DartType reportFailureAndCreateType(MessageKind messageKind,
+                                        Map messageArguments) {
+      onFailure(node, messageKind, messageArguments);
+      var erroneousElement = new ErroneousElementX(
+          messageKind, messageArguments, typeName.source, enclosingElement);
+      var arguments = new LinkBuilder<DartType>();
+      resolveTypeArguments(
+          node, null, enclosingElement,
+          scope, onFailure, whenResolved, arguments);
+      return new MalformedType(erroneousElement, null, arguments.toLink());
+    }
+
+    DartType checkNoTypeArguments(DartType type) {
+      var arguments = new LinkBuilder<DartType>();
+      bool hashTypeArgumentMismatch = resolveTypeArguments(
+          node, const Link<DartType>(), enclosingElement,
+          scope, onFailure, whenResolved, arguments);
+      if (hashTypeArgumentMismatch) {
+        type = new MalformedType(
+            new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH,
+                {'type': node}, typeName.source, enclosingElement),
+                type, arguments.toLink());
+      }
+      return type;
+    }
+
+    if (element == null) {
+      type = reportFailureAndCreateType(
+          MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node.typeName});
+    } else if (element.isAmbiguous()) {
+      AmbiguousElement ambiguous = element;
+      type = reportFailureAndCreateType(
+          ambiguous.messageKind, ambiguous.messageArguments);
+    } else if (!element.impliesType()) {
+      type = reportFailureAndCreateType(
+          MessageKind.NOT_A_TYPE, {'node': node.typeName});
+    } else {
+      if (identical(element, compiler.types.voidType.element) ||
+          identical(element, compiler.types.dynamicType.element)) {
+        type = checkNoTypeArguments(element.computeType(compiler));
+      } else if (element.isClass()) {
+        ClassElement cls = element;
+        compiler.resolver._ensureClassWillBeResolved(cls);
+        element.computeType(compiler);
+        var arguments = new LinkBuilder<DartType>();
+        bool hashTypeArgumentMismatch = resolveTypeArguments(
+            node, cls.typeVariables, enclosingElement,
+            scope, onFailure, whenResolved, arguments);
+        if (hashTypeArgumentMismatch) {
+          type = new MalformedType(
+              new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH,
+                  {'type': node}, typeName.source, enclosingElement),
+              new InterfaceType(cls.declaration, arguments.toLink()));
+        } else {
+          if (arguments.isEmpty) {
+            type = cls.rawType;
+          } else {
+            type = new InterfaceType(cls.declaration, arguments.toLink());
+          }
+        }
+      } else if (element.isTypedef()) {
+        TypedefElement typdef = element;
+        // TODO(ahe): Should be [ensureResolved].
+        compiler.resolveTypedef(typdef);
+        var arguments = new LinkBuilder<DartType>();
+        bool hashTypeArgumentMismatch = resolveTypeArguments(
+            node, typdef.typeVariables, enclosingElement,
+            scope, onFailure, whenResolved, arguments);
+        if (hashTypeArgumentMismatch) {
+          type = new MalformedType(
+              new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH,
+                  {'type': node}, typeName.source, enclosingElement),
+              new TypedefType(typdef, arguments.toLink()));
+        } else {
+          if (arguments.isEmpty) {
+            type = typdef.rawType;
+          } else {
+           type = new TypedefType(typdef, arguments.toLink());
+          }
+        }
+      } else if (element.isTypeVariable()) {
+        if (enclosingElement.isInStaticMember()) {
+          compiler.reportWarning(node,
+              MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER.message(
+                  {'typeVariableName': node}));
+          type = new MalformedType(
+              new ErroneousElementX(
+                  MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER,
+                  {'typeVariableName': node},
+                  typeName.source, enclosingElement),
+                  element.computeType(compiler));
+        } else {
+          type = element.computeType(compiler);
+        }
+        type = checkNoTypeArguments(type);
+      } else {
+        compiler.cancel("unexpected element kind ${element.kind}",
+                        node: node);
+      }
+    }
+    whenResolved(node, type);
+    return type;
+  }
+
+  /**
+   * Resolves the type arguments of [node] and adds these to [arguments].
+   *
+   * Returns [: true :] if the number of type arguments did not match the
+   * number of type variables.
+   */
+  bool resolveTypeArguments(
+      TypeAnnotation node,
+      Link<DartType> typeVariables,
+      Element enclosingElement,
+      Scope scope,
+      onFailure, whenResolved,
+      LinkBuilder<DartType> arguments) {
+    if (node.typeArguments == null) {
+      return false;
+    }
+    bool typeArgumentCountMismatch = false;
+    for (Link<Node> typeArguments = node.typeArguments.nodes;
+         !typeArguments.isEmpty;
+         typeArguments = typeArguments.tail) {
+      if (typeVariables != null && typeVariables.isEmpty) {
+        onFailure(typeArguments.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
+        typeArgumentCountMismatch = true;
+      }
+      DartType argType = resolveTypeAnnotationInContext(scope,
+                                                        typeArguments.head,
+                                                        enclosingElement,
+                                                        onFailure,
+                                                        whenResolved);
+      arguments.addLast(argType);
+      if (typeVariables != null && !typeVariables.isEmpty) {
+        typeVariables = typeVariables.tail;
+      }
+    }
+    if (typeVariables != null && !typeVariables.isEmpty) {
+      onFailure(node.typeArguments, MessageKind.MISSING_TYPE_ARGUMENT);
+      typeArgumentCountMismatch = true;
+    }
+    return typeArgumentCountMismatch;
+  }
+}
+
+/**
+ * Core implementation of resolution.
+ *
+ * Do not subclass or instantiate this class outside this library
+ * except for testing.
+ */
+class ResolverVisitor extends CommonResolverVisitor<Element> {
+  final TreeElementMapping mapping;
+  Element enclosingElement;
+  final TypeResolver typeResolver;
+  bool inInstanceContext;
+  bool inCheckContext;
+  bool inCatchBlock;
+  Scope scope;
+  ClassElement currentClass;
+  ExpressionStatement currentExpressionStatement;
+  bool typeRequired = false;
+  StatementScope statementScope;
+  int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION
+      | ElementCategory.IMPLIES_TYPE;
+
+  ResolverVisitor(Compiler compiler, Element element, this.mapping)
+    : this.enclosingElement = element,
+      // When the element is a field, we are actually resolving its
+      // initial value, which should not have access to instance
+      // fields.
+      inInstanceContext = (element.isInstanceMember() && !element.isField())
+          || element.isGenerativeConstructor(),
+      this.currentClass = element.isMember() ? element.getEnclosingClass()
+                                             : null,
+      this.statementScope = new StatementScope(),
+      typeResolver = new TypeResolver(compiler),
+      scope = element.buildScope(),
+      inCheckContext = compiler.enableTypeAssertions,
+      inCatchBlock = false,
+      super(compiler);
+
+  ResolutionEnqueuer get world => compiler.enqueuer.resolution;
+
+  Element lookup(Node node, SourceString name) {
+    Element result = scope.lookup(name);
+    if (!Elements.isUnresolved(result)) {
+      if (!inInstanceContext && result.isInstanceMember()) {
+        compiler.reportErrorCode(
+            node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name});
+        return new ErroneousElementX(MessageKind.NO_INSTANCE_AVAILABLE,
+                                     {'name': name},
+                                     name, enclosingElement);
+      } else if (result.isAmbiguous()) {
+        AmbiguousElement ambiguous = result;
+        compiler.reportErrorCode(
+            node, ambiguous.messageKind, ambiguous.messageArguments);
+        return new ErroneousElementX(ambiguous.messageKind,
+                                     ambiguous.messageArguments,
+                                     name, enclosingElement);
+      }
+    }
+    return result;
+  }
+
+  // Create, or reuse an already created, statement element for a statement.
+  TargetElement getOrCreateTargetElement(Node statement) {
+    TargetElement element = mapping[statement];
+    if (element == null) {
+      element = new TargetElementX(statement,
+                                   statementScope.nestingLevel,
+                                   enclosingElement);
+      mapping[statement] = element;
+    }
+    return element;
+  }
+
+  doInCheckContext(action()) {
+    bool wasInCheckContext = inCheckContext;
+    inCheckContext = true;
+    var result = action();
+    inCheckContext = wasInCheckContext;
+    return result;
+  }
+
+  inStaticContext(action()) {
+    bool wasInstanceContext = inInstanceContext;
+    inInstanceContext = false;
+    var result = action();
+    inInstanceContext = wasInstanceContext;
+    return result;
+  }
+
+  visitInStaticContext(Node node) {
+    inStaticContext(() => visit(node));
+  }
+
+  ErroneousElement warnAndCreateErroneousElement(Node node,
+                                                 SourceString name,
+                                                 MessageKind kind,
+                                                 [Map arguments = const {}]) {
+    ResolutionWarning warning = new ResolutionWarning(kind, arguments);
+    compiler.reportWarning(node, warning);
+    return new ErroneousElementX(kind, arguments, name, enclosingElement);
+  }
+
+  Element visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      if (!inInstanceContext) {
+        error(node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node});
+      }
+      return null;
+    } else if (node.isSuper()) {
+      if (!inInstanceContext) error(node, MessageKind.NO_SUPER_IN_STATIC);
+      if ((ElementCategory.SUPER & allowedCategory) == 0) {
+        error(node, MessageKind.INVALID_USE_OF_SUPER);
+      }
+      return null;
+    } else {
+      Element element = lookup(node, node.source);
+      if (element == null) {
+        if (!inInstanceContext) {
+          element = warnAndCreateErroneousElement(node, node.source,
+                                                  MessageKind.CANNOT_RESOLVE,
+                                                  {'name': node});
+        }
+      } else if (element.isErroneous()) {
+        // Use the erroneous element.
+      } else {
+        if ((element.kind.category & allowedCategory) == 0) {
+          // TODO(ahe): Improve error message. Need UX input.
+          error(node, MessageKind.GENERIC,
+                {'text': "is not an expression $element"});
+        }
+      }
+      if (!Elements.isUnresolved(element)
+          && element.kind == ElementKind.CLASS) {
+        ClassElement classElement = element;
+        classElement.ensureResolved(compiler);
+      }
+      return useElement(node, element);
+    }
+  }
+
+  Element visitTypeAnnotation(TypeAnnotation node) {
+    DartType type = resolveTypeAnnotation(node);
+    if (type != null) {
+      if (inCheckContext) {
+        compiler.enqueuer.resolution.registerIsCheck(type);
+      }
+      return type.element;
+    }
+    return null;
+  }
+
+  Element defineElement(Node node, Element element,
+                        {bool doAddToScope: true}) {
+    compiler.ensure(element != null);
+    mapping[node] = element;
+    if (doAddToScope) {
+      Element existing = scope.add(element);
+      if (existing != element) {
+        error(node, MessageKind.DUPLICATE_DEFINITION, {'name': node});
+      }
+    }
+    return element;
+  }
+
+  Element useElement(Node node, Element element) {
+    if (element == null) return null;
+    return mapping[node] = element;
+  }
+
+  DartType useType(TypeAnnotation annotation, DartType type) {
+    if (type != null) {
+      mapping.setType(annotation, type);
+      useElement(annotation, type.element);
+    }
+    return type;
+  }
+
+  bool isNamedConstructor(Send node) => node.receiver != null;
+
+  Selector getRedirectingThisOrSuperConstructorSelector(Send node) {
+    if (isNamedConstructor(node)) {
+      SourceString constructorName = node.selector.asIdentifier().source;
+      return new Selector.callConstructor(
+          constructorName,
+          enclosingElement.getLibrary());
+    } else {
+      return new Selector.callDefaultConstructor(
+          enclosingElement.getLibrary());
+    }
+  }
+
+  FunctionElement resolveConstructorRedirection(FunctionElement constructor) {
+    FunctionExpression node = constructor.parseNode(compiler);
+
+    // A synthetic constructor does not have a node.
+    if (node == null) return null;
+    if (node.initializers == null) return null;
+    Link<Node> initializers = node.initializers.nodes;
+    if (!initializers.isEmpty &&
+        Initializers.isConstructorRedirect(initializers.head)) {
+      Selector selector =
+          getRedirectingThisOrSuperConstructorSelector(initializers.head);
+      final ClassElement classElement = constructor.getEnclosingClass();
+      return classElement.lookupConstructor(selector);
+    }
+    return null;
+  }
+
+  void setupFunction(FunctionExpression node, FunctionElement function) {
+    scope = new MethodScope(scope, function);
+
+    // Put the parameters in scope.
+    FunctionSignature functionParameters =
+        function.computeSignature(compiler);
+    Link<Node> parameterNodes = (node.parameters == null)
+        ? const Link<Node>() : node.parameters.nodes;
+    functionParameters.forEachParameter((Element element) {
+      if (element == functionParameters.optionalParameters.head) {
+        NodeList nodes = parameterNodes.head;
+        parameterNodes = nodes.nodes;
+      }
+      VariableDefinitions variableDefinitions = parameterNodes.head;
+      Node parameterNode = variableDefinitions.definitions.nodes.head;
+      initializerDo(parameterNode, (n) => n.accept(this));
+      // Field parameters (this.x) are not visible inside the constructor. The
+      // fields they reference are visible, but must be resolved independently.
+      if (element.kind == ElementKind.FIELD_PARAMETER) {
+        useElement(parameterNode, element);
+      } else {
+        defineElement(variableDefinitions.definitions.nodes.head, element);
+      }
+      parameterNodes = parameterNodes.tail;
+    });
+  }
+
+  visitCascade(Cascade node) {
+    visit(node.expression);
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visit(node.expression);
+  }
+
+  Element visitClassNode(ClassNode node) {
+    cancel(node, "shouldn't be called");
+  }
+
+  visitIn(Node node, Scope nestedScope) {
+    Scope oldScope = scope;
+    scope = nestedScope;
+    Element element = visit(node);
+    scope = oldScope;
+    return element;
+  }
+
+  /**
+   * Introduces new default targets for break and continue
+   * before visiting the body of the loop
+   */
+  visitLoopBodyIn(Node loop, Node body, Scope bodyScope) {
+    TargetElement element = getOrCreateTargetElement(loop);
+    statementScope.enterLoop(element);
+    visitIn(body, bodyScope);
+    statementScope.exitLoop();
+    if (!element.isTarget) {
+      mapping.remove(loop);
+    }
+  }
+
+  visitBlock(Block node) {
+    visitIn(node.statements, new BlockScope(scope));
+  }
+
+  visitDoWhile(DoWhile node) {
+    visitLoopBodyIn(node, node.body, new BlockScope(scope));
+    visit(node.condition);
+  }
+
+  visitEmptyStatement(EmptyStatement node) { }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    ExpressionStatement oldExpressionStatement = currentExpressionStatement;
+    currentExpressionStatement = node;
+    visit(node.expression);
+    currentExpressionStatement = oldExpressionStatement;
+  }
+
+  visitFor(For node) {
+    Scope blockScope = new BlockScope(scope);
+    visitIn(node.initializer, blockScope);
+    visitIn(node.condition, blockScope);
+    visitIn(node.update, blockScope);
+    visitLoopBodyIn(node, node.body, blockScope);
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    assert(node.function.name != null);
+    visit(node.function);
+    FunctionElement functionElement = mapping[node.function];
+    // TODO(floitsch): this might lead to two errors complaining about
+    // shadowing.
+    defineElement(node, functionElement);
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    visit(node.returnType);
+    SourceString name;
+    if (node.name == null) {
+      name = const SourceString("");
+    } else {
+      name = node.name.asIdentifier().source;
+    }
+
+    FunctionElement function = new FunctionElementX.node(
+        name, node, ElementKind.FUNCTION, Modifiers.EMPTY,
+        enclosingElement);
+    Scope oldScope = scope; // The scope is modified by [setupFunction].
+    setupFunction(node, function);
+    defineElement(node, function, doAddToScope: node.name != null);
+
+    Element previousEnclosingElement = enclosingElement;
+    enclosingElement = function;
+    // Run the body in a fresh statement scope.
+    StatementScope oldStatementScope = statementScope;
+    statementScope = new StatementScope();
+    visit(node.body);
+    statementScope = oldStatementScope;
+
+    scope = oldScope;
+    enclosingElement = previousEnclosingElement;
+
+    world.registerInstantiatedClass(compiler.functionClass);
+  }
+
+  visitIf(If node) {
+    visit(node.condition);
+    visit(node.thenPart);
+    visit(node.elsePart);
+  }
+
+  static bool isLogicalOperator(Identifier op) {
+    String str = op.source.stringValue;
+    return (identical(str, '&&') || str == '||' || str == '!');
+  }
+
+  Element resolveSend(Send node) {
+    Selector selector = resolveSelector(node);
+    if (node.isSuperCall) mapping.superUses.add(node);
+
+    if (node.receiver == null) {
+      // If this send is of the form "assert(expr);", then
+      // this is an assertion.
+      if (selector.isAssert()) {
+        if (selector.argumentCount != 1) {
+          error(node.selector,
+                MessageKind.WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT,
+                {'argumentCount': selector.argumentCount});
+        } else if (selector.namedArgumentCount != 0) {
+          error(node.selector,
+                MessageKind.ASSERT_IS_GIVEN_NAMED_ARGUMENTS,
+                {'argumentCount': selector.namedArgumentCount});
+        }
+        return compiler.assertMethod;
+      }
+
+      return node.selector.accept(this);
+    }
+
+    var oldCategory = allowedCategory;
+    allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER;
+    Element resolvedReceiver = visit(node.receiver);
+    allowedCategory = oldCategory;
+
+    Element target;
+    SourceString name = node.selector.asIdentifier().source;
+    if (identical(name.stringValue, 'this')) {
+      error(node.selector, MessageKind.GENERIC,
+            {'text': "expected an identifier"});
+    } else if (node.isSuperCall) {
+      if (node.isOperator) {
+        if (isUserDefinableOperator(name.stringValue)) {
+          name = selector.name;
+        } else {
+          error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, {'name': name});
+        }
+      }
+      if (!inInstanceContext) {
+        error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name});
+        return null;
+      }
+      if (currentClass.supertype == null) {
+        // This is just to guard against internal errors, so no need
+        // for a real error message.
+        error(node.receiver, MessageKind.GENERIC,
+              {'text': "Object has no superclass"});
+      }
+      // TODO(johnniwinther): Ensure correct behavior if currentClass is a
+      // patch.
+      target = currentClass.lookupSuperMember(name);
+      // [target] may be null which means invoking noSuchMethod on
+      // super.
+    } else if (Elements.isUnresolved(resolvedReceiver)) {
+      return null;
+    } else if (identical(resolvedReceiver.kind, ElementKind.CLASS)) {
+      ClassElement receiverClass = resolvedReceiver;
+      receiverClass.ensureResolved(compiler);
+      if (node.isOperator) {
+        // When the resolved receiver is a class, we can have two cases:
+        //  1) a static send: C.foo, or
+        //  2) an operator send, where the receiver is a class literal: 'C + 1'.
+        // The following code that looks up the selector on the resolved
+        // receiver will treat the second as the invocation of a static operator
+        // if the resolved receiver is not null.
+        return null;
+      }
+      target = receiverClass.lookupLocalMember(name);
+      if (target == null) {
+        // TODO(johnniwinther): With the simplified [TreeElements] invariant,
+        // try to resolve injected elements if [currentClass] is in the patch
+        // library of [receiverClass].
+
+        // TODO(karlklose): this should be reported by the caller of
+        // [resolveSend] to select better warning messages for getters and
+        // setters.
+        return warnAndCreateErroneousElement(node, name,
+                                             MessageKind.METHOD_NOT_FOUND,
+                                             {'className': receiverClass.name,
+                                              'methodName': name});
+      } else if (target.isInstanceMember()) {
+        error(node, MessageKind.MEMBER_NOT_STATIC,
+              {'className': receiverClass.name,
+               'memberName': name});
+      }
+    } else if (identical(resolvedReceiver.kind, ElementKind.PREFIX)) {
+      PrefixElement prefix = resolvedReceiver;
+      target = prefix.lookupLocalMember(name);
+      if (Elements.isUnresolved(target)) {
+        return warnAndCreateErroneousElement(
+            node, name, MessageKind.NO_SUCH_LIBRARY_MEMBER,
+            {'libraryName': prefix.name, 'memberName': name});
+      } else if (target.kind == ElementKind.CLASS) {
+        ClassElement classElement = target;
+        classElement.ensureResolved(compiler);
+      }
+    }
+    return target;
+  }
+
+  DartType resolveTypeTest(Node argument) {
+    TypeAnnotation node = argument.asTypeAnnotation();
+    if (node == null) {
+      // node is of the form !Type.
+      node = argument.asSend().receiver.asTypeAnnotation();
+      if (node == null) compiler.cancel("malformed send");
+    }
+    return resolveTypeRequired(node);
+  }
+
+  static Selector computeSendSelector(Send node, LibraryElement library) {
+    // First determine if this is part of an assignment.
+    bool isSet = node.asSendSet() != null;
+
+    if (node.isIndex) {
+      return isSet ? new Selector.indexSet() : new Selector.index();
+    }
+
+    if (node.isOperator) {
+      SourceString source = node.selector.asOperator().source;
+      String string = source.stringValue;
+      if (identical(string, '!') ||
+          identical(string, '&&') || identical(string, '||') ||
+          identical(string, 'is') || identical(string, 'as') ||
+          identical(string, '===') || identical(string, '!==') ||
+          identical(string, '?') ||
+          identical(string, '>>>')) {
+        return null;
+      }
+      if (!isUserDefinableOperator(source.stringValue)) {
+        source = Elements.mapToUserOperator(source);
+      }
+      return node.arguments.isEmpty
+          ? new Selector.unaryOperator(source)
+          : new Selector.binaryOperator(source);
+    }
+
+    Identifier identifier = node.selector.asIdentifier();
+    if (node.isPropertyAccess) {
+      assert(!isSet);
+      return new Selector.getter(identifier.source, library);
+    } else if (isSet) {
+      return new Selector.setter(identifier.source, library);
+    }
+
+    // Compute the arity and the list of named arguments.
+    int arity = 0;
+    List<SourceString> named = <SourceString>[];
+    for (Link<Node> link = node.argumentsNode.nodes;
+        !link.isEmpty;
+        link = link.tail) {
+      Expression argument = link.head;
+      NamedArgument namedArgument = argument.asNamedArgument();
+      if (namedArgument != null) {
+        named.add(namedArgument.name.source);
+      }
+      arity++;
+    }
+
+    // If we're invoking a closure, we do not have an identifier.
+    return (identifier == null)
+        ? new Selector.callClosure(arity, named)
+        : new Selector.call(identifier.source, library, arity, named);
+  }
+
+  Selector resolveSelector(Send node) {
+    LibraryElement library = enclosingElement.getLibrary();
+    Selector selector = computeSendSelector(node, library);
+    if (selector != null) mapping.setSelector(node, selector);
+    return selector;
+  }
+
+  void resolveArguments(NodeList list) {
+    if (list == null) return;
+    List<SourceString> seenNamedArguments = <SourceString>[];
+    for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) {
+      Expression argument = link.head;
+      visit(argument);
+      NamedArgument namedArgument = argument.asNamedArgument();
+      if (namedArgument != null) {
+        SourceString source = namedArgument.name.source;
+        if (seenNamedArguments.contains(source)) {
+          error(argument, MessageKind.DUPLICATE_DEFINITION,
+                {'name': source});
+        }
+        seenNamedArguments.add(source);
+      } else if (!seenNamedArguments.isEmpty) {
+        error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED);
+      }
+    }
+  }
+
+  visitSend(Send node) {
+    Element target = resolveSend(node);
+    if (!Elements.isUnresolved(target)
+        && target.kind == ElementKind.ABSTRACT_FIELD) {
+      AbstractFieldElement field = target;
+      target = field.getter;
+      if (target == null && !inInstanceContext) {
+        target =
+            warnAndCreateErroneousElement(node.selector, field.name,
+                                          MessageKind.CANNOT_RESOLVE_GETTER);
+      }
+    }
+
+    bool resolvedArguments = false;
+    if (node.isOperator) {
+      String operatorString = node.selector.asOperator().source.stringValue;
+      if (identical(operatorString, 'is') || identical(operatorString, 'as')) {
+        assert(node.arguments.tail.isEmpty);
+        DartType type = resolveTypeTest(node.arguments.head);
+        if (type != null) {
+          compiler.enqueuer.resolution.registerIsCheck(type);
+        }
+        resolvedArguments = true;
+      } else if (identical(operatorString, '?')) {
+        Element parameter = mapping[node.receiver];
+        if (parameter == null
+            || !identical(parameter.kind, ElementKind.PARAMETER)) {
+          error(node.receiver, MessageKind.PARAMETER_NAME_EXPECTED);
+        } else {
+          mapping.checkedParameters.add(parameter);
+        }
+      }
+    }
+
+    if (!resolvedArguments) {
+      resolveArguments(node.argumentsNode);
+    }
+
+    // If the selector is null, it means that we will not be generating
+    // code for this as a send.
+    Selector selector = mapping.getSelector(node);
+    if (selector == null) return;
+
+    if (node.isCall) {
+      if (Elements.isUnresolved(target) ||
+          target.isGetter() ||
+          Elements.isClosureSend(node, target)) {
+        // If we don't know what we're calling or if we are calling a getter,
+        // we need to register that fact that we may be calling a closure
+        // with the same arguments.
+        Selector call = new Selector.callClosureFrom(selector);
+        world.registerDynamicInvocation(call.name, call);
+      } else if (target.impliesType()) {
+        // We call 'call()' on a Type instance returned from the reference to a
+        // class or typedef literal. We do not need to register this call as a
+        // dynamic invocation, because we statically know what the target is.
+      } else if (!selector.applies(target, compiler)) {
+        warnArgumentMismatch(node, target);
+      }
+
+      if (target != null &&
+          target.isForeign(compiler) &&
+          selector.name == const SourceString('JS')) {
+        world.registerJsCall(node, this);
+      }
+    }
+
+    // TODO(ngeoffray): Warn if target is null and the send is
+    // unqualified.
+    useElement(node, target);
+    registerSend(selector, target);
+    if (node.isPropertyAccess) {
+      // It might be the closurization of a method.
+      world.registerInstantiatedClass(compiler.functionClass);
+    }
+    return node.isPropertyAccess ? target : null;
+  }
+
+  void warnArgumentMismatch(Send node, Element target) {
+    // TODO(karlklose): we can be more precise about the reason of the
+    // mismatch.
+    warning(node.argumentsNode, MessageKind.INVALID_ARGUMENTS,
+            {'methodName': target.name});
+  }
+
+  /// Callback for native enqueuer to parse a type.  Returns [:null:] on error.
+  DartType resolveTypeFromString(String typeName) {
+    Element element = scope.lookup(new SourceString(typeName));
+    if (element == null) return null;
+    if (element is! ClassElement) return null;
+    element.ensureResolved(compiler);
+    return element.computeType(compiler);
+  }
+
+  visitSendSet(SendSet node) {
+    Element target = resolveSend(node);
+    Element setter = target;
+    Element getter = target;
+    SourceString operatorName = node.assignmentOperator.source;
+    String source = operatorName.stringValue;
+    bool isComplex = !identical(source, '=');
+    if (!Elements.isUnresolved(target)
+        && target.kind == ElementKind.ABSTRACT_FIELD) {
+      AbstractFieldElement field = target;
+      setter = field.setter;
+      getter = field.getter;
+      if (setter == null && !inInstanceContext) {
+        setter =
+            warnAndCreateErroneousElement(node.selector, field.name,
+                                          MessageKind.CANNOT_RESOLVE_SETTER);
+      }
+      if (isComplex && getter == null && !inInstanceContext) {
+        getter =
+            warnAndCreateErroneousElement(node.selector, field.name,
+                                          MessageKind.CANNOT_RESOLVE_GETTER);
+      }
+    }
+
+    visit(node.argumentsNode);
+
+    // TODO(ngeoffray): Check if the target can be assigned.
+    // TODO(ngeoffray): Warn if target is null and the send is
+    // unqualified.
+
+    Selector selector = mapping.getSelector(node);
+    if (isComplex) {
+      if (selector.isSetter()) {
+        // TODO(kasperl): We're registering the getter selector for
+        // compound assignments on the AST selector node. In the code
+        // generator, we then fetch it from there when generating the
+        // getter for a SendSet node.
+        Selector getterSelector = new Selector.getterFrom(selector);
+        registerSend(getterSelector, getter);
+        mapping.setSelector(node.selector, getterSelector);
+        useElement(node.selector, getter);
+      } else {
+        // TODO(kasperl): If [getter] is resolved, it will actually
+        // refer to the []= operator which isn't the one we want to
+        // register here. We should consider using some notion of
+        // abstract indexable element that we can resolve to so we can
+        // distinguish the two.
+        assert(selector.isIndexSet());
+        registerSend(new Selector.index(), null);
+      }
+
+      // Make sure we include the + and - operators if we are using
+      // the ++ and -- ones.  Also, if op= form is used, include op itself.
+      void registerBinaryOperator(SourceString name) {
+        Selector binop = new Selector.binaryOperator(name);
+        world.registerDynamicInvocation(binop.name, binop);
+      }
+      if (identical(source, '++')) registerBinaryOperator(const SourceString('+'));
+      if (identical(source, '--')) registerBinaryOperator(const SourceString('-'));
+      if (source.endsWith('=')) {
+        registerBinaryOperator(Elements.mapToUserOperator(operatorName));
+      }
+    }
+
+    registerSend(selector, setter);
+    return useElement(node, setter);
+  }
+
+  void registerSend(Selector selector, Element target) {
+    if (target == null || target.isInstanceMember()) {
+      if (selector.isGetter()) {
+        world.registerDynamicGetter(selector.name, selector);
+      } else if (selector.isSetter()) {
+        world.registerDynamicSetter(selector.name, selector);
+      } else {
+        world.registerDynamicInvocation(selector.name, selector);
+      }
+    } else if (Elements.isStaticOrTopLevel(target)) {
+      // TODO(kasperl): It seems like we're not supposed to register
+      // the use of classes. Wouldn't it be simpler if we just did?
+      if (!target.isClass()) {
+        // [target] might be the implementation element and only declaration
+        // elements may be registered.
+        world.registerStaticUse(target.declaration);
+      }
+    }
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    world.registerInstantiatedClass(compiler.intClass);
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    world.registerInstantiatedClass(compiler.doubleClass);
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    world.registerInstantiatedClass(compiler.boolClass);
+  }
+
+  visitLiteralString(LiteralString node) {
+    world.registerInstantiatedClass(compiler.stringClass);
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    world.registerInstantiatedClass(compiler.nullClass);
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    world.registerInstantiatedClass(compiler.stringClass);
+    node.visitChildren(this);
+  }
+
+  visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      visit(link.head);
+    }
+  }
+
+  visitOperator(Operator node) {
+    unimplemented(node, 'operator');
+  }
+
+  visitReturn(Return node) {
+    if (node.isRedirectingFactoryBody) {
+      handleRedirectingFactoryBody(node);
+    } else {
+      visit(node.expression);
+    }
+  }
+
+  void handleRedirectingFactoryBody(Return node) {
+    if (!enclosingElement.isFactoryConstructor()) {
+      compiler.reportErrorCode(
+          node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY);
+      compiler.reportErrorCode(
+          enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD);
+    }
+    Element redirectionTarget = resolveRedirectingFactory(node);
+    var type = mapping.getType(node.expression);
+    if (type is InterfaceType && !type.isRaw) {
+      unimplemented(node.expression, 'type arguments on redirecting factory');
+    }
+    useElement(node.expression, redirectionTarget);
+    FunctionElement constructor = enclosingElement;
+    if (constructor.modifiers.isConst() &&
+        !redirectionTarget.modifiers.isConst()) {
+      error(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST);
+    }
+    constructor.defaultImplementation = redirectionTarget;
+    if (Elements.isUnresolved(redirectionTarget)) return;
+
+    // TODO(ahe): Check that this doesn't lead to a cycle.  For now,
+    // just make sure that the redirection target isn't itself a
+    // redirecting factory.
+    { // This entire block is temporary code per the above TODO.
+      FunctionElement targetImplementation = redirectionTarget.implementation;
+      FunctionExpression function = targetImplementation.parseNode(compiler);
+      if (function.body != null && function.body.asReturn() != null
+          && function.body.asReturn().isRedirectingFactoryBody) {
+        unimplemented(node.expression, 'redirecing to redirecting factory');
+      }
+    }
+    world.registerStaticUse(redirectionTarget);
+    world.registerInstantiatedClass(
+        redirectionTarget.enclosingElement.declaration);
+  }
+
+  visitThrow(Throw node) {
+    if (!inCatchBlock && node.expression == null) {
+      error(node, MessageKind.THROW_WITHOUT_EXPRESSION);
+    }
+    visit(node.expression);
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    VariableDefinitionsVisitor visitor =
+        new VariableDefinitionsVisitor(compiler, node, this,
+                                       ElementKind.VARIABLE);
+    // Ensure that we set the type of the [VariableListElement] since it depends
+    // on the current scope. If the current scope is a [MethodScope] or
+    // [BlockScope] it will not be available for the
+    // [VariableListElement.computeType] method.
+    if (node.type != null) {
+      visitor.variables.type = resolveTypeAnnotation(node.type);
+    } else {
+      visitor.variables.type = compiler.types.dynamicType;
+    }
+    visitor.visit(node.definitions);
+  }
+
+  visitWhile(While node) {
+    visit(node.condition);
+    visitLoopBodyIn(node, node.body, new BlockScope(scope));
+  }
+
+  visitParenthesizedExpression(ParenthesizedExpression node) {
+    visit(node.expression);
+  }
+
+  visitNewExpression(NewExpression node) {
+    Node selector = node.send.selector;
+    FunctionElement constructor = resolveConstructor(node);
+    resolveSelector(node.send);
+    resolveArguments(node.send.argumentsNode);
+    useElement(node.send, constructor);
+    if (Elements.isUnresolved(constructor)) return constructor;
+    // TODO(karlklose): handle optional arguments.
+    if (node.send.argumentCount() != constructor.parameterCount(compiler)) {
+      // TODO(ngeoffray): resolution error with wrong number of
+      // parameters. We cannot do this rigth now because of the
+      // List constructor.
+    }
+    // [constructor] might be the implementation element and only declaration
+    // elements may be registered.
+    world.registerStaticUse(constructor.declaration);
+    compiler.withCurrentElement(constructor, () {
+      FunctionExpression tree = constructor.parseNode(compiler);
+      compiler.resolver.resolveConstructorImplementation(constructor, tree);
+    });
+    // [constructor.defaultImplementation] might be the implementation element
+    // and only declaration elements may be registered.
+    world.registerStaticUse(constructor.defaultImplementation.declaration);
+    ClassElement cls = constructor.defaultImplementation.getEnclosingClass();
+    // [cls] might be the implementation element and only declaration elements
+    // may be registered.
+    world.registerInstantiatedClass(cls.declaration);
+    // [cls] might be the declaration element and we want to include injected
+    // members.
+    cls.implementation.forEachInstanceField(
+        (ClassElement enclosingClass, Element member) {
+          world.addToWorkList(member);
+        },
+        includeBackendMembers: false,
+        includeSuperMembers: true);
+    return null;
+  }
+
+  /**
+   * Try to resolve the constructor that is referred to by [node].
+   * Note: this function may return an ErroneousFunctionElement instead of
+   * [null], if there is no corresponding constructor, class or library.
+   */
+  FunctionElement resolveConstructor(NewExpression node) {
+    return node.accept(new ConstructorResolver(compiler, this));
+  }
+
+  FunctionElement resolveRedirectingFactory(Return node) {
+    return node.accept(new ConstructorResolver(compiler, this));
+  }
+
+  DartType resolveTypeRequired(TypeAnnotation node) {
+    bool old = typeRequired;
+    typeRequired = true;
+    DartType result = resolveTypeAnnotation(node);
+    typeRequired = old;
+    return result;
+  }
+
+  void analyzeTypeArgument(DartType annotation, DartType argument) {
+    if (argument == null) return;
+    if (argument.element.isTypeVariable()) {
+      // Register a dependency between the class where the type
+      // variable is, and the annotation. If the annotation requires
+      // runtime type information, then the class of the type variable
+      // does too.
+      compiler.world.registerRtiDependency(
+          annotation.element,
+          argument.element.enclosingElement);
+    } else if (argument is InterfaceType) {
+      InterfaceType type = argument;
+      type.typeArguments.forEach((DartType argument) {
+        analyzeTypeArgument(type, argument);
+      });
+    }
+  }
+
+  DartType resolveTypeAnnotation(TypeAnnotation node) {
+    Function report = typeRequired ? error : warning;
+    DartType type = typeResolver.resolveTypeAnnotation(
+        node, scope, enclosingElement,
+        onFailure: report, whenResolved: useType);
+    if (type == null) return null;
+    if (inCheckContext) {
+      compiler.enqueuer.resolution.registerIsCheck(type);
+    }
+    if (typeRequired || inCheckContext) {
+      if (type is InterfaceType) {
+        InterfaceType itf = type;
+        itf.typeArguments.forEach((DartType argument) {
+          analyzeTypeArgument(type, argument);
+        });
+      }
+      // TODO(ngeoffray): Also handle cases like:
+      // 1) a is T
+      // 2) T a (in checked mode).
+    }
+    return type;
+  }
+
+  visitModifiers(Modifiers node) {
+    // TODO(ngeoffray): Implement this.
+    unimplemented(node, 'modifiers');
+  }
+
+  visitLiteralList(LiteralList node) {
+    world.registerInstantiatedClass(compiler.listClass);
+    NodeList arguments = node.typeArguments;
+    if (arguments != null) {
+      Link<Node> nodes = arguments.nodes;
+      if (nodes.isEmpty) {
+        error(arguments, MessageKind.MISSING_TYPE_ARGUMENT);
+      } else {
+        resolveTypeRequired(nodes.head);
+        for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) {
+          error(nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
+          resolveTypeRequired(nodes.head);
+        }
+      }
+    }
+    visit(node.elements);
+  }
+
+  visitConditional(Conditional node) {
+    node.visitChildren(this);
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    world.registerInstantiatedClass(compiler.stringClass);
+    node.visitChildren(this);
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    registerImplicitInvocation(const SourceString('toString'), 0);
+    node.visitChildren(this);
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    TargetElement target;
+    if (node.target == null) {
+      target = statementScope.currentBreakTarget();
+      if (target == null) {
+        error(node, MessageKind.NO_BREAK_TARGET);
+        return;
+      }
+      target.isBreakTarget = true;
+    } else {
+      String labelName = node.target.source.slowToString();
+      LabelElement label = statementScope.lookupLabel(labelName);
+      if (label == null) {
+        error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName});
+        return;
+      }
+      target = label.target;
+      if (!target.statement.isValidBreakTarget()) {
+        error(node.target, MessageKind.INVALID_BREAK);
+        return;
+      }
+      label.setBreakTarget();
+      mapping[node.target] = label;
+    }
+    if (mapping[node] != null) {
+      // TODO(ahe): I'm not sure why this node already has an element
+      // that is different from target.  I will talk to Lasse and
+      // figure out what is going on.
+      mapping.remove(node);
+    }
+    mapping[node] = target;
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    TargetElement target;
+    if (node.target == null) {
+      target = statementScope.currentContinueTarget();
+      if (target == null) {
+        error(node, MessageKind.NO_CONTINUE_TARGET);
+        return;
+      }
+      target.isContinueTarget = true;
+    } else {
+      String labelName = node.target.source.slowToString();
+      LabelElement label = statementScope.lookupLabel(labelName);
+      if (label == null) {
+        error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName});
+        return;
+      }
+      target = label.target;
+      if (!target.statement.isValidContinueTarget()) {
+        error(node.target, MessageKind.INVALID_CONTINUE);
+      }
+      // TODO(lrn): Handle continues to switch cases.
+      if (target.statement is SwitchCase) {
+        unimplemented(node, "continue to switch case");
+      }
+      label.setContinueTarget();
+      mapping[node.target] = label;
+    }
+    mapping[node] = target;
+  }
+
+  registerImplicitInvocation(SourceString name, int arity) {
+    Selector selector = new Selector.call(name, null, arity);
+    world.registerDynamicInvocation(name, selector);
+  }
+
+  registerImplicitFieldGet(SourceString name) {
+    Selector selector = new Selector.getter(name, null);
+    world.registerDynamicGetter(name, selector);
+  }
+
+  visitForIn(ForIn node) {
+    for (final name in const [
+        const SourceString('iterator'),
+        const SourceString('current')]) {
+      registerImplicitFieldGet(name);
+    }
+    registerImplicitInvocation(const SourceString('moveNext'), 0);
+    visit(node.expression);
+    Scope blockScope = new BlockScope(scope);
+    Node declaration = node.declaredIdentifier;
+    visitIn(declaration, blockScope);
+    visitLoopBodyIn(node, node.body, blockScope);
+
+    // TODO(lrn): Also allow a single identifier.
+    if ((declaration is !Send || declaration.asSend().selector is !Identifier
+        || declaration.asSend().receiver != null)
+        && (declaration is !VariableDefinitions ||
+        !declaration.asVariableDefinitions().definitions.nodes.tail.isEmpty))
+    {
+      // The variable declaration is either not an identifier, not a
+      // declaration, or it's declaring more than one variable.
+      error(node.declaredIdentifier, MessageKind.INVALID_FOR_IN);
+    }
+  }
+
+  visitLabel(Label node) {
+    // Labels are handled by their containing statements/cases.
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    Statement body = node.statement;
+    TargetElement targetElement = getOrCreateTargetElement(body);
+    Map<String, LabelElement> labelElements = <String, LabelElement>{};
+    for (Label label in node.labels) {
+      String labelName = label.slowToString();
+      if (labelElements.containsKey(labelName)) continue;
+      LabelElement element = targetElement.addLabel(label, labelName);
+      labelElements[labelName] = element;
+    }
+    statementScope.enterLabelScope(labelElements);
+    visit(node.statement);
+    statementScope.exitLabelScope();
+    labelElements.forEach((String labelName, LabelElement element) {
+      if (element.isTarget) {
+        mapping[element.label] = element;
+      } else {
+        warning(element.label, MessageKind.UNUSED_LABEL,
+                {'labelName': labelName});
+      }
+    });
+    if (!targetElement.isTarget && identical(mapping[body], targetElement)) {
+      // If the body is itself a break or continue for another target, it
+      // might have updated its mapping to the target it actually does target.
+      mapping.remove(body);
+    }
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    world.registerInstantiatedClass(compiler.mapClass);
+    node.visitChildren(this);
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    node.visitChildren(this);
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visit(node.expression);
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    node.expression.accept(this);
+
+    TargetElement breakElement = getOrCreateTargetElement(node);
+    Map<String, LabelElement> continueLabels = <String, LabelElement>{};
+    Link<Node> cases = node.cases.nodes;
+    while (!cases.isEmpty) {
+      SwitchCase switchCase = cases.head;
+      for (Node labelOrCase in switchCase.labelsAndCases) {
+        if (labelOrCase is! Label) continue;
+        Label label = labelOrCase;
+        String labelName = label.slowToString();
+
+        LabelElement existingElement = continueLabels[labelName];
+        if (existingElement != null) {
+          // It's an error if the same label occurs twice in the same switch.
+          warning(label, MessageKind.DUPLICATE_LABEL, {'labelName': labelName});
+          error(existingElement.label, MessageKind.EXISTING_LABEL,
+                {'labelName': labelName});
+        } else {
+          // It's only a warning if it shadows another label.
+          existingElement = statementScope.lookupLabel(labelName);
+          if (existingElement != null) {
+            warning(label, MessageKind.DUPLICATE_LABEL,
+                    {'labelName': labelName});
+            warning(existingElement.label,
+                    MessageKind.EXISTING_LABEL, {'labelName': labelName});
+          }
+        }
+
+        TargetElement targetElement =
+            new TargetElementX(switchCase,
+                               statementScope.nestingLevel,
+                               enclosingElement);
+        if (mapping[switchCase] != null) {
+          // TODO(ahe): Talk to Lasse about this.
+          mapping.remove(switchCase);
+        }
+        mapping[switchCase] = targetElement;
+
+        LabelElement labelElement =
+            new LabelElementX(label, labelName,
+                              targetElement, enclosingElement);
+        mapping[label] = labelElement;
+        continueLabels[labelName] = labelElement;
+      }
+      cases = cases.tail;
+      // Test that only the last case, if any, is a default case.
+      if (switchCase.defaultKeyword != null && !cases.isEmpty) {
+        error(switchCase, MessageKind.INVALID_CASE_DEFAULT);
+      }
+    }
+
+    statementScope.enterSwitch(breakElement, continueLabels);
+    node.cases.accept(this);
+    statementScope.exitSwitch();
+
+    // Clean-up unused labels.
+    continueLabels.forEach((String key, LabelElement label) {
+      if (!label.isContinueTarget) {
+        TargetElement targetElement = label.target;
+        SwitchCase switchCase = targetElement.statement;
+        mapping.remove(switchCase);
+        mapping.remove(label.label);
+      }
+    });
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    node.labelsAndCases.accept(this);
+    visitIn(node.statements, new BlockScope(scope));
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    visit(node.expression);
+  }
+
+  visitTryStatement(TryStatement node) {
+    visit(node.tryBlock);
+    if (node.catchBlocks.isEmpty && node.finallyBlock == null) {
+      // TODO(ngeoffray): The precise location is
+      // node.getEndtoken.next. Adjust when issue #1581 is fixed.
+      error(node, MessageKind.NO_CATCH_NOR_FINALLY);
+    }
+    visit(node.catchBlocks);
+    visit(node.finallyBlock);
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    // Check that if catch part is present, then
+    // it has one or two formal parameters.
+    if (node.formals != null) {
+      if (node.formals.isEmpty) {
+        error(node, MessageKind.EMPTY_CATCH_DECLARATION);
+      }
+      if (!node.formals.nodes.tail.isEmpty &&
+          !node.formals.nodes.tail.tail.isEmpty) {
+        for (Node extra in node.formals.nodes.tail.tail) {
+          error(extra, MessageKind.EXTRA_CATCH_DECLARATION);
+        }
+      }
+
+      // Check that the formals aren't optional and that they have no
+      // modifiers or type.
+      for (Link<Node> link = node.formals.nodes;
+           !link.isEmpty;
+           link = link.tail) {
+        // If the formal parameter is a node list, it means that it is a
+        // sequence of optional parameters.
+        NodeList nodeList = link.head.asNodeList();
+        if (nodeList != null) {
+          error(nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH);
+        } else {
+        VariableDefinitions declaration = link.head;
+          for (Node modifier in declaration.modifiers.nodes) {
+            error(modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH);
+          }
+          TypeAnnotation type = declaration.type;
+          if (type != null) {
+            error(type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH);
+          }
+        }
+      }
+    }
+
+    Scope blockScope = new BlockScope(scope);
+    var wasTypeRequired = typeRequired;
+    typeRequired = true;
+    doInCheckContext(() => visitIn(node.type, blockScope));
+    typeRequired = wasTypeRequired;
+    visitIn(node.formals, blockScope);
+    var oldInCatchBlock = inCatchBlock;
+    inCatchBlock = true;
+    visitIn(node.block, blockScope);
+    inCatchBlock = oldInCatchBlock;
+  }
+
+  visitTypedef(Typedef node) {
+    unimplemented(node, 'typedef');
+  }
+}
+
+class TypeDefinitionVisitor extends CommonResolverVisitor<DartType> {
+  Scope scope;
+  TypeDeclarationElement element;
+  TypeResolver typeResolver;
+
+  TypeDefinitionVisitor(Compiler compiler, TypeDeclarationElement element)
+      : this.element = element,
+        scope = Scope.buildEnclosingScope(element),
+        typeResolver = new TypeResolver(compiler),
+        super(compiler);
+
+  void resolveTypeVariableBounds(NodeList node) {
+    if (node == null) return;
+
+    var nameSet = new Set<SourceString>();
+    // Resolve the bounds of type variables.
+    Link<DartType> typeLink = element.typeVariables;
+    Link<Node> nodeLink = node.nodes;
+    while (!nodeLink.isEmpty) {
+      TypeVariableType typeVariable = typeLink.head;
+      SourceString typeName = typeVariable.name;
+      TypeVariable typeNode = nodeLink.head;
+      if (nameSet.contains(typeName)) {
+        error(typeNode, MessageKind.DUPLICATE_TYPE_VARIABLE_NAME,
+              {'typeVariableName': typeName});
+      }
+      nameSet.add(typeName);
+
+      TypeVariableElement variableElement = typeVariable.element;
+      if (typeNode.bound != null) {
+        DartType boundType = typeResolver.resolveTypeAnnotation(
+            typeNode.bound, scope, element, onFailure: warning);
+        if (boundType != null && boundType.element == variableElement) {
+          // TODO(johnniwinther): Check for more general cycles, like
+          // [: <A extends B, B extends C, C extends B> :].
+          warning(node, MessageKind.CYCLIC_TYPE_VARIABLE,
+                  {'typeVariableName': variableElement.name});
+        } else if (boundType != null) {
+          variableElement.bound = boundType;
+        } else {
+          // TODO(johnniwinther): Should be an erroneous type.
+          variableElement.bound = compiler.objectClass.computeType(compiler);
+        }
+      } else {
+        variableElement.bound = compiler.objectClass.computeType(compiler);
+      }
+      nodeLink = nodeLink.tail;
+      typeLink = typeLink.tail;
+    }
+    assert(typeLink.isEmpty);
+  }
+}
+
+class TypedefResolverVisitor extends TypeDefinitionVisitor {
+  TypedefElement get element => super.element;
+
+  TypedefResolverVisitor(Compiler compiler, TypedefElement typedefElement)
+      : super(compiler, typedefElement);
+
+  visitTypedef(Typedef node) {
+    TypedefType type = element.computeType(compiler);
+    scope = new TypeDeclarationScope(scope, element);
+    resolveTypeVariableBounds(node.typeParameters);
+
+    element.functionSignature = SignatureResolver.analyze(
+        compiler, node.formals, node.returnType, element);
+
+    element.alias = compiler.computeFunctionType(
+        element, element.functionSignature);
+
+    // TODO(johnniwinther): Check for cyclic references in the typedef alias.
+  }
+}
+
+/**
+ * The implementation of [ResolverTask.resolveClass].
+ *
+ * This visitor has to be extra careful as it is building the basic
+ * element information, and cannot safely look at other elements as
+ * this may lead to cycles.
+ *
+ * This visitor can assume that the supertypes have already been
+ * resolved, but it cannot call [ResolverTask.resolveClass] directly
+ * or indirectly (through [ClassElement.ensureResolved]) for any other
+ * types.
+ */
+class ClassResolverVisitor extends TypeDefinitionVisitor {
+  ClassElement get element => super.element;
+
+  ClassResolverVisitor(Compiler compiler, ClassElement classElement)
+    : super(compiler, classElement);
+
+  DartType visitClassNode(ClassNode node) {
+    compiler.ensure(element != null);
+    compiler.ensure(element.resolutionState == STATE_STARTED);
+
+    InterfaceType type = element.computeType(compiler);
+    scope = new TypeDeclarationScope(scope, element);
+    // TODO(ahe): It is not safe to call resolveTypeVariableBounds yet.
+    // As a side-effect, this may get us back here trying to
+    // resolve this class again.
+    resolveTypeVariableBounds(node.typeParameters);
+
+    // Setup the supertype for the element.
+    assert(element.supertype == null);
+    if (node.superclass != null) {
+      MixinApplication superMixin = node.superclass.asMixinApplication();
+      if (superMixin != null) {
+        DartType supertype = resolveSupertype(element, superMixin.superclass);
+        Link<Node> link = superMixin.mixins.nodes;
+        while (!link.isEmpty) {
+          supertype = applyMixin(supertype, visit(link.head));
+          link = link.tail;
+        }
+        element.supertype = supertype;
+      } else {
+        element.supertype = resolveSupertype(element, node.superclass);
+      }
+    }
+
+    // If the super type isn't specified, we make it Object.
+    final objectElement = compiler.objectClass;
+    if (!identical(element, objectElement) && element.supertype == null) {
+      if (objectElement == null) {
+        compiler.internalError("Internal error: cannot resolve Object",
+                               node: node);
+      } else {
+        objectElement.ensureResolved(compiler);
+      }
+      element.supertype = objectElement.computeType(compiler);
+    }
+
+    assert(element.interfaces == null);
+    element.interfaces = resolveInterfaces(node.interfaces, node.superclass);
+    calculateAllSupertypes(element);
+
+    if (node.defaultClause != null) {
+      element.defaultClass = visit(node.defaultClause);
+    }
+    element.addDefaultConstructorIfNeeded(compiler);
+    return element.computeType(compiler);
+  }
+
+  DartType visitNamedMixinApplication(NamedMixinApplication node) {
+    compiler.ensure(element != null);
+    compiler.ensure(element.resolutionState == STATE_STARTED);
+
+    InterfaceType type = element.computeType(compiler);
+    scope = new TypeDeclarationScope(scope, element);
+    resolveTypeVariableBounds(node.typeParameters);
+
+    // Generate anonymous mixin application elements for the
+    // intermediate mixin applications (excluding the last).
+    DartType supertype = resolveSupertype(element, node.superclass);
+    Link<Node> link = node.mixins.nodes;
+    while (!link.tail.isEmpty) {
+      supertype = applyMixin(supertype, visit(link.head));
+      link = link.tail;
+    }
+    doApplyMixinTo(element, supertype, visit(link.head));
+    return element.computeType(compiler);
+  }
+
+  DartType applyMixin(DartType supertype, DartType mixinType) {
+    String superName = supertype.name.slowToString();
+    String mixinName = mixinType.name.slowToString();
+    ClassElement mixinApplication = new MixinApplicationElementX(
+        new SourceString("${superName}_${mixinName}"),
+        element.getCompilationUnit(),
+        compiler.getNextFreeClassId(),
+        element.parseNode(compiler),
+        Modifiers.EMPTY);  // TODO(kasperl): Should this be abstract?
+    doApplyMixinTo(mixinApplication, supertype, mixinType);
+    mixinApplication.resolutionState = STATE_DONE;
+    mixinApplication.supertypeLoadState = STATE_DONE;
+    return mixinApplication.computeType(compiler);
+  }
+
+  void doApplyMixinTo(MixinApplicationElement mixinApplication,
+                      DartType supertype,
+                      DartType mixinType) {
+    assert(mixinApplication.supertype == null);
+    mixinApplication.supertype = supertype;
+
+    // Named mixin application may have an 'implements' clause.
+    NamedMixinApplication namedMixinApplication =
+        mixinApplication.parseNode(compiler).asNamedMixinApplication();
+    Link<DartType> interfaces = (namedMixinApplication != null)
+        ? resolveInterfaces(namedMixinApplication.interfaces,
+                            namedMixinApplication.superclass)
+        : const Link<DartType>();
+
+    // The class that is the result of a mixin application implements
+    // the interface of the class that was mixed in so always prepend
+    // that to the interface list.
+    interfaces = interfaces.prepend(mixinType);
+    assert(mixinApplication.interfaces == null);
+    mixinApplication.interfaces = interfaces;
+
+    assert(mixinApplication.mixin == null);
+    mixinApplication.mixin = resolveMixinFor(mixinApplication, mixinType);
+    mixinApplication.addDefaultConstructorIfNeeded(compiler);
+    calculateAllSupertypes(mixinApplication);
+  }
+
+  ClassElement resolveMixinFor(MixinApplicationElement mixinApplication,
+                               DartType mixinType) {
+    ClassElement mixin = mixinType.element;
+    mixin.ensureResolved(compiler);
+
+    // Check for cycles in the mixin chain.
+    ClassElement previous = mixinApplication;  // For better error messages.
+    ClassElement current = mixin;
+    while (current != null && current.isMixinApplication) {
+      MixinApplicationElement currentMixinApplication = current;
+      if (currentMixinApplication == mixinApplication) {
+        compiler.reportErrorCode(
+            mixinApplication, MessageKind.ILLEGAL_MIXIN_CYCLE,
+            {'mixinName1': current.name, 'mixinName2': previous.name});
+        // We have found a cycle in the mixin chain. Return null as
+        // the mixin for this application to avoid getting into
+        // infinite recursion when traversing members.
+        return null;
+      }
+      previous = current;
+      current = currentMixinApplication.mixin;
+    }
+    compiler.world.registerMixinUse(mixinApplication, mixin);
+    return mixin;
+  }
+
+  // TODO(johnniwinther): Remove when default class is no longer supported.
+  DartType visitTypeAnnotation(TypeAnnotation node) {
+    return visit(node.typeName);
+  }
+
+  // TODO(johnniwinther): Remove when default class is no longer supported.
+  DartType visitIdentifier(Identifier node) {
+    Element element = scope.lookup(node.source);
+    if (element == null) {
+      error(node, MessageKind.CANNOT_RESOLVE_TYPE,  {'typeName': node});
+      return null;
+    } else if (!element.impliesType() && !element.isTypeVariable()) {
+      error(node, MessageKind.NOT_A_TYPE, {'node': node});
+      return null;
+    } else {
+      if (element.isTypeVariable()) {
+        TypeVariableElement variableElement = element;
+        return variableElement.type;
+      } else if (element.isTypedef()) {
+        compiler.unimplemented('visitIdentifier for typedefs', node: node);
+      } else {
+        // TODO(ngeoffray): Use type variables.
+        return element.computeType(compiler);
+      }
+    }
+    return null;
+  }
+
+  // TODO(johnniwinther): Remove when default class is no longer supported.
+  DartType visitSend(Send node) {
+    Identifier prefix = node.receiver.asIdentifier();
+    if (prefix == null) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return null;
+    }
+    Element element = scope.lookup(prefix.source);
+    if (element == null || !identical(element.kind, ElementKind.PREFIX)) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return null;
+    }
+    PrefixElement prefixElement = element;
+    Identifier selector = node.selector.asIdentifier();
+    var e = prefixElement.lookupLocalMember(selector.source);
+    if (e == null || !e.impliesType()) {
+      error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE,
+            {'typeName': node.selector});
+      return null;
+    }
+    return e.computeType(compiler);
+  }
+
+  DartType resolveSupertype(ClassElement cls, TypeAnnotation superclass) {
+    DartType supertype = typeResolver.resolveTypeAnnotation(
+        superclass, scope, cls, onFailure: error);
+    if (supertype != null) {
+      if (identical(supertype.kind, TypeKind.MALFORMED_TYPE)) {
+        // Error has already been reported.
+        return null;
+      } else if (!identical(supertype.kind, TypeKind.INTERFACE)) {
+        // TODO(johnniwinther): Handle dynamic.
+        error(superclass.typeName, MessageKind.CLASS_NAME_EXPECTED);
+        return null;
+      } else if (isBlackListed(supertype)) {
+        error(superclass, MessageKind.CANNOT_EXTEND, {'type': supertype});
+        return null;
+      }
+    }
+    return supertype;
+  }
+
+  Link<DartType> resolveInterfaces(NodeList interfaces, Node superclass) {
+    Link<DartType> result = const Link<DartType>();
+    if (interfaces == null) return result;
+    for (Link<Node> link = interfaces.nodes; !link.isEmpty; link = link.tail) {
+      DartType interfaceType = typeResolver.resolveTypeAnnotation(
+          link.head, scope, element, onFailure: error);
+      if (interfaceType != null) {
+        if (identical(interfaceType.kind, TypeKind.MALFORMED_TYPE)) {
+          // Error has already been reported.
+        } else if (!identical(interfaceType.kind, TypeKind.INTERFACE)) {
+          // TODO(johnniwinther): Handle dynamic.
+          TypeAnnotation typeAnnotation = link.head;
+          error(typeAnnotation.typeName, MessageKind.CLASS_NAME_EXPECTED);
+        } else {
+          if (interfaceType == element.supertype) {
+            compiler.reportErrorCode(
+                superclass,
+                MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS,
+                {'type': interfaceType});
+            compiler.reportErrorCode(
+                link.head,
+                MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS,
+                {'type': interfaceType});
+          }
+          if (result.contains(interfaceType)) {
+            compiler.reportErrorCode(
+                link.head,
+                MessageKind.DUPLICATE_IMPLEMENTS,
+                {'type': interfaceType});
+          }
+          result = result.prepend(interfaceType);
+          if (isBlackListed(interfaceType)) {
+            error(link.head, MessageKind.CANNOT_IMPLEMENT,
+                  {'type': interfaceType});
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  void calculateAllSupertypes(ClassElement cls) {
+    // TODO(karlklose): Check if type arguments match, if a class
+    // element occurs more than once in the supertypes.
+    if (cls.allSupertypes != null) return;
+    final DartType supertype = cls.supertype;
+    if (supertype != null) {
+      var allSupertypes = new LinkBuilder<DartType>();
+      addAllSupertypes(allSupertypes, supertype);
+      for (Link<DartType> interfaces = cls.interfaces;
+           !interfaces.isEmpty;
+           interfaces = interfaces.tail) {
+        addAllSupertypes(allSupertypes, interfaces.head);
+      }
+      cls.allSupertypes = allSupertypes.toLink();
+    } else {
+      assert(identical(cls, compiler.objectClass));
+      cls.allSupertypes = const Link<DartType>();
+    }
+ }
+
+  /**
+   * Adds [type] and all supertypes of [type] to [builder] while substituting
+   * type variables.
+   */
+  void addAllSupertypes(LinkBuilder<DartType> builder, InterfaceType type) {
+    builder.addLast(type);
+    Link<DartType> typeArguments = type.typeArguments;
+    ClassElement classElement = type.element;
+    Link<DartType> typeVariables = classElement.typeVariables;
+    Link<DartType> supertypes = classElement.allSupertypes;
+    assert(invariant(element, supertypes != null,
+        message: "Supertypes not computed on $classElement "
+                 "during resolution of $element"));
+    while (!supertypes.isEmpty) {
+      DartType supertype = supertypes.head;
+      builder.addLast(supertype.subst(typeArguments, typeVariables));
+      supertypes = supertypes.tail;
+    }
+  }
+
+  isBlackListed(DartType type) {
+    LibraryElement lib = element.getLibrary();
+    return
+      !identical(lib, compiler.coreLibrary) &&
+      !identical(lib, compiler.jsHelperLibrary) &&
+      !identical(lib, compiler.interceptorsLibrary) &&
+      (identical(type.element, compiler.dynamicClass) ||
+       identical(type.element, compiler.boolClass) ||
+       identical(type.element, compiler.numClass) ||
+       identical(type.element, compiler.intClass) ||
+       identical(type.element, compiler.doubleClass) ||
+       identical(type.element, compiler.stringClass) ||
+       identical(type.element, compiler.nullClass) ||
+       identical(type.element, compiler.functionClass));
+  }
+}
+
+class ClassSupertypeResolver extends CommonResolverVisitor {
+  Scope context;
+  ClassElement classElement;
+
+  ClassSupertypeResolver(Compiler compiler, ClassElement cls)
+    : context = Scope.buildEnclosingScope(cls),
+      this.classElement = cls,
+      super(compiler);
+
+  void loadSupertype(ClassElement element, Node from) {
+    compiler.resolver.loadSupertypes(element, from);
+    element.ensureResolved(compiler);
+  }
+
+  void visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      link.head.accept(this);
+    }
+  }
+
+  void visitClassNode(ClassNode node) {
+    if (node.superclass == null) {
+      if (!identical(classElement, compiler.objectClass)) {
+        loadSupertype(compiler.objectClass, node);
+      }
+    } else {
+      node.superclass.accept(this);
+    }
+    visitNodeList(node.interfaces);
+  }
+
+  void visitMixinApplication(MixinApplication node) {
+    node.superclass.accept(this);
+    visitNodeList(node.mixins);
+  }
+
+  void visitTypeAnnotation(TypeAnnotation node) {
+    node.typeName.accept(this);
+  }
+
+  void visitIdentifier(Identifier node) {
+    Element element = context.lookup(node.source);
+    if (element == null) {
+      error(node, MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node});
+    } else if (!element.impliesType()) {
+      error(node, MessageKind.NOT_A_TYPE, {'node': node});
+    } else {
+      if (element.isClass()) {
+        loadSupertype(element, node);
+      } else {
+        compiler.reportErrorCode(node, MessageKind.CLASS_NAME_EXPECTED);
+      }
+    }
+  }
+
+  void visitSend(Send node) {
+    Identifier prefix = node.receiver.asIdentifier();
+    if (prefix == null) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return;
+    }
+    Element element = context.lookup(prefix.source);
+    if (element == null || !identical(element.kind, ElementKind.PREFIX)) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return;
+    }
+    PrefixElement prefixElement = element;
+    Identifier selector = node.selector.asIdentifier();
+    var e = prefixElement.lookupLocalMember(selector.source);
+    if (e == null || !e.impliesType()) {
+      error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE,
+            {'typeName': node.selector});
+      return;
+    }
+    loadSupertype(e, node);
+  }
+}
+
+class VariableDefinitionsVisitor extends CommonResolverVisitor<SourceString> {
+  VariableDefinitions definitions;
+  ResolverVisitor resolver;
+  ElementKind kind;
+  VariableListElement variables;
+
+  VariableDefinitionsVisitor(Compiler compiler,
+                             this.definitions, this.resolver, this.kind)
+      : super(compiler) {
+    variables = new VariableListElementX.node(
+        definitions, ElementKind.VARIABLE_LIST, resolver.enclosingElement);
+  }
+
+  SourceString visitSendSet(SendSet node) {
+    assert(node.arguments.tail.isEmpty); // Sanity check
+    resolver.visit(node.arguments.head);
+    return visit(node.selector);
+  }
+
+  SourceString visitIdentifier(Identifier node) {
+    // The variable is initialized to null.
+    resolver.world.registerInstantiatedClass(compiler.nullClass);
+    return node.source;
+  }
+
+  visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      SourceString name = visit(link.head);
+      VariableElement element =
+          new VariableElementX(name, variables, kind, link.head);
+      resolver.defineElement(link.head, element);
+    }
+  }
+}
+
+/**
+ * [SignatureResolver] resolves function signatures.
+ */
+class SignatureResolver extends CommonResolverVisitor<Element> {
+  final Element enclosingElement;
+  Link<Element> optionalParameters = const Link<Element>();
+  int optionalParameterCount = 0;
+  bool optionalParametersAreNamed = false;
+  VariableDefinitions currentDefinitions;
+
+  SignatureResolver(Compiler compiler, this.enclosingElement) : super(compiler);
+
+  Element visitNodeList(NodeList node) {
+    // This must be a list of optional arguments.
+    String value = node.beginToken.stringValue;
+    if ((!identical(value, '[')) && (!identical(value, '{'))) {
+      internalError(node, "expected optional parameters");
+    }
+    optionalParametersAreNamed = (identical(value, '{'));
+    LinkBuilder<Element> elements = analyzeNodes(node.nodes);
+    optionalParameterCount = elements.length;
+    optionalParameters = elements.toLink();
+    return null;
+  }
+
+  Element visitVariableDefinitions(VariableDefinitions node) {
+    Link<Node> definitions = node.definitions.nodes;
+    if (definitions.isEmpty) {
+      cancel(node, 'internal error: no parameter definition');
+      return null;
+    }
+    if (!definitions.tail.isEmpty) {
+      cancel(definitions.tail.head, 'internal error: extra definition');
+      return null;
+    }
+    Node definition = definitions.head;
+    if (definition is NodeList) {
+      cancel(node, 'optional parameters are not implemented');
+    }
+
+    if (currentDefinitions != null) {
+      cancel(node, 'function type parameters not supported');
+    }
+    currentDefinitions = node;
+    Element element = definition.accept(this);
+    currentDefinitions = null;
+    return element;
+  }
+
+  Element visitIdentifier(Identifier node) {
+    Element variables = new VariableListElementX.node(currentDefinitions,
+        ElementKind.VARIABLE_LIST, enclosingElement);
+    // Ensure a parameter is not typed 'void'.
+    variables.computeType(compiler);
+    return new VariableElementX(node.source, variables,
+        ElementKind.PARAMETER, node);
+  }
+
+  SourceString getParameterName(Send node) {
+    var identifier = node.selector.asIdentifier();
+    if (identifier != null) {
+      // Normal parameter: [:Type name:].
+      return identifier.source;
+    } else {
+      // Function type parameter: [:void name(DartType arg):].
+      var functionExpression = node.selector.asFunctionExpression();
+      if (functionExpression != null &&
+          functionExpression.name.asIdentifier() != null) {
+        return functionExpression.name.asIdentifier().source;
+      } else {
+        cancel(node,
+            'internal error: unimplemented receiver on parameter send');
+      }
+    }
+  }
+
+  // The only valid [Send] can be in constructors and must be of the form
+  // [:this.x:] (where [:x:] represents an instance field).
+  FieldParameterElement visitSend(Send node) {
+    FieldParameterElement element;
+    if (node.receiver.asIdentifier() == null ||
+        !node.receiver.asIdentifier().isThis()) {
+      error(node, MessageKind.INVALID_PARAMETER);
+    } else if (!identical(enclosingElement.kind,
+                          ElementKind.GENERATIVE_CONSTRUCTOR)) {
+      error(node, MessageKind.FIELD_PARAMETER_NOT_ALLOWED);
+    } else {
+      SourceString name = getParameterName(node);
+      Element fieldElement = currentClass.lookupLocalMember(name);
+      if (fieldElement == null ||
+          !identical(fieldElement.kind, ElementKind.FIELD)) {
+        error(node, MessageKind.NOT_A_FIELD, {'fieldName': name});
+      } else if (!fieldElement.isInstanceMember()) {
+        error(node, MessageKind.NOT_INSTANCE_FIELD, {'fieldName': name});
+      }
+      Element variables = new VariableListElementX.node(currentDefinitions,
+          ElementKind.VARIABLE_LIST, enclosingElement);
+      element = new FieldParameterElementX(name, fieldElement, variables, node);
+    }
+    return element;
+  }
+
+  Element visitSendSet(SendSet node) {
+    Element element;
+    if (node.receiver != null) {
+      element = visitSend(node);
+    } else if (node.selector.asIdentifier() != null) {
+      Element variables = new VariableListElementX.node(currentDefinitions,
+          ElementKind.VARIABLE_LIST, enclosingElement);
+      element = new VariableElementX(node.selector.asIdentifier().source,
+          variables, ElementKind.PARAMETER, node);
+    }
+    // Visit the value. The compile time constant handler will
+    // make sure it's a compile time constant.
+    resolveExpression(node.arguments.head);
+    return element;
+  }
+
+  Element visitFunctionExpression(FunctionExpression node) {
+    // This is a function typed parameter.
+    // TODO(ahe): Resolve the function type.
+    return visit(node.name);
+  }
+
+  LinkBuilder<Element> analyzeNodes(Link<Node> link) {
+    LinkBuilder<Element> elements = new LinkBuilder<Element>();
+    for (; !link.isEmpty; link = link.tail) {
+      Element element = link.head.accept(this);
+      if (element != null) {
+        elements.addLast(element);
+      } else {
+        // If parameter is null, the current node should be the last,
+        // and a list of optional named parameters.
+        if (!link.tail.isEmpty || (link.head is !NodeList)) {
+          internalError(link.head, "expected optional parameters");
+        }
+      }
+    }
+    return elements;
+  }
+
+  /**
+   * Resolves formal parameters and return type to a [FunctionSignature].
+   */
+  static FunctionSignature analyze(Compiler compiler,
+                                   NodeList formalParameters,
+                                   Node returnNode,
+                                   Element element) {
+    SignatureResolver visitor = new SignatureResolver(compiler, element);
+    Link<Element> parameters = const Link<Element>();
+    int requiredParameterCount = 0;
+    if (formalParameters == null) {
+      if (!element.isGetter()) {
+        compiler.reportErrorCode(element, MessageKind.MISSING_FORMALS);
+      }
+    } else {
+      if (element.isGetter()) {
+        if (!identical(formalParameters.getEndToken().next.stringValue,
+                       // TODO(ahe): Remove the check for native keyword.
+                       'native')) {
+          if (compiler.rejectDeprecatedFeatures &&
+              // TODO(ahe): Remove isPlatformLibrary check.
+              !element.getLibrary().isPlatformLibrary) {
+            compiler.reportErrorCode(formalParameters,
+                                     MessageKind.EXTRA_FORMALS);
+          } else {
+            compiler.onDeprecatedFeature(formalParameters, 'getter parameters');
+          }
+        }
+      }
+      LinkBuilder<Element> parametersBuilder =
+        visitor.analyzeNodes(formalParameters.nodes);
+      requiredParameterCount  = parametersBuilder.length;
+      parameters = parametersBuilder.toLink();
+    }
+    DartType returnType = compiler.resolveReturnType(element, returnNode);
+    if (element.isSetter() && (requiredParameterCount != 1 ||
+                               visitor.optionalParameterCount != 0)) {
+      // If there are no formal parameters, we already reported an error above.
+      if (formalParameters != null) {
+        compiler.reportErrorCode(formalParameters,
+                                 MessageKind.ILLEGAL_SETTER_FORMALS);
+      }
+    }
+    if (element.isGetter() && (requiredParameterCount != 0
+                               || visitor.optionalParameterCount != 0)) {
+      compiler.reportErrorCode(formalParameters, MessageKind.EXTRA_FORMALS);
+    }
+    return new FunctionSignatureX(parameters,
+                                  visitor.optionalParameters,
+                                  requiredParameterCount,
+                                  visitor.optionalParameterCount,
+                                  visitor.optionalParametersAreNamed,
+                                  returnType);
+  }
+
+  // TODO(ahe): This is temporary.
+  void resolveExpression(Node node) {
+    if (node == null) return;
+    node.accept(new ResolverVisitor(compiler, enclosingElement,
+                                    new TreeElementMapping(enclosingElement)));
+  }
+
+  // TODO(ahe): This is temporary.
+  ClassElement get currentClass {
+    return enclosingElement.isMember()
+      ? enclosingElement.getEnclosingClass() : null;
+  }
+}
+
+class ConstructorResolver extends CommonResolverVisitor<Element> {
+  final ResolverVisitor resolver;
+  bool inConstContext = false;
+  DartType type;
+
+  ConstructorResolver(Compiler compiler, this.resolver) : super(compiler);
+
+  visitNode(Node node) {
+    throw 'not supported';
+  }
+
+  failOrReturnErroneousElement(Element enclosing, Node diagnosticNode,
+                               SourceString targetName, MessageKind kind,
+                               Map arguments) {
+    if (inConstContext) {
+      error(diagnosticNode, kind, arguments);
+    } else {
+      ResolutionWarning warning  = new ResolutionWarning(kind, arguments);
+      compiler.reportWarning(diagnosticNode, warning);
+      return new ErroneousElementX(kind, arguments, targetName, enclosing);
+    }
+  }
+
+  Selector createConstructorSelector(SourceString constructorName) {
+    return constructorName == const SourceString('')
+        ? new Selector.callDefaultConstructor(
+            resolver.enclosingElement.getLibrary())
+        : new Selector.callConstructor(
+            constructorName,
+            resolver.enclosingElement.getLibrary());
+  }
+
+  // TODO(ngeoffray): method named lookup should not report errors.
+  FunctionElement lookupConstructor(ClassElement cls,
+                                    Node diagnosticNode,
+                                    SourceString constructorName) {
+    cls.ensureResolved(compiler);
+    Selector selector = createConstructorSelector(constructorName);
+    Element result = cls.lookupConstructor(selector);
+    if (result == null) {
+      String fullConstructorName =
+          resolver.compiler.resolver.constructorNameForDiagnostics(
+              cls.name,
+              constructorName);
+      return failOrReturnErroneousElement(
+          cls,
+          diagnosticNode,
+          new SourceString(fullConstructorName),
+          MessageKind.CANNOT_FIND_CONSTRUCTOR,
+          {'constructorName': fullConstructorName});
+    } else if (inConstContext && !result.modifiers.isConst()) {
+      error(diagnosticNode, MessageKind.CONSTRUCTOR_IS_NOT_CONST);
+    }
+    return result;
+  }
+
+  visitNewExpression(NewExpression node) {
+    inConstContext = node.isConst();
+    Node selector = node.send.selector;
+    Element e = visit(selector);
+    return finishConstructorReference(e, node.send.selector, node);
+  }
+
+  /// Finishes resolution of a constructor reference and records the
+  /// type of the constructed instance on [expression].
+  FunctionElement finishConstructorReference(Element e,
+                                             Node diagnosticNode,
+                                             Node expression) {
+    // Find the unnamed constructor if the reference resolved to a
+    // class.
+    if (!Elements.isUnresolved(e) && e.isClass()) {
+      ClassElement cls = e;
+      cls.ensureResolved(compiler);
+      if (cls.isInterface() && (cls.defaultClass == null)) {
+        // TODO(ahe): Remove this check and error message when we
+        // don't have interfaces anymore.
+        error(diagnosticNode,
+              MessageKind.CANNOT_INSTANTIATE_INTERFACE,
+              {'interfaceName': cls.name});
+      }
+      // The unnamed constructor may not exist, so [e] may become unresolved.
+      e = lookupConstructor(cls, diagnosticNode, const SourceString(''));
+    }
+    if (type == null) {
+      if (Elements.isUnresolved(e)) {
+        type = compiler.dynamicClass.computeType(compiler);
+      } else {
+        type = e.getEnclosingClass().computeType(compiler).asRaw();
+      }
+    }
+    resolver.mapping.setType(expression, type);
+    return e;
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    assert(invariant(node, type == null));
+    type = resolver.resolveTypeRequired(node);
+    return resolver.mapping[node];
+  }
+
+  visitSend(Send node) {
+    Element e = visit(node.receiver);
+    if (Elements.isUnresolved(e)) return e;
+    Identifier name = node.selector.asIdentifier();
+    if (name == null) internalError(node.selector, 'unexpected node');
+
+    if (identical(e.kind, ElementKind.CLASS)) {
+      ClassElement cls = e;
+      cls.ensureResolved(compiler);
+      if (cls.isInterface() && (cls.defaultClass == null)) {
+        error(node.receiver,
+              MessageKind.CANNOT_INSTANTIATE_INTERFACE,
+              {'interfaceName': cls.name});
+      }
+      return lookupConstructor(cls, name, name.source);
+    } else if (identical(e.kind, ElementKind.PREFIX)) {
+      PrefixElement prefix = e;
+      e = prefix.lookupLocalMember(name.source);
+      if (e == null) {
+        return failOrReturnErroneousElement(resolver.enclosingElement, name,
+                                            name.source,
+                                            MessageKind.CANNOT_RESOLVE,
+                                            {'name': name});
+      } else if (!identical(e.kind, ElementKind.CLASS)) {
+        error(node, MessageKind.NOT_A_TYPE, {'node': name});
+      }
+    } else {
+      internalError(node.receiver, 'unexpected element $e');
+    }
+    return e;
+  }
+
+  Element visitIdentifier(Identifier node) {
+    SourceString name = node.source;
+    Element e = resolver.lookup(node, name);
+    // TODO(johnniwinther): Change errors to warnings, cf. 11.11.1.
+    if (e == null) {
+      return failOrReturnErroneousElement(resolver.enclosingElement, node, name,
+                                          MessageKind.CANNOT_RESOLVE,
+                                          {'name': name});
+    } else if (e.isErroneous()) {
+      return e;
+    } else if (identical(e.kind, ElementKind.TYPEDEF)) {
+      error(node, MessageKind.CANNOT_INSTANTIATE_TYPEDEF,
+            {'typedefName': name});
+    } else if (identical(e.kind, ElementKind.TYPE_VARIABLE)) {
+      error(node, MessageKind.CANNOT_INSTANTIATE_TYPE_VARIABLE,
+            {'typeVariableName': name});
+    } else if (!identical(e.kind, ElementKind.CLASS)
+        && !identical(e.kind, ElementKind.PREFIX)) {
+      error(node, MessageKind.NOT_A_TYPE, {'node': name});
+    }
+    return e;
+  }
+
+  /// Assumed to be called by [resolveRedirectingFactory].
+  Element visitReturn(Return node) {
+    Node expression = node.expression;
+    return finishConstructorReference(visit(expression),
+                                      expression, expression);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart
new file mode 100644
index 0000000..130486c
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart
@@ -0,0 +1,30 @@
+// 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.
+
+library resolution;
+
+import 'dart:collection' show Queue, LinkedHashMap;
+
+import '../dart2jslib.dart' hide Diagnostic;
+import '../dart_types.dart';
+import '../../compiler.dart' show Diagnostic;
+import '../tree/tree.dart';
+import '../elements/elements.dart';
+import '../elements/modelx.dart'
+    show FunctionElementX,
+         ErroneousElementX,
+         VariableElementX,
+         FieldParameterElementX,
+         VariableListElementX,
+         FunctionSignatureX,
+         LabelElementX,
+         TargetElementX,
+         MixinApplicationElementX;
+import '../util/util.dart';
+import '../scanner/scannerlib.dart' show PartialMetadataAnnotation;
+
+import 'secret_tree_element.dart' show getTreeElement, setTreeElement;
+
+part 'members.dart';
+part 'scope.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart
new file mode 100644
index 0000000..92e7840
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart
@@ -0,0 +1,163 @@
+// 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.
+
+part of resolution;
+
+abstract class Scope {
+  /**
+   * Adds [element] to this scope. This operation is only allowed on mutable
+   * scopes such as [MethodScope] and [BlockScope].
+   */
+  Element add(Element element);
+
+  /**
+   * Looks up the [Element] for [name] in this scope.
+   */
+  Element lookup(SourceString name);
+
+  static Scope buildEnclosingScope(Element element) {
+    return element.enclosingElement != null
+        ? element.enclosingElement.buildScope() : element.buildScope();
+  }
+}
+
+abstract class NestedScope extends Scope {
+  final Scope parent;
+
+  NestedScope(this.parent);
+
+  Element lookup(SourceString name) {
+    Element result = localLookup(name);
+    if (result != null) return result;
+    return parent.lookup(name);
+  }
+
+  Element localLookup(SourceString name);
+
+  static Scope buildEnclosingScope(Element element) {
+    return element.enclosingElement != null
+        ? element.enclosingElement.buildScope() : element.buildScope();
+  }
+}
+
+/**
+ * [TypeDeclarationScope] defines the outer scope of a type declaration in
+ * which the declared type variables and the entities in the enclosing scope are
+ * available but where declared and inherited members are not available. This
+ * scope is only used for class/interface declarations during resolution of the
+ * class hierarchy. In all other cases [ClassScope] is used.
+ */
+class TypeDeclarationScope extends NestedScope {
+  final TypeDeclarationElement element;
+
+  TypeDeclarationScope(parent, this.element)
+      : super(parent) {
+    assert(parent != null);
+  }
+
+  Element add(Element newElement) {
+    throw "Cannot add element to TypeDeclarationScope";
+  }
+
+  Element lookupTypeVariable(SourceString name) {
+    Link<DartType> typeVariableLink = element.typeVariables;
+    while (!typeVariableLink.isEmpty) {
+      TypeVariableType typeVariable = typeVariableLink.head;
+      if (typeVariable.name == name) {
+        return typeVariable.element;
+      }
+      typeVariableLink = typeVariableLink.tail;
+    }
+    return null;
+  }
+
+  Element localLookup(SourceString name) => lookupTypeVariable(name);
+
+  String toString() =>
+      'TypeDeclarationScope($element)';
+}
+
+abstract class MutableScope extends NestedScope {
+  final Map<SourceString, Element> elements;
+
+  MutableScope(Scope parent)
+      : super(parent),
+        this.elements = new Map<SourceString, Element>() {
+    assert(parent != null);
+  }
+
+  Element add(Element newElement) {
+    if (elements.containsKey(newElement.name)) {
+      return elements[newElement.name];
+    }
+    elements[newElement.name] = newElement;
+    return newElement;
+  }
+
+  Element localLookup(SourceString name) => elements[name];
+}
+
+class MethodScope extends MutableScope {
+  final Element element;
+
+  MethodScope(Scope parent, this.element)
+      : super(parent);
+
+  String toString() => 'MethodScope($element${elements.keys.toList()})';
+}
+
+class BlockScope extends MutableScope {
+  BlockScope(Scope parent) : super(parent);
+
+  String toString() => 'BlockScope(${elements.keys.toList()})';
+}
+
+/**
+ * [ClassScope] defines the inner scope of a class/interface declaration in
+ * which declared members, declared type variables, entities in the enclosing
+ * scope and inherited members are available, in the given order.
+ */
+class ClassScope extends TypeDeclarationScope {
+  ClassElement get element => super.element;
+
+  ClassScope(Scope parentScope, ClassElement element)
+      : super(parentScope, element)  {
+    assert(parent != null);
+  }
+
+  Element localLookup(SourceString name) {
+    Element result = element.lookupLocalMember(name);
+    if (result != null) return result;
+    return super.localLookup(name);
+  }
+
+  Element lookup(SourceString name) {
+    Element result = localLookup(name);
+    if (result != null) return result;
+    result = parent.lookup(name);
+    if (result != null) return result;
+    return element.lookupSuperMember(name);
+  }
+
+  Element add(Element newElement) {
+    throw "Cannot add an element in a class scope";
+  }
+
+  String toString() => 'ClassScope($element)';
+}
+
+class LibraryScope implements Scope {
+  final LibraryElement library;
+
+  LibraryScope(LibraryElement this.library);
+
+  Element localLookup(SourceString name) => library.find(name);
+  Element lookup(SourceString name) => localLookup(name);
+
+  Element add(Element newElement) {
+    throw "Cannot add an element to a library scope";
+  }
+
+  String toString() => 'LibraryScope($library)';
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart
new file mode 100644
index 0000000..e8ee0f1
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart
@@ -0,0 +1,46 @@
+// 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.
+
+/**
+ * Encapsulates the field [TreeElementMixin._element].
+ *
+ * This library is an implementation detail of dart2js, and should not
+ * be imported except by resolution and tree node libraries, or for
+ * testing.
+ *
+ * We have taken great care to ensure AST nodes can be cached between
+ * compiler instances.  Part of this is requires that we always access
+ * resolution results through [TreeElements].
+ *
+ * So please, do not add additional elements to this library, and do
+ * not import it.
+ */
+library secret_tree_element;
+
+/**
+ * The superclass of all AST nodes.
+ */
+abstract class TreeElementMixin {
+  // Deliberately using [Object] here to thwart code completion.
+  // You're not really supposed to access this field anyways.
+  Object _element;
+}
+
+/**
+ * Do not call this method directly.  Instead, use an instance of
+ * [TreeElements].
+ *
+ * Using [Object] as return type to thwart code completion.
+ */
+Object getTreeElement(TreeElementMixin node) {
+  return node._element;
+}
+
+/**
+ * Do not call this method directly.  Instead, use an instance of
+ * [TreeElements].
+ */
+void setTreeElement(TreeElementMixin node, Object value) {
+  node._element = value;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart b/pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart
new file mode 100644
index 0000000..3bfadb2
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart
@@ -0,0 +1,67 @@
+// 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.
+
+part of dart2js;
+
+abstract class ResolvedVisitor<R> extends Visitor<R> {
+  TreeElements elements;
+
+  ResolvedVisitor(this.elements);
+
+  R visitSend(Send node) {
+    if (node.isSuperCall) {
+      return visitSuperSend(node);
+    } else if (node.isOperator) {
+      return visitOperatorSend(node);
+    } else if (node.isPropertyAccess) {
+      Element element = elements[node];
+      if (!Elements.isUnresolved(element) && element.impliesType()) {
+        // A reference to a class literal, typedef or type variable.
+        return visitTypeReferenceSend(node);
+      } else {
+        return visitGetterSend(node);
+      }
+    } else if (Elements.isClosureSend(node, elements[node])) {
+      return visitClosureSend(node);
+    } else {
+      Element element = elements[node];
+      if (Elements.isUnresolved(element)) {
+        if (element == null) {
+          // Example: f() with 'f' unbound.
+          // This can only happen inside an instance method.
+          return visitDynamicSend(node);
+        } else {
+          return visitStaticSend(node);
+        }
+      } else if (element.impliesType()) {
+        // A reference to a class literal, typedef or type variable.
+        return visitTypeReferenceSend(node);
+      } else if (element.isInstanceMember()) {
+        // Example: f() with 'f' bound to instance method.
+        return visitDynamicSend(node);
+      } else if (!element.isInstanceMember()) {
+        // Example: A.f() or f() with 'f' bound to a static function.
+        // Also includes new A() or new A.named() which is treated like a
+        // static call to a factory.
+        return visitStaticSend(node);
+      } else {
+        internalError("Cannot generate code for send", node: node);
+      }
+    }
+  }
+
+  R visitSuperSend(Send node);
+  R visitOperatorSend(Send node);
+  R visitGetterSend(Send node);
+  R visitClosureSend(Send node);
+  R visitDynamicSend(Send node);
+  R visitStaticSend(Send node);
+  R visitTypeReferenceSend(Send node);
+
+  void internalError(String reason, {Node node});
+
+  R visitNode(Node node) {
+    internalError("Unhandled node", node: node);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart
new file mode 100644
index 0000000..01f8e9e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2011, 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.
+
+part of scanner_implementation;
+
+abstract
+class ArrayBasedScanner<S extends SourceString> extends AbstractScanner<S> {
+  int get charOffset => byteOffset + extraCharOffset;
+  final Token tokens;
+  Token tail;
+  int tokenStart;
+  int byteOffset;
+  final bool includeComments;
+
+  /** Since the input is UTF8, some characters are represented by more
+   * than one byte. [extraCharOffset] tracks the difference. */
+  int extraCharOffset;
+  Link<BeginGroupToken> groupingStack = const Link<BeginGroupToken>();
+
+  ArrayBasedScanner(this.includeComments)
+    : this.extraCharOffset = 0,
+      this.tokenStart = -1,
+      this.byteOffset = -1,
+      this.tokens = new Token(EOF_INFO, -1) {
+    this.tail = this.tokens;
+  }
+
+  int advance() {
+    int next = nextByte();
+    return next;
+  }
+
+  int select(int choice, PrecedenceInfo yes, PrecedenceInfo no) {
+    int next = advance();
+    if (identical(next, choice)) {
+      appendPrecedenceToken(yes);
+      return advance();
+    } else {
+      appendPrecedenceToken(no);
+      return next;
+    }
+  }
+
+  void appendPrecedenceToken(PrecedenceInfo info) {
+    tail.next = new Token(info, tokenStart);
+    tail = tail.next;
+  }
+
+  void appendStringToken(PrecedenceInfo info, String value) {
+    tail.next = new StringToken(info, value, tokenStart);
+    tail = tail.next;
+  }
+
+  void appendKeywordToken(Keyword keyword) {
+    String syntax = keyword.syntax;
+
+    // Type parameters and arguments cannot contain 'this' or 'super'.
+    if (identical(syntax, 'this') || identical(syntax, 'super')) discardOpenLt();
+    tail.next = new KeywordToken(keyword, tokenStart);
+    tail = tail.next;
+  }
+
+  void appendEofToken() {
+    tail.next = new Token(EOF_INFO, charOffset);
+    tail = tail.next;
+    // EOF points to itself so there's always infinite look-ahead.
+    tail.next = tail;
+    discardOpenLt();
+    while (!groupingStack.isEmpty) {
+      unmatchedBeginGroup(groupingStack.head);
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void beginToken() {
+    tokenStart = charOffset;
+  }
+
+  Token firstToken() {
+    return tokens.next;
+  }
+
+  Token previousToken() {
+    return tail;
+  }
+
+  void addToCharOffset(int offset) {
+    extraCharOffset += offset;
+  }
+
+  void appendWhiteSpace(int next) {
+    // Do nothing, we don't collect white space.
+  }
+
+  void appendBeginGroup(PrecedenceInfo info, String value) {
+    Token token = new BeginGroupToken(info, value, tokenStart);
+    tail.next = token;
+    tail = tail.next;
+    if (!identical(info.kind, LT_TOKEN)) discardOpenLt();
+    groupingStack = groupingStack.prepend(token);
+  }
+
+  int appendEndGroup(PrecedenceInfo info, String value, int openKind) {
+    assert(!identical(openKind, LT_TOKEN));
+    appendStringToken(info, value);
+    discardOpenLt();
+    if (groupingStack.isEmpty) {
+      return advance();
+    }
+    BeginGroupToken begin = groupingStack.head;
+    if (!identical(begin.kind, openKind)) {
+      if (!identical(openKind, OPEN_CURLY_BRACKET_TOKEN) ||
+          !identical(begin.kind, STRING_INTERPOLATION_TOKEN)) {
+        // Not ending string interpolation.
+        return error(new SourceString('Unmatched ${begin.stringValue}'));
+      }
+      // We're ending an interpolated expression.
+      begin.endGroup = tail;
+      groupingStack = groupingStack.tail;
+      // Using "start-of-text" to signal that we're back in string
+      // scanning mode.
+      return $STX;
+    }
+    begin.endGroup = tail;
+    groupingStack = groupingStack.tail;
+    return advance();
+  }
+
+  void appendGt(PrecedenceInfo info, String value) {
+    appendStringToken(info, value);
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack.head.endGroup = tail;
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void appendGtGt(PrecedenceInfo info, String value) {
+    appendStringToken(info, value);
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack.head.endGroup = tail;
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void appendGtGtGt(PrecedenceInfo info, String value) {
+    appendStringToken(info, value);
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack.head.endGroup = tail;
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void appendComment() {
+    if (!includeComments) return;
+    SourceString value = utf8String(tokenStart, -1);
+    appendByteStringToken(COMMENT_INFO, value);
+  }
+
+  void discardOpenLt() {
+    while (!groupingStack.isEmpty
+        && identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void unmatchedBeginGroup(BeginGroupToken begin);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart
new file mode 100644
index 0000000..4cf9eeb
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2011, 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.
+
+part of parser;
+
+/**
+ * Scanner that reads from a byte array and creates tokens that points
+ * to the same array.
+ */
+class ByteArrayScanner extends ArrayBasedScanner<ByteString> {
+  final List<int> bytes;
+
+  ByteArrayScanner(List<int> this.bytes, [bool includeComments = false])
+    : super(includeComments);
+
+  int nextByte() => byteAt(++byteOffset);
+
+  int peek() => byteAt(byteOffset + 1);
+
+  int byteAt(int index) => bytes[index];
+
+  AsciiString asciiString(int start, int offset) {
+    return AsciiString.of(bytes, start, byteOffset - start + offset);
+  }
+
+  Utf8String utf8String(int start, int offset) {
+    return Utf8String.of(bytes, start, byteOffset - start + offset + 1);
+  }
+
+  void appendByteStringToken(PrecedenceInfo info, ByteString value) {
+    tail.next = new ByteStringToken(info, value, tokenStart);
+    tail = tail.next;
+  }
+
+  // This method should be equivalent to the one in super. However,
+  // this is a *HOT* method and Dart VM performs better if it is easy
+  // to inline.
+  int advance() => bytes[++byteOffset];
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart
new file mode 100644
index 0000000..4d79898
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2011, 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.
+
+/**
+ * An abstract string representation.
+ */
+abstract class ByteString extends Iterable<int> implements SourceString {
+  final List<int> bytes;
+  final int offset;
+  final int length;
+  int _hashCode;
+
+  ByteString(List<int> this.bytes, int this.offset, int this.length);
+
+  String get charset;
+
+  String slowToString() => new String.fromCharCodes(
+      new Utf8Decoder(bytes, offset, length).decodeRest());
+
+  String toString() => "ByteString(${slowToString()})";
+
+  bool operator ==(other) {
+    throw "should be overridden in subclass";
+  }
+
+  Iterator<int> get iterator => new Utf8Decoder(bytes, offset, length);
+
+  int get hashCode {
+    if (_hashCode == null) {
+      _hashCode = computeHashCode();
+    }
+    return _hashCode;
+  }
+
+  int computeHashCode() {
+    int code = 1;
+    int end = offset + length;
+    for (int i = offset; i < end; i++) {
+      code += 19 * code + bytes[i];
+    }
+    return code;
+  }
+
+  printOn(StringBuffer sb) {
+    sb.add(slowToString());
+  }
+
+  bool get isEmpty => length == 0;
+  bool isPrivate() => !isEmpty && identical(bytes[offset], $_);
+
+  String get stringValue => null;
+}
+
+/**
+ * A string that consists purely of 7bit ASCII characters.
+ */
+class AsciiString extends ByteString {
+  final String charset = "ASCII";
+
+  AsciiString(List<int> bytes, int offset, int length)
+    : super(bytes, offset, length);
+
+  static AsciiString of(List<int> bytes, int offset, int length) {
+    AsciiString string = new AsciiString(bytes, offset, length);
+    return string;
+  }
+
+  Iterator<int> get iterator => new AsciiStringIterator(bytes);
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    return new AsciiString(bytes, offset + initial,
+                           length - initial - terminal);
+  }
+
+
+  static AsciiString fromString(String string) {
+    List<int> bytes = string.charCodes;
+    return AsciiString.of(bytes, 0, bytes.length);
+  }
+}
+
+
+class AsciiStringIterator implements Iterator<int> {
+  final List<int> bytes;
+  int offset;
+  final int end;
+  int _current;
+
+  AsciiStringIterator(List<int> bytes)
+      : this.bytes = bytes, offset = 0, end = bytes.length;
+  AsciiStringIterator.range(List<int> bytes, int from, int length)
+      : this.bytes = bytes, offset = from, end = from + length;
+
+  int get current => _current;
+  bool moveNext() {
+    if (offset < end) {
+      _current = bytes[offset++];
+      return true;
+    }
+    _current = null;
+    return false;
+  }
+}
+
+
+/**
+ * A string that consists of characters that can be encoded as UTF-8.
+ */
+class Utf8String extends ByteString {
+  final String charset = "UTF8";
+
+  Utf8String(List<int> bytes, int offset, int length)
+    : super(bytes, offset, length);
+
+  static Utf8String of(List<int> bytes, int offset, int length) {
+    return new Utf8String(bytes, offset, length);
+  }
+
+  static Utf8String fromString(String string) {
+    throw "not implemented yet";
+  }
+
+  Iterator<int> get iterator => new Utf8Decoder(bytes, 0, length);
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    assert((){
+      // Only allow dropping ASCII characters, to guarantee that
+      // the resulting Utf8String is still valid.
+      for (int i = 0; i < initial; i++) {
+        if (bytes[offset + i] >= 0x80) return false;
+      }
+      for (int i = 0; i < terminal; i++) {
+        if (bytes[offset + length - terminal + i] >= 0x80) return false;
+      }
+      return true;
+    });
+    // TODO(lrn): Check that first and last bytes use the same type of quotes.
+    return new Utf8String(bytes, offset + initial,
+                          length - initial - terminal);
+  }
+}
+
+/**
+ * A ByteString-valued token.
+ */
+class ByteStringToken extends Token {
+  final ByteString value;
+
+  ByteStringToken(PrecedenceInfo info, ByteString this.value, int charOffset)
+    : super(info, charOffset);
+
+  String toString() => value.toString();
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart
new file mode 100644
index 0000000..1e359aa
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart
@@ -0,0 +1,197 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+class ClassElementParser extends PartialParser {
+  ClassElementParser(Listener listener) : super(listener);
+
+  Token parseClassBody(Token token) => fullParseClassBody(token);
+}
+
+class PartialClassElement extends ClassElementX {
+  final Token beginToken;
+  final Token endToken;
+  ClassNode cachedNode;
+
+  PartialClassElement(SourceString name,
+                      Token this.beginToken,
+                      Token this.endToken,
+                      Element enclosing,
+                      int id)
+      : super(name, enclosing, id, STATE_NOT_STARTED);
+
+  void set supertypeLoadState(int state) {
+    assert(state == supertypeLoadState + 1);
+    assert(state <= STATE_DONE);
+    super.supertypeLoadState = state;
+  }
+
+  void set resolutionState(int state) {
+    assert(state == resolutionState + 1);
+    assert(state <= STATE_DONE);
+    super.resolutionState = state;
+  }
+
+  ClassNode parseNode(Compiler compiler) {
+    if (cachedNode != null) return cachedNode;
+    compiler.withCurrentElement(this, () {
+      compiler.parser.measure(() {
+        MemberListener listener = new MemberListener(compiler, this);
+        Parser parser = new ClassElementParser(listener);
+        Token token = parser.parseTopLevelDeclaration(beginToken);
+        assert(identical(token, endToken.next));
+        cachedNode = listener.popNode();
+        assert(listener.nodes.isEmpty);
+      });
+      compiler.patchParser.measure(() {
+        if (isPatched) {
+          // TODO(lrn): Perhaps extract functionality so it doesn't
+          // need compiler.
+          compiler.patchParser.parsePatchClassNode(patch);
+        }
+      });
+    });
+    return cachedNode;
+  }
+
+  Token position() => beginToken;
+
+  // TODO(johnniwinther): Ensure that modifiers are always available.
+  Modifiers get modifiers =>
+      cachedNode != null ? cachedNode.modifiers : Modifiers.EMPTY;
+
+  bool isInterface() => identical(beginToken.stringValue, "interface");
+}
+
+class MemberListener extends NodeListener {
+  final ClassElement enclosingElement;
+
+  MemberListener(DiagnosticListener listener,
+                 Element enclosingElement)
+      : this.enclosingElement = enclosingElement,
+        super(listener, enclosingElement.getCompilationUnit());
+
+  bool isConstructorName(Node nameNode) {
+    if (enclosingElement == null ||
+        enclosingElement.kind != ElementKind.CLASS) {
+      return false;
+    }
+    SourceString name;
+    if (nameNode.asIdentifier() != null) {
+      name = nameNode.asIdentifier().source;
+    } else {
+      Send send = nameNode.asSend();
+      name = send.receiver.asIdentifier().source;
+    }
+    return enclosingElement.name == name;
+  }
+
+  SourceString getMethodNameHack(Node methodName) {
+    Send send = methodName.asSend();
+    if (send == null) return methodName.asIdentifier().source;
+    Identifier receiver = send.receiver.asIdentifier();
+    Identifier selector = send.selector.asIdentifier();
+    Operator operator = selector.asOperator();
+    if (operator != null) {
+      assert(identical(receiver.source.stringValue, 'operator'));
+      // TODO(ahe): It is a hack to compare to ')', but it beats
+      // parsing the node.
+      bool isUnary = identical(operator.token.next.next.stringValue, ')');
+      return Elements.constructOperatorName(operator.source, isUnary);
+    } else {
+      if (receiver == null) {
+        listener.cancel('library prefix in named factory constructor not '
+                        'implemented', node: send.receiver);
+      }
+      if (receiver.source != enclosingElement.name) {
+        listener.onDeprecatedFeature(receiver, 'interface factories');
+      }
+      return Elements.constructConstructorName(receiver.source,
+                                               selector.source);
+    }
+  }
+
+  void endMethod(Token getOrSet, Token beginToken, Token endToken) {
+    super.endMethod(getOrSet, beginToken, endToken);
+    FunctionExpression method = popNode();
+    pushNode(null);
+    bool isConstructor = isConstructorName(method.name);
+    SourceString name = getMethodNameHack(method.name);
+    ElementKind kind = ElementKind.FUNCTION;
+    if (isConstructor) {
+      if (getOrSet != null) {
+        recoverableError('illegal modifier', token: getOrSet);
+      }
+      kind = ElementKind.GENERATIVE_CONSTRUCTOR;
+    } else if (getOrSet != null) {
+      kind = (identical(getOrSet.stringValue, 'get'))
+             ? ElementKind.GETTER : ElementKind.SETTER;
+    }
+    Element memberElement =
+        new PartialFunctionElement(name, beginToken, getOrSet, endToken,
+                                   kind, method.modifiers, enclosingElement);
+    addMember(memberElement);
+  }
+
+  void endFactoryMethod(Token beginToken, Token endToken) {
+    super.endFactoryMethod(beginToken, endToken);
+    FunctionExpression method = popNode();
+    pushNode(null);
+    SourceString name = getMethodNameHack(method.name);
+    Identifier singleIdentifierName = method.name.asIdentifier();
+    if (singleIdentifierName != null && singleIdentifierName.source == name) {
+      if (name != enclosingElement.name) {
+        listener.onDeprecatedFeature(method.name, 'interface factories');
+      }
+    }
+    ElementKind kind = ElementKind.FUNCTION;
+    Element memberElement =
+        new PartialFunctionElement(name, beginToken, null, endToken,
+                                   kind, method.modifiers, enclosingElement);
+    addMember(memberElement);
+  }
+
+  void endFields(int count, Token beginToken, Token endToken) {
+    super.endFields(count, beginToken, endToken);
+    VariableDefinitions variableDefinitions = popNode();
+    Modifiers modifiers = variableDefinitions.modifiers;
+    pushNode(null);
+    void buildFieldElement(SourceString name, Element fields) {
+      Element element =
+          new VariableElementX(name, fields, ElementKind.FIELD, null);
+      addMember(element);
+    }
+    buildFieldElements(modifiers, variableDefinitions.definitions,
+                       enclosingElement,
+                       buildFieldElement, beginToken, endToken);
+  }
+
+  void endInitializer(Token assignmentOperator) {
+    pushNode(null); // Super expects an expression, but
+                    // ClassElementParser just skips expressions.
+    super.endInitializer(assignmentOperator);
+  }
+
+  void endInitializers(int count, Token beginToken, Token endToken) {
+    pushNode(null);
+  }
+
+  void addMember(Element memberElement) {
+    for (Link link = metadata; !link.isEmpty; link = link.tail) {
+      memberElement.addMetadata(link.head);
+    }
+    metadata = const Link<MetadataAnnotation>();
+    enclosingElement.addMember(memberElement, listener);
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    popNode(); // Discard arguments.
+    if (periodBeforeName != null) {
+      popNode(); // Discard name.
+    }
+    popNode(); // Discard node (Send or Identifier).
+    pushMetadata(new PartialMetadataAnnotation(beginToken, endToken));
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart
new file mode 100644
index 0000000..5d3c9a8
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart
@@ -0,0 +1,235 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+/**
+ * A keyword in the Dart programming language.
+ */
+class Keyword extends Iterable<int> implements SourceString {
+  static const List<Keyword> values = const <Keyword> [
+      const Keyword("assert"),
+      const Keyword("break"),
+      const Keyword("case"),
+      const Keyword("catch"),
+      const Keyword("class"),
+      const Keyword("const"),
+      const Keyword("continue"),
+      const Keyword("default"),
+      const Keyword("do"),
+      const Keyword("else"),
+      const Keyword("extends"),
+      const Keyword("false"),
+      const Keyword("final"),
+      const Keyword("finally"),
+      const Keyword("for"),
+      const Keyword("if"),
+      const Keyword("in"),
+      const Keyword("new"),
+      const Keyword("null"),
+      const Keyword("return"),
+      const Keyword("super"),
+      const Keyword("switch"),
+      const Keyword("this"),
+      const Keyword("throw"),
+      const Keyword("true"),
+      const Keyword("try"),
+      const Keyword("var"),
+      const Keyword("void"),
+      const Keyword("while"),
+      const Keyword("with"),
+
+      // TODO(ahe): Don't think this is a reserved word.
+      // See: http://dartbug.com/5579
+      const Keyword("is", info: IS_INFO),
+
+      const Keyword("abstract", isBuiltIn: true),
+      const Keyword("as", info: AS_INFO, isBuiltIn: true),
+      const Keyword("dynamic", isBuiltIn: true),
+      const Keyword("export", isBuiltIn: true),
+      const Keyword("external", isBuiltIn: true),
+      const Keyword("factory", isBuiltIn: true),
+      const Keyword("get", isBuiltIn: true),
+      const Keyword("implements", isBuiltIn: true),
+      const Keyword("import", isBuiltIn: true),
+      const Keyword("interface", isBuiltIn: true),
+      const Keyword("library", isBuiltIn: true),
+      const Keyword("operator", isBuiltIn: true),
+      const Keyword("part", isBuiltIn: true),
+      const Keyword("set", isBuiltIn: true),
+      const Keyword("static", isBuiltIn: true),
+      const Keyword("typedef", isBuiltIn: true),
+
+      const Keyword("hide", isPseudo: true),
+      const Keyword("native", isPseudo: true),
+      const Keyword("of", isPseudo: true),
+      const Keyword("on", isPseudo: true),
+      const Keyword("show", isPseudo: true),
+      const Keyword("source", isPseudo: true) ];
+
+  // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+  static const DYNAMIC_DEPRECATED = const Keyword("Dynamic", isBuiltIn: true);
+
+  final String syntax;
+  final bool isPseudo;
+  final bool isBuiltIn;
+  final PrecedenceInfo info;
+
+  static Map<String, Keyword> _keywords;
+  static Map<String, Keyword> get keywords {
+    if (_keywords == null) {
+      _keywords = computeKeywordMap();
+    }
+    return _keywords;
+  }
+
+  const Keyword(String this.syntax,
+                {bool this.isPseudo: false,
+                 bool this.isBuiltIn: false,
+                 PrecedenceInfo this.info: KEYWORD_INFO});
+
+  static Map<String, Keyword> computeKeywordMap() {
+    Map<String, Keyword> result = new LinkedHashMap<String, Keyword>();
+    for (Keyword keyword in values) {
+      result[keyword.syntax] = keyword;
+    }
+    return result;
+  }
+
+  int get hashCode => syntax.hashCode;
+
+  bool operator ==(other) {
+    return other is SourceString && toString() == other.slowToString();
+  }
+
+  Iterator<int> get iterator => new StringCodeIterator(syntax);
+
+  void printOn(StringBuffer sb) {
+    sb.add(syntax);
+  }
+
+  String toString() => syntax;
+  String slowToString() => syntax;
+  String get stringValue => syntax;
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    // TODO(lrn): consider remodelling to avoid having this method in keywords.
+    return this;
+  }
+
+  bool get isEmpty => false;
+  bool isPrivate() => false;
+}
+
+/**
+ * Abstract state in a state machine for scanning keywords.
+ */
+abstract class KeywordState {
+  bool isLeaf();
+  KeywordState next(int c);
+  Keyword get keyword;
+
+  static KeywordState _KEYWORD_STATE;
+  static KeywordState get KEYWORD_STATE {
+    if (_KEYWORD_STATE == null) {
+      List<String> strings =
+          new List<String>.fixedLength(Keyword.values.length);
+      for (int i = 0; i < Keyword.values.length; i++) {
+        strings[i] = Keyword.values[i].syntax;
+      }
+      strings.sort((a,b) => a.compareTo(b));
+      _KEYWORD_STATE = computeKeywordStateTable(0, strings, 0, strings.length);
+    }
+    return _KEYWORD_STATE;
+  }
+
+  static KeywordState computeKeywordStateTable(int start, List<String> strings,
+                                               int offset, int length) {
+    List<KeywordState> result = new List<KeywordState>.fixedLength(26);
+    assert(length != 0);
+    int chunk = 0;
+    int chunkStart = -1;
+    bool isLeaf = false;
+    for (int i = offset; i < offset + length; i++) {
+      if (strings[i].length == start) {
+        isLeaf = true;
+      }
+      if (strings[i].length > start) {
+        int c = strings[i].charCodeAt(start);
+        if (chunk != c) {
+          if (chunkStart != -1) {
+            assert(result[chunk - $a] == null);
+            result[chunk - $a] = computeKeywordStateTable(start + 1, strings,
+                                                          chunkStart,
+                                                          i - chunkStart);
+          }
+          chunkStart = i;
+          chunk = c;
+        }
+      }
+    }
+    if (chunkStart != -1) {
+      assert(result[chunk - $a] == null);
+      result[chunk - $a] =
+        computeKeywordStateTable(start + 1, strings, chunkStart,
+                                 offset + length - chunkStart);
+    } else {
+      assert(length == 1);
+      return new LeafKeywordState(strings[offset]);
+    }
+    if (isLeaf) {
+      return new ArrayKeywordState(result, strings[offset]);
+    } else {
+      return new ArrayKeywordState(result, null);
+    }
+  }
+}
+
+/**
+ * A state with multiple outgoing transitions.
+ */
+class ArrayKeywordState extends KeywordState {
+  final List<KeywordState> table;
+  final Keyword keyword;
+
+  ArrayKeywordState(List<KeywordState> this.table, String syntax)
+    : keyword = (syntax == null) ? null : Keyword.keywords[syntax];
+
+  bool isLeaf() => false;
+
+  KeywordState next(int c) => table[c - $a];
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.add("[");
+    if (keyword != null) {
+      sb.add("*");
+      sb.add(keyword);
+      sb.add(" ");
+    }
+    List<KeywordState> foo = table;
+    for (int i = 0; i < foo.length; i++) {
+      if (foo[i] != null) {
+        sb.add("${new String.fromCharCodes([i + $a])}: ${foo[i]}; ");
+      }
+    }
+    sb.add("]");
+    return sb.toString();
+  }
+}
+
+/**
+ * A state that has no outgoing transitions.
+ */
+class LeafKeywordState extends KeywordState {
+  final Keyword keyword;
+
+  LeafKeywordState(String syntax) : keyword = Keyword.keywords[syntax];
+
+  bool isLeaf() => true;
+
+  KeywordState next(int c) => null;
+
+  String toString() => keyword.syntax;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart
new file mode 100644
index 0000000..e535f62
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart
@@ -0,0 +1,2064 @@
+// 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.
+
+part of scanner;
+
+const bool VERBOSE = false;
+
+/**
+ * A parser event listener that does nothing except throw exceptions
+ * on parser errors.
+ */
+class Listener {
+  void beginArgumentDefinitionTest(Token token) {
+  }
+
+  void endArgumentDefinitionTest(Token beginToken, Token endToken) {
+  }
+
+  void beginArguments(Token token) {
+  }
+
+  void endArguments(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginBlock(Token token) {
+  }
+
+  void endBlock(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginCascade(Token token) {
+  }
+
+  void endCascade() {
+  }
+
+  void beginClassBody(Token token) {
+  }
+
+  void endClassBody(int memberCount, Token beginToken, Token endToken) {
+  }
+
+  void beginClassDeclaration(Token token) {
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+  }
+
+  void beginCombinators(Token token) {
+  }
+
+  void endCombinators(int count) {
+  }
+
+  void beginCompilationUnit(Token token) {
+  }
+
+  void endCompilationUnit(int count, Token token) {
+  }
+
+  void beginConstructorReference(Token start) {
+  }
+
+  void endConstructorReference(Token start, Token periodBeforeName,
+                               Token endToken) {
+  }
+
+  void beginDoWhileStatement(Token token) {
+  }
+
+  void endDoWhileStatement(Token doKeyword, Token whileKeyword,
+                           Token endToken) {
+  }
+
+  void beginExport(Token token) {
+  }
+
+  void endExport(Token exportKeyword, Token semicolon) {
+  }
+
+  void beginExpressionStatement(Token token) {
+  }
+
+  void endExpressionStatement(Token token) {
+  }
+
+  void beginDefaultClause(Token token) {
+  }
+
+  void handleNoDefaultClause(Token token) {
+  }
+
+  void endDefaultClause(Token defaultKeyword) {
+  }
+
+  void beginFactoryMethod(Token token) {
+  }
+
+  void endFactoryMethod(Token beginToken, Token endToken) {
+  }
+
+  void beginFormalParameter(Token token) {
+  }
+
+  void endFormalParameter(Token thisKeyword) {
+  }
+
+  void handleNoFormalParameters(Token token) {
+  }
+
+  void beginFormalParameters(Token token) {
+  }
+
+  void endFormalParameters(int count, Token beginToken, Token endToken) {
+  }
+
+  void endFields(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginForStatement(Token token) {
+  }
+
+  void endForStatement(int updateExpressionCount,
+                       Token beginToken, Token endToken) {
+  }
+
+  void endForIn(Token beginToken, Token inKeyword, Token endToken) {
+  }
+
+  void beginFunction(Token token) {
+  }
+
+  void endFunction(Token getOrSet, Token endToken) {
+  }
+
+  void beginFunctionDeclaration(Token token) {
+  }
+
+  void endFunctionDeclaration(Token token) {
+  }
+
+  void beginFunctionBody(Token token) {
+  }
+
+  void endFunctionBody(int count, Token beginToken, Token endToken) {
+  }
+
+  void handleNoFunctionBody(Token token) {
+  }
+
+  void beginFunctionName(Token token) {
+  }
+
+  void endFunctionName(Token token) {
+  }
+
+  void beginFunctionTypeAlias(Token token) {
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+  }
+
+  void beginMixinApplication(Token token) {
+  }
+
+  void endMixinApplication() {
+  }
+
+  void beginNamedMixinApplication(Token token) {
+  }
+
+  void endNamedMixinApplication(Token typedefKeyword,
+                                Token implementsKeyword,
+                                Token endToken) {
+  }
+
+  void beginHide(Token hideKeyword) {
+  }
+
+  void endHide(Token hideKeyword) {
+  }
+
+  void beginIdentifierList(Token token) {
+  }
+
+  void endIdentifierList(int count) {
+  }
+
+  void beginTypeList(Token token) {
+  }
+
+  void endTypeList(int count) {
+  }
+
+  void beginIfStatement(Token token) {
+  }
+
+  void endIfStatement(Token ifToken, Token elseToken) {
+  }
+
+  void beginImport(Token importKeyword) {
+  }
+
+  void endImport(Token importKeyword, Token asKeyword, Token semicolon) {
+  }
+
+  void beginInitializedIdentifier(Token token) {
+  }
+
+  void endInitializedIdentifier() {
+  }
+
+  void beginInitializer(Token token) {
+  }
+
+  void endInitializer(Token assignmentOperator) {
+  }
+
+  void beginInitializers(Token token) {
+  }
+
+  void endInitializers(int count, Token beginToken, Token endToken) {
+  }
+
+  void handleNoInitializers() {
+  }
+
+  void beginInterface(Token token) {
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+  }
+
+  void handleLabel(Token token) {
+  }
+
+  void beginLabeledStatement(Token token, int labelCount) {
+  }
+
+  void endLabeledStatement(int labelCount) {
+  }
+
+  void beginLibraryName(Token token) {
+  }
+
+  void endLibraryName(Token libraryKeyword, Token semicolon) {
+  }
+
+  void beginLiteralMapEntry(Token token) {
+  }
+
+  void endLiteralMapEntry(Token colon, Token endToken) {
+  }
+
+  void beginLiteralString(Token token) {
+  }
+
+  void endLiteralString(int interpolationCount) {
+  }
+
+  void handleStringJuxtaposition(int literalCount) {
+  }
+
+  void beginMember(Token token) {
+  }
+
+  void endMethod(Token getOrSet, Token beginToken, Token endToken) {
+  }
+
+  void beginMetadata(Token token) {
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+  }
+
+  void beginOptionalFormalParameters(Token token) {
+  }
+
+  void endOptionalFormalParameters(int count,
+                                   Token beginToken, Token endToken) {
+  }
+
+  void beginPart(Token token) {
+  }
+
+  void endPart(Token partKeyword, Token semicolon) {
+  }
+
+  void beginPartOf(Token token) {
+  }
+
+  void endPartOf(Token partKeyword, Token semicolon) {
+  }
+
+  void beginRedirectingFactoryBody(Token token) {
+  }
+
+  void endRedirectingFactoryBody(Token beginToken, Token endToken) {
+  }
+
+  void beginReturnStatement(Token token) {
+  }
+
+  void endReturnStatement(bool hasExpression,
+                          Token beginToken, Token endToken) {
+  }
+
+  void beginScriptTag(Token token) {
+  }
+
+  void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) {
+  }
+
+  void beginSend(Token token) {
+  }
+
+  void endSend(Token token) {
+  }
+
+  void beginShow(Token showKeyword) {
+  }
+
+  void endShow(Token showKeyword) {
+  }
+
+  void beginSwitchStatement(Token token) {
+  }
+
+  void endSwitchStatement(Token switchKeyword, Token endToken) {
+  }
+
+  void beginSwitchBlock(Token token) {
+  }
+
+  void endSwitchBlock(int caseCount, Token beginToken, Token endToken) {
+  }
+
+  void beginThrowStatement(Token token) {
+  }
+
+  void endThrowStatement(Token throwToken, Token endToken) {
+  }
+
+  void endRethrowStatement(Token throwToken, Token endToken) {
+  }
+
+  void endTopLevelDeclaration(Token token) {
+  }
+
+  void beginTopLevelMember(Token token) {
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+  }
+
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+  }
+
+  void beginTryStatement(Token token) {
+  }
+
+  void handleCaseMatch(Token caseKeyword, Token colon) {
+  }
+
+  void handleCatchBlock(Token onKeyword, Token catchKeyword) {
+  }
+
+  void handleFinallyBlock(Token finallyKeyword) {
+  }
+
+  void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
+  }
+
+  void endType(Token beginToken, Token endToken) {
+  }
+
+  void beginTypeArguments(Token token) {
+  }
+
+  void endTypeArguments(int count, Token beginToken, Token endToken) {
+  }
+
+  void handleNoTypeArguments(Token token) {
+  }
+
+  void beginTypeVariable(Token token) {
+  }
+
+  void endTypeVariable(Token token) {
+  }
+
+  void beginTypeVariables(Token token) {
+  }
+
+  void endTypeVariables(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginUnamedFunction(Token token) {
+  }
+
+  void endUnamedFunction(Token token) {
+  }
+
+  void beginVariablesDeclaration(Token token) {
+  }
+
+  void endVariablesDeclaration(int count, Token endToken) {
+  }
+
+  void beginWhileStatement(Token token) {
+  }
+
+  void endWhileStatement(Token whileKeyword, Token endToken) {
+  }
+
+  void handleAsOperator(Token operathor, Token endToken) {
+    // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed.
+  }
+
+  void handleAssignmentExpression(Token token) {
+  }
+
+  void handleBinaryExpression(Token token) {
+  }
+
+  void handleConditionalExpression(Token question, Token colon) {
+  }
+
+  void handleConstExpression(Token token) {
+  }
+
+  void handleFunctionTypedFormalParameter(Token token) {
+  }
+
+  void handleIdentifier(Token token) {
+  }
+
+  void handleIndexedExpression(Token openCurlyBracket,
+                               Token closeCurlyBracket) {
+  }
+
+  void handleIsOperator(Token operathor, Token not, Token endToken) {
+    // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed.
+  }
+
+  void handleLiteralBool(Token token) {
+  }
+
+  void handleBreakStatement(bool hasTarget,
+                            Token breakKeyword, Token endToken) {
+  }
+
+  void handleContinueStatement(bool hasTarget,
+                               Token continueKeyword, Token endToken) {
+  }
+
+  void handleEmptyStatement(Token token) {
+  }
+
+  void handleAssertStatement(Token assertKeyword, Token semicolonToken) {
+  }
+
+  /** Called with either the token containing a double literal, or
+    * an immediately preceding "unary plus" token.
+    */
+  void handleLiteralDouble(Token token) {
+  }
+
+  /** Called with either the token containing an integer literal,
+    * or an immediately preceding "unary plus" token.
+    */
+  void handleLiteralInt(Token token) {
+  }
+
+  void handleLiteralList(int count, Token beginToken, Token constKeyword,
+                         Token endToken) {
+  }
+
+  void handleLiteralMap(int count, Token beginToken, Token constKeyword,
+                        Token endToken) {
+  }
+
+  void handleLiteralNull(Token token) {
+  }
+
+  void handleModifier(Token token) {
+  }
+
+  void handleModifiers(int count) {
+  }
+
+  void handleNamedArgument(Token colon) {
+  }
+
+  void handleNewExpression(Token token) {
+  }
+
+  void handleNoArguments(Token token) {
+  }
+
+  void handleNoExpression(Token token) {
+  }
+
+  void handleNoType(Token token) {
+  }
+
+  void handleNoTypeVariables(Token token) {
+  }
+
+  void handleOperatorName(Token operatorKeyword, Token token) {
+  }
+
+  void handleParenthesizedExpression(BeginGroupToken token) {
+  }
+
+  void handleQualified(Token period) {
+  }
+
+  void handleStringPart(Token token) {
+  }
+
+  void handleSuperExpression(Token token) {
+  }
+
+  void handleSwitchCase(int labelCount, int expressionCount,
+                        Token defaultKeyword, int statementCount,
+                        Token firstToken, Token endToken) {
+  }
+
+  void handleThisExpression(Token token) {
+  }
+
+  void handleUnaryPostfixAssignmentExpression(Token token) {
+  }
+
+  void handleUnaryPrefixExpression(Token token) {
+  }
+
+  void handleUnaryPrefixAssignmentExpression(Token token) {
+  }
+
+  void handleValuedFormalParameter(Token equals, Token token) {
+  }
+
+  void handleVoidKeyword(Token token) {
+  }
+
+  Token expected(String string, Token token) {
+    error("expected '$string', but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  void expectedIdentifier(Token token) {
+    error("expected identifier, but got '${token.slowToString()}'", token);
+  }
+
+  Token expectedType(Token token) {
+    error("expected a type, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedExpression(Token token) {
+    error("expected an expression, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token unexpected(Token token) {
+    error("unexpected token '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedBlockToSkip(Token token) {
+    error("expected a block, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedFunctionBody(Token token) {
+    error("expected a function body, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBody(Token token) {
+    error("expected a class body, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBodyToSkip(Token token) {
+    error("expected a class body, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Link<Token> expectedDeclaration(Token token) {
+    error("expected a declaration, but got '${token.slowToString()}'", token);
+    return const Link<Token>();
+  }
+
+  Token unmatched(Token token) {
+    error("unmatched '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  skipToEof(Token token) {
+    while (!identical(token.info, EOF_INFO)) {
+      token = token.next;
+    }
+    return token;
+  }
+
+  void recoverableError(String message, {Token token, Node node}) {
+    if (token == null && node != null) {
+      token = node.getBeginToken();
+    }
+    error(message, token);
+  }
+
+  void error(String message, Token token) {
+    throw new ParserError("$message @ ${token.charOffset}");
+  }
+}
+
+class ParserError {
+  final String reason;
+  ParserError(this.reason);
+  toString() => reason;
+}
+
+typedef int IdGenerator();
+
+/**
+ * A parser event listener designed to work with [PartialParser]. It
+ * builds elements representing the top-level declarations found in
+ * the parsed compilation unit and records them in
+ * [compilationUnitElement].
+ */
+class ElementListener extends Listener {
+  final IdGenerator idGenerator;
+  final DiagnosticListener listener;
+  final CompilationUnitElement compilationUnitElement;
+  final StringValidator stringValidator;
+  Link<StringQuoting> interpolationScope;
+
+  Link<Node> nodes = const Link<Node>();
+
+  Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>();
+
+  ElementListener(DiagnosticListener listener,
+                  this.compilationUnitElement,
+                  this.idGenerator)
+      : this.listener = listener,
+        stringValidator = new StringValidator(listener),
+        interpolationScope = const Link<StringQuoting>();
+
+  void pushQuoting(StringQuoting quoting) {
+    interpolationScope = interpolationScope.prepend(quoting);
+  }
+
+  StringQuoting popQuoting() {
+    StringQuoting result = interpolationScope.head;
+    interpolationScope = interpolationScope.tail;
+    return result;
+  }
+
+  StringNode popLiteralString() {
+    StringNode node = popNode();
+    // TODO(lrn): Handle interpolations in script tags.
+    if (node.isInterpolation) {
+      listener.cancel("String interpolation not supported in library tags",
+                      node: node);
+      return null;
+    }
+    return node;
+  }
+
+  bool allowLibraryTags() {
+    // Library tags are only allowed in the library file itself, not
+    // in sourced files.
+    LibraryElement library = compilationUnitElement.getLibrary();
+    return !compilationUnitElement.hasMembers
+      && library.entryCompilationUnit == compilationUnitElement;
+  }
+
+  void endLibraryName(Token libraryKeyword, Token semicolon) {
+    Expression name = popNode();
+    addLibraryTag(new LibraryName(libraryKeyword, name,
+                                  popMetadata(compilationUnitElement)));
+  }
+
+  void endImport(Token importKeyword, Token asKeyword, Token semicolon) {
+    NodeList combinators = popNode();
+    Identifier prefix;
+    if (asKeyword != null) {
+      prefix = popNode();
+    }
+    StringNode uri = popLiteralString();
+    addLibraryTag(new Import(importKeyword, uri, prefix, combinators,
+                             popMetadata(compilationUnitElement)));
+  }
+
+  void endExport(Token exportKeyword, Token semicolon) {
+    NodeList combinators = popNode();
+    StringNode uri = popNode();
+    addLibraryTag(new Export(exportKeyword, uri, combinators,
+                             popMetadata(compilationUnitElement)));
+  }
+
+  void endCombinators(int count) {
+    if (0 == count) {
+      pushNode(null);
+    } else {
+      pushNode(makeNodeList(count, null, null, " "));
+    }
+  }
+
+  void endHide(Token hideKeyword) => pushCombinator(hideKeyword);
+
+  void endShow(Token showKeyword) => pushCombinator(showKeyword);
+
+  void pushCombinator(Token keywordToken) {
+    NodeList identifiers = popNode();
+    pushNode(new Combinator(identifiers, keywordToken));
+  }
+
+  void endIdentifierList(int count) {
+    pushNode(makeNodeList(count, null, null, ","));
+  }
+
+  void endTypeList(int count) {
+    pushNode(makeNodeList(count, null, null, ","));
+  }
+
+  void endPart(Token partKeyword, Token semicolon) {
+    StringNode uri = popLiteralString();
+    addLibraryTag(new Part(partKeyword, uri,
+                           popMetadata(compilationUnitElement)));
+  }
+
+  void endPartOf(Token partKeyword, Token semicolon) {
+    Expression name = popNode();
+    addPartOfTag(new PartOf(partKeyword, name,
+                            popMetadata(compilationUnitElement)));
+  }
+
+  void addPartOfTag(PartOf tag) {
+    compilationUnitElement.setPartOf(tag, listener);
+  }
+
+  void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) {
+    LiteralString prefix = null;
+    Identifier argumentName = null;
+    if (hasPrefix) {
+      prefix = popLiteralString();
+      argumentName = popNode();
+    }
+    LiteralString firstArgument = popLiteralString();
+    Identifier tag = popNode();
+    ScriptTag scriptTag = new ScriptTag(tag, firstArgument, argumentName,
+                                        prefix, beginToken, endToken);
+    if (const SourceString('import') == tag.source ||
+        const SourceString('source') == tag.source ||
+        const SourceString('library') == tag.source) {
+      addScriptTag(scriptTag);
+    } else {
+      recoverableError('unknown tag: ${tag.source.slowToString()}', node: tag);
+    }
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    if (periodBeforeName != null) {
+      popNode(); // Discard name.
+    }
+    popNode(); // Discard node (Send or Identifier).
+    pushMetadata(new PartialMetadataAnnotation(beginToken, endToken));
+  }
+
+  void endTopLevelDeclaration(Token token) {
+    if (!metadata.isEmpty) {
+      recoverableError('Error: Metadata not supported here.',
+                       token: metadata.head.beginToken);
+      metadata = const Link<MetadataAnnotation>();
+    }
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+    SourceString nativeTagInfo = native.checkForNativeClass(this);
+    NodeList interfaces =
+        makeNodeList(interfacesCount, implementsKeyword, null, ",");
+    Node supertype = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    int id = idGenerator();
+    ClassElement element = new PartialClassElement(
+        name.source, beginToken, endToken, compilationUnitElement, id);
+    element.nativeTagInfo = nativeTagInfo;
+    pushElement(element);
+    rejectBuiltInIdentifier(name);
+  }
+
+  void rejectBuiltInIdentifier(Identifier name) {
+    if (name.source is Keyword) {
+      Keyword keyword = name.source;
+      if (!keyword.isPseudo) {
+        recoverableError('illegal name ${keyword.syntax}', node: name);
+      }
+    }
+  }
+
+  void endDefaultClause(Token defaultKeyword) {
+    NodeList typeParameters = popNode();
+    Node name = popNode();
+    pushNode(new TypeAnnotation(name, typeParameters));
+  }
+
+  void handleNoDefaultClause(Token token) {
+    pushNode(null);
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+    // TODO(ahe): Record the defaultClause.
+    Node defaultClause = popNode();
+    NodeList supertypes =
+        makeNodeList(supertypeCount, extendsKeyword, null, ",");
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    int id = idGenerator();
+    pushElement(new PartialClassElement(
+        name.source, interfaceKeyword, endToken, compilationUnitElement, id));
+    rejectBuiltInIdentifier(name);
+    listener.onDeprecatedFeature(interfaceKeyword, 'interface declarations');
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+    NodeList typeVariables = popNode(); // TOOD(karlklose): do not throw away.
+    Identifier name = popNode();
+    TypeAnnotation returnType = popNode();
+    pushElement(new PartialTypedefElement(name.source, compilationUnitElement,
+                                          typedefKeyword));
+    rejectBuiltInIdentifier(name);
+  }
+
+  void endNamedMixinApplication(Token typedefKeyword,
+                                Token implementsKeyword,
+                                Token endToken) {
+    NodeList interfaces = (implementsKeyword != null) ? popNode() : null;
+    MixinApplication mixinApplication = popNode();
+    Modifiers modifiers = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    NamedMixinApplication namedMixinApplication = new NamedMixinApplication(
+        name, typeParameters, modifiers, mixinApplication, interfaces,
+        typedefKeyword, endToken);
+
+    int id = idGenerator();
+    Element enclosing = compilationUnitElement;
+    pushElement(new MixinApplicationElementX(name.source, enclosing, id,
+                                             namedMixinApplication,
+                                             modifiers));
+    rejectBuiltInIdentifier(name);
+  }
+
+  void endMixinApplication() {
+    NodeList mixins = popNode();
+    TypeAnnotation superclass = popNode();
+    pushNode(new MixinApplication(superclass, mixins));
+  }
+
+  void handleVoidKeyword(Token token) {
+    pushNode(new TypeAnnotation(new Identifier(token), null));
+  }
+
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+    Identifier name = popNode();
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    ElementKind kind;
+    if (getOrSet == null) {
+      kind = ElementKind.FUNCTION;
+    } else if (identical(getOrSet.stringValue, 'get')) {
+      kind = ElementKind.GETTER;
+    } else if (identical(getOrSet.stringValue, 'set')) {
+      kind = ElementKind.SETTER;
+    }
+    pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet,
+                                           endToken, kind,
+                                           modifiers, compilationUnitElement));
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+    void buildFieldElement(SourceString name, Element fields) {
+      pushElement(new VariableElementX(name, fields, ElementKind.FIELD, null));
+    }
+    NodeList variables = makeNodeList(count, null, null, ",");
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    buildFieldElements(modifiers, variables, compilationUnitElement,
+                       buildFieldElement,
+                       beginToken, endToken);
+  }
+
+  void buildFieldElements(Modifiers modifiers,
+                          NodeList variables,
+                          Element enclosingElement,
+                          void buildFieldElement(SourceString name,
+                                                 Element fields),
+                          Token beginToken, Token endToken) {
+    Element fields = new PartialFieldListElement(beginToken,
+                                                 endToken,
+                                                 modifiers,
+                                                 enclosingElement);
+    for (Link<Node> variableNodes = variables.nodes;
+         !variableNodes.isEmpty;
+         variableNodes = variableNodes.tail) {
+      Expression initializedIdentifier = variableNodes.head;
+      Identifier identifier = initializedIdentifier.asIdentifier();
+      if (identifier == null) {
+        identifier = initializedIdentifier.asSendSet().selector.asIdentifier();
+      }
+      SourceString name = identifier.source;
+      buildFieldElement(name, fields);
+    }
+  }
+
+  void handleIdentifier(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleQualified(Token period) {
+    Identifier last = popNode();
+    Expression first = popNode();
+    pushNode(new Send(first, last));
+  }
+
+  void handleNoType(Token token) {
+    pushNode(null);
+  }
+
+  void endTypeVariable(Token token) {
+    TypeAnnotation bound = popNode();
+    Identifier name = popNode();
+    pushNode(new TypeVariable(name, bound));
+  }
+
+  void endTypeVariables(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ','));
+  }
+
+  void handleNoTypeVariables(token) {
+    pushNode(null);
+  }
+
+  void endTypeArguments(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ','));
+  }
+
+  void handleNoTypeArguments(Token token) {
+    pushNode(null);
+  }
+
+  void endType(Token beginToken, Token endToken) {
+    NodeList typeArguments = popNode();
+    Expression typeName = popNode();
+    pushNode(new TypeAnnotation(typeName, typeArguments));
+  }
+
+  void handleParenthesizedExpression(BeginGroupToken token) {
+    Expression expression = popNode();
+    pushNode(new ParenthesizedExpression(expression, token));
+  }
+
+  void handleModifier(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleModifiers(int count) {
+    if (count == 0) {
+      pushNode(Modifiers.EMPTY);
+    } else {
+      NodeList modifierNodes = makeNodeList(count, null, null, ' ');
+      pushNode(new Modifiers(modifierNodes));
+    }
+  }
+
+  Token expected(String string, Token token) {
+    listener.cancel("expected '$string', but got '${token.slowToString()}'",
+                    token: token);
+    return skipToEof(token);
+  }
+
+  void expectedIdentifier(Token token) {
+    listener.cancel("expected identifier, but got '${token.slowToString()}'",
+                    token: token);
+    pushNode(null);
+  }
+
+  Token expectedType(Token token) {
+    listener.cancel("expected a type, but got '${token.slowToString()}'",
+                    token: token);
+    pushNode(null);
+    return skipToEof(token);
+  }
+
+  Token expectedExpression(Token token) {
+    listener.cancel("expected an expression, but got '${token.slowToString()}'",
+                    token: token);
+    pushNode(null);
+    return skipToEof(token);
+  }
+
+  Token unexpected(Token token) {
+    String message = "unexpected token '${token.slowToString()}'";
+    if (token.info == BAD_INPUT_INFO) {
+      message = token.stringValue;
+    }
+    listener.cancel(message, token: token);
+    return skipToEof(token);
+  }
+
+  Token expectedBlockToSkip(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeBlockToSkip(this, token);
+    } else {
+      return unexpected(token);
+    }
+  }
+
+  Token expectedFunctionBody(Token token) {
+    String printString = token.slowToString();
+    listener.cancel("expected a function body, but got '$printString'",
+                    token: token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBody(Token token) {
+    listener.cancel("expected a class body, but got '${token.slowToString()}'",
+                    token: token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBodyToSkip(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeClassBodyToSkip(this, token);
+    } else {
+      return unexpected(token);
+    }
+  }
+
+  Link<Token> expectedDeclaration(Token token) {
+    listener.cancel("expected a declaration, but got '${token.slowToString()}'",
+                    token: token);
+    return const Link<Token>();
+  }
+
+  Token unmatched(Token token) {
+    listener.cancel("unmatched '${token.slowToString()}'", token: token);
+    return skipToEof(token);
+  }
+
+  void recoverableError(String message, {Token token, Node node}) {
+    listener.cancel(message, token: token, node: node);
+  }
+
+  void pushElement(Element element) {
+    popMetadata(element);
+    compilationUnitElement.addMember(element, listener);
+  }
+
+  Link<MetadataAnnotation> popMetadata(Element element) {
+    var result = metadata;
+    for (Link link = metadata; !link.isEmpty; link = link.tail) {
+      element.addMetadata(link.head);
+    }
+    metadata = const Link<MetadataAnnotation>();
+    return result;
+  }
+
+  void pushMetadata(MetadataAnnotation annotation) {
+    metadata = metadata.prepend(annotation);
+  }
+
+  // TODO(ahe): Remove this method.
+  void addScriptTag(ScriptTag tag) {
+    listener.onDeprecatedFeature(tag, '# tags');
+    addLibraryTag(tag.toLibraryTag());
+  }
+
+  void addLibraryTag(LibraryTag tag) {
+    if (!allowLibraryTags()) {
+      recoverableError('library tags not allowed here', node: tag);
+    }
+    compilationUnitElement.getImplementationLibrary().addTag(tag, listener);
+  }
+
+  void pushNode(Node node) {
+    nodes = nodes.prepend(node);
+    if (VERBOSE) log("push $nodes");
+  }
+
+  Node popNode() {
+    assert(!nodes.isEmpty);
+    Node node = nodes.head;
+    nodes = nodes.tail;
+    if (VERBOSE) log("pop $nodes");
+    return node;
+  }
+
+  Node peekNode() {
+    assert(!nodes.isEmpty);
+    Node node = nodes.head;
+    if (VERBOSE) log("peek $node");
+    return node;
+  }
+
+  void log(message) {
+    print(message);
+  }
+
+  NodeList makeNodeList(int count, Token beginToken, Token endToken,
+                        String delimiter) {
+    Link<Node> poppedNodes = const Link<Node>();
+    for (; count > 0; --count) {
+      // This effectively reverses the order of nodes so they end up
+      // in correct (source) order.
+      poppedNodes = poppedNodes.prepend(popNode());
+    }
+    SourceString sourceDelimiter =
+        (delimiter == null) ? null : new SourceString(delimiter);
+    return new NodeList(beginToken, poppedNodes, endToken, sourceDelimiter);
+  }
+
+  void beginLiteralString(Token token) {
+    SourceString source = token.value;
+    StringQuoting quoting = StringValidator.quotingFromString(source);
+    pushQuoting(quoting);
+    // Just wrap the token for now. At the end of the interpolation,
+    // when we know how many there are, go back and validate the tokens.
+    pushNode(new LiteralString(token, null));
+  }
+
+  void handleStringPart(Token token) {
+    // Just push an unvalidated token now, and replace it when we know the
+    // end of the interpolation.
+    pushNode(new LiteralString(token, null));
+  }
+
+  void endLiteralString(int count) {
+    StringQuoting quoting = popQuoting();
+
+    Link<StringInterpolationPart> parts =
+        const Link<StringInterpolationPart>();
+    // Parts of the string interpolation are popped in reverse order,
+    // starting with the last literal string part.
+    bool isLast = true;
+    for (int i = 0; i < count; i++) {
+      LiteralString string = popNode();
+      DartString validation =
+          stringValidator.validateInterpolationPart(string.token, quoting,
+                                                    isFirst: false,
+                                                    isLast: isLast);
+      // Replace the unvalidated LiteralString with a new LiteralString
+      // object that has the validation result included.
+      string = new LiteralString(string.token, validation);
+      Expression expression = popNode();
+      parts = parts.prepend(new StringInterpolationPart(expression, string));
+      isLast = false;
+    }
+
+    LiteralString string = popNode();
+    DartString validation =
+        stringValidator.validateInterpolationPart(string.token, quoting,
+                                                  isFirst: true,
+                                                  isLast: isLast);
+    string = new LiteralString(string.token, validation);
+    if (isLast) {
+      pushNode(string);
+    } else {
+      NodeList partNodes =
+          new NodeList(null, parts, null, const SourceString(""));
+      pushNode(new StringInterpolation(string, partNodes));
+    }
+  }
+
+  void handleStringJuxtaposition(int stringCount) {
+    assert(stringCount != 0);
+    Expression accumulator = popNode();
+    stringCount--;
+    while (stringCount > 0) {
+      Expression expression = popNode();
+      accumulator = new StringJuxtaposition(expression, accumulator);
+      stringCount--;
+    }
+    pushNode(accumulator);
+  }
+}
+
+class NodeListener extends ElementListener {
+  NodeListener(DiagnosticListener listener, CompilationUnitElement element)
+    : super(listener, element, null);
+
+  void addLibraryTag(LibraryTag tag) {
+    pushNode(tag);
+  }
+
+  void addPartOfTag(PartOf tag) {
+    pushNode(tag);
+  }
+
+  void endArgumentDefinitionTest(Token beginToken, Token endToken) {
+    pushNode(new Send.prefix(popNode(), new Operator(beginToken)));
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+    NodeList body = popNode();
+    NodeList interfaces =
+        makeNodeList(interfacesCount, implementsKeyword, null, ",");
+    Node supertype = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new ClassNode(modifiers, name, typeParameters, supertype,
+                           interfaces, null, beginToken, extendsKeyword, body,
+                           endToken));
+  }
+
+  void endCompilationUnit(int count, Token token) {
+    pushNode(makeNodeList(count, null, null, '\n'));
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+    NodeList formals = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    TypeAnnotation returnType = popNode();
+    pushNode(new Typedef(returnType, name, typeParameters, formals,
+                         typedefKeyword, endToken));
+  }
+
+  void endNamedMixinApplication(Token typedefKeyword,
+                                Token implementsKeyword,
+                                Token endToken) {
+    NodeList interfaces = (implementsKeyword != null) ? popNode() : null;
+    Node mixinApplication = popNode();
+    Modifiers modifiers = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    pushNode(new NamedMixinApplication(name, typeParameters,
+                                       modifiers, mixinApplication,
+                                       interfaces,
+                                       typedefKeyword, endToken));
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+    NodeList body = popNode();
+    TypeAnnotation defaultClause = popNode();
+    NodeList supertypes = makeNodeList(supertypeCount, extendsKeyword,
+                                       null, ',');
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    pushNode(new ClassNode(Modifiers.EMPTY, name, typeParameters, null,
+                           supertypes, defaultClause, interfaceKeyword, null,
+                           body, endToken));
+  }
+
+  void endClassBody(int memberCount, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(memberCount, beginToken, endToken, null));
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+    NodeList variables = makeNodeList(count, null, endToken, ",");
+    Modifiers modifiers = popNode();
+    pushNode(new VariableDefinitions(null, modifiers, variables));
+  }
+
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+    Statement body = popNode();
+    NodeList formalParameters = popNode();
+    Identifier name = popNode();
+    Modifiers modifiers = popNode();
+    ElementKind kind;
+    if (getOrSet == null) {
+      kind = ElementKind.FUNCTION;
+    } else if (identical(getOrSet.stringValue, 'get')) {
+      kind = ElementKind.GETTER;
+    } else if (identical(getOrSet.stringValue, 'set')) {
+      kind = ElementKind.SETTER;
+    }
+    pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet,
+                                           endToken, kind,
+                                           modifiers, compilationUnitElement));
+  }
+
+  void endFormalParameter(Token thisKeyword) {
+    Expression name = popNode();
+    if (thisKeyword != null) {
+      Identifier thisIdentifier = new Identifier(thisKeyword);
+      if (name.asSend() == null) {
+        name = new Send(thisIdentifier, name);
+      } else {
+        name = name.asSend().copyWithReceiver(thisIdentifier);
+      }
+    }
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(
+        new VariableDefinitions(type, modifiers, new NodeList.singleton(name)));
+  }
+
+  void endFormalParameters(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ","));
+  }
+
+  void handleNoFormalParameters(Token token) {
+    pushNode(null);
+  }
+
+  void endArguments(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ","));
+  }
+
+  void handleNoArguments(Token token) {
+    pushNode(null);
+  }
+
+  void endConstructorReference(Token start, Token periodBeforeName,
+                               Token endToken) {
+    Identifier name = null;
+    if (periodBeforeName != null) {
+      name = popNode();
+    }
+    NodeList typeArguments = popNode();
+    Node classReference = popNode();
+    if (typeArguments != null) {
+      classReference = new TypeAnnotation(classReference, typeArguments);
+    } else {
+      Identifier identifier = classReference.asIdentifier();
+      Send send = classReference.asSend();
+      if (identifier != null) {
+        // TODO(ahe): Should be:
+        // classReference = new Send(null, identifier);
+        classReference = identifier;
+      } else if (send != null) {
+        classReference = send;
+      } else {
+        internalError(node: classReference);
+      }
+    }
+    Node constructor = classReference;
+    if (name != null) {
+      // Either typeName<args>.name or x.y.name.
+      constructor = new Send(classReference, name);
+    }
+    pushNode(constructor);
+  }
+
+  void endRedirectingFactoryBody(Token beginToken,
+                                 Token endToken) {
+    pushNode(new Return(beginToken, endToken, popNode()));
+  }
+
+  void endReturnStatement(bool hasExpression,
+                          Token beginToken, Token endToken) {
+    Expression expression = hasExpression ? popNode() : null;
+    pushNode(new Return(beginToken, endToken, expression));
+  }
+
+  void endExpressionStatement(Token token) {
+    pushNode(new ExpressionStatement(popNode(), token));
+  }
+
+  void handleOnError(Token token, var errorInformation) {
+    listener.cancel("internal error: '${token.value}': ${errorInformation}",
+                    token: token);
+  }
+
+  Token expectedFunctionBody(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeFunctionBody(this, token);
+    } else {
+      listener.cancel(
+          "expected a function body, but got '${token.slowToString()}'",
+          token: token);
+      return skipToEof(token);
+    }
+  }
+
+  Token expectedClassBody(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeClassBody(this, token);
+    } else {
+      listener.cancel(
+          "expected a class body, but got '${token.slowToString()}'",
+          token: token);
+      return skipToEof(token);
+    }
+  }
+
+  void handleLiteralInt(Token token) {
+    pushNode(new LiteralInt(token, (t, e) => handleOnError(t, e)));
+  }
+
+  void handleLiteralDouble(Token token) {
+    pushNode(new LiteralDouble(token, (t, e) => handleOnError(t, e)));
+  }
+
+  void handleLiteralBool(Token token) {
+    pushNode(new LiteralBool(token, (t, e) => handleOnError(t, e)));
+  }
+
+  void handleLiteralNull(Token token) {
+    pushNode(new LiteralNull(token));
+  }
+
+  void handleBinaryExpression(Token token) {
+    Node argument = popNode();
+    Node receiver = popNode();
+    String tokenString = token.stringValue;
+    if (identical(tokenString, '.') || identical(tokenString, '..')) {
+      Send argumentSend = argument.asSend();
+      if (argumentSend == null) {
+        // TODO(ahe): The parser should diagnose this problem, not
+        // this listener.
+        listener.cancel('Syntax error: Expected an identifier.',
+                        node: argument);
+      }
+      if (argumentSend.receiver != null) internalError(node: argument);
+      if (argument is SendSet) internalError(node: argument);
+      pushNode(argument.asSend().copyWithReceiver(receiver));
+    } else {
+      NodeList arguments = new NodeList.singleton(argument);
+      pushNode(new Send(receiver, new Operator(token), arguments));
+    }
+    if (identical(tokenString, '===') || identical(tokenString, '!==')) {
+      listener.onDeprecatedFeature(token, tokenString);
+    }
+  }
+
+  void beginCascade(Token token) {
+    pushNode(new CascadeReceiver(popNode(), token));
+  }
+
+  void endCascade() {
+    pushNode(new Cascade(popNode()));
+  }
+
+  void handleAsOperator(Token operathor, Token endToken) {
+    TypeAnnotation type = popNode();
+    Expression expression = popNode();
+    NodeList arguments = new NodeList.singleton(type);
+    pushNode(new Send(expression, new Operator(operathor), arguments));
+  }
+
+  void handleAssignmentExpression(Token token) {
+    Node arg = popNode();
+    Node node = popNode();
+    Send send = node.asSend();
+    if (send == null || !(send.isPropertyAccess || send.isIndex)) {
+      reportNotAssignable(node);
+    }
+    if (send.asSendSet() != null) internalError(node: send);
+    NodeList arguments;
+    if (send.isIndex) {
+      Link<Node> link = const Link<Node>().prepend(arg);
+      link = link.prepend(send.arguments.head);
+      arguments = new NodeList(null, link);
+    } else {
+      arguments = new NodeList.singleton(arg);
+    }
+    Operator op = new Operator(token);
+    pushNode(new SendSet(send.receiver, send.selector, op, arguments));
+  }
+
+  void reportNotAssignable(Node node) {
+    // TODO(ahe): The parser should diagnose this problem, not this
+    // listener.
+    listener.cancel('Syntax error: Not assignable.', node: node);
+  }
+
+  void handleConditionalExpression(Token question, Token colon) {
+    Node elseExpression = popNode();
+    Node thenExpression = popNode();
+    Node condition = popNode();
+    pushNode(new Conditional(
+        condition, thenExpression, elseExpression, question, colon));
+  }
+
+  void endSend(Token token) {
+    NodeList arguments = popNode();
+    Node selector = popNode();
+    // TODO(ahe): Handle receiver.
+    pushNode(new Send(null, selector, arguments));
+  }
+
+  void endFunctionBody(int count, Token beginToken, Token endToken) {
+    if (count == 0 && beginToken == null) {
+      pushNode(new EmptyStatement(endToken));
+    } else {
+      pushNode(new Block(makeNodeList(count, beginToken, endToken, null)));
+    }
+  }
+
+  void handleNoFunctionBody(Token token) {
+    pushNode(null);
+  }
+
+  void endFunction(Token getOrSet, Token endToken) {
+    Statement body = popNode();
+    NodeList initializers = popNode();
+    NodeList formals = popNode();
+    // The name can be an identifier or a send in case of named constructors.
+    Expression name = popNode();
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new FunctionExpression(name, formals, body, type,
+                                    modifiers, initializers, getOrSet));
+  }
+
+  void endFunctionDeclaration(Token endToken) {
+    pushNode(new FunctionDeclaration(popNode()));
+  }
+
+  void endVariablesDeclaration(int count, Token endToken) {
+    // TODO(ahe): Pick one name for this concept, either
+    // VariablesDeclaration or VariableDefinitions.
+    NodeList variables = makeNodeList(count, null, endToken, ",");
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new VariableDefinitions(type, modifiers, variables));
+  }
+
+  void endInitializer(Token assignmentOperator) {
+    Expression initializer = popNode();
+    NodeList arguments = new NodeList.singleton(initializer);
+    Expression name = popNode();
+    Operator op = new Operator(assignmentOperator);
+    pushNode(new SendSet(null, name, op, arguments));
+  }
+
+  void endIfStatement(Token ifToken, Token elseToken) {
+    Statement elsePart = (elseToken == null) ? null : popNode();
+    Statement thenPart = popNode();
+    ParenthesizedExpression condition = popNode();
+    pushNode(new If(condition, thenPart, elsePart, ifToken, elseToken));
+  }
+
+  void endForStatement(int updateExpressionCount,
+                       Token beginToken, Token endToken) {
+    Statement body = popNode();
+    NodeList updates = makeNodeList(updateExpressionCount, null, null, ',');
+    Statement condition = popNode();
+    Node initializer = popNode();
+    pushNode(new For(initializer, condition, updates, body, beginToken));
+  }
+
+  void handleNoExpression(Token token) {
+    pushNode(null);
+  }
+
+  void endDoWhileStatement(Token doKeyword, Token whileKeyword,
+                           Token endToken) {
+    Expression condition = popNode();
+    Statement body = popNode();
+    pushNode(new DoWhile(body, condition, doKeyword, whileKeyword, endToken));
+  }
+
+  void endWhileStatement(Token whileKeyword, Token endToken) {
+    Statement body = popNode();
+    Expression condition = popNode();
+    pushNode(new While(condition, body, whileKeyword));
+  }
+
+  void endBlock(int count, Token beginToken, Token endToken) {
+    pushNode(new Block(makeNodeList(count, beginToken, endToken, null)));
+  }
+
+  void endThrowStatement(Token throwToken, Token endToken) {
+    Expression expression = popNode();
+    pushNode(new Throw(expression, throwToken, endToken));
+  }
+
+  void endRethrowStatement(Token throwToken, Token endToken) {
+    pushNode(new Throw(null, throwToken, endToken));
+  }
+
+  void handleUnaryPrefixExpression(Token token) {
+    pushNode(new Send.prefix(popNode(), new Operator(token)));
+  }
+
+  void handleSuperExpression(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleThisExpression(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleUnaryAssignmentExpression(Token token, bool isPrefix) {
+    Node node = popNode();
+    Send send = node.asSend();
+    if (send == null) {
+      reportNotAssignable(node);
+    }
+    if (!(send.isPropertyAccess || send.isIndex)) {
+      reportNotAssignable(node);
+    }
+    if (send.asSendSet() != null) internalError(node: send);
+    Node argument = null;
+    if (send.isIndex) argument = send.arguments.head;
+    Operator op = new Operator(token);
+
+    if (isPrefix) {
+      pushNode(new SendSet.prefix(send.receiver, send.selector, op, argument));
+    } else {
+      pushNode(new SendSet.postfix(send.receiver, send.selector, op, argument));
+    }
+  }
+
+  void handleUnaryPostfixAssignmentExpression(Token token) {
+    handleUnaryAssignmentExpression(token, false);
+  }
+
+  void handleUnaryPrefixAssignmentExpression(Token token) {
+    handleUnaryAssignmentExpression(token, true);
+  }
+
+  void endInitializers(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, null, ','));
+  }
+
+  void handleNoInitializers() {
+    pushNode(null);
+  }
+
+  void endFields(int count, Token beginToken, Token endToken) {
+    NodeList variables = makeNodeList(count, null, endToken, ",");
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new VariableDefinitions(type, modifiers, variables));
+  }
+
+  void endMethod(Token getOrSet, Token beginToken, Token endToken) {
+    Statement body = popNode();
+    NodeList initializers = popNode();
+    NodeList formalParameters = popNode();
+    Expression name = popNode();
+    TypeAnnotation returnType = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new FunctionExpression(name, formalParameters, body, returnType,
+                                    modifiers, initializers, getOrSet));
+  }
+
+  void handleLiteralMap(int count, Token beginToken, Token constKeyword,
+                        Token endToken) {
+    NodeList entries = makeNodeList(count, beginToken, endToken, ',');
+    NodeList typeArguments = popNode();
+    pushNode(new LiteralMap(typeArguments, entries, constKeyword));
+  }
+
+  void endLiteralMapEntry(Token colon, Token endToken) {
+    Expression value = popNode();
+    Expression key = popNode();
+    if (key.asStringNode() == null) {
+      recoverableError('expected a string', node: key);
+    }
+    pushNode(new LiteralMapEntry(key, colon, value));
+  }
+
+  void handleLiteralList(int count, Token beginToken, Token constKeyword,
+                         Token endToken) {
+    NodeList elements = makeNodeList(count, beginToken, endToken, ',');
+    pushNode(new LiteralList(popNode(), elements, constKeyword));
+  }
+
+  void handleIndexedExpression(Token openSquareBracket,
+                               Token closeSquareBracket) {
+    NodeList arguments =
+        makeNodeList(1, openSquareBracket, closeSquareBracket, null);
+    Node receiver = popNode();
+    Token token =
+      new StringToken(INDEX_INFO, '[]', openSquareBracket.charOffset);
+    Node selector = new Operator(token);
+    pushNode(new Send(receiver, selector, arguments));
+  }
+
+  void handleNewExpression(Token token) {
+    NodeList arguments = popNode();
+    Node name = popNode();
+    pushNode(new NewExpression(token, new Send(null, name, arguments)));
+  }
+
+  void handleConstExpression(Token token) {
+    // [token] carries the 'const' information.
+    handleNewExpression(token);
+  }
+
+  void handleOperatorName(Token operatorKeyword, Token token) {
+    Operator op = new Operator(token);
+    pushNode(new Send(new Identifier(operatorKeyword), op, null));
+  }
+
+  void handleNamedArgument(Token colon) {
+    Expression expression = popNode();
+    Identifier name = popNode();
+    pushNode(new NamedArgument(name, colon, expression));
+  }
+
+  void endOptionalFormalParameters(int count,
+                                   Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ','));
+  }
+
+  void handleFunctionTypedFormalParameter(Token endToken) {
+    NodeList formals = popNode();
+    Identifier name = popNode();
+    TypeAnnotation returnType = popNode();
+    pushNode(null); // Signal "no type" to endFormalParameter.
+    pushNode(new FunctionExpression(name, formals, null, returnType,
+                                    Modifiers.EMPTY, null, null));
+  }
+
+  void handleValuedFormalParameter(Token equals, Token token) {
+    Expression defaultValue = popNode();
+    Expression parameterName = popNode();
+    pushNode(new SendSet(null, parameterName, new Operator(equals),
+                         new NodeList.singleton(defaultValue)));
+  }
+
+  void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
+    Block finallyBlock = null;
+    if (finallyKeyword != null) {
+      finallyBlock = popNode();
+    }
+    NodeList catchBlocks = makeNodeList(catchCount, null, null, null);
+    Block tryBlock = popNode();
+    pushNode(new TryStatement(tryBlock, catchBlocks, finallyBlock,
+                              tryKeyword, finallyKeyword));
+  }
+
+  void handleCaseMatch(Token caseKeyword, Token colon) {
+    pushNode(new CaseMatch(caseKeyword, popNode(), colon));
+  }
+
+  void handleCatchBlock(Token onKeyword, Token catchKeyword) {
+    Block block = popNode();
+    NodeList formals = catchKeyword != null? popNode(): null;
+    TypeAnnotation type = onKeyword != null ? popNode() : null;
+    pushNode(new CatchBlock(type, formals, block, onKeyword, catchKeyword));
+  }
+
+  void endSwitchStatement(Token switchKeyword, Token endToken) {
+    NodeList cases = popNode();
+    ParenthesizedExpression expression = popNode();
+    pushNode(new SwitchStatement(expression, cases, switchKeyword));
+  }
+
+  void endSwitchBlock(int caseCount, Token beginToken, Token endToken) {
+    Link<Node> caseNodes = const Link<Node>();
+    while (caseCount > 0) {
+      SwitchCase switchCase = popNode();
+      caseNodes = caseNodes.prepend(switchCase);
+      caseCount--;
+    }
+    pushNode(new NodeList(beginToken, caseNodes, endToken, null));
+  }
+
+  void handleSwitchCase(int labelCount, int caseCount,
+                        Token defaultKeyword, int statementCount,
+                        Token firstToken, Token endToken) {
+    NodeList statements = makeNodeList(statementCount, null, null, null);
+    NodeList labelsAndCases =
+        makeNodeList(labelCount + caseCount, null, null, null);
+    pushNode(new SwitchCase(labelsAndCases, defaultKeyword, statements,
+                            firstToken));
+  }
+
+  void handleBreakStatement(bool hasTarget,
+                            Token breakKeyword, Token endToken) {
+    Identifier target = null;
+    if (hasTarget) {
+      target = popNode();
+    }
+    pushNode(new BreakStatement(target, breakKeyword, endToken));
+  }
+
+  void handleContinueStatement(bool hasTarget,
+                               Token continueKeyword, Token endToken) {
+    Identifier target = null;
+    if (hasTarget) {
+      target = popNode();
+    }
+    pushNode(new ContinueStatement(target, continueKeyword, endToken));
+  }
+
+  void handleEmptyStatement(Token token) {
+    pushNode(new EmptyStatement(token));
+  }
+
+  void endFactoryMethod(Token beginToken, Token endToken) {
+    Statement body = popNode();
+    NodeList formals = popNode();
+    Node name = popNode();
+
+    // TODO(ahe): Move this parsing to the parser.
+    int modifierCount = 0;
+    Token modifier = beginToken;
+    if (modifier.stringValue == "external") {
+      handleModifier(modifier);
+      modifierCount++;
+      modifier = modifier.next;
+    }
+    if (modifier.stringValue == "const") {
+      handleModifier(modifier);
+      modifierCount++;
+      modifier = modifier.next;
+    }
+    assert(modifier.stringValue == "factory");
+    handleModifier(modifier);
+    modifierCount++;
+    handleModifiers(modifierCount);
+    Modifiers modifiers = popNode();
+
+    pushNode(new FunctionExpression(name, formals, body, null,
+                                    modifiers, null, null));
+  }
+
+  void endForIn(Token beginToken, Token inKeyword, Token endToken) {
+    Statement body = popNode();
+    Expression expression = popNode();
+    Node declaredIdentifier = popNode();
+    pushNode(new ForIn(declaredIdentifier, expression, body,
+                                beginToken, inKeyword));
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    NodeList arguments = popNode();
+    if (arguments == null) {
+      // This is a constant expression.
+      Identifier name;
+      if (periodBeforeName != null) {
+        name = popNode();
+      }
+      NodeList typeArguments = popNode();
+      Node receiver = popNode();
+      if (typeArguments != null) {
+        receiver = new TypeAnnotation(receiver, typeArguments);
+        recoverableError('Error: type arguments are not allowed here',
+                         node: typeArguments);
+      } else {
+        Identifier identifier = receiver.asIdentifier();
+        Send send = receiver.asSend();
+        if (identifier != null) {
+          receiver = new Send(null, identifier);
+        } else if (send == null) {
+          internalError(node: receiver);
+        }
+      }
+      Send send = receiver;
+      if (name != null) {
+        send = new Send(receiver, name);
+      }
+      pushNode(send);
+    } else {
+      // This is a const constructor call.
+      endConstructorReference(beginToken, periodBeforeName, endToken);
+      Node constructor = popNode();
+      pushNode(new NewExpression(beginToken,
+                                 new Send(null, constructor, arguments)));
+    }
+  }
+
+  void handleAssertStatement(Token assertKeyword, Token semicolonToken) {
+    NodeList arguments = popNode();
+    Node selector = new Identifier(assertKeyword);
+    Node send = new Send(null, selector, arguments);
+    pushNode(new ExpressionStatement(send, semicolonToken));
+  }
+
+  void endUnamedFunction(Token token) {
+    Statement body = popNode();
+    NodeList formals = popNode();
+    pushNode(new FunctionExpression(null, formals, body, null,
+                                    Modifiers.EMPTY, null, null));
+  }
+
+  void handleIsOperator(Token operathor, Token not, Token endToken) {
+    TypeAnnotation type = popNode();
+    Expression expression = popNode();
+    Node argument;
+    if (not != null) {
+      argument = new Send.prefix(type, new Operator(not));
+    } else {
+      argument = type;
+    }
+
+    NodeList arguments = new NodeList.singleton(argument);
+    pushNode(new Send(expression, new Operator(operathor), arguments));
+  }
+
+  void handleLabel(Token colon) {
+    Identifier name = popNode();
+    pushNode(new Label(name, colon));
+  }
+
+  void endLabeledStatement(int labelCount) {
+    Statement statement = popNode();
+    NodeList labels = makeNodeList(labelCount, null, null, null);
+    pushNode(new LabeledStatement(labels, statement));
+  }
+
+  void log(message) {
+    listener.log(message);
+  }
+
+  void internalError({Token token, Node node}) {
+    // TODO(ahe): This should call listener.internalError.
+    Spannable spannable = (token == null) ? node : token;
+    throw new SpannableAssertionFailure(spannable, 'internal error in parser');
+  }
+}
+
+class PartialFunctionElement extends FunctionElementX {
+  final Token beginToken;
+  final Token getOrSet;
+  final Token endToken;
+
+  PartialFunctionElement(SourceString name,
+                         Token this.beginToken,
+                         Token this.getOrSet,
+                         Token this.endToken,
+                         ElementKind kind,
+                         Modifiers modifiers,
+                         Element enclosing)
+    : super(name, kind, modifiers, enclosing);
+
+  FunctionExpression parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    parseFunction(Parser p) {
+      if (isMember() && modifiers.isFactory()) {
+        p.parseFactoryMethod(beginToken);
+      } else {
+        p.parseFunction(beginToken, getOrSet);
+      }
+    }
+    cachedNode = parse(listener, getCompilationUnit(), parseFunction);
+    return cachedNode;
+  }
+
+  Token position() {
+    return findMyName(beginToken);
+  }
+
+  PartialFunctionElement cloneTo(Element enclosing,
+                                 DiagnosticListener listener) {
+    if (patch != null) {
+      listener.cancel("Cloning a patched function.", element: this);
+    }
+    PartialFunctionElement result = new PartialFunctionElement(
+        name, beginToken, getOrSet, endToken, kind, modifiers, enclosing);
+    return result;
+  }
+}
+
+class PartialFieldListElement extends VariableListElementX {
+  final Token beginToken;
+  final Token endToken;
+
+  PartialFieldListElement(Token this.beginToken,
+                          Token this.endToken,
+                          Modifiers modifiers,
+                          Element enclosing)
+    : super(ElementKind.VARIABLE_LIST, modifiers, enclosing);
+
+  VariableDefinitions parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = parse(listener,
+                       getCompilationUnit(),
+                       (p) => p.parseVariablesDeclaration(beginToken));
+    if (!cachedNode.modifiers.isVar() &&
+        !cachedNode.modifiers.isFinal() &&
+        !cachedNode.modifiers.isConst() &&
+        cachedNode.type == null) {
+      listener.cancel('A field declaration must start with var, final, '
+                      'const, or a type annotation.',
+                      node: cachedNode);
+    }
+    return cachedNode;
+  }
+
+  Token position() => beginToken; // findMyName doesn't work. I'm nameless.
+
+  PartialFieldListElement cloneTo(Element enclosing,
+                                  DiagnosticListener listener) {
+    PartialFieldListElement result = new PartialFieldListElement(
+        beginToken, endToken, modifiers, enclosing);
+    return result;
+  }
+}
+
+class PartialTypedefElement extends TypedefElementX {
+  final Token token;
+
+  PartialTypedefElement(SourceString name, Element enclosing, this.token)
+      : super(name, enclosing);
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = parse(listener,
+                       getCompilationUnit(),
+                       (p) => p.parseTopLevelDeclaration(token));
+    return cachedNode;
+  }
+
+  position() => findMyName(token);
+
+  PartialTypedefElement cloneTo(Element enclosing,
+                                DiagnosticListener listener) {
+    PartialTypedefElement result =
+        new PartialTypedefElement(name, enclosing, token);
+    return result;
+  }
+}
+
+/// A [MetadataAnnotation] which is constructed on demand.
+class PartialMetadataAnnotation extends MetadataAnnotationX {
+  final Token beginToken;
+  final Token tokenAfterEndToken;
+  Expression cachedNode;
+  Constant value;
+
+  PartialMetadataAnnotation(this.beginToken, this.tokenAfterEndToken);
+
+  Token get endToken {
+    Token token = beginToken;
+    while (token.kind != EOF_TOKEN) {
+      if (identical(token.next, tokenAfterEndToken)) return token;
+      token = token.next;
+    }
+  }
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = parse(listener,
+                       annotatedElement.getCompilationUnit(),
+                       (p) => p.parseMetadata(beginToken));
+    return cachedNode;
+  }
+}
+
+Node parse(DiagnosticListener diagnosticListener,
+           CompilationUnitElement element,
+           doParse(Parser parser)) {
+  NodeListener listener = new NodeListener(diagnosticListener, element);
+  doParse(new Parser(listener));
+  Node node = listener.popNode();
+  assert(listener.nodes.isEmpty);
+  return node;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart
new file mode 100644
index 0000000..5a85e00
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart
@@ -0,0 +1,2231 @@
+// 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.
+
+part of scanner;
+
+/**
+ * An event generating parser of Dart programs. This parser expects
+ * all tokens in a linked list (aka a token stream).
+ *
+ * The class [Scanner] is used to generate a token stream. See the
+ * file scanner.dart.
+ *
+ * Subclasses of the class [Listener] are used to listen to events.
+ *
+ * Most methods of this class belong in one of two major categories:
+ * parse metods and peek methods. Parse methods all have the prefix
+ * parse, and peek methods all have the prefix peek.
+ *
+ * Parse methods generate events (by calling methods on [listener])
+ * and return the next token to parse. Peek methods do not generate
+ * events (except for errors) and may return null.
+ *
+ * Parse methods are generally named parseGrammarProductionSuffix. The
+ * suffix can be one of "opt", or "star". "opt" means zero or one
+ * matches, "star" means zero or more matches. For example,
+ * [parseMetadataStar] corresponds to this grammar snippet: [:
+ * metadata* :], and [parseTypeOpt] corresponds to: [: type? :].
+ */
+class Parser {
+  final Listener listener;
+  bool mayParseFunctionExpressions = true;
+
+  Parser(this.listener);
+
+  Token parseUnit(Token token) {
+    listener.beginCompilationUnit(token);
+    int count = 0;
+    while (!identical(token.kind, EOF_TOKEN)) {
+      token = parseTopLevelDeclaration(token);
+      listener.endTopLevelDeclaration(token);
+      count++;
+    }
+    listener.endCompilationUnit(count, token);
+    return token;
+  }
+
+  Token parseTopLevelDeclaration(Token token) {
+    token = parseMetadataStar(token);
+    final String value = token.stringValue;
+    if (identical(value, 'interface')) {
+      return parseInterface(token);
+    } else if ((identical(value, 'abstract')) || (identical(value, 'class'))) {
+      return parseClass(token);
+    } else if (identical(value, 'typedef')) {
+      return parseTypedef(token);
+    } else if (identical(value, '#')) {
+      return parseScriptTags(token);
+    } else if (identical(value, 'library')) {
+      return parseLibraryName(token);
+    } else if (identical(value, 'import')) {
+      return parseImport(token);
+    } else if (identical(value, 'export')) {
+      return parseExport(token);
+    } else if (identical(value, 'part')) {
+      return parsePartOrPartOf(token);
+    } else {
+      return parseTopLevelMember(token);
+    }
+  }
+
+  /// library qualified ';'
+  Token parseLibraryName(Token token) {
+    Token libraryKeyword = token;
+    listener.beginLibraryName(libraryKeyword);
+    assert(optional('library', token));
+    token = parseQualified(token.next);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endLibraryName(libraryKeyword, semicolon);
+    return token;
+  }
+
+  /// import uri (as identifier)? combinator* ';'
+  Token parseImport(Token token) {
+    Token importKeyword = token;
+    listener.beginImport(importKeyword);
+    assert(optional('import', token));
+    token = parseLiteralStringOrRecoverExpression(token.next);
+    Token asKeyword;
+    if (optional('as', token)) {
+      asKeyword = token;
+      token = parseIdentifier(token.next);
+    }
+    token = parseCombinators(token);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endImport(importKeyword, asKeyword, semicolon);
+    return token;
+  }
+
+  /// export uri combinator* ';'
+  Token parseExport(Token token) {
+    Token exportKeyword = token;
+    listener.beginExport(exportKeyword);
+    assert(optional('export', token));
+    token = parseLiteralStringOrRecoverExpression(token.next);
+    token = parseCombinators(token);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endExport(exportKeyword, semicolon);
+    return token;
+  }
+
+  Token parseCombinators(Token token) {
+    listener.beginCombinators(token);
+    int count = 0;
+    while (true) {
+      String value = token.stringValue;
+      if (identical('hide', value)) {
+        token = parseHide(token);
+      } else if (identical('show', value)) {
+        token = parseShow(token);
+      } else {
+        listener.endCombinators(count);
+        return token;
+      }
+      count++;
+    }
+  }
+
+  /// hide identifierList
+  Token parseHide(Token token) {
+    Token hideKeyword = token;
+    listener.beginHide(hideKeyword);
+    assert(optional('hide', token));
+    token = parseIdentifierList(token.next);
+    listener.endHide(hideKeyword);
+    return token;
+  }
+
+  /// show identifierList
+  Token parseShow(Token token) {
+    Token showKeyword = token;
+    listener.beginShow(showKeyword);
+    assert(optional('show', token));
+    token = parseIdentifierList(token.next);
+    listener.endShow(showKeyword);
+    return token;
+  }
+
+  /// identifier (, identifier)*
+  Token parseIdentifierList(Token token) {
+    listener.beginIdentifierList(token);
+    token = parseIdentifier(token);
+    int count = 1;
+    while (optional(',', token)) {
+      token = parseIdentifier(token.next);
+      count++;
+    }
+    listener.endIdentifierList(count);
+    return token;
+  }
+
+  /// type (, type)*
+  Token parseTypeList(Token token) {
+    listener.beginTypeList(token);
+    token = parseType(token);
+    int count = 1;
+    while (optional(',', token)) {
+      token = parseType(token.next);
+      count++;
+    }
+    listener.endTypeList(count);
+    return token;
+  }
+
+  Token parsePartOrPartOf(Token token) {
+    assert(optional('part', token));
+    if (optional('of', token.next)) {
+      return parsePartOf(token);
+    } else {
+      return parsePart(token);
+    }
+  }
+
+  Token parsePart(Token token) {
+    Token partKeyword = token;
+    listener.beginPart(token);
+    assert(optional('part', token));
+    token = parseLiteralStringOrRecoverExpression(token.next);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endPart(partKeyword, semicolon);
+    return token;
+  }
+
+  Token parsePartOf(Token token) {
+    listener.beginPartOf(token);
+    assert(optional('part', token));
+    assert(optional('of', token.next));
+    Token partKeyword = token;
+    token = parseQualified(token.next.next);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endPartOf(partKeyword, semicolon);
+    return token;
+  }
+
+  Token parseMetadataStar(Token token) {
+    while (optional('@', token)) {
+      token = parseMetadata(token);
+    }
+    return token;
+  }
+
+  /**
+   * Parse
+   * [: '@' qualified (‘.’ identifier)? (arguments)? :]
+   */
+  Token parseMetadata(Token token) {
+    listener.beginMetadata(token);
+    Token atToken = token;
+    assert(optional('@', token));
+    token = parseIdentifier(token.next);
+    token = parseQualifiedRestOpt(token);
+    token = parseTypeArgumentsOpt(token);
+    Token period = null;
+    if (optional('.', token)) {
+      period = token;
+      token = parseIdentifier(token.next);
+    }
+    token = parseArgumentsOpt(token);
+    listener.endMetadata(atToken, period, token);
+    return token;
+  }
+
+  Token parseInterface(Token token) {
+    Token interfaceKeyword = token;
+    listener.beginInterface(token);
+    token = parseIdentifier(token.next);
+    token = parseTypeVariablesOpt(token);
+    int supertypeCount = 0;
+    Token extendsKeyword = null;
+    if (optional('extends', token)) {
+      extendsKeyword = token;
+      do {
+        token = parseType(token.next);
+        ++supertypeCount;
+      } while (optional(',', token));
+    }
+    token = parseDefaultClauseOpt(token);
+    token = parseInterfaceBody(token);
+    listener.endInterface(supertypeCount, interfaceKeyword,
+                          extendsKeyword, token);
+    return token.next;
+  }
+
+  Token parseInterfaceBody(Token token) {
+    return parseClassBody(token);
+  }
+
+  Token parseTypedef(Token token) {
+    Token typedefKeyword = token;
+    if (optional('=', peekAfterType(token.next))) {
+      listener.beginNamedMixinApplication(token);
+      token = parseIdentifier(token.next);
+      token = parseTypeVariablesOpt(token);
+      token = expect('=', token);
+      token = parseModifiers(token);
+      token = parseMixinApplication(token);
+      Token implementsKeyword = null;
+      if (optional('implements', token)) {
+        implementsKeyword = token;
+        token = parseTypeList(token.next);
+      }
+      listener.endNamedMixinApplication(
+          typedefKeyword, implementsKeyword, token);
+    } else {
+      listener.beginFunctionTypeAlias(token);
+      token = parseReturnTypeOpt(token.next);
+      token = parseIdentifier(token);
+      token = parseTypeVariablesOpt(token);
+      token = parseFormalParameters(token);
+      listener.endFunctionTypeAlias(typedefKeyword, token);
+    }
+    return expect(';', token);
+  }
+
+  Token parseMixinApplication(Token token) {
+    listener.beginMixinApplication(token);
+    token = parseType(token);
+    token = expect('with', token);
+    token = parseTypeList(token);
+    listener.endMixinApplication();
+    return token;
+  }
+
+  Token parseReturnTypeOpt(Token token) {
+    if (identical(token.stringValue, 'void')) {
+      listener.handleVoidKeyword(token);
+      return token.next;
+    } else {
+      return parseTypeOpt(token);
+    }
+  }
+
+  Token parseFormalParametersOpt(Token token) {
+    if (optional('(', token)) {
+      return parseFormalParameters(token);
+    } else {
+      listener.handleNoFormalParameters(token);
+      return token;
+    }
+  }
+
+  Token parseFormalParameters(Token token) {
+    Token begin = token;
+    listener.beginFormalParameters(begin);
+    expect('(', token);
+    int parameterCount = 0;
+    if (optional(')', token.next)) {
+      listener.endFormalParameters(parameterCount, begin, token.next);
+      return token.next.next;
+    }
+    do {
+      ++parameterCount;
+      token = token.next;
+      String value = token.stringValue;
+      if (identical(value, '[')) {
+        token = parseOptionalFormalParameters(token, false);
+        break;
+      } else if (identical(value, '{')) {
+        token = parseOptionalFormalParameters(token, true);
+        break;
+      }
+      token = parseFormalParameter(token);
+    } while (optional(',', token));
+    listener.endFormalParameters(parameterCount, begin, token);
+    return expect(')', token);
+  }
+
+  Token parseFormalParameter(Token token) {
+    listener.beginFormalParameter(token);
+    token = parseModifiers(token);
+    // TODO(ahe): Validate that there are formal parameters if void.
+    token = parseReturnTypeOpt(token);
+    Token thisKeyword = null;
+    if (optional('this', token)) {
+      thisKeyword = token;
+      // TODO(ahe): Validate field initializers are only used in
+      // constructors, and not for function-typed arguments.
+      token = expect('.', token.next);
+    }
+    token = parseIdentifier(token);
+    if (optional('(', token)) {
+      token = parseFormalParameters(token);
+      listener.handleFunctionTypedFormalParameter(token);
+    }
+    String value = token.stringValue;
+    if ((identical('=', value)) || (identical(':', value))) {
+      // TODO(ahe): Validate that these are only used for optional parameters.
+      Token equal = token;
+      token = parseExpression(token.next);
+      listener.handleValuedFormalParameter(equal, token);
+    }
+    listener.endFormalParameter(thisKeyword);
+    return token;
+  }
+
+  Token parseOptionalFormalParameters(Token token, bool isNamed) {
+    Token begin = token;
+    listener.beginOptionalFormalParameters(begin);
+    assert((isNamed && optional('{', token)) || optional('[', token));
+    int parameterCount = 0;
+    do {
+      token = token.next;
+      token = parseFormalParameter(token);
+      ++parameterCount;
+    } while (optional(',', token));
+    listener.endOptionalFormalParameters(parameterCount, begin, token);
+    if (isNamed) {
+      return expect('}', token);
+    } else {
+      return expect(']', token);
+    }
+  }
+
+  Token parseTypeOpt(Token token) {
+    String value = token.stringValue;
+    if (!identical(value, 'this')) {
+      Token peek = peekAfterExpectedType(token);
+      if (peek.isIdentifier() || optional('this', peek)) {
+        return parseType(token);
+      }
+    }
+    listener.handleNoType(token);
+    return token;
+  }
+
+  bool isValidTypeReference(Token token) {
+    final kind = token.kind;
+    if (identical(kind, IDENTIFIER_TOKEN)) return true;
+    if (identical(kind, KEYWORD_TOKEN)) {
+      Keyword keyword = token.value;
+      String value = keyword.stringValue;
+      // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+      return keyword.isPseudo
+          || (identical(value, 'dynamic'))
+          || (identical(value, 'Dynamic'))
+          || (identical(value, 'void'));
+    }
+    return false;
+  }
+
+  Token parseDefaultClauseOpt(Token token) {
+    if (isDefaultKeyword(token)) {
+      // TODO(ahe): Remove support for 'factory' in this position.
+      Token defaultKeyword = token;
+      listener.beginDefaultClause(defaultKeyword);
+      token = parseIdentifier(token.next);
+      token = parseQualifiedRestOpt(token);
+      token = parseTypeVariablesOpt(token);
+      listener.endDefaultClause(defaultKeyword);
+    } else {
+      listener.handleNoDefaultClause(token);
+    }
+    return token;
+  }
+
+  Token parseQualified(Token token) {
+    token = parseIdentifier(token);
+    while (optional('.', token)) {
+      token = parseQualifiedRest(token);
+    }
+    return token;
+  }
+
+  Token parseQualifiedRestOpt(Token token) {
+    if (optional('.', token)) {
+      return parseQualifiedRest(token);
+    } else {
+      return token;
+    }
+  }
+
+  Token parseQualifiedRest(Token token) {
+    assert(optional('.', token));
+    Token period = token;
+    token = parseIdentifier(token.next);
+    listener.handleQualified(period);
+    return token;
+  }
+
+  bool isDefaultKeyword(Token token) {
+    String value = token.stringValue;
+    if (identical(value, 'default')) return true;
+    if (identical(value, 'factory')) {
+      listener.recoverableError("expected 'default'", token: token);
+      return true;
+    }
+    return false;
+  }
+
+  Token skipBlock(Token token) {
+    if (!optional('{', token)) {
+      return listener.expectedBlockToSkip(token);
+    }
+    BeginGroupToken beginGroupToken = token;
+    Token endGroup = beginGroupToken.endGroup;
+    if (endGroup == null) {
+      return listener.unmatched(beginGroupToken);
+    } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) {
+      return listener.unmatched(beginGroupToken);
+    }
+    return beginGroupToken.endGroup;
+  }
+
+  Token parseClass(Token token) {
+    Token begin = token;
+    listener.beginClassDeclaration(token);
+    int modifierCount = 0;
+    if (optional('abstract', token)) {
+      listener.handleModifier(token);
+      modifierCount++;
+      token = token.next;
+    }
+    listener.handleModifiers(modifierCount);
+    token = parseIdentifier(token.next);
+    token = parseTypeVariablesOpt(token);
+    Token extendsKeyword;
+    if (optional('extends', token)) {
+      extendsKeyword = token;
+      if (optional('with', peekAfterType(token.next))) {
+        token = parseMixinApplication(token.next);
+      } else {
+        token = parseType(token.next);
+      }
+    } else {
+      extendsKeyword = null;
+      listener.handleNoType(token);
+    }
+    Token implementsKeyword;
+    int interfacesCount = 0;
+    if (optional('implements', token)) {
+      implementsKeyword = token;
+      do {
+        token = parseType(token.next);
+        ++interfacesCount;
+      } while (optional(',', token));
+    }
+    token = parseClassBody(token);
+    listener.endClassDeclaration(interfacesCount, begin, extendsKeyword,
+                                 implementsKeyword, token);
+    return token.next;
+  }
+
+  Token parseStringPart(Token token) {
+    if (identical(token.kind, STRING_TOKEN)) {
+      listener.handleStringPart(token);
+      return token.next;
+    } else {
+      return listener.expected('string', token);
+    }
+  }
+
+  Token parseIdentifier(Token token) {
+    if (token.isIdentifier()) {
+      listener.handleIdentifier(token);
+    } else {
+      listener.expectedIdentifier(token);
+    }
+    return token.next;
+  }
+
+  Token expect(String string, Token token) {
+    if (!identical(string, token.stringValue)) {
+      return listener.expected(string, token);
+    }
+    return token.next;
+  }
+
+  Token parseTypeVariable(Token token) {
+    listener.beginTypeVariable(token);
+    token = parseIdentifier(token);
+    if (optional('extends', token)) {
+      token = parseType(token.next);
+    } else {
+      listener.handleNoType(token);
+    }
+    listener.endTypeVariable(token);
+    return token;
+  }
+
+  /**
+   * Returns true if the stringValue of the [token] is [value].
+   */
+  bool optional(String value, Token token) {
+      return identical(value, token.stringValue);
+  }
+
+  /**
+   * Returns true if the stringValue of the [token] is either [value1],
+   * [value2], [value3], or [value4].
+   */
+  bool isOneOf4(Token token,
+                String value1, String value2, String value3, String value4) {
+    String stringValue = token.stringValue;
+    return identical(value1, stringValue) ||
+           identical(value2, stringValue) ||
+           identical(value3, stringValue) ||
+           identical(value4, stringValue);
+  }
+
+  bool notEofOrValue(String value, Token token) {
+    return !identical(token.kind, EOF_TOKEN) &&
+           !identical(value, token.stringValue);
+  }
+
+  Token parseType(Token token) {
+    Token begin = token;
+    if (isValidTypeReference(token)) {
+      token = parseIdentifier(token);
+      token = parseQualifiedRestOpt(token);
+    } else {
+      token = listener.expectedType(token);
+    }
+    token = parseTypeArgumentsOpt(token);
+    listener.endType(begin, token);
+    return token;
+  }
+
+  Token parseTypeArgumentsOpt(Token token) {
+    return parseStuff(token,
+                      (t) => listener.beginTypeArguments(t),
+                      (t) => parseType(t),
+                      (c, bt, et) => listener.endTypeArguments(c, bt, et),
+                      (t) => listener.handleNoTypeArguments(t));
+  }
+
+  Token parseTypeVariablesOpt(Token token) {
+    return parseStuff(token,
+                      (t) => listener.beginTypeVariables(t),
+                      (t) => parseTypeVariable(t),
+                      (c, bt, et) => listener.endTypeVariables(c, bt, et),
+                      (t) => listener.handleNoTypeVariables(t));
+  }
+
+  // TODO(ahe): Clean this up.
+  Token parseStuff(Token token, Function beginStuff, Function stuffParser,
+                   Function endStuff, Function handleNoStuff) {
+    if (optional('<', token)) {
+      Token begin = token;
+      beginStuff(begin);
+      int count = 0;
+      do {
+        token = stuffParser(token.next);
+        ++count;
+      } while (optional(',', token));
+      Token next = token.next;
+      if (identical(token.stringValue, '>>')) {
+        token = new Token(GT_INFO, token.charOffset);
+        token.next = new Token(GT_INFO, token.charOffset + 1);
+        token.next.next = next;
+      } else if (identical(token.stringValue, '>>>')) {
+        token = new Token(GT_INFO, token.charOffset);
+        token.next = new Token(GT_GT_INFO, token.charOffset + 1);
+        token.next.next = next;
+      }
+      endStuff(count, begin, token);
+      return expect('>', token);
+    }
+    handleNoStuff(token);
+    return token;
+  }
+
+  Token parseTopLevelMember(Token token) {
+    Token start = token;
+    listener.beginTopLevelMember(token);
+
+    Link<Token> identifiers = findMemberName(token);
+    if (identifiers.isEmpty) {
+      return listener.unexpected(start);
+    }
+    Token name = identifiers.head;
+    identifiers = identifiers.tail;
+    Token getOrSet;
+    if (!identifiers.isEmpty) {
+      String value = identifiers.head.stringValue;
+      if ((identical(value, 'get')) || (identical(value, 'set'))) {
+        getOrSet = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    Token type;
+    if (!identifiers.isEmpty) {
+      if (isValidTypeReference(identifiers.head)) {
+        type = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    parseModifierList(identifiers.reverse());
+    if (type == null) {
+      listener.handleNoType(token);
+    } else {
+      parseReturnTypeOpt(type);
+    }
+    token = parseIdentifier(name);
+
+    bool isField;
+    while (true) {
+      // Loop to allow the listener to rewrite the token stream for
+      // error handling.
+      final String value = token.stringValue;
+      if ((identical(value, '(')) || (identical(value, '{'))
+          || (identical(value, '=>'))) {
+        isField = false;
+        break;
+      } else if ((identical(value, '=')) || (identical(value, ','))) {
+        isField = true;
+        break;
+      } else if (identical(value, ';')) {
+        if (getOrSet != null) {
+          // If we found a "get" keyword, this must be an abstract
+          // getter.
+          isField = (!identical(getOrSet.stringValue, 'get'));
+          // TODO(ahe): This feels like a hack.
+        } else {
+          isField = true;
+        }
+        break;
+      } else {
+        token = listener.unexpected(token);
+        if (identical(token.kind, EOF_TOKEN)) {
+          // TODO(ahe): This is a hack. It would be better to tell the
+          // listener more explicitly that it must pop an identifier.
+          listener.endTopLevelFields(1, start, token);
+          return token;
+        }
+      }
+    }
+    if (isField) {
+      int fieldCount = 1;
+      token = parseVariableInitializerOpt(token);
+      while (optional(',', token)) {
+        token = parseIdentifier(token.next);
+        token = parseVariableInitializerOpt(token);
+        ++fieldCount;
+      }
+      expectSemicolon(token);
+      listener.endTopLevelFields(fieldCount, start, token);
+    } else {
+      token = parseFormalParametersOpt(token);
+      token = parseFunctionBody(token, false);
+      listener.endTopLevelMethod(start, getOrSet, token);
+    }
+    return token.next;
+  }
+
+  Link<Token> findMemberName(Token token) {
+    Token start = token;
+    Link<Token> identifiers = const Link<Token>();
+    while (!identical(token.kind, EOF_TOKEN)) {
+      String value = token.stringValue;
+      if ((identical(value, '(')) || (identical(value, '{'))
+          || (identical(value, '=>'))) {
+        // A method.
+        return identifiers;
+      } else if ((identical(value, '=')) || (identical(value, ';'))
+          || (identical(value, ','))) {
+        // A field or abstract getter.
+        return identifiers;
+      }
+      identifiers = identifiers.prepend(token);
+      if (isValidTypeReference(token)) {
+        // type ...
+        if (optional('.', token.next)) {
+          // type '.' ...
+          if (token.next.next.isIdentifier()) {
+            // type '.' identifier
+            token = token.next.next;
+          }
+        }
+        if (optional('<', token.next)) {
+          if (token.next is BeginGroupToken) {
+            BeginGroupToken beginGroup = token.next;
+            token = beginGroup.endGroup;
+          }
+        }
+      }
+      token = token.next;
+    }
+    return listener.expectedDeclaration(start);
+  }
+
+  Token parseVariableInitializerOpt(Token token) {
+    if (optional('=', token)) {
+      Token assignment = token;
+      listener.beginInitializer(token);
+      token = parseExpression(token.next);
+      listener.endInitializer(assignment);
+    }
+    return token;
+  }
+
+  Token parseInitializersOpt(Token token) {
+    if (optional(':', token)) {
+      return parseInitializers(token);
+    } else {
+      listener.handleNoInitializers();
+      return token;
+    }
+  }
+
+  Token parseInitializers(Token token) {
+    Token begin = token;
+    listener.beginInitializers(begin);
+    expect(':', token);
+    int count = 0;
+    bool old = mayParseFunctionExpressions;
+    mayParseFunctionExpressions = false;
+    do {
+      token = parseExpression(token.next);
+      ++count;
+    } while (optional(',', token));
+    mayParseFunctionExpressions = old;
+    listener.endInitializers(count, begin, token);
+    return token;
+  }
+
+  Token parseScriptTags(Token token) {
+    Token begin = token;
+    listener.beginScriptTag(token);
+    token = parseIdentifier(token.next);
+    token = expect('(', token);
+    token = parseLiteralStringOrRecoverExpression(token);
+    bool hasPrefix = false;
+    if (optional(',', token)) {
+      hasPrefix = true;
+      token = parseIdentifier(token.next);
+      token = expect(':', token);
+      token = parseLiteralStringOrRecoverExpression(token);
+    }
+    token = expect(')', token);
+    listener.endScriptTag(hasPrefix, begin, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseLiteralStringOrRecoverExpression(Token token) {
+    if (identical(token.kind, STRING_TOKEN)) {
+      return parseLiteralString(token);
+    } else {
+      listener.recoverableError("unexpected", token: token);
+      return parseExpression(token);
+    }
+  }
+
+  Token expectSemicolon(Token token) {
+    return expect(';', token);
+  }
+
+  bool isModifier(Token token) {
+    final String value = token.stringValue;
+    return (identical('final', value)) ||
+           (identical('var', value)) ||
+           (identical('const', value)) ||
+           (identical('abstract', value)) ||
+           (identical('static', value)) ||
+           (identical('external', value));
+  }
+
+  Token parseModifier(Token token) {
+    assert(isModifier(token));
+    listener.handleModifier(token);
+    return token.next;
+  }
+
+  void parseModifierList(Link<Token> tokens) {
+    int count = 0;
+    for (; !tokens.isEmpty; tokens = tokens.tail) {
+      Token token = tokens.head;
+      if (isModifier(token)) {
+        parseModifier(token);
+      } else {
+        listener.unexpected(token);
+      }
+      count++;
+    }
+    listener.handleModifiers(count);
+  }
+
+  Token parseModifiers(Token token) {
+    int count = 0;
+    while (identical(token.kind, KEYWORD_TOKEN)) {
+      if (!isModifier(token))
+        break;
+      token = parseModifier(token);
+      count++;
+    }
+    listener.handleModifiers(count);
+    return token;
+  }
+
+  Token peekAfterType(Token token) {
+    // TODO(ahe): Also handle var?
+    // We are looking at "identifier ...".
+    Token peek = token.next;
+    if (identical(peek.kind, PERIOD_TOKEN)) {
+      if (peek.next.isIdentifier()) {
+        // Look past a library prefix.
+        peek = peek.next.next;
+      }
+    }
+    // We are looking at "qualified ...".
+    if (identical(peek.kind, LT_TOKEN)) {
+      // Possibly generic type.
+      // We are looking at "qualified '<'".
+      BeginGroupToken beginGroupToken = peek;
+      Token gtToken = beginGroupToken.endGroup;
+      if (gtToken != null) {
+        // We are looking at "qualified '<' ... '>' ...".
+        return gtToken.next;
+      }
+    }
+    return peek;
+  }
+
+  /**
+   * Returns the token after the type which is expected to begin at [token].
+   * If [token] is not the start of a type, [Listener.unexpectedType] is called.
+   */
+  Token peekAfterExpectedType(Token token) {
+    if (!identical('void', token.stringValue) && !token.isIdentifier()) {
+      return listener.expectedType(token);
+    }
+    return peekAfterType(token);
+  }
+
+  Token parseClassBody(Token token) {
+    Token begin = token;
+    listener.beginClassBody(token);
+    if (!optional('{', token)) {
+      token = listener.expectedClassBody(token);
+    }
+    token = token.next;
+    int count = 0;
+    while (notEofOrValue('}', token)) {
+      token = parseMember(token);
+      ++count;
+    }
+    expect('}', token);
+    listener.endClassBody(count, begin, token);
+    return token;
+  }
+
+  bool isGetOrSet(Token token) {
+    final String value = token.stringValue;
+    return (identical(value, 'get')) || (identical(value, 'set'));
+  }
+
+  bool isFactoryDeclaration(Token token) {
+    if (optional('external', token)) token = token.next;
+    if (optional('const', token)) token = token.next;
+    return optional('factory', token);
+  }
+
+  Token parseMember(Token token) {
+    token = parseMetadataStar(token);
+    String value = token.stringValue;
+    if (isFactoryDeclaration(token)) {
+      return parseFactoryMethod(token);
+    }
+    Token start = token;
+    listener.beginMember(token);
+
+    Link<Token> identifiers = findMemberName(token);
+    if (identifiers.isEmpty) {
+      return listener.unexpected(start);
+    }
+    Token name = identifiers.head;
+    identifiers = identifiers.tail;
+    if (!identifiers.isEmpty) {
+      if (optional('operator', identifiers.head)) {
+        name = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    Token getOrSet;
+    if (!identifiers.isEmpty) {
+      if (isGetOrSet(identifiers.head)) {
+        getOrSet = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    Token type;
+    if (!identifiers.isEmpty) {
+      if (isValidTypeReference(identifiers.head)) {
+        type = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    parseModifierList(identifiers.reverse());
+    if (type == null) {
+      listener.handleNoType(token);
+    } else {
+      parseReturnTypeOpt(type);
+    }
+
+    if (optional('operator', name)) {
+      token = parseOperatorName(name);
+    } else {
+      token = parseIdentifier(name);
+    }
+    bool isField;
+    while (true) {
+      // Loop to allow the listener to rewrite the token stream for
+      // error handling.
+      final String value = token.stringValue;
+      if ((identical(value, '(')) || (identical(value, '.'))
+          || (identical(value, '{')) || (identical(value, '=>'))) {
+        isField = false;
+        break;
+      } else if (identical(value, ';')) {
+        if (getOrSet != null) {
+          // If we found a "get" keyword, this must be an abstract
+          // getter.
+          isField = (!identical(getOrSet.stringValue, 'get'));
+          // TODO(ahe): This feels like a hack.
+        } else {
+          isField = true;
+        }
+        break;
+      } else if ((identical(value, '=')) || (identical(value, ','))) {
+        isField = true;
+        break;
+      } else {
+        token = listener.unexpected(token);
+        if (identical(token.kind, EOF_TOKEN)) {
+          // TODO(ahe): This is a hack, see parseTopLevelMember.
+          listener.endFields(1, start, token);
+          return token;
+        }
+      }
+    }
+    if (isField) {
+      int fieldCount = 1;
+      token = parseVariableInitializerOpt(token);
+      if (getOrSet != null) {
+        listener.recoverableError("unexpected", token: getOrSet);
+      }
+      while (optional(',', token)) {
+        // TODO(ahe): Count these.
+        token = parseIdentifier(token.next);
+        token = parseVariableInitializerOpt(token);
+        ++fieldCount;
+      }
+      expectSemicolon(token);
+      listener.endFields(fieldCount, start, token);
+    } else {
+      token = parseQualifiedRestOpt(token);
+      token = parseFormalParametersOpt(token);
+      token = parseInitializersOpt(token);
+      if (optional('=', token)) {
+        token = parseRedirectingFactoryBody(token);
+      } else {
+        token = parseFunctionBody(token, false);
+      }
+      listener.endMethod(getOrSet, start, token);
+    }
+    return token.next;
+  }
+
+  Token parseFactoryMethod(Token token) {
+    assert(isFactoryDeclaration(token));
+    Token start = token;
+    if (identical(token.stringValue, 'external')) token = token.next;
+    Token constKeyword = null;
+    if (optional('const', token)) {
+      constKeyword = token;
+      token = token.next;
+    }
+    Token factoryKeyword = token;
+    listener.beginFactoryMethod(factoryKeyword);
+    token = token.next; // Skip 'factory'.
+    token = parseConstructorReference(token);
+    token = parseFormalParameters(token);
+    if (optional('=', token)) {
+      token = parseRedirectingFactoryBody(token);
+    } else {
+      token = parseFunctionBody(token, false);
+    }
+    listener.endFactoryMethod(start, token);
+    return token.next;
+  }
+
+  Token parseOperatorName(Token token) {
+    assert(optional('operator', token));
+    if (isUserDefinableOperator(token.next.stringValue)) {
+      Token operator = token;
+      token = token.next;
+      listener.handleOperatorName(operator, token);
+      return token.next;
+    } else {
+      return parseIdentifier(token);
+    }
+  }
+
+  Token parseFunction(Token token, Token getOrSet) {
+    listener.beginFunction(token);
+    token = parseModifiers(token);
+    if (identical(getOrSet, token)) token = token.next;
+    if (optional('operator', token)) {
+      listener.handleNoType(token);
+      listener.beginFunctionName(token);
+      token = parseOperatorName(token);
+    } else {
+      token = parseReturnTypeOpt(token);
+      if (identical(getOrSet, token)) token = token.next;
+      listener.beginFunctionName(token);
+      if (optional('operator', token)) {
+        token = parseOperatorName(token);
+      } else {
+        token = parseIdentifier(token);
+      }
+    }
+    token = parseQualifiedRestOpt(token);
+    listener.endFunctionName(token);
+    token = parseFormalParametersOpt(token);
+    token = parseInitializersOpt(token);
+    if (optional('=', token)) {
+      token = parseRedirectingFactoryBody(token);
+    } else {
+      token = parseFunctionBody(token, false);
+    }
+    listener.endFunction(getOrSet, token);
+    return token.next;
+  }
+
+  Token parseUnamedFunction(Token token) {
+    listener.beginUnamedFunction(token);
+    token = parseFormalParameters(token);
+    bool isBlock = optional('{', token);
+    token = parseFunctionBody(token, true);
+    listener.endUnamedFunction(token);
+    return isBlock ? token.next : token;
+  }
+
+  Token parseFunctionDeclaration(Token token) {
+    listener.beginFunctionDeclaration(token);
+    token = parseFunction(token, null);
+    listener.endFunctionDeclaration(token);
+    return token;
+  }
+
+  Token parseFunctionExpression(Token token) {
+    listener.beginFunction(token);
+    listener.handleModifiers(0);
+    token = parseReturnTypeOpt(token);
+    listener.beginFunctionName(token);
+    token = parseIdentifier(token);
+    listener.endFunctionName(token);
+    token = parseFormalParameters(token);
+    listener.handleNoInitializers();
+    bool isBlock = optional('{', token);
+    token = parseFunctionBody(token, true);
+    listener.endFunction(null, token);
+    return isBlock ? token.next : token;
+  }
+
+  Token parseConstructorReference(Token token) {
+    Token start = token;
+    listener.beginConstructorReference(start);
+    token = parseIdentifier(token);
+    token = parseQualifiedRestOpt(token);
+    token = parseTypeArgumentsOpt(token);
+    Token period = null;
+    if (optional('.', token)) {
+      period = token;
+      token = parseIdentifier(token.next);
+    }
+    listener.endConstructorReference(start, period, token);
+    return token;
+  }
+
+  Token parseRedirectingFactoryBody(Token token) {
+    listener.beginRedirectingFactoryBody(token);
+    assert(optional('=', token));
+    Token equals = token;
+    token = parseConstructorReference(token.next);
+    Token semicolon = token;
+    expectSemicolon(token);
+    listener.endRedirectingFactoryBody(equals, semicolon);
+    return token;
+  }
+
+  Token parseFunctionBody(Token token, bool isExpression) {
+    if (optional(';', token)) {
+      listener.endFunctionBody(0, null, token);
+      return token;
+    } else if (optional('=>', token)) {
+      Token begin = token;
+      token = parseExpression(token.next);
+      if (!isExpression) {
+        expectSemicolon(token);
+        listener.endReturnStatement(true, begin, token);
+      } else {
+        listener.endReturnStatement(true, begin, null);
+      }
+      return token;
+    }
+    Token begin = token;
+    int statementCount = 0;
+    if (!optional('{', token)) {
+      return listener.expectedFunctionBody(token);
+    }
+
+    listener.beginFunctionBody(begin);
+    token = token.next;
+    while (notEofOrValue('}', token)) {
+      token = parseStatement(token);
+      ++statementCount;
+    }
+    listener.endFunctionBody(statementCount, begin, token);
+    expect('}', token);
+    return token;
+  }
+
+  Token parseStatement(Token token) {
+    final value = token.stringValue;
+    if (identical(token.kind, IDENTIFIER_TOKEN)) {
+      return parseExpressionStatementOrDeclaration(token);
+    } else if (identical(value, '{')) {
+      return parseBlock(token);
+    } else if (identical(value, 'return')) {
+      return parseReturnStatement(token);
+    } else if (identical(value, 'var') || identical(value, 'final')) {
+      return parseVariablesDeclaration(token);
+    } else if (identical(value, 'if')) {
+      return parseIfStatement(token);
+    } else if (identical(value, 'for')) {
+      return parseForStatement(token);
+    } else if (identical(value, 'throw')) {
+      return parseThrowStatement(token);
+    } else if (identical(value, 'void')) {
+      return parseExpressionStatementOrDeclaration(token);
+    } else if (identical(value, 'while')) {
+      return parseWhileStatement(token);
+    } else if (identical(value, 'do')) {
+      return parseDoWhileStatement(token);
+    } else if (identical(value, 'try')) {
+      return parseTryStatement(token);
+    } else if (identical(value, 'switch')) {
+      return parseSwitchStatement(token);
+    } else if (identical(value, 'break')) {
+      return parseBreakStatement(token);
+    } else if (identical(value, 'continue')) {
+      return parseContinueStatement(token);
+    } else if (identical(value, 'assert')) {
+      return parseAssertStatement(token);
+    } else if (identical(value, ';')) {
+      return parseEmptyStatement(token);
+    } else if (identical(value, 'const')) {
+      return parseExpressionStatementOrConstDeclaration(token);
+    } else if (token.isIdentifier()) {
+      return parseExpressionStatementOrDeclaration(token);
+    } else {
+      return parseExpressionStatement(token);
+    }
+  }
+
+  Token parseReturnStatement(Token token) {
+    Token begin = token;
+    listener.beginReturnStatement(begin);
+    assert(identical('return', token.stringValue));
+    token = token.next;
+    if (optional(';', token)) {
+      listener.endReturnStatement(false, begin, token);
+    } else {
+      token = parseExpression(token);
+      listener.endReturnStatement(true, begin, token);
+    }
+    return expectSemicolon(token);
+  }
+
+  Token peekIdentifierAfterType(Token token) {
+    Token peek = peekAfterType(token);
+    if (peek != null && peek.isIdentifier()) {
+      // We are looking at "type identifier".
+      return peek;
+    } else {
+      return null;
+    }
+  }
+
+  Token peekIdentifierAfterOptionalType(Token token) {
+    Token peek = peekIdentifierAfterType(token);
+    if (peek != null) {
+      // We are looking at "type identifier".
+      return peek;
+    } else if (token.isIdentifier()) {
+      // We are looking at "identifier".
+      return token;
+    } else {
+      return null;
+    }
+  }
+
+  Token parseExpressionStatementOrDeclaration(Token token) {
+    assert(token.isIdentifier() || identical(token.stringValue, 'void'));
+    Token identifier = peekIdentifierAfterType(token);
+    if (identifier != null) {
+      assert(identifier.isIdentifier());
+      Token afterId = identifier.next;
+      int afterIdKind = afterId.kind;
+      if (identical(afterIdKind, EQ_TOKEN) ||
+          identical(afterIdKind, SEMICOLON_TOKEN) ||
+          identical(afterIdKind, COMMA_TOKEN)) {
+        // We are looking at "type identifier" followed by '=', ';', ','.
+        return parseVariablesDeclaration(token);
+      } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) {
+        // We are looking at "type identifier '('".
+        BeginGroupToken beginParen = afterId;
+        Token endParen = beginParen.endGroup;
+        Token afterParens = endParen.next;
+        if (optional('{', afterParens) || optional('=>', afterParens)) {
+          // We are looking at "type identifier '(' ... ')'" followed
+          // by '=>' or '{'.
+          return parseFunctionDeclaration(token);
+        }
+      }
+      // Fall-through to expression statement.
+    } else {
+      if (optional(':', token.next)) {
+        return parseLabeledStatement(token);
+      } else if (optional('(', token.next)) {
+        BeginGroupToken begin = token.next;
+        String afterParens = begin.endGroup.next.stringValue;
+        if (identical(afterParens, '{') || identical(afterParens, '=>')) {
+          return parseFunctionDeclaration(token);
+        }
+      }
+    }
+    return parseExpressionStatement(token);
+  }
+
+  Token parseExpressionStatementOrConstDeclaration(Token token) {
+    assert(identical(token.stringValue, 'const'));
+    if (isModifier(token.next)) {
+      return parseVariablesDeclaration(token);
+    }
+    Token identifier = peekIdentifierAfterOptionalType(token.next);
+    if (identifier != null) {
+      assert(identifier.isIdentifier());
+      Token afterId = identifier.next;
+      int afterIdKind = afterId.kind;
+      if (identical(afterIdKind, EQ_TOKEN) ||
+          identical(afterIdKind, SEMICOLON_TOKEN) ||
+          identical(afterIdKind, COMMA_TOKEN)) {
+        // We are looking at "const type identifier" followed by '=', ';', or
+        // ','.
+        return parseVariablesDeclaration(token);
+      }
+      // Fall-through to expression statement.
+    }
+    return parseExpressionStatement(token);
+  }
+
+  Token parseLabel(Token token) {
+    token = parseIdentifier(token);
+    Token colon = token;
+    token = expect(':', token);
+    listener.handleLabel(colon);
+    return token;
+  }
+
+  Token parseLabeledStatement(Token token) {
+    int labelCount = 0;
+    do {
+      token = parseLabel(token);
+      labelCount++;
+    } while (token.isIdentifier() && optional(':', token.next));
+    listener.beginLabeledStatement(token, labelCount);
+    token = parseStatement(token);
+    listener.endLabeledStatement(labelCount);
+    return token;
+  }
+
+  Token parseExpressionStatement(Token token) {
+    listener.beginExpressionStatement(token);
+    token = parseExpression(token);
+    listener.endExpressionStatement(token);
+    return expectSemicolon(token);
+  }
+
+  Token parseExpression(Token token) {
+    return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true);
+  }
+
+  Token parseExpressionWithoutCascade(Token token) {
+    return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false);
+  }
+
+  Token parseConditionalExpressionRest(Token token) {
+    assert(optional('?', token));
+    Token question = token;
+    token = parseExpressionWithoutCascade(token.next);
+    Token colon = token;
+    token = expect(':', token);
+    token = parseExpressionWithoutCascade(token);
+    listener.handleConditionalExpression(question, colon);
+    return token;
+  }
+
+  Token parsePrecedenceExpression(Token token, int precedence,
+                                  bool allowCascades) {
+    assert(precedence >= 1);
+    assert(precedence <= POSTFIX_PRECEDENCE);
+    token = parseUnaryExpression(token, allowCascades);
+    PrecedenceInfo info = token.info;
+    int tokenLevel = info.precedence;
+    for (int level = tokenLevel; level >= precedence; --level) {
+      while (identical(tokenLevel, level)) {
+        Token operator = token;
+        if (identical(tokenLevel, CASCADE_PRECEDENCE)) {
+          if (!allowCascades) {
+            return token;
+          }
+          token = parseCascadeExpression(token);
+        } else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) {
+          // Right associative, so we recurse at the same precedence
+          // level.
+          token = parsePrecedenceExpression(token.next, level, allowCascades);
+          listener.handleAssignmentExpression(operator);
+        } else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) {
+          if (identical(info, PERIOD_INFO)) {
+            // Left associative, so we recurse at the next higher
+            // precedence level. However, POSTFIX_PRECEDENCE is the
+            // highest level, so we just call parseUnaryExpression
+            // directly.
+            token = parseUnaryExpression(token.next, allowCascades);
+            listener.handleBinaryExpression(operator);
+          } else if ((identical(info, OPEN_PAREN_INFO)) ||
+                     (identical(info, OPEN_SQUARE_BRACKET_INFO))) {
+            token = parseArgumentOrIndexStar(token);
+          } else if ((identical(info, PLUS_PLUS_INFO)) ||
+                     (identical(info, MINUS_MINUS_INFO))) {
+            listener.handleUnaryPostfixAssignmentExpression(token);
+            token = token.next;
+          } else {
+            token = listener.unexpected(token);
+          }
+        } else if (identical(info, IS_INFO)) {
+          token = parseIsOperatorRest(token);
+        } else if (identical(info, AS_INFO)) {
+          token = parseAsOperatorRest(token);
+        } else if (identical(info, QUESTION_INFO)) {
+          token = parseConditionalExpressionRest(token);
+        } else {
+          // Left associative, so we recurse at the next higher
+          // precedence level.
+          token = parsePrecedenceExpression(token.next, level + 1,
+                                            allowCascades);
+          listener.handleBinaryExpression(operator);
+        }
+        info = token.info;
+        tokenLevel = info.precedence;
+      }
+    }
+    return token;
+  }
+
+  Token parseCascadeExpression(Token token) {
+    listener.beginCascade(token);
+    assert(optional('..', token));
+    Token cascadeOperator = token;
+    token = token.next;
+    if (optional('[', token)) {
+      token = parseArgumentOrIndexStar(token);
+    } else if (token.isIdentifier()) {
+      token = parseSend(token);
+      listener.handleBinaryExpression(cascadeOperator);
+    } else {
+      return listener.unexpected(token);
+    }
+    Token mark;
+    do {
+      mark = token;
+      if (optional('.', token)) {
+        Token period = token;
+        token = parseSend(token.next);
+        listener.handleBinaryExpression(period);
+      }
+      token = parseArgumentOrIndexStar(token);
+    } while (!identical(mark, token));
+
+    if (identical(token.info.precedence, ASSIGNMENT_PRECEDENCE)) {
+      Token assignment = token;
+      token = parseExpressionWithoutCascade(token.next);
+      listener.handleAssignmentExpression(assignment);
+    }
+    listener.endCascade();
+    return token;
+  }
+
+  Token parseUnaryExpression(Token token, bool allowCascades) {
+    String value = token.stringValue;
+    // Prefix:
+    if (identical(value, '+')) {
+      // Dart only allows "prefix plus" as an initial part of a
+      // decimal literal. We scan it as a separate token and let
+      // the parser listener combine it with the digits.
+      Token next = token.next;
+      if (identical(next.charOffset, token.charOffset + 1)) {
+        if (identical(next.kind, INT_TOKEN)) {
+          listener.handleLiteralInt(token);
+          return next.next;
+        }
+        if (identical(next.kind, DOUBLE_TOKEN)) {
+          listener.handleLiteralDouble(token);
+          return next.next;
+        }
+      }
+      listener.recoverableError("Unexpected token '+'", token: token);
+      return parsePrecedenceExpression(next, POSTFIX_PRECEDENCE,
+                                       allowCascades);
+    } else if ((identical(value, '!')) ||
+               (identical(value, '-')) ||
+               (identical(value, '~'))) {
+      Token operator = token;
+      // Right associative, so we recurse at the same precedence
+      // level.
+      token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE,
+                                        allowCascades);
+      listener.handleUnaryPrefixExpression(operator);
+    } else if ((identical(value, '++')) || identical(value, '--')) {
+      // TODO(ahe): Validate this is used correctly.
+      Token operator = token;
+      // Right associative, so we recurse at the same precedence
+      // level.
+      token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE,
+                                        allowCascades);
+      listener.handleUnaryPrefixAssignmentExpression(operator);
+    } else {
+      token = parsePrimary(token);
+    }
+    return token;
+  }
+
+  Token parseArgumentOrIndexStar(Token token) {
+    while (true) {
+      if (optional('[', token)) {
+        Token openSquareBracket = token;
+        bool old = mayParseFunctionExpressions;
+        mayParseFunctionExpressions = true;
+        token = parseExpression(token.next);
+        mayParseFunctionExpressions = old;
+        listener.handleIndexedExpression(openSquareBracket, token);
+        token = expect(']', token);
+      } else if (optional('(', token)) {
+        token = parseArguments(token);
+        listener.endSend(token);
+      } else {
+        break;
+      }
+    }
+    return token;
+  }
+
+  Token parsePrimary(Token token) {
+    final kind = token.kind;
+    if (identical(kind, IDENTIFIER_TOKEN)) {
+      return parseSendOrFunctionLiteral(token);
+    } else if (identical(kind, INT_TOKEN)
+        || identical(kind, HEXADECIMAL_TOKEN)) {
+      return parseLiteralInt(token);
+    } else if (identical(kind, DOUBLE_TOKEN)) {
+      return parseLiteralDouble(token);
+    } else if (identical(kind, STRING_TOKEN)) {
+      return parseLiteralString(token);
+    } else if (identical(kind, KEYWORD_TOKEN)) {
+      final value = token.stringValue;
+      if ((identical(value, 'true')) || (identical(value, 'false'))) {
+        return parseLiteralBool(token);
+      } else if (identical(value, 'null')) {
+        return parseLiteralNull(token);
+      } else if (identical(value, 'this')) {
+        return parseThisExpression(token);
+      } else if (identical(value, 'super')) {
+        return parseSuperExpression(token);
+      } else if (identical(value, 'new')) {
+        return parseNewExpression(token);
+      } else if (identical(value, 'const')) {
+        return parseConstExpression(token);
+      } else if (identical(value, 'void')) {
+        return parseFunctionExpression(token);
+      } else if (token.isIdentifier()) {
+        return parseSendOrFunctionLiteral(token);
+      } else {
+        return listener.expectedExpression(token);
+      }
+    } else if (identical(kind, OPEN_PAREN_TOKEN)) {
+      return parseParenthesizedExpressionOrFunctionLiteral(token);
+    } else if ((identical(kind, LT_TOKEN)) ||
+               (identical(kind, OPEN_SQUARE_BRACKET_TOKEN)) ||
+               (identical(kind, OPEN_CURLY_BRACKET_TOKEN)) ||
+               identical(token.stringValue, '[]')) {
+      return parseLiteralListOrMap(token);
+    } else if (identical(kind, QUESTION_TOKEN)) {
+      return parseArgumentDefinitionTest(token);
+    } else {
+      return listener.expectedExpression(token);
+    }
+  }
+
+  Token parseArgumentDefinitionTest(Token token) {
+    Token questionToken = token;
+    listener.beginArgumentDefinitionTest(questionToken);
+    assert(optional('?', token));
+    token = parseIdentifier(token.next);
+    listener.endArgumentDefinitionTest(questionToken, token);
+    return token;
+  }
+
+  Token parseParenthesizedExpressionOrFunctionLiteral(Token token) {
+    BeginGroupToken beginGroup = token;
+    int kind = beginGroup.endGroup.next.kind;
+    if (mayParseFunctionExpressions &&
+        (identical(kind, FUNCTION_TOKEN)
+            || identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
+      return parseUnamedFunction(token);
+    } else {
+      bool old = mayParseFunctionExpressions;
+      mayParseFunctionExpressions = true;
+      token = parseParenthesizedExpression(token);
+      mayParseFunctionExpressions = old;
+      return token;
+    }
+  }
+
+  Token parseParenthesizedExpression(Token token) {
+    var begin = token;
+    token = expect('(', token);
+    token = parseExpression(token);
+    if (!identical(begin.endGroup, token)) {
+      listener.unexpected(token);
+      token = begin.endGroup;
+    }
+    listener.handleParenthesizedExpression(begin);
+    return expect(')', token);
+  }
+
+  Token parseThisExpression(Token token) {
+    listener.handleThisExpression(token);
+    token = token.next;
+    if (optional('(', token)) {
+      // Constructor forwarding.
+      token = parseArguments(token);
+      listener.endSend(token);
+    }
+    return token;
+  }
+
+  Token parseSuperExpression(Token token) {
+    listener.handleSuperExpression(token);
+    token = token.next;
+    if (optional('(', token)) {
+      // Super constructor.
+      token = parseArguments(token);
+      listener.endSend(token);
+    }
+    return token;
+  }
+
+  Token parseLiteralListOrMap(Token token) {
+    Token constKeyword = null;
+    if (optional('const', token)) {
+      constKeyword = token;
+      token = token.next;
+    }
+    token = parseTypeArgumentsOpt(token);
+    Token beginToken = token;
+    int count = 0;
+    if (optional('{', token)) {
+      bool old = mayParseFunctionExpressions;
+      mayParseFunctionExpressions = true;
+      do {
+        if (optional('}', token.next)) {
+          token = token.next;
+          break;
+        }
+        token = parseMapLiteralEntry(token.next);
+        ++count;
+      } while (optional(',', token));
+      mayParseFunctionExpressions = old;
+      listener.handleLiteralMap(count, beginToken, constKeyword, token);
+      return expect('}', token);
+    } else if (optional('[', token)) {
+      bool old = mayParseFunctionExpressions;
+      mayParseFunctionExpressions = true;
+      do {
+        if (optional(']', token.next)) {
+          token = token.next;
+          break;
+        }
+        token = parseExpression(token.next);
+        ++count;
+      } while (optional(',', token));
+      mayParseFunctionExpressions = old;
+      listener.handleLiteralList(count, beginToken, constKeyword, token);
+      return expect(']', token);
+    } else if (optional('[]', token)) {
+      listener.handleLiteralList(0, token, constKeyword, token);
+      return token.next;
+    } else {
+      listener.unexpected(token);
+    }
+  }
+
+  Token parseMapLiteralEntry(Token token) {
+    listener.beginLiteralMapEntry(token);
+    // Assume the listener rejects non-string keys.
+    token = parseExpression(token);
+    Token colon = token;
+    token = expect(':', token);
+    token = parseExpression(token);
+    listener.endLiteralMapEntry(colon, token);
+    return token;
+  }
+
+  Token parseSendOrFunctionLiteral(Token token) {
+    if (!mayParseFunctionExpressions) return parseSend(token);
+    Token peek = peekAfterExpectedType(token);
+    if (identical(peek.kind, IDENTIFIER_TOKEN) && isFunctionDeclaration(peek.next)) {
+      return parseFunctionExpression(token);
+    } else if (isFunctionDeclaration(token.next)) {
+      return parseFunctionExpression(token);
+    } else {
+      return parseSend(token);
+    }
+  }
+
+  bool isFunctionDeclaration(Token token) {
+    if (optional('(', token)) {
+      BeginGroupToken begin = token;
+      String afterParens = begin.endGroup.next.stringValue;
+      if (identical(afterParens, '{') || identical(afterParens, '=>')) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Token parseRequiredArguments(Token token) {
+    if (optional('(', token)) {
+      token = parseArguments(token);
+    } else {
+      listener.handleNoArguments(token);
+      token = listener.unexpected(token);
+    }
+    return token;
+  }
+
+  Token parseNewExpression(Token token) {
+    Token newKeyword = token;
+    token = expect('new', token);
+    token = parseConstructorReference(token);
+    token = parseRequiredArguments(token);
+    listener.handleNewExpression(newKeyword);
+    return token;
+  }
+
+  Token parseConstExpression(Token token) {
+    Token constKeyword = token;
+    token = expect('const', token);
+    final String value = token.stringValue;
+    if ((identical(value, '<')) ||
+        (identical(value, '[')) ||
+        (identical(value, '[]')) ||
+        (identical(value, '{'))) {
+      return parseLiteralListOrMap(constKeyword);
+    }
+    token = parseConstructorReference(token);
+    token = parseRequiredArguments(token);
+    listener.handleConstExpression(constKeyword);
+    return token;
+  }
+
+  Token parseLiteralInt(Token token) {
+    listener.handleLiteralInt(token);
+    return token.next;
+  }
+
+  Token parseLiteralDouble(Token token) {
+    listener.handleLiteralDouble(token);
+    return token.next;
+  }
+
+  Token parseLiteralString(Token token) {
+    token = parseSingleLiteralString(token);
+    int count = 1;
+    while (identical(token.kind, STRING_TOKEN)) {
+      token = parseSingleLiteralString(token);
+      count++;
+    }
+    if (count > 1) {
+      listener.handleStringJuxtaposition(count);
+    }
+    return token;
+  }
+
+  /**
+   * Only called when [:token.kind === STRING_TOKEN:].
+   */
+  Token parseSingleLiteralString(Token token) {
+    listener.beginLiteralString(token);
+    // Parsing the prefix, for instance 'x of 'x${id}y${id}z'
+    token = token.next;
+    int interpolationCount = 0;
+    var kind = token.kind;
+    while (kind != EOF_TOKEN) {
+      if (identical(kind, STRING_INTERPOLATION_TOKEN)) {
+        // Parsing ${expression}.
+        token = token.next;
+        token = parseExpression(token);
+        token = expect('}', token);
+      } else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) {
+        // Parsing $identifier.
+        token = token.next;
+        token = parseExpression(token);
+      } else {
+        break;
+      }
+      ++interpolationCount;
+      // Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z'
+      token = parseStringPart(token);
+      kind = token.kind;
+    }
+    listener.endLiteralString(interpolationCount);
+    return token;
+  }
+
+  Token parseLiteralBool(Token token) {
+    listener.handleLiteralBool(token);
+    return token.next;
+  }
+
+  Token parseLiteralNull(Token token) {
+    listener.handleLiteralNull(token);
+    return token.next;
+  }
+
+  Token parseSend(Token token) {
+    listener.beginSend(token);
+    token = parseIdentifier(token);
+    token = parseArgumentsOpt(token);
+    listener.endSend(token);
+    return token;
+  }
+
+  Token parseArgumentsOpt(Token token) {
+    if (!optional('(', token)) {
+      listener.handleNoArguments(token);
+      return token;
+    } else {
+      return parseArguments(token);
+    }
+  }
+
+  Token parseArguments(Token token) {
+    Token begin = token;
+    listener.beginArguments(begin);
+    assert(identical('(', token.stringValue));
+    int argumentCount = 0;
+    if (optional(')', token.next)) {
+      listener.endArguments(argumentCount, begin, token.next);
+      return token.next.next;
+    }
+    bool old = mayParseFunctionExpressions;
+    mayParseFunctionExpressions = true;
+    do {
+      Token colon = null;
+      if (optional(':', token.next.next)) {
+        token = parseIdentifier(token.next);
+        colon = token;
+      }
+      token = parseExpression(token.next);
+      if (colon != null) listener.handleNamedArgument(colon);
+      ++argumentCount;
+    } while (optional(',', token));
+    mayParseFunctionExpressions = old;
+    listener.endArguments(argumentCount, begin, token);
+    return expect(')', token);
+  }
+
+  Token parseIsOperatorRest(Token token) {
+    assert(optional('is', token));
+    Token operator = token;
+    Token not = null;
+    if (optional('!', token.next)) {
+      token = token.next;
+      not = token;
+    }
+    token = parseType(token.next);
+    listener.handleIsOperator(operator, not, token);
+    String value = token.stringValue;
+    if (identical(value, 'is') || identical(value, 'as')) {
+      // The is- and as-operators cannot be chained, but they can take part of
+      // expressions like: foo is Foo || foo is Bar.
+      listener.unexpected(token);
+    }
+    return token;
+  }
+
+  Token parseAsOperatorRest(Token token) {
+    assert(optional('as', token));
+    Token operator = token;
+    token = parseType(token.next);
+    listener.handleAsOperator(operator, token);
+    String value = token.stringValue;
+    if (identical(value, 'is') || identical(value, 'as')) {
+      // The is- and as-operators cannot be chained.
+      listener.unexpected(token);
+    }
+    return token;
+  }
+
+  Token parseVariablesDeclaration(Token token) {
+    return parseVariablesDeclarationMaybeSemicolon(token, true);
+  }
+
+  Token parseVariablesDeclarationNoSemicolon(Token token) {
+    return parseVariablesDeclarationMaybeSemicolon(token, false);
+  }
+
+  Token parseVariablesDeclarationMaybeSemicolon(Token token,
+                                                bool endWithSemicolon) {
+    int count = 1;
+    listener.beginVariablesDeclaration(token);
+    token = parseModifiers(token);
+    token = parseTypeOpt(token);
+    token = parseOptionallyInitializedIdentifier(token);
+    while (optional(',', token)) {
+      token = parseOptionallyInitializedIdentifier(token.next);
+      ++count;
+    }
+    if (endWithSemicolon) {
+      Token semicolon = token;
+      token = expectSemicolon(semicolon);
+      listener.endVariablesDeclaration(count, semicolon);
+      return token;
+    } else {
+      listener.endVariablesDeclaration(count, null);
+      return token;
+    }
+  }
+
+  Token parseOptionallyInitializedIdentifier(Token token) {
+    listener.beginInitializedIdentifier(token);
+    token = parseIdentifier(token);
+    token = parseVariableInitializerOpt(token);
+    listener.endInitializedIdentifier();
+    return token;
+  }
+
+  Token parseIfStatement(Token token) {
+    Token ifToken = token;
+    listener.beginIfStatement(ifToken);
+    token = expect('if', token);
+    token = parseParenthesizedExpression(token);
+    token = parseStatement(token);
+    Token elseToken = null;
+    if (optional('else', token)) {
+      elseToken = token;
+      token = parseStatement(token.next);
+    }
+    listener.endIfStatement(ifToken, elseToken);
+    return token;
+  }
+
+  Token parseForStatement(Token token) {
+    Token forToken = token;
+    listener.beginForStatement(forToken);
+    token = expect('for', token);
+    token = expect('(', token);
+    token = parseVariablesDeclarationOrExpressionOpt(token);
+    if (optional('in', token)) {
+      return parseForInRest(forToken, token);
+    } else {
+      return parseForRest(forToken, token);
+    }
+  }
+
+  Token parseVariablesDeclarationOrExpressionOpt(Token token) {
+    final String value = token.stringValue;
+    if (identical(value, ';')) {
+      listener.handleNoExpression(token);
+      return token;
+    } else if ((identical(value, 'var')) || (identical(value, 'final'))) {
+      return parseVariablesDeclarationNoSemicolon(token);
+    }
+    Token identifier = peekIdentifierAfterType(token);
+    if (identifier != null) {
+      assert(identifier.isIdentifier());
+      if (isOneOf4(identifier.next, '=', ';', ',', 'in')) {
+        return parseVariablesDeclarationNoSemicolon(token);
+      }
+    }
+    return parseExpression(token);
+  }
+
+  Token parseForRest(Token forToken, Token token) {
+    token = expectSemicolon(token);
+    if (optional(';', token)) {
+      token = parseEmptyStatement(token);
+    } else {
+      token = parseExpressionStatement(token);
+    }
+    int expressionCount = 0;
+    while (true) {
+      if (optional(')', token)) break;
+      token = parseExpression(token);
+      ++expressionCount;
+      if (optional(',', token)) {
+        token = token.next;
+      } else {
+        break;
+      }
+    }
+    token = expect(')', token);
+    token = parseStatement(token);
+    listener.endForStatement(expressionCount, forToken, token);
+    return token;
+  }
+
+  Token parseForInRest(Token forToken, Token token) {
+    assert(optional('in', token));
+    Token inKeyword = token;
+    token = parseExpression(token.next);
+    token = expect(')', token);
+    token = parseStatement(token);
+    listener.endForIn(forToken, inKeyword, token);
+    return token;
+  }
+
+  Token parseWhileStatement(Token token) {
+    Token whileToken = token;
+    listener.beginWhileStatement(whileToken);
+    token = expect('while', token);
+    token = parseParenthesizedExpression(token);
+    token = parseStatement(token);
+    listener.endWhileStatement(whileToken, token);
+    return token;
+  }
+
+  Token parseDoWhileStatement(Token token) {
+    Token doToken = token;
+    listener.beginDoWhileStatement(doToken);
+    token = expect('do', token);
+    token = parseStatement(token);
+    Token whileToken = token;
+    token = expect('while', token);
+    token = parseParenthesizedExpression(token);
+    listener.endDoWhileStatement(doToken, whileToken, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseBlock(Token token) {
+    Token begin = token;
+    listener.beginBlock(begin);
+    int statementCount = 0;
+    token = expect('{', token);
+    while (notEofOrValue('}', token)) {
+      token = parseStatement(token);
+      ++statementCount;
+    }
+    listener.endBlock(statementCount, begin, token);
+    return expect('}', token);
+  }
+
+  Token parseThrowStatement(Token token) {
+    Token throwToken = token;
+    listener.beginThrowStatement(throwToken);
+    token = expect('throw', token);
+    if (optional(';', token)) {
+      listener.endRethrowStatement(throwToken, token);
+      return token.next;
+    } else {
+      token = parseExpression(token);
+      listener.endThrowStatement(throwToken, token);
+      return expectSemicolon(token);
+    }
+  }
+
+  Token parseTryStatement(Token token) {
+    assert(optional('try', token));
+    Token tryKeyword = token;
+    listener.beginTryStatement(tryKeyword);
+    token = parseBlock(token.next);
+    int catchCount = 0;
+
+    String value = token.stringValue;
+    while (identical(value, 'catch') || identical(value, 'on')) {
+      var onKeyword = null;
+      if (identical(value, 'on')) {
+        // on qualified catchPart?
+        onKeyword = token;
+        token = parseType(token.next);
+        value = token.stringValue;
+      }
+      Token catchKeyword = null;
+      if (identical(value, 'catch')) {
+        catchKeyword = token;
+        // TODO(ahe): Validate the "parameters".
+        token = parseFormalParameters(token.next);
+      }
+      token = parseBlock(token);
+      ++catchCount;
+      listener.handleCatchBlock(onKeyword, catchKeyword);
+      value = token.stringValue; // while condition
+    }
+
+    Token finallyKeyword = null;
+    if (optional('finally', token)) {
+      finallyKeyword = token;
+      token = parseBlock(token.next);
+      listener.handleFinallyBlock(finallyKeyword);
+    }
+    listener.endTryStatement(catchCount, tryKeyword, finallyKeyword);
+    return token;
+  }
+
+  Token parseSwitchStatement(Token token) {
+    assert(optional('switch', token));
+    Token switchKeyword = token;
+    listener.beginSwitchStatement(switchKeyword);
+    token = parseParenthesizedExpression(token.next);
+    token = parseSwitchBlock(token);
+    listener.endSwitchStatement(switchKeyword, token);
+    return token.next;
+  }
+
+  Token parseSwitchBlock(Token token) {
+    Token begin = token;
+    listener.beginSwitchBlock(begin);
+    token = expect('{', token);
+    int caseCount = 0;
+    while (!identical(token.kind, EOF_TOKEN)) {
+      if (optional('}', token)) {
+        break;
+      }
+      token = parseSwitchCase(token);
+      ++caseCount;
+    }
+    listener.endSwitchBlock(caseCount, begin, token);
+    expect('}', token);
+    return token;
+  }
+
+  /**
+   * Peek after the following labels (if any). The following token
+   * is used to determine if the labels belong to a statement or a
+   * switch case.
+   */
+  Token peekPastLabels(Token token) {
+    while (token.isIdentifier() && optional(':', token.next)) {
+      token = token.next.next;
+    }
+    return token;
+  }
+
+  /**
+   * Parse a group of labels, cases and possibly a default keyword and
+   * the statements that they select.
+   */
+  Token parseSwitchCase(Token token) {
+    Token begin = token;
+    Token defaultKeyword = null;
+    int expressionCount = 0;
+    int labelCount = 0;
+    Token peek = peekPastLabels(token);
+    while (true) {
+      // Loop until we find something that can't be part of a switch case.
+      String value = peek.stringValue;
+      if (identical(value, 'default')) {
+        while (!identical(token, peek)) {
+          token = parseLabel(token);
+          labelCount++;
+        }
+        defaultKeyword = token;
+        token = expect(':', token.next);
+        peek = token;
+        break;
+      } else if (identical(value, 'case')) {
+        while (!identical(token, peek)) {
+          token = parseLabel(token);
+          labelCount++;
+        }
+        Token caseKeyword = token;
+        token = parseExpression(token.next);
+        Token colonToken = token;
+        token = expect(':', token);
+        listener.handleCaseMatch(caseKeyword, colonToken);
+        expressionCount++;
+        peek = peekPastLabels(token);
+      } else {
+        if (expressionCount == 0) {
+          listener.expected("case", token);
+        }
+        break;
+      }
+    }
+    // Finally zero or more statements.
+    int statementCount = 0;
+    while (!identical(token.kind, EOF_TOKEN)) {
+      String value = peek.stringValue;
+      if ((identical(value, 'case')) ||
+          (identical(value, 'default')) ||
+          ((identical(value, '}')) && (identical(token, peek)))) {
+        // A label just before "}" will be handled as a statement error.
+        break;
+      } else {
+        token = parseStatement(token);
+      }
+      statementCount++;
+      peek = peekPastLabels(token);
+    }
+    listener.handleSwitchCase(labelCount, expressionCount, defaultKeyword,
+                              statementCount, begin, token);
+    return token;
+  }
+
+  Token parseBreakStatement(Token token) {
+    assert(optional('break', token));
+    Token breakKeyword = token;
+    token = token.next;
+    bool hasTarget = false;
+    if (token.isIdentifier()) {
+      token = parseIdentifier(token);
+      hasTarget = true;
+    }
+    listener.handleBreakStatement(hasTarget, breakKeyword, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseAssertStatement(Token token) {
+    Token assertKeyword = token;
+    token = expect('assert', token);
+    expect('(', token);
+    token = parseArguments(token);
+    listener.handleAssertStatement(assertKeyword, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseContinueStatement(Token token) {
+    assert(optional('continue', token));
+    Token continueKeyword = token;
+    token = token.next;
+    bool hasTarget = false;
+    if (token.isIdentifier()) {
+      token = parseIdentifier(token);
+      hasTarget = true;
+    }
+    listener.handleContinueStatement(hasTarget, continueKeyword, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseEmptyStatement(Token token) {
+    listener.handleEmptyStatement(token);
+    return expectSemicolon(token);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart
new file mode 100644
index 0000000..eca572b
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+class ParserTask extends CompilerTask {
+  ParserTask(Compiler compiler) : super(compiler);
+  String get name => 'Parser';
+
+  Node parse(Element element) {
+    return measure(() => element.parseNode(compiler));
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart
new file mode 100644
index 0000000..02f409a
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+class PartialParser extends Parser {
+  PartialParser(Listener listener) : super(listener);
+
+  Token parseClassBody(Token token) => skipClassBody(token);
+
+  Token fullParseClassBody(Token token) => super.parseClassBody(token);
+
+  Token parseExpression(Token token) => skipExpression(token);
+
+  Token parseArgumentsOpt(Token token) {
+    // This method is overridden for two reasons:
+    // 1. Avoid generating events for arguments.
+    // 2. Avoid calling skip expression for each argument (which doesn't work).
+    if (optional('(', token)) {
+      BeginGroupToken begin = token;
+      return begin.endGroup.next;
+    } else {
+      return token;
+    }
+  }
+
+  Token skipExpression(Token token) {
+    while (true) {
+      final kind = token.kind;
+      final value = token.stringValue;
+      if ((identical(kind, EOF_TOKEN)) ||
+          (identical(value, ';')) ||
+          (identical(value, ',')) ||
+          (identical(value, ']')))
+        return token;
+      if (identical(value, '=')) {
+        var nextValue = token.next.stringValue;
+        if (identical(nextValue, 'const')) {
+          token = token.next;
+          nextValue = token.next.stringValue;
+        }
+        if (identical(nextValue, '{')) {
+          // Handle cases like this:
+          // class Foo {
+          //   var map;
+          //   Foo() : map = {};
+          // }
+          BeginGroupToken begin = token.next;
+          token = (begin.endGroup != null) ? begin.endGroup : token;
+          token = token.next;
+          continue;
+        }
+        if (identical(nextValue, '<')) {
+          // Handle cases like this:
+          // class Foo {
+          //   var map;
+          //   Foo() : map = <String, Foo>{};
+          // }
+          BeginGroupToken begin = token.next;
+          token = (begin.endGroup != null) ? begin.endGroup : token;
+          token = token.next;
+          if (identical(token.stringValue, '{')) {
+            begin = token;
+            token = (begin.endGroup != null) ? begin.endGroup : token;
+            token = token.next;
+          }
+          continue;
+        }
+      }
+      if (!mayParseFunctionExpressions && identical(value, '{')) return token;
+      if (token is BeginGroupToken) {
+        BeginGroupToken begin = token;
+        token = (begin.endGroup != null) ? begin.endGroup : token;
+      }
+      token = token.next;
+    }
+  }
+
+  Token skipClassBody(Token token) {
+    if (!optional('{', token)) {
+      return listener.expectedClassBodyToSkip(token);
+    }
+    BeginGroupToken beginGroupToken = token;
+    Token endGroup = beginGroupToken.endGroup;
+    if (endGroup == null) {
+      return listener.unmatched(beginGroupToken);
+    } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) {
+      return listener.unmatched(beginGroupToken);
+    }
+    return endGroup;
+  }
+
+  Token parseFunctionBody(Token token, bool isExpression) {
+    assert(!isExpression);
+    String value = token.stringValue;
+    if (identical(value, ';')) {
+      // No body.
+    } else if (identical(value, '=>')) {
+      token = parseExpression(token.next);
+      expectSemicolon(token);
+    } else if (value == '=') {
+      token = parseRedirectingFactoryBody(token);
+      expectSemicolon(token);
+    } else {
+      token = skipBlock(token);
+    }
+    // There is no "skipped function body event", so we use
+    // handleNoFunctionBody instead.
+    listener.handleNoFunctionBody(token);
+    return token;
+  }
+
+  Token parseFormalParameters(Token token) => skipFormals(token);
+
+  Token skipFormals(Token token) {
+    listener.beginOptionalFormalParameters(token);
+    if (!optional('(', token)) {
+      if (optional(';', token)) {
+        listener.recoverableError("expected '('", token: token);
+        return token;
+      }
+      return listener.unexpected(token);
+    }
+    BeginGroupToken beginGroupToken = token;
+    Token endToken = beginGroupToken.endGroup;
+    listener.endFormalParameters(0, token, endToken);
+    return endToken.next;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart
new file mode 100644
index 0000000..123b650
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart
@@ -0,0 +1,875 @@
+// 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.
+
+part of scanner;
+
+abstract class Scanner {
+  Token tokenize();
+}
+
+/**
+ * Common base class for a Dart scanner.
+ */
+abstract class AbstractScanner<T extends SourceString> implements Scanner {
+  int advance();
+  int nextByte();
+
+  /**
+   * Returns the current character or byte depending on the underlying input
+   * kind. For example, [StringScanner] operates on [String] and thus returns
+   * characters (Unicode codepoints represented as int) whereas
+   * [ByteArrayScanner] operates on byte arrays and thus returns bytes.
+   */
+  int peek();
+
+  /**
+   * Appends a fixed token based on whether the current char is [choice] or not.
+   * If the current char is [choice] a fixed token whose kind and content
+   * is determined by [yes] is appended, otherwise a fixed token whose kind
+   * and content is determined by [no] is appended.
+   */
+  int select(int choice, PrecedenceInfo yes, PrecedenceInfo no);
+
+  /**
+   * Appends a fixed token whose kind and content is determined by [info].
+   */
+  void appendPrecedenceToken(PrecedenceInfo info);
+
+  /**
+   * Appends a token whose kind is determined by [info] and content is [value].
+   */
+  void appendStringToken(PrecedenceInfo info, String value);
+
+  /**
+   * Appends a token whose kind is determined by [info] and content is defined
+   * by the SourceString [value].
+   */
+  void appendByteStringToken(PrecedenceInfo info, T value);
+
+  /**
+   * Appends a keyword token whose kind is determined by [keyword].
+   */
+  void appendKeywordToken(Keyword keyword);
+  void appendWhiteSpace(int next);
+  void appendEofToken();
+
+  /**
+   * Creates an ASCII SourceString whose content begins at the source byte
+   * offset [start] and ends at [offset] bytes from the current byte offset of
+   * the scanner. For example, if the current byte offset is 10,
+   * [:asciiString(0,-1):] creates an ASCII SourceString whose content is found
+   * at the [0,9[ byte interval of the source text.
+   */
+  T asciiString(int start, int offset);
+  T utf8String(int start, int offset);
+  Token firstToken();
+  Token previousToken();
+  void beginToken();
+  void addToCharOffset(int offset);
+  int get charOffset;
+  int get byteOffset;
+  void appendBeginGroup(PrecedenceInfo info, String value);
+  int appendEndGroup(PrecedenceInfo info, String value, int openKind);
+  void appendGt(PrecedenceInfo info, String value);
+  void appendGtGt(PrecedenceInfo info, String value);
+  void appendGtGtGt(PrecedenceInfo info, String value);
+  void appendComment();
+
+  /**
+   * We call this method to discard '<' from the "grouping" stack
+   * (maintained by subclasses).
+   *
+   * [PartialParser.skipExpression] relies on the fact that we do not
+   * create groups for stuff like:
+   * [:a = b < c, d = e > f:].
+   *
+   * In other words, this method is called when the scanner recognizes
+   * something which cannot possibly be part of a type
+   * parameter/argument list.
+   */
+  void discardOpenLt();
+
+  // TODO(ahe): Move this class to implementation.
+
+  Token tokenize() {
+    int next = advance();
+    while (!identical(next, $EOF)) {
+      next = bigSwitch(next);
+    }
+    appendEofToken();
+    return firstToken();
+  }
+
+  int bigSwitch(int next) {
+    beginToken();
+    if (identical(next, $SPACE) || identical(next, $TAB)
+        || identical(next, $LF) || identical(next, $CR)) {
+      appendWhiteSpace(next);
+      next = advance();
+      while (identical(next, $SPACE)) {
+        appendWhiteSpace(next);
+        next = advance();
+      }
+      return next;
+    }
+
+    if ($a <= next && next <= $z) {
+      if (identical($r, next)) {
+        return tokenizeRawStringKeywordOrIdentifier(next);
+      }
+      return tokenizeKeywordOrIdentifier(next, true);
+    }
+
+    if (($A <= next && next <= $Z) || identical(next, $_) || identical(next, $$)) {
+      return tokenizeIdentifier(next, byteOffset, true);
+    }
+
+    if (identical(next, $LT)) {
+      return tokenizeLessThan(next);
+    }
+
+    if (identical(next, $GT)) {
+      return tokenizeGreaterThan(next);
+    }
+
+    if (identical(next, $EQ)) {
+      return tokenizeEquals(next);
+    }
+
+    if (identical(next, $BANG)) {
+      return tokenizeExclamation(next);
+    }
+
+    if (identical(next, $PLUS)) {
+      return tokenizePlus(next);
+    }
+
+    if (identical(next, $MINUS)) {
+      return tokenizeMinus(next);
+    }
+
+    if (identical(next, $STAR)) {
+      return tokenizeMultiply(next);
+    }
+
+    if (identical(next, $PERCENT)) {
+      return tokenizePercent(next);
+    }
+
+    if (identical(next, $AMPERSAND)) {
+      return tokenizeAmpersand(next);
+    }
+
+    if (identical(next, $BAR)) {
+      return tokenizeBar(next);
+    }
+
+    if (identical(next, $CARET)) {
+      return tokenizeCaret(next);
+    }
+
+    if (identical(next, $OPEN_SQUARE_BRACKET)) {
+      return tokenizeOpenSquareBracket(next);
+    }
+
+    if (identical(next, $TILDE)) {
+      return tokenizeTilde(next);
+    }
+
+    if (identical(next, $BACKSLASH)) {
+      appendPrecedenceToken(BACKSLASH_INFO);
+      return advance();
+    }
+
+    if (identical(next, $HASH)) {
+      return tokenizeTag(next);
+    }
+
+    if (identical(next, $OPEN_PAREN)) {
+      appendBeginGroup(OPEN_PAREN_INFO, "(");
+      return advance();
+    }
+
+    if (identical(next, $CLOSE_PAREN)) {
+      return appendEndGroup(CLOSE_PAREN_INFO, ")", OPEN_PAREN_TOKEN);
+    }
+
+    if (identical(next, $COMMA)) {
+      appendPrecedenceToken(COMMA_INFO);
+      return advance();
+    }
+
+    if (identical(next, $COLON)) {
+      appendPrecedenceToken(COLON_INFO);
+      return advance();
+    }
+
+    if (identical(next, $SEMICOLON)) {
+      appendPrecedenceToken(SEMICOLON_INFO);
+      // Type parameters and arguments cannot contain semicolon.
+      discardOpenLt();
+      return advance();
+    }
+
+    if (identical(next, $QUESTION)) {
+      appendPrecedenceToken(QUESTION_INFO);
+      return advance();
+    }
+
+    if (identical(next, $CLOSE_SQUARE_BRACKET)) {
+      return appendEndGroup(CLOSE_SQUARE_BRACKET_INFO, "]",
+                            OPEN_SQUARE_BRACKET_TOKEN);
+    }
+
+    if (identical(next, $BACKPING)) {
+      appendPrecedenceToken(BACKPING_INFO);
+      return advance();
+    }
+
+    if (identical(next, $OPEN_CURLY_BRACKET)) {
+      appendBeginGroup(OPEN_CURLY_BRACKET_INFO, "{");
+      return advance();
+    }
+
+    if (identical(next, $CLOSE_CURLY_BRACKET)) {
+      return appendEndGroup(CLOSE_CURLY_BRACKET_INFO, "}",
+                            OPEN_CURLY_BRACKET_TOKEN);
+    }
+
+    if (identical(next, $SLASH)) {
+      return tokenizeSlashOrComment(next);
+    }
+
+    if (identical(next, $AT)) {
+      return tokenizeAt(next);
+    }
+
+    if (identical(next, $DQ) || identical(next, $SQ)) {
+      return tokenizeString(next, byteOffset, false);
+    }
+
+    if (identical(next, $PERIOD)) {
+      return tokenizeDotsOrNumber(next);
+    }
+
+    if (identical(next, $0)) {
+      return tokenizeHexOrNumber(next);
+    }
+
+    // TODO(ahe): Would a range check be faster?
+    if (identical(next, $1) || identical(next, $2) || identical(next, $3)
+        || identical(next, $4) ||  identical(next, $5) || identical(next, $6)
+        || identical(next, $7) || identical(next, $8) || identical(next, $9)) {
+      return tokenizeNumber(next);
+    }
+
+    if (identical(next, $EOF)) {
+      return $EOF;
+    }
+    if (next < 0x1f) {
+      return error(new SourceString("unexpected character $next"));
+    }
+
+    // The following are non-ASCII characters.
+
+    if (identical(next, $NBSP)) {
+      appendWhiteSpace(next);
+      return advance();
+    }
+
+    return tokenizeIdentifier(next, byteOffset, true);
+  }
+
+  int tokenizeTag(int next) {
+    // # or #!.*[\n\r]
+    if (byteOffset == 0) {
+      if (identical(peek(), $BANG)) {
+        do {
+          next = advance();
+        } while (!identical(next, $LF) && !identical(next, $CR) && !identical(next, $EOF));
+        return next;
+      }
+    }
+    appendPrecedenceToken(HASH_INFO);
+    return advance();
+  }
+
+  int tokenizeTilde(int next) {
+    // ~ ~/ ~/=
+    next = advance();
+    if (identical(next, $SLASH)) {
+      return select($EQ, TILDE_SLASH_EQ_INFO, TILDE_SLASH_INFO);
+    } else {
+      appendPrecedenceToken(TILDE_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeOpenSquareBracket(int next) {
+    // [ [] []=
+    next = advance();
+    if (identical(next, $CLOSE_SQUARE_BRACKET)) {
+      Token token = previousToken();
+      if (token is KeywordToken && identical(token.value.stringValue, 'operator')) {
+        return select($EQ, INDEX_EQ_INFO, INDEX_INFO);
+      }
+    }
+    appendBeginGroup(OPEN_SQUARE_BRACKET_INFO, "[");
+    return next;
+  }
+
+  int tokenizeCaret(int next) {
+    // ^ ^=
+    return select($EQ, CARET_EQ_INFO, CARET_INFO);
+  }
+
+  int tokenizeBar(int next) {
+    // | || |=
+    next = advance();
+    if (identical(next, $BAR)) {
+      appendPrecedenceToken(BAR_BAR_INFO);
+      return advance();
+    } else if (identical(next, $EQ)) {
+      appendPrecedenceToken(BAR_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(BAR_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeAmpersand(int next) {
+    // && &= &
+    next = advance();
+    if (identical(next, $AMPERSAND)) {
+      appendPrecedenceToken(AMPERSAND_AMPERSAND_INFO);
+      return advance();
+    } else if (identical(next, $EQ)) {
+      appendPrecedenceToken(AMPERSAND_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(AMPERSAND_INFO);
+      return next;
+    }
+  }
+
+  int tokenizePercent(int next) {
+    // % %=
+    return select($EQ, PERCENT_EQ_INFO, PERCENT_INFO);
+  }
+
+  int tokenizeMultiply(int next) {
+    // * *=
+    return select($EQ, STAR_EQ_INFO, STAR_INFO);
+  }
+
+  int tokenizeMinus(int next) {
+    // - -- -=
+    next = advance();
+    if (identical(next, $MINUS)) {
+      appendPrecedenceToken(MINUS_MINUS_INFO);
+      return advance();
+    } else if (identical(next, $EQ)) {
+      appendPrecedenceToken(MINUS_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(MINUS_INFO);
+      return next;
+    }
+  }
+
+
+  int tokenizePlus(int next) {
+    // + ++ +=
+    next = advance();
+    if (identical($PLUS, next)) {
+      appendPrecedenceToken(PLUS_PLUS_INFO);
+      return advance();
+    } else if (identical($EQ, next)) {
+      appendPrecedenceToken(PLUS_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(PLUS_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeExclamation(int next) {
+    // ! != !==
+    next = advance();
+    if (identical(next, $EQ)) {
+      return select($EQ, BANG_EQ_EQ_INFO, BANG_EQ_INFO);
+    }
+    appendPrecedenceToken(BANG_INFO);
+    return next;
+  }
+
+  int tokenizeEquals(int next) {
+    // = == ===
+
+    // Type parameters and arguments cannot contain any token that
+    // starts with '='.
+    discardOpenLt();
+
+    next = advance();
+    if (identical(next, $EQ)) {
+      return select($EQ, EQ_EQ_EQ_INFO, EQ_EQ_INFO);
+    } else if (identical(next, $GT)) {
+      appendPrecedenceToken(FUNCTION_INFO);
+      return advance();
+    }
+    appendPrecedenceToken(EQ_INFO);
+    return next;
+  }
+
+  int tokenizeGreaterThan(int next) {
+    // > >= >> >>= >>> >>>=
+    next = advance();
+    if (identical($EQ, next)) {
+      appendPrecedenceToken(GT_EQ_INFO);
+      return advance();
+    } else if (identical($GT, next)) {
+      next = advance();
+      if (identical($EQ, next)) {
+        appendPrecedenceToken(GT_GT_EQ_INFO);
+        return advance();
+      } else {
+        appendGtGt(GT_GT_INFO, ">>");
+        return next;
+      }
+    } else {
+      appendGt(GT_INFO, ">");
+      return next;
+    }
+  }
+
+  int tokenizeLessThan(int next) {
+    // < <= << <<=
+    next = advance();
+    if (identical($EQ, next)) {
+      appendPrecedenceToken(LT_EQ_INFO);
+      return advance();
+    } else if (identical($LT, next)) {
+      return select($EQ, LT_LT_EQ_INFO, LT_LT_INFO);
+    } else {
+      appendBeginGroup(LT_INFO, "<");
+      return next;
+    }
+  }
+
+  int tokenizeNumber(int next) {
+    int start = byteOffset;
+    while (true) {
+      next = advance();
+      if ($0 <= next && next <= $9) {
+        continue;
+      } else if (identical(next, $PERIOD)) {
+        return tokenizeFractionPart(advance(), start);
+      } else if (identical(next, $e) || identical(next, $E)
+          || identical(next, $d) || identical(next, $D)) {
+        return tokenizeFractionPart(next, start);
+      } else {
+        appendByteStringToken(INT_INFO, asciiString(start, 0));
+        return next;
+      }
+    }
+  }
+
+  int tokenizeHexOrNumber(int next) {
+    int x = peek();
+    if (identical(x, $x) || identical(x, $X)) {
+      advance();
+      return tokenizeHex(x);
+    }
+    return tokenizeNumber(next);
+  }
+
+  int tokenizeHex(int next) {
+    int start = byteOffset - 1;
+    bool hasDigits = false;
+    while (true) {
+      next = advance();
+      if (($0 <= next && next <= $9)
+          || ($A <= next && next <= $F)
+          || ($a <= next && next <= $f)) {
+        hasDigits = true;
+      } else {
+        if (!hasDigits) {
+          return error(const SourceString("hex digit expected"));
+        }
+        appendByteStringToken(HEXADECIMAL_INFO, asciiString(start, 0));
+        return next;
+      }
+    }
+  }
+
+  int tokenizeDotsOrNumber(int next) {
+    int start = byteOffset;
+    next = advance();
+    if (($0 <= next && next <= $9)) {
+      return tokenizeFractionPart(next, start);
+    } else if (identical($PERIOD, next)) {
+      return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO);
+    } else {
+      appendPrecedenceToken(PERIOD_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeFractionPart(int next, int start) {
+    bool done = false;
+    bool hasDigit = false;
+    LOOP: while (!done) {
+      if ($0 <= next && next <= $9) {
+        hasDigit = true;
+      } else if (identical($e, next) || identical($E, next)) {
+        hasDigit = true;
+        next = tokenizeExponent(advance());
+        done = true;
+        continue LOOP;
+      } else {
+        done = true;
+        continue LOOP;
+      }
+      next = advance();
+    }
+    if (!hasDigit) {
+      appendByteStringToken(INT_INFO, asciiString(start, -1));
+      if (identical($PERIOD, next)) {
+        return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO);
+      }
+      // TODO(ahe): Wrong offset for the period.
+      appendPrecedenceToken(PERIOD_INFO);
+      return bigSwitch(next);
+    }
+    if (identical(next, $d) || identical(next, $D)) {
+      next = advance();
+    }
+    appendByteStringToken(DOUBLE_INFO, asciiString(start, 0));
+    return next;
+  }
+
+  int tokenizeExponent(int next) {
+    if (identical(next, $PLUS) || identical(next, $MINUS)) {
+      next = advance();
+    }
+    bool hasDigits = false;
+    while (true) {
+      if ($0 <= next && next <= $9) {
+        hasDigits = true;
+      } else {
+        if (!hasDigits) {
+          return error(const SourceString("digit expected"));
+        }
+        return next;
+      }
+      next = advance();
+    }
+  }
+
+  int tokenizeSlashOrComment(int next) {
+    next = advance();
+    if (identical($STAR, next)) {
+      return tokenizeMultiLineComment(next);
+    } else if (identical($SLASH, next)) {
+      return tokenizeSingleLineComment(next);
+    } else if (identical($EQ, next)) {
+      appendPrecedenceToken(SLASH_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(SLASH_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeSingleLineComment(int next) {
+    while (true) {
+      next = advance();
+      if (identical($LF, next) || identical($CR, next) || identical($EOF, next)) {
+        appendComment();
+        return next;
+      }
+    }
+  }
+
+  int tokenizeMultiLineComment(int next) {
+    int nesting = 1;
+    next = advance();
+    while (true) {
+      if (identical($EOF, next)) {
+        // TODO(ahe): Report error.
+        return next;
+      } else if (identical($STAR, next)) {
+        next = advance();
+        if (identical($SLASH, next)) {
+          --nesting;
+          if (0 == nesting) {
+            next = advance();
+            appendComment();
+            return next;
+          } else {
+            next = advance();
+          }
+        }
+      } else if (identical($SLASH, next)) {
+        next = advance();
+        if (identical($STAR, next)) {
+          next = advance();
+          ++nesting;
+        }
+      } else {
+        next = advance();
+      }
+    }
+  }
+
+  int tokenizeRawStringKeywordOrIdentifier(int next) {
+    int nextnext = peek();
+    if (identical(nextnext, $DQ) || identical(nextnext, $SQ)) {
+      int start = byteOffset;
+      next = advance();
+      return tokenizeString(next, start, true);
+    }
+    return tokenizeKeywordOrIdentifier(next, true);
+  }
+
+  int tokenizeKeywordOrIdentifier(int next, bool allowDollar) {
+    KeywordState state = KeywordState.KEYWORD_STATE;
+    int start = byteOffset;
+    while (state != null && $a <= next && next <= $z) {
+      state = state.next(next);
+      next = advance();
+    }
+    if (state == null || state.keyword == null) {
+      return tokenizeIdentifier(next, start, allowDollar);
+    }
+    if (($A <= next && next <= $Z) ||
+        ($0 <= next && next <= $9) ||
+        identical(next, $_) ||
+        identical(next, $$)) {
+      return tokenizeIdentifier(next, start, allowDollar);
+    } else if (next < 128) {
+      appendKeywordToken(state.keyword);
+      return next;
+    } else {
+      return tokenizeIdentifier(next, start, allowDollar);
+    }
+  }
+
+  int tokenizeIdentifier(int next, int start, bool allowDollar) {
+    bool isAscii = true;
+
+    // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+    bool isDynamicBuiltIn = false;
+
+    if (identical(next, $D)) {
+      next = advance();
+      if (identical(next, $y)) {
+        next = advance();
+        if (identical(next, $n)) {
+          next = advance();
+          if (identical(next, $a)) {
+            next = advance();
+            if (identical(next, $m)) {
+              next = advance();
+              if (identical(next, $i)) {
+                next = advance();
+                if (identical(next, $c)) {
+                  isDynamicBuiltIn = true;
+                  next = advance();
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    while (true) {
+      if (($a <= next && next <= $z) ||
+          ($A <= next && next <= $Z) ||
+          ($0 <= next && next <= $9) ||
+          identical(next, $_) ||
+          (identical(next, $$) && allowDollar)) {
+        isDynamicBuiltIn = false;
+        next = advance();
+      } else if ((next < 128) || (identical(next, $NBSP))) {
+        // Identifier ends here.
+        if (start == byteOffset) {
+          return error(const SourceString("expected identifier"));
+        } else if (isDynamicBuiltIn) {
+          appendKeywordToken(Keyword.DYNAMIC_DEPRECATED);
+        } else if (isAscii) {
+          appendByteStringToken(IDENTIFIER_INFO, asciiString(start, 0));
+        } else {
+          appendByteStringToken(BAD_INPUT_INFO, utf8String(start, -1));
+        }
+        return next;
+      } else {
+        isDynamicBuiltIn = false;
+        int nonAsciiStart = byteOffset;
+        do {
+          next = nextByte();
+          if (identical(next, $NBSP)) break;
+        } while (next > 127);
+        String string = utf8String(nonAsciiStart, -1).slowToString();
+        isAscii = false;
+        int byteLength = nonAsciiStart - byteOffset;
+        addToCharOffset(string.length - byteLength);
+      }
+    }
+  }
+
+  int tokenizeAt(int next) {
+    int start = byteOffset;
+    next = advance();
+    appendPrecedenceToken(AT_INFO);
+    return next;
+  }
+
+  int tokenizeString(int next, int start, bool raw) {
+    int quoteChar = next;
+    next = advance();
+    if (identical(quoteChar, next)) {
+      next = advance();
+      if (identical(quoteChar, next)) {
+        // Multiline string.
+        return tokenizeMultiLineString(quoteChar, start, raw);
+      } else {
+        // Empty string.
+        appendByteStringToken(STRING_INFO, utf8String(start, -1));
+        return next;
+      }
+    }
+    if (raw) {
+      return tokenizeSingleLineRawString(next, quoteChar, start);
+    } else {
+      return tokenizeSingleLineString(next, quoteChar, start);
+    }
+  }
+
+  static bool isHexDigit(int character) {
+    if ($0 <= character && character <= $9) return true;
+    character |= 0x20;
+    return ($a <= character && character <= $f);
+  }
+
+  int tokenizeSingleLineString(int next, int quoteChar, int start) {
+    while (!identical(next, quoteChar)) {
+      if (identical(next, $BACKSLASH)) {
+        next = advance();
+      } else if (identical(next, $$)) {
+        next = tokenizeStringInterpolation(start);
+        start = byteOffset;
+        continue;
+      }
+      if (next <= $CR
+          && (identical(next, $LF) || identical(next, $CR) || identical(next, $EOF))) {
+        return error(const SourceString("unterminated string literal"));
+      }
+      next = advance();
+    }
+    appendByteStringToken(STRING_INFO, utf8String(start, 0));
+    return advance();
+  }
+
+  int tokenizeStringInterpolation(int start) {
+    appendByteStringToken(STRING_INFO, utf8String(start, -1));
+    beginToken(); // $ starts here.
+    int next = advance();
+    if (identical(next, $OPEN_CURLY_BRACKET)) {
+      return tokenizeInterpolatedExpression(next, start);
+    } else {
+      return tokenizeInterpolatedIdentifier(next, start);
+    }
+  }
+
+  int tokenizeInterpolatedExpression(int next, int start) {
+    appendBeginGroup(STRING_INTERPOLATION_INFO, "\${");
+    beginToken(); // The expression starts here.
+    next = advance();
+    while (!identical(next, $EOF) && !identical(next, $STX)) {
+      next = bigSwitch(next);
+    }
+    if (identical(next, $EOF)) return next;
+    next = advance();
+    beginToken(); // The string interpolation suffix starts here.
+    return next;
+  }
+
+  int tokenizeInterpolatedIdentifier(int next, int start) {
+    appendPrecedenceToken(STRING_INTERPOLATION_IDENTIFIER_INFO);
+    beginToken(); // The identifier starts here.
+    next = tokenizeKeywordOrIdentifier(next, false);
+    beginToken(); // The string interpolation suffix starts here.
+    return next;
+  }
+
+  int tokenizeSingleLineRawString(int next, int quoteChar, int start) {
+    next = advance();
+    while (next != $EOF) {
+      if (identical(next, quoteChar)) {
+        appendByteStringToken(STRING_INFO, utf8String(start, 0));
+        return advance();
+      } else if (identical(next, $LF) || identical(next, $CR)) {
+        return error(const SourceString("unterminated string literal"));
+      }
+      next = advance();
+    }
+    return error(const SourceString("unterminated string literal"));
+  }
+
+  int tokenizeMultiLineRawString(int quoteChar, int start) {
+    int next = advance();
+    outer: while (!identical(next, $EOF)) {
+      while (!identical(next, quoteChar)) {
+        next = advance();
+        if (identical(next, $EOF)) break outer;
+      }
+      next = advance();
+      if (identical(next, quoteChar)) {
+        next = advance();
+        if (identical(next, quoteChar)) {
+          appendByteStringToken(STRING_INFO, utf8String(start, 0));
+          return advance();
+        }
+      }
+    }
+    return error(const SourceString("unterminated string literal"));
+  }
+
+  int tokenizeMultiLineString(int quoteChar, int start, bool raw) {
+    if (raw) return tokenizeMultiLineRawString(quoteChar, start);
+    int next = advance();
+    while (!identical(next, $EOF)) {
+      if (identical(next, $$)) {
+        next = tokenizeStringInterpolation(start);
+        start = byteOffset;
+        continue;
+      }
+      if (identical(next, quoteChar)) {
+        next = advance();
+        if (identical(next, quoteChar)) {
+          next = advance();
+          if (identical(next, quoteChar)) {
+            appendByteStringToken(STRING_INFO, utf8String(start, 0));
+            return advance();
+          }
+        }
+        continue;
+      }
+      if (identical(next, $BACKSLASH)) {
+        next = advance();
+        if (identical(next, $EOF)) break;
+      }
+      next = advance();
+    }
+    return error(const SourceString("unterminated string literal"));
+  }
+
+  int error(SourceString message) {
+    appendByteStringToken(BAD_INPUT_INFO, message);
+    return advance(); // Ensure progress.
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart
new file mode 100644
index 0000000..1bd8327
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2011, 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.
+
+library scanner_implementation;
+
+import 'scannerlib.dart';
+import '../util/util.dart';
+import '../util/characters.dart';
+
+part 'array_based_scanner.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart
new file mode 100644
index 0000000..0738355
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart
@@ -0,0 +1,53 @@
+// 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.
+
+part of scanner;
+
+class ScannerTask extends CompilerTask {
+  ScannerTask(Compiler compiler) : super(compiler);
+  String get name => 'Scanner';
+
+  void scanLibrary(LibraryElement library) {
+    var compilationUnit = library.entryCompilationUnit;
+    var canonicalUri = library.canonicalUri.toString();
+    var resolvedUri = compilationUnit.script.uri.toString();
+    if (canonicalUri == resolvedUri) {
+      compiler.log("scanning library $canonicalUri");
+    } else {
+      compiler.log("scanning library $canonicalUri ($resolvedUri)");
+    }
+    scan(compilationUnit);
+  }
+
+  void scan(CompilationUnitElement compilationUnit) {
+    measure(() {
+      scanElements(compilationUnit);
+    });
+  }
+
+  void scanElements(CompilationUnitElement compilationUnit) {
+    Script script = compilationUnit.script;
+    Token tokens = new StringScanner(script.text,
+        includeComments: compiler.preserveComments).tokenize();
+    if (compiler.preserveComments) {
+      tokens = compiler.processAndStripComments(tokens);
+    }
+    compiler.dietParser.dietParse(compilationUnit, tokens);
+  }
+}
+
+class DietParserTask extends CompilerTask {
+  DietParserTask(Compiler compiler) : super(compiler);
+  final String name = 'Diet Parser';
+
+  dietParse(CompilationUnitElement compilationUnit, Token tokens) {
+    measure(() {
+      Function idGenerator = compiler.getNextFreeClassId;
+      ElementListener listener =
+          new ElementListener(compiler, compilationUnit, idGenerator);
+      PartialParser parser = new PartialParser(listener);
+      parser.parseUnit(tokens);
+    });
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart
new file mode 100644
index 0000000..e3cd591
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2011, 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.
+
+library scanner;
+
+import 'dart:collection' show LinkedHashMap;
+import 'dart:uri';
+
+import 'scanner_implementation.dart';
+import '../elements/elements.dart';
+import '../elements/modelx.dart'
+    show FunctionElementX,
+         TypedefElementX,
+         VariableElementX,
+         VariableListElementX,
+         ClassElementX,
+         MetadataAnnotationX,
+         MixinApplicationElementX;
+import '../dart2jslib.dart';
+import '../native_handler.dart' as native;
+import '../string_validator.dart';
+import '../tree/tree.dart';
+import '../util/characters.dart';
+import '../util/util.dart';
+// TODO(ahe): Rename prefix to 'api' when VM bug is fixed.
+import '../../compiler.dart' as api_s;
+
+part 'class_element_parser.dart';
+part 'keyword.dart';
+part 'listener.dart';
+part 'parser.dart';
+part 'parser_task.dart';
+part 'partial_parser.dart';
+part 'scanner.dart';
+part 'scanner_task.dart';
+part 'string_scanner.dart';
+part 'token.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart
new file mode 100644
index 0000000..0fa3489
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+/**
+ * Scanner that reads from a String and creates tokens that points to
+ * substrings.
+ */
+class StringScanner extends ArrayBasedScanner<SourceString> {
+  final String string;
+
+  StringScanner(String this.string, {bool includeComments: false})
+    : super(includeComments);
+
+  int nextByte() => charAt(++byteOffset);
+
+  int peek() => charAt(byteOffset + 1);
+
+  int charAt(index)
+      => (string.length > index) ? string.charCodeAt(index) : $EOF;
+
+  SourceString asciiString(int start, int offset) {
+    return new SubstringWrapper(string, start, byteOffset + offset);
+  }
+
+  SourceString utf8String(int start, int offset) {
+    return new SubstringWrapper(string, start, byteOffset + offset + 1);
+  }
+
+  void appendByteStringToken(PrecedenceInfo info, SourceString value) {
+    // assert(kind != $a || keywords.get(value) == null);
+    tail.next = new StringToken.fromSource(info, value, tokenStart);
+    tail = tail.next;
+  }
+
+  void unmatchedBeginGroup(BeginGroupToken begin) {
+    SourceString error = new SourceString('unmatched "${begin.stringValue}"');
+    Token close =
+        new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset);
+    // We want to ensure that unmatched BeginGroupTokens are reported
+    // as errors. However, the rest of the parser assume the groups
+    // are well-balanced and will never look at the endGroup
+    // token. This is a nice property that allows us to skip quickly
+    // over correct code. By inserting an additional error token in
+    // the stream, we can keep ignoring endGroup tokens.
+    Token next =
+        new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset);
+    begin.endGroup = close;
+    close.next = next;
+    next.next = begin.next;
+  }
+}
+
+class SubstringWrapper extends Iterable<int> implements SourceString {
+  final String internalString;
+  final int begin;
+  final int end;
+  int cashedHash = 0;
+  String cachedSubString;
+
+  SubstringWrapper(String this.internalString,
+                   int this.begin, int this.end);
+
+  int get hashCode {
+    if (0 == cashedHash) {
+      cashedHash = slowToString().hashCode;
+    }
+    return cashedHash;
+  }
+
+  bool operator ==(other) {
+    return other is SourceString && slowToString() == other.slowToString();
+  }
+
+  void printOn(StringBuffer sb) {
+    sb.add(internalString.substring(begin, end));
+  }
+
+  String slowToString() {
+    if (cachedSubString == null) {
+      cachedSubString = internalString.substring(begin, end);
+    }
+    return cachedSubString;
+  }
+
+  String toString() => "SubstringWrapper(${slowToString()})";
+
+  String get stringValue => null;
+
+  Iterator<int> get iterator =>
+      new StringCodeIterator.substring(internalString, begin, end);
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    assert(0 <= initial);
+    assert(0 <= terminal);
+    assert(initial + terminal <= internalString.length);
+    return new SubstringWrapper(internalString,
+                                begin + initial, end - terminal);
+  }
+
+  bool get isEmpty => begin == end;
+
+  bool isPrivate() => !isEmpty && identical(internalString.charCodeAt(begin), $_);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart
new file mode 100644
index 0000000..a236eae
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart
@@ -0,0 +1,530 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+const int EOF_TOKEN = 0;
+
+const int KEYWORD_TOKEN = $k;
+const int IDENTIFIER_TOKEN = $a;
+const int BAD_INPUT_TOKEN = $X;
+const int DOUBLE_TOKEN = $d;
+const int INT_TOKEN = $i;
+const int HEXADECIMAL_TOKEN = $x;
+const int STRING_TOKEN = $SQ;
+
+const int AMPERSAND_TOKEN = $AMPERSAND;
+const int BACKPING_TOKEN = $BACKPING;
+const int BACKSLASH_TOKEN = $BACKSLASH;
+const int BANG_TOKEN = $BANG;
+const int BAR_TOKEN = $BAR;
+const int COLON_TOKEN = $COLON;
+const int COMMA_TOKEN = $COMMA;
+const int EQ_TOKEN = $EQ;
+const int GT_TOKEN = $GT;
+const int HASH_TOKEN = $HASH;
+const int OPEN_CURLY_BRACKET_TOKEN = $OPEN_CURLY_BRACKET;
+const int OPEN_SQUARE_BRACKET_TOKEN = $OPEN_SQUARE_BRACKET;
+const int OPEN_PAREN_TOKEN = $OPEN_PAREN;
+const int LT_TOKEN = $LT;
+const int MINUS_TOKEN = $MINUS;
+const int PERIOD_TOKEN = $PERIOD;
+const int PLUS_TOKEN = $PLUS;
+const int QUESTION_TOKEN = $QUESTION;
+const int AT_TOKEN = $AT;
+const int CLOSE_CURLY_BRACKET_TOKEN = $CLOSE_CURLY_BRACKET;
+const int CLOSE_SQUARE_BRACKET_TOKEN = $CLOSE_SQUARE_BRACKET;
+const int CLOSE_PAREN_TOKEN = $CLOSE_PAREN;
+const int SEMICOLON_TOKEN = $SEMICOLON;
+const int SLASH_TOKEN = $SLASH;
+const int TILDE_TOKEN = $TILDE;
+const int STAR_TOKEN = $STAR;
+const int PERCENT_TOKEN = $PERCENT;
+const int CARET_TOKEN = $CARET;
+
+const int STRING_INTERPOLATION_TOKEN = 128;
+const int LT_EQ_TOKEN = STRING_INTERPOLATION_TOKEN + 1;
+const int FUNCTION_TOKEN = LT_EQ_TOKEN + 1;
+const int SLASH_EQ_TOKEN = FUNCTION_TOKEN + 1;
+const int PERIOD_PERIOD_PERIOD_TOKEN = SLASH_EQ_TOKEN + 1;
+const int PERIOD_PERIOD_TOKEN = PERIOD_PERIOD_PERIOD_TOKEN + 1;
+const int EQ_EQ_EQ_TOKEN = PERIOD_PERIOD_TOKEN + 1;
+const int EQ_EQ_TOKEN = EQ_EQ_EQ_TOKEN + 1;
+const int LT_LT_EQ_TOKEN = EQ_EQ_TOKEN + 1;
+const int LT_LT_TOKEN = LT_LT_EQ_TOKEN + 1;
+const int GT_EQ_TOKEN = LT_LT_TOKEN + 1;
+const int GT_GT_EQ_TOKEN = GT_EQ_TOKEN + 1;
+const int INDEX_EQ_TOKEN = GT_GT_EQ_TOKEN + 1;
+const int INDEX_TOKEN = INDEX_EQ_TOKEN + 1;
+const int BANG_EQ_EQ_TOKEN = INDEX_TOKEN + 1;
+const int BANG_EQ_TOKEN = BANG_EQ_EQ_TOKEN + 1;
+const int AMPERSAND_AMPERSAND_TOKEN = BANG_EQ_TOKEN + 1;
+const int AMPERSAND_EQ_TOKEN = AMPERSAND_AMPERSAND_TOKEN + 1;
+const int BAR_BAR_TOKEN = AMPERSAND_EQ_TOKEN + 1;
+const int BAR_EQ_TOKEN = BAR_BAR_TOKEN + 1;
+const int STAR_EQ_TOKEN = BAR_EQ_TOKEN + 1;
+const int PLUS_PLUS_TOKEN = STAR_EQ_TOKEN + 1;
+const int PLUS_EQ_TOKEN = PLUS_PLUS_TOKEN + 1;
+const int MINUS_MINUS_TOKEN = PLUS_EQ_TOKEN + 1;
+const int MINUS_EQ_TOKEN = MINUS_MINUS_TOKEN + 1;
+const int TILDE_SLASH_EQ_TOKEN = MINUS_EQ_TOKEN + 1;
+const int TILDE_SLASH_TOKEN = TILDE_SLASH_EQ_TOKEN + 1;
+const int PERCENT_EQ_TOKEN = TILDE_SLASH_TOKEN + 1;
+const int GT_GT_TOKEN = PERCENT_EQ_TOKEN + 1;
+const int CARET_EQ_TOKEN = GT_GT_TOKEN + 1;
+const int COMMENT_TOKEN = CARET_EQ_TOKEN + 1;
+const int STRING_INTERPOLATION_IDENTIFIER_TOKEN = COMMENT_TOKEN + 1;
+
+// TODO(ahe): Get rid of this.
+const int UNKNOWN_TOKEN = 1024;
+
+/**
+ * A token that doubles as a linked list.
+ */
+class Token implements Spannable {
+  /**
+   * The precedence info for this token. [info] determines the kind and the
+   * precedence level of this token.
+   */
+  final PrecedenceInfo info;
+
+  /**
+   * The character offset of the start of this token within the source text.
+   */
+  final int charOffset;
+
+  /**
+   * The next token in the token stream.
+   */
+  Token next;
+
+  Token(PrecedenceInfo this.info, int this.charOffset);
+
+  get value => info.value;
+
+  /**
+   * Returns the string value for keywords and symbols. For instance 'class' for
+   * the [CLASS] keyword token and '*' for a [Token] based on [STAR_INFO]. For
+   * other tokens, such identifiers, strings, numbers, etc, [stringValue]
+   * returns [:null:].
+   *
+   * [stringValue] should only be used for testing keywords and symbols.
+   */
+  String get stringValue => info.value.stringValue;
+
+  /**
+   * The kind enum of this token as determined by its [info].
+   */
+  int get kind => info.kind;
+
+  /**
+   * The precedence level for this token.
+   */
+  int get precedence => info.precedence;
+
+  bool isIdentifier() => identical(kind, IDENTIFIER_TOKEN);
+
+  /**
+   * Returns a textual representation of this token to be used for debugging
+   * purposes. The resulting string might contain information about the
+   * structure of the token, for example 'StringToken(foo)' for the identifier
+   * token 'foo'. Use [slowToString] for the text actually parsed by the token.
+   */
+  String toString() => info.value.toString();
+
+  /**
+   * The text parsed by this token.
+   */
+  String slowToString() => toString();
+
+  /**
+   * The number of characters parsed by this token.
+   */
+  int get slowCharCount {
+    if (info == BAD_INPUT_INFO) {
+      // This is a token that wraps around an error message. Return 1
+      // instead of the size of the length of the error message.
+      return 1;
+    } else {
+      return slowToString().length;
+    }
+  }
+}
+
+/**
+ * A keyword token.
+ */
+class KeywordToken extends Token {
+  final Keyword value;
+  String get stringValue => value.syntax;
+
+  KeywordToken(Keyword value, int charOffset)
+    : this.value = value, super(value.info, charOffset);
+
+  bool isIdentifier() => value.isPseudo || value.isBuiltIn;
+
+  String toString() => value.syntax;
+}
+
+/**
+ * A String-valued token.
+ */
+class StringToken extends Token {
+  final SourceString value;
+  String get stringValue => value.stringValue;
+
+  StringToken(PrecedenceInfo info, String value, int charOffset)
+    : this.fromSource(info, new SourceString(value), charOffset);
+
+  StringToken.fromSource(PrecedenceInfo info, this.value, int charOffset)
+    : super(info, charOffset);
+
+  String toString() => "StringToken(${value.slowToString()})";
+
+  String slowToString() => value.slowToString();
+}
+
+abstract class SourceString extends Iterable<int> {
+  const factory SourceString(String string) = StringWrapper;
+
+  void printOn(StringBuffer sb);
+
+  /** Gives a [SourceString] that is not including the [initial] first and
+   * [terminal] last characters. This is only intended to be used to remove
+   * quotes from string literals (including an initial '@' for raw strings).
+   */
+  SourceString copyWithoutQuotes(int initial, int terminal);
+
+  String get stringValue;
+
+  String slowToString();
+
+  bool get isEmpty;
+
+  bool isPrivate();
+}
+
+class StringWrapper extends Iterable<int> implements SourceString {
+  final String stringValue;
+
+  const StringWrapper(String this.stringValue);
+
+  int get hashCode => stringValue.hashCode;
+
+  bool operator ==(other) {
+    return other is SourceString && toString() == other.slowToString();
+  }
+
+  Iterator<int> get iterator => new StringCodeIterator(stringValue);
+
+  void printOn(StringBuffer sb) {
+    sb.add(stringValue);
+  }
+
+  String toString() => stringValue;
+
+  String slowToString() => stringValue;
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    assert(0 <= initial);
+    assert(0 <= terminal);
+    assert(initial + terminal <= stringValue.length);
+    return new StringWrapper(
+        stringValue.substring(initial, stringValue.length - terminal));
+  }
+
+  bool get isEmpty => stringValue.isEmpty;
+
+  bool isPrivate() => !isEmpty && identical(stringValue.charCodeAt(0), $_);
+}
+
+class StringCodeIterator implements Iterator<int> {
+  final String string;
+  int index;
+  final int end;
+  int _current;
+
+  StringCodeIterator(String string) :
+    this.string = string, index = 0, end = string.length;
+
+  StringCodeIterator.substring(this.string, this.index, this.end) {
+    assert(0 <= index);
+    assert(index <= end);
+    assert(end <= string.length);
+  }
+
+  int get current => _current;
+
+  bool moveNext() {
+    _current = null;
+    if (index >= end) return false;
+    _current = string.charCodeAt(index++);
+    return true;
+  }
+}
+
+class BeginGroupToken extends StringToken {
+  Token endGroup;
+  BeginGroupToken(PrecedenceInfo info, String value, int charOffset)
+    : super(info, value, charOffset);
+}
+
+bool isUserDefinableOperator(String value) {
+  return
+      isBinaryOperator(value) ||
+      isMinusOperator(value) ||
+      isTernaryOperator(value) ||
+      isUnaryOperator(value);
+}
+
+bool isUnaryOperator(String value) => identical(value, '~');
+
+bool isBinaryOperator(String value) {
+  return
+      (identical(value, '==')) ||
+      (identical(value, '[]')) ||
+      (identical(value, '*')) ||
+      (identical(value, '/')) ||
+      (identical(value, '%')) ||
+      (identical(value, '~/')) ||
+      (identical(value, '+')) ||
+      (identical(value, '<<')) ||
+      (identical(value, '>>')) ||
+      (identical(value, '>=')) ||
+      (identical(value, '>')) ||
+      (identical(value, '<=')) ||
+      (identical(value, '<')) ||
+      (identical(value, '&')) ||
+      (identical(value, '^')) ||
+      (identical(value, '|'));
+}
+
+bool isTernaryOperator(String value) => identical(value, '[]=');
+
+bool isMinusOperator(String value) => identical(value, '-');
+
+class PrecedenceInfo {
+  final SourceString value;
+  final int precedence;
+  final int kind;
+
+  const PrecedenceInfo(this.value, this.precedence, this.kind);
+
+  toString() => 'PrecedenceInfo($value, $precedence, $kind)';
+}
+
+// TODO(ahe): The following are not tokens in Dart.
+const PrecedenceInfo BACKPING_INFO =
+  const PrecedenceInfo(const SourceString('`'), 0, BACKPING_TOKEN);
+const PrecedenceInfo BACKSLASH_INFO =
+  const PrecedenceInfo(const SourceString('\\'), 0, BACKSLASH_TOKEN);
+const PrecedenceInfo PERIOD_PERIOD_PERIOD_INFO =
+  const PrecedenceInfo(const SourceString('...'), 0,
+                       PERIOD_PERIOD_PERIOD_TOKEN);
+
+/**
+ * The cascade operator has the lowest precedence of any operator
+ * except assignment.
+ */
+const int CASCADE_PRECEDENCE = 2;
+const PrecedenceInfo PERIOD_PERIOD_INFO =
+  const PrecedenceInfo(const SourceString('..'), CASCADE_PRECEDENCE,
+                       PERIOD_PERIOD_TOKEN);
+
+const PrecedenceInfo BANG_INFO =
+  const PrecedenceInfo(const SourceString('!'), 0, BANG_TOKEN);
+const PrecedenceInfo COLON_INFO =
+  const PrecedenceInfo(const SourceString(':'), 0, COLON_TOKEN);
+const PrecedenceInfo INDEX_INFO =
+  const PrecedenceInfo(const SourceString('[]'), 0, INDEX_TOKEN);
+const PrecedenceInfo MINUS_MINUS_INFO =
+  const PrecedenceInfo(const SourceString('--'), POSTFIX_PRECEDENCE,
+                       MINUS_MINUS_TOKEN);
+const PrecedenceInfo PLUS_PLUS_INFO =
+  const PrecedenceInfo(const SourceString('++'), POSTFIX_PRECEDENCE,
+                       PLUS_PLUS_TOKEN);
+const PrecedenceInfo TILDE_INFO =
+  const PrecedenceInfo(const SourceString('~'), 0, TILDE_TOKEN);
+
+const PrecedenceInfo FUNCTION_INFO =
+  const PrecedenceInfo(const SourceString('=>'), 0, FUNCTION_TOKEN);
+const PrecedenceInfo HASH_INFO =
+  const PrecedenceInfo(const SourceString('#'), 0, HASH_TOKEN);
+const PrecedenceInfo INDEX_EQ_INFO =
+  const PrecedenceInfo(const SourceString('[]='), 0, INDEX_EQ_TOKEN);
+const PrecedenceInfo SEMICOLON_INFO =
+  const PrecedenceInfo(const SourceString(';'), 0, SEMICOLON_TOKEN);
+const PrecedenceInfo COMMA_INFO =
+  const PrecedenceInfo(const SourceString(','), 0, COMMA_TOKEN);
+
+const PrecedenceInfo AT_INFO =
+  const PrecedenceInfo(const SourceString('@'), 0, AT_TOKEN);
+
+// Assignment operators.
+const int ASSIGNMENT_PRECEDENCE = 1;
+const PrecedenceInfo AMPERSAND_EQ_INFO =
+  const PrecedenceInfo(const SourceString('&='),
+                       ASSIGNMENT_PRECEDENCE, AMPERSAND_EQ_TOKEN);
+const PrecedenceInfo BAR_EQ_INFO =
+  const PrecedenceInfo(const SourceString('|='),
+                       ASSIGNMENT_PRECEDENCE, BAR_EQ_TOKEN);
+const PrecedenceInfo CARET_EQ_INFO =
+  const PrecedenceInfo(const SourceString('^='),
+                       ASSIGNMENT_PRECEDENCE, CARET_EQ_TOKEN);
+const PrecedenceInfo EQ_INFO =
+  const PrecedenceInfo(const SourceString('='),
+                       ASSIGNMENT_PRECEDENCE, EQ_TOKEN);
+const PrecedenceInfo GT_GT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('>>='),
+                       ASSIGNMENT_PRECEDENCE, GT_GT_EQ_TOKEN);
+const PrecedenceInfo LT_LT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('<<='),
+                       ASSIGNMENT_PRECEDENCE, LT_LT_EQ_TOKEN);
+const PrecedenceInfo MINUS_EQ_INFO =
+  const PrecedenceInfo(const SourceString('-='),
+                       ASSIGNMENT_PRECEDENCE, MINUS_EQ_TOKEN);
+const PrecedenceInfo PERCENT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('%='),
+                       ASSIGNMENT_PRECEDENCE, PERCENT_EQ_TOKEN);
+const PrecedenceInfo PLUS_EQ_INFO =
+  const PrecedenceInfo(const SourceString('+='),
+                       ASSIGNMENT_PRECEDENCE, PLUS_EQ_TOKEN);
+const PrecedenceInfo SLASH_EQ_INFO =
+  const PrecedenceInfo(const SourceString('/='),
+                       ASSIGNMENT_PRECEDENCE, SLASH_EQ_TOKEN);
+const PrecedenceInfo STAR_EQ_INFO =
+  const PrecedenceInfo(const SourceString('*='),
+                       ASSIGNMENT_PRECEDENCE, STAR_EQ_TOKEN);
+const PrecedenceInfo TILDE_SLASH_EQ_INFO =
+  const PrecedenceInfo(const SourceString('~/='),
+                       ASSIGNMENT_PRECEDENCE, TILDE_SLASH_EQ_TOKEN);
+
+const PrecedenceInfo QUESTION_INFO =
+  const PrecedenceInfo(const SourceString('?'), 3, QUESTION_TOKEN);
+
+const PrecedenceInfo BAR_BAR_INFO =
+  const PrecedenceInfo(const SourceString('||'), 4, BAR_BAR_TOKEN);
+
+const PrecedenceInfo AMPERSAND_AMPERSAND_INFO =
+  const PrecedenceInfo(const SourceString('&&'), 5, AMPERSAND_AMPERSAND_TOKEN);
+
+const PrecedenceInfo BAR_INFO =
+  const PrecedenceInfo(const SourceString('|'), 6, BAR_TOKEN);
+
+const PrecedenceInfo CARET_INFO =
+  const PrecedenceInfo(const SourceString('^'), 7, CARET_TOKEN);
+
+const PrecedenceInfo AMPERSAND_INFO =
+  const PrecedenceInfo(const SourceString('&'), 8, AMPERSAND_TOKEN);
+
+// Equality operators.
+const PrecedenceInfo BANG_EQ_EQ_INFO =
+  const PrecedenceInfo(const SourceString('!=='), 9, BANG_EQ_EQ_TOKEN);
+const PrecedenceInfo BANG_EQ_INFO =
+  const PrecedenceInfo(const SourceString('!='), 9, BANG_EQ_TOKEN);
+const PrecedenceInfo EQ_EQ_EQ_INFO =
+  const PrecedenceInfo(const SourceString('==='), 9, EQ_EQ_EQ_TOKEN);
+const PrecedenceInfo EQ_EQ_INFO =
+  const PrecedenceInfo(const SourceString('=='), 9, EQ_EQ_TOKEN);
+
+// Relational operators.
+const PrecedenceInfo GT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('>='), 10, GT_EQ_TOKEN);
+const PrecedenceInfo GT_INFO =
+  const PrecedenceInfo(const SourceString('>'), 10, GT_TOKEN);
+const PrecedenceInfo IS_INFO =
+  const PrecedenceInfo(const SourceString('is'), 10, KEYWORD_TOKEN);
+const PrecedenceInfo AS_INFO =
+  const PrecedenceInfo(const SourceString('as'), 10, KEYWORD_TOKEN);
+const PrecedenceInfo LT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('<='), 10, LT_EQ_TOKEN);
+const PrecedenceInfo LT_INFO =
+  const PrecedenceInfo(const SourceString('<'), 10, LT_TOKEN);
+
+// Shift operators.
+const PrecedenceInfo GT_GT_INFO =
+  const PrecedenceInfo(const SourceString('>>'), 11, GT_GT_TOKEN);
+const PrecedenceInfo LT_LT_INFO =
+  const PrecedenceInfo(const SourceString('<<'), 11, LT_LT_TOKEN);
+
+// Additive operators.
+const PrecedenceInfo MINUS_INFO =
+  const PrecedenceInfo(const SourceString('-'), 12, MINUS_TOKEN);
+const PrecedenceInfo PLUS_INFO =
+  const PrecedenceInfo(const SourceString('+'), 12, PLUS_TOKEN);
+
+// Multiplicative operators.
+const PrecedenceInfo PERCENT_INFO =
+  const PrecedenceInfo(const SourceString('%'), 13, PERCENT_TOKEN);
+const PrecedenceInfo SLASH_INFO =
+  const PrecedenceInfo(const SourceString('/'), 13, SLASH_TOKEN);
+const PrecedenceInfo STAR_INFO =
+  const PrecedenceInfo(const SourceString('*'), 13, STAR_TOKEN);
+const PrecedenceInfo TILDE_SLASH_INFO =
+  const PrecedenceInfo(const SourceString('~/'), 13, TILDE_SLASH_TOKEN);
+
+const int POSTFIX_PRECEDENCE = 14;
+const PrecedenceInfo PERIOD_INFO =
+  const PrecedenceInfo(const SourceString('.'), POSTFIX_PRECEDENCE,
+                       PERIOD_TOKEN);
+
+const PrecedenceInfo KEYWORD_INFO =
+  const PrecedenceInfo(const SourceString('keyword'), 0, KEYWORD_TOKEN);
+
+const PrecedenceInfo EOF_INFO =
+  const PrecedenceInfo(const SourceString('EOF'), 0, EOF_TOKEN);
+
+const PrecedenceInfo IDENTIFIER_INFO =
+  const PrecedenceInfo(const SourceString('identifier'), 0, IDENTIFIER_TOKEN);
+
+const PrecedenceInfo BAD_INPUT_INFO =
+  const PrecedenceInfo(const SourceString('malformed input'), 0,
+                       BAD_INPUT_TOKEN);
+
+const PrecedenceInfo OPEN_PAREN_INFO =
+  const PrecedenceInfo(const SourceString('('), POSTFIX_PRECEDENCE,
+                       OPEN_PAREN_TOKEN);
+
+const PrecedenceInfo CLOSE_PAREN_INFO =
+  const PrecedenceInfo(const SourceString(')'), 0, CLOSE_PAREN_TOKEN);
+
+const PrecedenceInfo OPEN_CURLY_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString('{'), 0, OPEN_CURLY_BRACKET_TOKEN);
+
+const PrecedenceInfo CLOSE_CURLY_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString('}'), 0, CLOSE_CURLY_BRACKET_TOKEN);
+
+const PrecedenceInfo INT_INFO =
+  const PrecedenceInfo(const SourceString('int'), 0, INT_TOKEN);
+
+const PrecedenceInfo STRING_INFO =
+  const PrecedenceInfo(const SourceString('string'), 0, STRING_TOKEN);
+
+const PrecedenceInfo OPEN_SQUARE_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString('['), POSTFIX_PRECEDENCE,
+                       OPEN_SQUARE_BRACKET_TOKEN);
+
+const PrecedenceInfo CLOSE_SQUARE_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString(']'), 0, CLOSE_SQUARE_BRACKET_TOKEN);
+
+const PrecedenceInfo DOUBLE_INFO =
+  const PrecedenceInfo(const SourceString('double'), 0, DOUBLE_TOKEN);
+
+const PrecedenceInfo STRING_INTERPOLATION_INFO =
+  const PrecedenceInfo(const SourceString('\${'), 0,
+                       STRING_INTERPOLATION_TOKEN);
+
+const PrecedenceInfo STRING_INTERPOLATION_IDENTIFIER_INFO =
+  const PrecedenceInfo(const SourceString('\$'), 0,
+                       STRING_INTERPOLATION_IDENTIFIER_TOKEN);
+
+const PrecedenceInfo HEXADECIMAL_INFO =
+  const PrecedenceInfo(const SourceString('hexadecimal'), 0, HEXADECIMAL_TOKEN);
+
+const PrecedenceInfo COMMENT_INFO =
+  const PrecedenceInfo(const SourceString('comment'), 0, COMMENT_TOKEN);
+
+// For reporting lexical errors.
+const PrecedenceInfo ERROR_INFO =
+  const PrecedenceInfo(const SourceString('?'), 0, UNKNOWN_TOKEN);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/script.dart b/pkgs/markdown/lib/src/compiler/implementation/script.dart
new file mode 100644
index 0000000..b130616
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/script.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2011, 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.
+
+part of dart2js;
+
+class Script {
+  // TODO(kasperl): Once MockFile in tests/compiler/dart2js/parser_helper.dart
+  // implements SourceFile, we should be able to type the [file] field as
+  // such.
+  final file;
+
+  /**
+   * The readable URI from which this script was loaded.
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  final Uri uri;
+
+  Script(this.uri, this.file);
+
+  String get text => (file == null) ? null : file.text;
+  String get name => (file == null) ? null : file.filename;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/source_file.dart b/pkgs/markdown/lib/src/compiler/implementation/source_file.dart
new file mode 100644
index 0000000..14a4175
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/source_file.dart
@@ -0,0 +1,105 @@
+// 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.
+
+library source_file;
+
+import 'dart:math';
+
+import 'colors.dart' as colors;
+
+/**
+ * Represents a file of source code.
+ */
+class SourceFile {
+
+  /** The name of the file. */
+  final String filename;
+
+  /** The text content of the file. */
+  final String text;
+
+  List<int> _lineStarts;
+
+  SourceFile(this.filename, this.text);
+
+  List<int> get lineStarts {
+    if (_lineStarts == null) {
+      var starts = [0];
+      var index = 0;
+      while (index < text.length) {
+        index = text.indexOf('\n', index) + 1;
+        if (index <= 0) break;
+        starts.add(index);
+      }
+      starts.add(text.length + 1);
+      _lineStarts = starts;
+    }
+    return _lineStarts;
+  }
+
+  int getLine(int position) {
+    List<int> starts = lineStarts;
+    if (position < 0 || starts.last <= position) {
+      throw 'bad position #$position in file $filename with '
+            'length ${text.length}.';
+    }
+    int first = 0;
+    int count = starts.length;
+    while (count > 1) {
+      int step = count ~/ 2;
+      int middle = first + step;
+      int lineStart = starts[middle];
+      if (position < lineStart) {
+        count = step;
+      } else {
+        first = middle;
+        count -= step;
+      }
+    }
+    return first;
+  }
+
+  int getColumn(int line, int position) {
+    return position - lineStarts[line];
+  }
+
+  /**
+   * Create a pretty string representation from a character position
+   * in the file.
+   */
+  String getLocationMessage(String message, int start, int end,
+                            bool includeText, String color(String x)) {
+    var line = getLine(start);
+    var column = getColumn(line, start);
+
+    var buf = new StringBuffer(
+        '${filename}:${line + 1}:${column + 1}: $message');
+    if (includeText) {
+      buf.add('\n');
+      var textLine;
+      // +1 for 0-indexing, +1 again to avoid the last line of the file
+      if ((line + 2) < _lineStarts.length) {
+        textLine = text.substring(_lineStarts[line], _lineStarts[line+1]);
+      } else {
+        textLine = '${text.substring(_lineStarts[line])}\n';
+      }
+
+      int toColumn = min(column + (end-start), textLine.length);
+      buf.add(textLine.substring(0, column));
+      buf.add(color(textLine.substring(column, toColumn)));
+      buf.add(textLine.substring(toColumn));
+
+      int i = 0;
+      for (; i < column; i++) {
+        buf.add(' ');
+      }
+
+      for (; i < toColumn; i++) {
+        buf.add(color('^'));
+      }
+    }
+
+    return buf.toString();
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart b/pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart
new file mode 100644
index 0000000..c100694
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart
@@ -0,0 +1,125 @@
+// 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.
+
+library source_file_provider;
+
+import 'dart:async';
+import 'dart:uri';
+import 'dart:io';
+import 'dart:utf';
+
+import '../compiler.dart' as api show Diagnostic;
+import 'dart2js.dart' show AbortLeg;
+import 'colors.dart' as colors;
+import 'source_file.dart';
+import 'filenames.dart';
+import 'util/uri_extras.dart';
+
+String readAll(String filename) {
+  var file = (new File(filename)).openSync(FileMode.READ);
+  var length = file.lengthSync();
+  var buffer = new List<int>.fixedLength(length);
+  var bytes = file.readListSync(buffer, 0, length);
+  file.closeSync();
+  return new String.fromCharCodes(new Utf8Decoder(buffer).decodeRest());
+}
+
+class SourceFileProvider {
+  bool isWindows = (Platform.operatingSystem == 'windows');
+  Uri cwd = getCurrentDirectory();
+  Map<String, SourceFile> sourceFiles = <String, SourceFile>{};
+  int dartCharactersRead = 0;
+
+  Future<String> readStringFromUri(Uri resourceUri) {
+    if (resourceUri.scheme != 'file') {
+      throw new ArgumentError("Unknown scheme in uri '$resourceUri'");
+    }
+    String source;
+    try {
+      source = readAll(uriPathToNative(resourceUri.path));
+    } on FileIOException catch (ex) {
+      throw 'Error: Cannot read "${relativize(cwd, resourceUri, isWindows)}" '
+            '(${ex.osError}).';
+    }
+    dartCharactersRead += source.length;
+    sourceFiles[resourceUri.toString()] =
+      new SourceFile(relativize(cwd, resourceUri, isWindows), source);
+    return new Future.immediate(source);
+  }
+}
+
+void silentDiagnosticHandler(Uri uri, int begin, int end, String message,
+                             api.Diagnostic kind) {
+}
+
+class FormattingDiagnosticHandler {
+  final SourceFileProvider provider;
+  bool showWarnings = true;
+  bool verbose = false;
+  bool isAborting = false;
+  bool enableColors = false;
+  bool throwOnError = false;
+
+  final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal;
+  final int INFO =
+      api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal;
+
+  FormattingDiagnosticHandler(SourceFileProvider this.provider);
+
+  void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) {
+    if (!verbose && identical(kind, api.Diagnostic.VERBOSE_INFO)) return;
+    if (enableColors) {
+      print('${colors.green("info:")} $message');
+    } else {
+      print('info: $message');
+    }
+  }
+
+  void diagnosticHandler(Uri uri, int begin, int end, String message,
+                         api.Diagnostic kind) {
+    // TODO(ahe): Remove this when source map is handled differently.
+    if (identical(kind.name, 'source map')) return;
+
+    if (isAborting) return;
+    isAborting = identical(kind, api.Diagnostic.CRASH);
+    bool fatal = (kind.ordinal & FATAL) != 0;
+    bool isInfo = (kind.ordinal & INFO) != 0;
+    if (isInfo && uri == null && !identical(kind, api.Diagnostic.INFO)) {
+      info(message, kind);
+      return;
+    }
+    var color;
+    if (!enableColors) {
+      color = (x) => x;
+    } else if (identical(kind, api.Diagnostic.ERROR)) {
+      color = colors.red;
+    } else if (identical(kind, api.Diagnostic.WARNING)) {
+      color = colors.magenta;
+    } else if (identical(kind, api.Diagnostic.LINT)) {
+      color = colors.magenta;
+    } else if (identical(kind, api.Diagnostic.CRASH)) {
+      color = colors.red;
+    } else if (identical(kind, api.Diagnostic.INFO)) {
+      color = colors.green;
+    } else {
+      throw 'Unknown kind: $kind (${kind.ordinal})';
+    }
+    if (uri == null) {
+      assert(fatal);
+      print(color(message));
+    } else if (fatal || showWarnings) {
+      SourceFile file = provider.sourceFiles[uri.toString()];
+      if (file == null) {
+        throw '$uri: file is null';
+      }
+      print(file.getLocationMessage(color(message), begin, end, true, color));
+    }
+    if (fatal && throwOnError) {
+      isAborting = true;
+      throw new AbortLeg(message);
+    }
+  }
+}
+
+
diff --git a/pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart b/pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart
new file mode 100644
index 0000000..a2cd6fc
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart
@@ -0,0 +1,192 @@
+// 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.
+
+library source_map_builder;
+
+import 'util/util.dart';
+import 'scanner/scannerlib.dart' show Token;
+import 'source_file.dart';
+
+class SourceMapBuilder {
+  static const int VLQ_BASE_SHIFT = 5;
+  static const int VLQ_BASE_MASK = (1 << 5) - 1;
+  static const int VLQ_CONTINUATION_BIT = 1 << 5;
+  static const int VLQ_CONTINUATION_MASK = 1 << 5;
+  static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn'
+                                      'opqrstuvwxyz0123456789+/';
+
+  List<SourceMapEntry> entries;
+
+  Map<String, int> sourceUrlMap;
+  List<String> sourceUrlList;
+  Map<String, int> sourceNameMap;
+  List<String> sourceNameList;
+
+  int previousTargetLine;
+  int previousTargetColumn;
+  int previousSourceUrlIndex;
+  int previousSourceLine;
+  int previousSourceColumn;
+  int previousSourceNameIndex;
+  bool firstEntryInLine;
+
+  SourceMapBuilder() {
+    entries = new List<SourceMapEntry>();
+
+    sourceUrlMap = new Map<String, int>();
+    sourceUrlList = new List<String>();
+    sourceNameMap = new Map<String, int>();
+    sourceNameList = new List<String>();
+
+    previousTargetLine = 0;
+    previousTargetColumn = 0;
+    previousSourceUrlIndex = 0;
+    previousSourceLine = 0;
+    previousSourceColumn = 0;
+    previousSourceNameIndex = 0;
+    firstEntryInLine = true;
+  }
+
+  void addMapping(int targetOffset, SourceFileLocation sourceLocation) {
+    entries.add(new SourceMapEntry(sourceLocation, targetOffset));
+  }
+
+  void printStringListOn(List<String> strings, StringBuffer buffer) {
+    bool first = true;
+    buffer.add('[');
+    for (String string in strings) {
+      if (!first) buffer.add(',');
+      buffer.add('"');
+      writeJsonEscapedCharsOn(string, buffer);
+      buffer.add('"');
+      first = false;
+    }
+    buffer.add(']');
+  }
+
+  String build(SourceFile targetFile) {
+    StringBuffer mappingsBuffer = new StringBuffer();
+    entries.forEach((SourceMapEntry entry) => writeEntry(entry, targetFile,
+                                                         mappingsBuffer));
+    StringBuffer buffer = new StringBuffer();
+    buffer.add('{\n');
+    buffer.add('  "version": 3,\n');
+    buffer.add('  "sourceRoot": "",\n');
+    buffer.add('  "sources": ');
+    printStringListOn(sourceUrlList, buffer);
+    buffer.add(',\n');
+    buffer.add('  "names": ');
+    printStringListOn(sourceNameList, buffer);
+    buffer.add(',\n');
+    buffer.add('  "mappings": "');
+    buffer.add(mappingsBuffer);
+    buffer.add('"\n}\n');
+    return buffer.toString();
+  }
+
+  void writeEntry(SourceMapEntry entry, SourceFile targetFile, StringBuffer output) {
+    int targetLine = targetFile.getLine(entry.targetOffset);
+    int targetColumn = targetFile.getColumn(targetLine, entry.targetOffset);
+
+    if (targetLine > previousTargetLine) {
+      for (int i = previousTargetLine; i < targetLine; ++i) {
+        output.add(';');
+      }
+      previousTargetLine = targetLine;
+      previousTargetColumn = 0;
+      firstEntryInLine = true;
+    }
+
+    if (!firstEntryInLine) {
+      output.add(',');
+    }
+    firstEntryInLine = false;
+
+    encodeVLQ(output, targetColumn - previousTargetColumn);
+    previousTargetColumn = targetColumn;
+
+    if (entry.sourceLocation == null) return;
+
+    String sourceUrl = entry.sourceLocation.getSourceUrl();
+    int sourceLine = entry.sourceLocation.getLine();
+    int sourceColumn = entry.sourceLocation.getColumn();
+    String sourceName = entry.sourceLocation.getSourceName();
+
+    int sourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap);
+    encodeVLQ(output, sourceUrlIndex - previousSourceUrlIndex);
+    previousSourceUrlIndex = sourceUrlIndex;
+
+    encodeVLQ(output, sourceLine - previousSourceLine);
+    previousSourceLine = sourceLine;
+    encodeVLQ(output, sourceColumn - previousSourceColumn);
+    previousSourceColumn = sourceColumn;
+
+    if (sourceName == null) {
+      return;
+    }
+
+    int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap);
+    encodeVLQ(output, sourceNameIndex - previousSourceNameIndex);
+    previousSourceNameIndex = sourceNameIndex;
+  }
+
+  int indexOf(List<String> list, String value, Map<String, int> map) {
+    return map.putIfAbsent(value, () {
+      int index = list.length;
+      map[value] = index;
+      list.add(value);
+      return index;
+    });
+  }
+
+  static void encodeVLQ(StringBuffer output, int value) {
+    int signBit = 0;
+    if (value < 0) {
+      signBit = 1;
+      value = -value;
+    }
+    value = (value << 1) | signBit;
+    do {
+      int digit = value & VLQ_BASE_MASK;
+      value >>= VLQ_BASE_SHIFT;
+      if (value > 0) {
+        digit |= VLQ_CONTINUATION_BIT;
+      }
+      output.add(BASE64_DIGITS[digit]);
+    } while (value > 0);
+  }
+}
+
+class SourceMapEntry {
+  SourceFileLocation sourceLocation;
+  int targetOffset;
+
+  SourceMapEntry(this.sourceLocation, this.targetOffset);
+}
+
+class SourceFileLocation {
+  SourceFile sourceFile;
+  Token token;
+  int line;
+
+  SourceFileLocation(this.sourceFile, this.token) {
+    assert(isValid());
+  }
+
+  String getSourceUrl() => sourceFile.filename;
+
+  int getLine() {
+    if (line == null) line = sourceFile.getLine(token.charOffset);
+    return line;
+  }
+
+  int getColumn() => sourceFile.getColumn(getLine(), token.charOffset);
+
+  String getSourceName() {
+    if (token.isIdentifier()) return token.slowToString();
+    return null;
+  }
+
+  bool isValid() => token.charOffset < sourceFile.text.length;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart
new file mode 100644
index 0000000..0e10ecf
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart
@@ -0,0 +1,630 @@
+// 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.
+
+part of ssa;
+
+class BailoutInfo {
+  int instructionId;
+  int bailoutId;
+  BailoutInfo(this.instructionId, this.bailoutId);
+}
+
+/**
+ * Keeps track of the execution environment for instructions. An
+ * execution environment contains the SSA instructions that are live.
+ */
+class Environment {
+  final Set<HInstruction> lives;
+  final List<HBasicBlock> loopMarkers;
+  Environment() : lives = new Set<HInstruction>(),
+                  loopMarkers = new List<HBasicBlock>();
+  Environment.from(Environment other)
+    : lives = new Set<HInstruction>.from(other.lives),
+      loopMarkers = new List<HBasicBlock>.from(other.loopMarkers);
+
+  void remove(HInstruction instruction) {
+    lives.remove(instruction);
+  }
+
+  void add(HInstruction instruction) {
+    // If the instruction is a check, we add its checked input
+    // instead. This allows sharing the same environment between
+    // different type guards.
+    //
+    // Also, we don't need to add code motion invariant instructions
+    // in the live set (because we generate them at use-site), except
+    // for parameters that are not 'this', which is always passed as
+    // the receiver.
+    if (instruction is HCheck) {
+      add(instruction.checkedInput);
+    } else if (!instruction.isCodeMotionInvariant()
+               || (instruction is HParameterValue && instruction is !HThis)) {
+      lives.add(instruction);
+    } else {
+      for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+        add(instruction.inputs[i]);
+      }
+    }
+  }
+
+  void addAll(Environment other) {
+    lives.addAll(other.lives);
+  }
+
+  bool get isEmpty => lives.isEmpty && loopMarkers.isEmpty;
+}
+
+
+/**
+ * Visits the graph in dominator order and inserts TypeGuards in places where
+ * we consider the guard to be of value.
+ *
+ * Might modify the [types] in an inconsistent way. No further analysis should
+ * rely on them.
+ */
+class SsaTypeGuardInserter extends HGraphVisitor implements OptimizationPhase {
+  final Compiler compiler;
+  final String name = 'SsaTypeGuardInserter';
+  final CodegenWorkItem work;
+  final HTypeMap types;
+  bool calledInLoop = false;
+  bool isRecursiveMethod = false;
+  int stateId = 1;
+
+  SsaTypeGuardInserter(this.compiler, this.work, this.types);
+
+  void visitGraph(HGraph graph) {
+    isRecursiveMethod = graph.isRecursiveMethod;
+    calledInLoop = graph.calledInLoop;
+    work.guards = <HTypeGuard>[];
+    visitDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    block.forEachPhi(visitInstruction);
+
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      // Note that visitInstruction (from the phis and here) might insert an
+      // HTypeGuard instruction. We have to skip those.
+      if (instruction is !HTypeGuard) visitInstruction(instruction);
+      instruction = instruction.next;
+    }
+  }
+
+  // Primitive types that are not null are valuable. These include
+  // indexable arrays.
+  bool typeValuable(HType type) {
+    return type.isPrimitive() && !type.isNull();
+  }
+
+  bool get hasTypeGuards => work.guards.length != 0;
+
+  bool typeGuardWouldBeValuable(HInstruction instruction,
+                                HType speculativeType) {
+    // If the type itself is not valuable, do not generate a guard for it.
+    if (!typeValuable(speculativeType)) return false;
+
+    // Do not insert a type guard if the instruction has a type
+    // annotation that disagrees with the speculated type.
+    Element source = instruction.sourceElement;
+    if (source != null) {
+      DartType sourceType = source.computeType(compiler);
+      DartType speculatedType = speculativeType.computeType(compiler);
+      JavaScriptBackend backend = compiler.backend;
+      if (speculatedType != null) {
+        // Use the num type instead of JSNumber because JSNumber
+        // is not assignment compatible with int and double, but we
+        // still want to generate a type guard.
+        if (speculatedType.element == backend.jsNumberClass) {
+          speculatedType = compiler.numClass.computeType(compiler);
+        }
+        if (!compiler.types.isAssignable(speculatedType, sourceType)) {
+          return false;
+        }
+      }
+    }
+
+    // Insert type guards for recursive methods.
+    if (isRecursiveMethod) return true;
+
+    // Insert type guards if there are uses in loops.
+    bool isNested(HBasicBlock inner, HBasicBlock outer) {
+      if (identical(inner, outer)) return false;
+      if (outer == null) return true;
+      while (inner != null) {
+        if (identical(inner, outer)) return true;
+        inner = inner.parentLoopHeader;
+      }
+      return false;
+    }
+
+    // If the instruction is not in a loop then the header will be null.
+    HBasicBlock currentLoopHeader = instruction.block.enclosingLoopHeader;
+    for (HInstruction user in instruction.usedBy) {
+      HBasicBlock userLoopHeader = user.block.enclosingLoopHeader;
+      if (isNested(userLoopHeader, currentLoopHeader)) return true;
+    }
+
+    bool isIndexOperatorOnIndexablePrimitive(instruction, types) {
+      return instruction is HIndex
+          || (instruction is HInvokeDynamicMethod
+              && instruction.isIndexOperatorOnIndexablePrimitive(types));
+    }
+
+    // To speed up computations on values loaded from arrays, we
+    // insert type guards for builtin array indexing operations in
+    // nested loops. Since this can blow up code size quite
+    // significantly, we only do it if type guards have already been
+    // inserted for this method. The code size price for an additional
+    // type guard is much smaller than the first one that causes the
+    // generation of a bailout method.
+    if (hasTypeGuards
+        && isIndexOperatorOnIndexablePrimitive(instruction, types)) {
+      HBasicBlock loopHeader = instruction.block.enclosingLoopHeader;
+      if (loopHeader != null && loopHeader.parentLoopHeader != null) {
+        return true;
+      }
+    }
+
+    // If the instruction is used by a phi where a guard would be
+    // valuable, put the guard on that instruction.
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HPhi
+          && user.block.id > instruction.id
+          && typeGuardWouldBeValuable(user, speculativeType)) {
+        return true;
+      }
+    }
+
+    // Insert type guards if the method is likely to be called in a
+    // loop.
+    return calledInLoop;
+  }
+
+  bool shouldInsertTypeGuard(HInstruction instruction,
+                             HType speculativeType,
+                             HType computedType) {
+    if (!speculativeType.isUseful()) return false;
+    // If the types agree we don't need to check.
+    if (speculativeType == computedType) return false;
+    // If a bailout check is more expensive than doing the actual operation
+    // don't do it either.
+    return typeGuardWouldBeValuable(instruction, speculativeType);
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    HType speculativeType = types[instruction];
+    HType computedType = instruction.computeTypeFromInputTypes(types, compiler);
+    // Currently the type in [types] is the speculative type each instruction
+    // would like to have. We start by recomputing the type non-speculatively.
+    // If we add a type guard then the guard will expose the speculative type.
+    // If we don't add a type guard then this avoids that subsequent
+    // instructions use the wrong (speculative) type.
+    //
+    // Note that just setting the speculative type of the instruction is not
+    // complete since the type could lead to a phi node which in turn could
+    // change the speculative type. In this case we might miss some guards we
+    // would have liked to insert. Most of the time this should however be
+    // fine, due to dominator-order visiting.
+    types[instruction] = computedType;
+
+    if (shouldInsertTypeGuard(instruction, speculativeType, computedType)) {
+      HInstruction insertionPoint;
+      if (instruction is HPhi) {
+        insertionPoint = instruction.block.first;
+      } else if (instruction is HParameterValue) {
+        // We insert the type guard at the end of the entry block
+        // because if a parameter is live, it must be kept in the live
+        // environment. Not doing so would mean we could visit a
+        // parameter and remove it from the environment before
+        // visiting a type guard.
+        insertionPoint = instruction.block.last;
+      } else {
+        insertionPoint = instruction.next;
+      }
+      // If the previous instruction is also a type guard, then both
+      // guards have the same environment, and can therefore share the
+      // same state id.
+      HBailoutTarget target;
+      int state;
+      if (insertionPoint.previous is HTypeGuard) {
+        HTypeGuard other = insertionPoint.previous;
+        target = other.bailoutTarget;
+      } else {
+        state = stateId++;
+        target = new HBailoutTarget(state);
+        insertionPoint.block.addBefore(insertionPoint, target);
+      }
+      HTypeGuard guard = new HTypeGuard(speculativeType, instruction, target);
+      types[guard] = speculativeType;
+      work.guards.add(guard);
+      instruction.block.rewrite(instruction, guard);
+      insertionPoint.block.addBefore(insertionPoint, guard);
+    }
+  }
+}
+
+/**
+ * Computes the environment for each SSA instruction: visits the graph
+ * in post-dominator order. Removes an instruction from the environment
+ * and adds its inputs to the environment at the instruction's
+ * definition.
+ *
+ * At the end of the computation, insert type guards in the graph.
+ */
+class SsaEnvironmentBuilder extends HBaseVisitor implements OptimizationPhase {
+  final Compiler compiler;
+  final String name = 'SsaEnvironmentBuilder';
+
+  final Map<HBailoutTarget, Environment> capturedEnvironments;
+  final Map<HBasicBlock, Environment> liveInstructions;
+  Environment environment;
+  /**
+   * The set of current loop headers that dominate the current block.
+   */
+  Set<HBasicBlock> loopMarkers;
+
+  SsaEnvironmentBuilder(Compiler this.compiler)
+    : capturedEnvironments = new Map<HBailoutTarget, Environment>(),
+      liveInstructions = new Map<HBasicBlock, Environment>(),
+      loopMarkers = new Set<HBasicBlock>();
+
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+    if (!liveInstructions[graph.entry].isEmpty) {
+      compiler.internalError('Bailout environment computation',
+          node: compiler.currentElement.parseNode(compiler));
+    }
+    updateLoopMarkers();
+    insertCapturedEnvironments();
+  }
+
+  void updateLoopMarkers() {
+    // If the block is a loop header, we need to merge the loop
+    // header's live instructions into every environment that contains
+    // the loop marker.
+    // For example with the following loop (read the example in
+    // reverse):
+    //
+    // while (true) { <-- (4) update the marker with the environment
+    //   use(x);      <-- (3) environment = {x}
+    //   bailout;     <-- (2) has the marker when computed
+    // }              <-- (1) create a loop marker
+    //
+    // The bailout instruction first captures the marker, but it
+    // will be replaced by the live environment at the loop entry,
+    // in this case {x}.
+    capturedEnvironments.forEach((ignoredInstruction, env) {
+      env.loopMarkers.forEach((HBasicBlock header) {
+        env.addAll(liveInstructions[header]);
+      });
+      env.loopMarkers.clear();
+    });
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    environment = new Environment();
+
+    // Add to the environment the live instructions of its successor, as well as
+    // the inputs of the phis of the successor that flow from this block.
+    for (int i = 0; i < block.successors.length; i++) {
+      HBasicBlock successor = block.successors[i];
+      Environment successorEnv = liveInstructions[successor];
+      if (successorEnv != null) {
+        environment.addAll(successorEnv);
+      } else {
+        // If we haven't computed the liveInstructions of that successor, we
+        // know it must be a loop header.
+        assert(successor.isLoopHeader());
+        assert(!block.isLoopHeader());
+        loopMarkers.add(successor);
+      }
+
+      int index = successor.predecessors.indexOf(block);
+      for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) {
+        environment.add(phi.inputs[index]);
+      }
+    }
+
+    if (block.isLoopHeader()) {
+      loopMarkers.remove(block);
+    }
+
+    // If the block is a loop header, we're adding all [loopMarkers]
+    // after removing it from the list of [loopMarkers], because
+    // it will just recompute the loop phis.
+    environment.loopMarkers.addAll(loopMarkers);
+
+    // Iterate over all instructions to remove an instruction from the
+    // environment and add its inputs.
+    HInstruction instruction = block.last;
+    while (instruction != null) {
+      instruction.accept(this);
+      instruction = instruction.previous;
+    }
+
+    // We just remove the phis from the environment. The inputs of the
+    // phis will be put in the environment of the predecessors.
+    for (HPhi phi = block.phis.first; phi != null; phi = phi.next) {
+      environment.remove(phi);
+    }
+
+    // Finally save the liveInstructions of that block.
+    liveInstructions[block] = environment;
+  }
+
+  void visitBailoutTarget(HBailoutTarget target) {
+    visitInstruction(target);
+    capturedEnvironments[target] = new Environment.from(environment);
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    environment.remove(instruction);
+    for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+      environment.add(instruction.inputs[i]);
+    }
+  }
+
+  /**
+   * Stores all live variables in the bailout target and the guards.
+   */
+  void insertCapturedEnvironments() {
+    capturedEnvironments.forEach((HBailoutTarget target, Environment env) {
+      assert(target.inputs.length == 0);
+      target.inputs.addAll(env.lives);
+      // TODO(floitsch): we should add the bailout-target's input variables
+      // as input to the guards only in the optimized version. The
+      // non-optimized version does not use the bailout guards and it is
+      // unnecessary to keep the variables alive until the check.
+      for (HTypeGuard guard in target.usedBy) {
+        // A type-guard initially only has two inputs: the guarded instruction
+        // and the bailout-target. Only after adding the environment is it
+        // allowed to have more inputs.
+        assert(guard.inputs.length == 2);
+        guard.inputs.addAll(env.lives);
+      }
+      for (HInstruction live in env.lives) {
+        live.usedBy.add(target);
+        live.usedBy.addAll(target.usedBy);
+      }
+    });
+  }
+}
+
+/**
+ * Propagates bailout information to blocks that need it. This visitor
+ * is run before codegen, to know which blocks have to deal with
+ * bailouts.
+ */
+class SsaBailoutPropagator extends HBaseVisitor {
+  final Compiler compiler;
+  /**
+   * A list to propagate bailout information to blocks that start a
+   * guarded or labeled list of statements. Currently, these blocks
+   * are:
+   *    - first block of a then branch,
+   *    - first block of an else branch,
+   *    - a loop header,
+   *    - labeled block.
+   */
+  final List<HBasicBlock> blocks;
+
+  /**
+   * The current subgraph we are visiting.
+   */
+  SubGraph subGraph;
+
+  /**
+   * The current block information we are visiting.
+   */
+  HBlockInformation currentBlockInformation;
+
+  /**
+   * Max number of arguments to the bailout (not counting the state).
+   */
+  int bailoutArity;
+  /**
+   * A map from variables to their names.  These are the names in the
+   * unoptimized (bailout) version of the function.  Their names could be
+   * different in the optimized version.
+   */
+  VariableNames variableNames;
+  /**
+   * Maps from the variable names to their positions in the argument list of the
+   * bailout instruction.  Because of the way the variable allocator works,
+   * several variables can end up with the same name (if their live ranges do
+   * not overlap), therefore they can have the same position in the bailout
+   * argument list
+   */
+  Map<String, int> parameterNames;
+
+  /**
+   * If set to true, the graph has either multiple bailouts in
+   * different places, or a bailout inside an if or a loop. For such a
+   * graph, the code generator will emit a generic switch.
+   */
+  bool hasComplexBailoutTargets = false;
+
+  /**
+   * The first type guard in the graph.
+   */
+  HBailoutTarget firstBailoutTarget;
+
+  /**
+   * If set, it is the first block in the graph where we generate
+   * code. Blocks before this one are dead code in the bailout
+   * version.
+   */
+
+  SsaBailoutPropagator(this.compiler, this.variableNames)
+      : blocks = <HBasicBlock>[],
+        bailoutArity = 0,
+        parameterNames = new Map<String, int>();
+
+  void visitGraph(HGraph graph) {
+    subGraph = new SubGraph(graph.entry, graph.exit);
+    visitBasicBlock(graph.entry);
+    if (!blocks.isEmpty) {
+      compiler.internalError('Bailout propagation',
+          node: compiler.currentElement.parseNode(compiler));
+    }
+  }
+
+  /**
+   * Returns true if we can visit the given [blockFlow]. False
+   * otherwise. Currently, try/catch and switch are not in bailout
+   * methods, so this method only deals with loops and labeled blocks.
+   * If [blockFlow] is a labeled block or a loop, we also visit the
+   * continuation of the block flow.
+   */
+  bool handleBlockFlow(HBlockFlow blockFlow) {
+    HBlockInformation body = blockFlow.body;
+
+    // We reach here again when starting to visit a subgraph. Just
+    // return to visiting the block.
+    if (currentBlockInformation == body) return false;
+
+    HBlockInformation oldInformation = currentBlockInformation;
+    if (body is HLabeledBlockInformation) {
+      currentBlockInformation = body;
+      HLabeledBlockInformation info = body;
+      visitStatements(info.body, newFlow: true);
+    } else if (body is HLoopBlockInformation) {
+      currentBlockInformation = body;
+      HLoopBlockInformation info = body;
+      if (info.initializer != null) {
+        visitExpression(info.initializer);
+      }
+      blocks.addLast(info.loopHeader);
+      if (!info.isDoWhile()) {
+        visitExpression(info.condition);
+      }
+      visitStatements(info.body, newFlow: false);
+      if (info.isDoWhile()) {
+        visitExpression(info.condition);
+      }
+      if (info.updates != null) {
+        visitExpression(info.updates);
+      }
+      blocks.removeLast();
+    } else {
+      assert(body is! HTryBlockInformation);
+      assert(body is! HSwitchBlockInformation);
+      // [HIfBlockInformation] is handled by visitIf.
+      return false;
+    }
+
+    currentBlockInformation = oldInformation;
+    if (blockFlow.continuation != null) {
+      visitBasicBlock(blockFlow.continuation);
+    }
+    return true;
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    // Abort traversal if we are leaving the currently active sub-graph.
+    if (!subGraph.contains(block)) return;
+
+    HBlockFlow blockFlow = block.blockFlow;
+    if (blockFlow != null && handleBlockFlow(blockFlow)) return;
+
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      instruction.accept(this);
+      instruction = instruction.next;
+    }
+  }
+
+  void visitExpression(HSubExpressionBlockInformation info) {
+    visitSubGraph(info.subExpression);
+  }
+
+  /**
+   * Visit the statements in [info]. If [newFlow] is true, we add the
+   * first block of [statements] to the list of [blocks].
+   */
+  void visitStatements(HSubGraphBlockInformation info, {bool newFlow}) {
+    SubGraph graph = info.subGraph;
+    if (newFlow) blocks.addLast(graph.start);
+    visitSubGraph(graph);
+    if (newFlow) blocks.removeLast();
+  }
+
+  void visitSubGraph(SubGraph graph) {
+    SubGraph oldSubGraph = subGraph;
+    subGraph = graph;
+    visitBasicBlock(graph.start);
+    subGraph = oldSubGraph;
+  }
+
+  void visitIf(HIf instruction) {
+    int preVisitedBlocks = 0;
+    HIfBlockInformation info = instruction.blockInformation.body;
+    visitStatements(info.thenGraph, newFlow: true);
+    preVisitedBlocks++;
+    visitStatements(info.elseGraph, newFlow: true);
+    preVisitedBlocks++;
+
+    HBasicBlock joinBlock = instruction.joinBlock;
+    if (joinBlock != null
+        && !identical(joinBlock.dominator, instruction.block)) {
+      // The join block is dominated by a block in one of the branches.
+      // The subgraph traversal never reached it, so we visit it here
+      // instead.
+      visitBasicBlock(joinBlock);
+    }
+
+    // Visit all the dominated blocks that are not part of the then or else
+    // branches, and is not the join block.
+    // Depending on how the then/else branches terminate
+    // (e.g., return/throw/break) there can be any number of these.
+    List<HBasicBlock> dominated = instruction.block.dominatedBlocks;
+    int dominatedCount = dominated.length;
+    for (int i = preVisitedBlocks; i < dominatedCount; i++) {
+      HBasicBlock dominatedBlock = dominated[i];
+      visitBasicBlock(dominatedBlock);
+    }
+  }
+
+  void visitGoto(HGoto goto) {
+    HBasicBlock block = goto.block;
+    HBasicBlock successor = block.successors[0];
+    if (identical(successor.dominator, block)) {
+      visitBasicBlock(block.successors[0]);
+    }
+  }
+
+  void visitLoopBranch(HLoopBranch branch) {
+    // For a do-while loop, the body has already been visited.
+    if (!branch.isDoWhile()) {
+      visitBasicBlock(branch.block.dominatedBlocks[0]);
+    }
+  }
+
+  visitBailoutTarget(HBailoutTarget target) {
+    int inputLength = target.inputs.length;
+    for (HInstruction input in target.inputs) {
+      String inputName = variableNames.getName(input);
+      int position = parameterNames[inputName];
+      if (position == null) {
+        position = parameterNames[inputName] = bailoutArity++;
+      }
+    }
+
+    if (blocks.isEmpty) {
+      if (firstBailoutTarget == null) {
+        firstBailoutTarget = target;
+      } else {
+        hasComplexBailoutTargets = true;
+      }
+    } else {
+      hasComplexBailoutTargets = true;
+      blocks.forEach((HBasicBlock block) {
+        block.bailoutTargets.add(target);
+      });
+    }
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart
new file mode 100644
index 0000000..ac19fea
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart
@@ -0,0 +1,5034 @@
+// 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.
+
+part of ssa;
+
+/**
+ * A special element for the extra parameter taken by intercepted
+ * methods. We need to override [Element.computeType] because our
+ * optimizers may look at its declared type.
+ */
+class InterceptedElement extends ElementX {
+  final HType ssaType;
+  InterceptedElement(this.ssaType, Element enclosing)
+      : super(const SourceString('receiver'),
+              ElementKind.PARAMETER,
+              enclosing);
+
+  DartType computeType(Compiler compiler) => ssaType.computeType(compiler);
+}
+
+class SsaBuilderTask extends CompilerTask {
+  final CodeEmitterTask emitter;
+  // Loop tracking information.
+  final Set<FunctionElement> functionsCalledInLoop;
+  final Map<SourceString, Selector> selectorsCalledInLoop;
+  final JavaScriptBackend backend;
+
+  String get name => 'SSA builder';
+
+  SsaBuilderTask(JavaScriptBackend backend)
+    : emitter = backend.emitter,
+      functionsCalledInLoop = new Set<FunctionElement>(),
+      selectorsCalledInLoop = new Map<SourceString, Selector>(),
+      backend = backend,
+      super(backend.compiler);
+
+  HGraph build(CodegenWorkItem work) {
+    return measure(() {
+      Element element = work.element.implementation;
+      HInstruction.idCounter = 0;
+      ConstantSystem constantSystem = compiler.backend.constantSystem;
+      SsaBuilder builder = new SsaBuilder(constantSystem, this, work);
+      HGraph graph;
+      ElementKind kind = element.kind;
+      if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR)) {
+        graph = compileConstructor(builder, work);
+      } else if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY) ||
+                 identical(kind, ElementKind.FUNCTION) ||
+                 identical(kind, ElementKind.GETTER) ||
+                 identical(kind, ElementKind.SETTER)) {
+        graph = builder.buildMethod(element);
+      } else if (identical(kind, ElementKind.FIELD)) {
+        graph = builder.buildLazyInitializer(element);
+      } else {
+        compiler.internalErrorOnElement(element,
+                                        'unexpected element kind $kind');
+      }
+      assert(graph.isValid());
+      if (!identical(kind, ElementKind.FIELD)) {
+        bool inLoop = functionsCalledInLoop.contains(element.declaration);
+        if (!inLoop) {
+          Selector selector = selectorsCalledInLoop[element.name];
+          inLoop = selector != null && selector.applies(element, compiler);
+        }
+        graph.calledInLoop = inLoop;
+
+        // If there is an estimate of the parameter types assume these types
+        // when compiling.
+        // TODO(karlklose,ngeoffray): add a check to make sure that element is
+        // of type FunctionElement.
+        FunctionElement function = element;
+        OptionalParameterTypes defaultValueTypes = null;
+        FunctionSignature signature = function.computeSignature(compiler);
+        if (signature.optionalParameterCount > 0) {
+          defaultValueTypes =
+              new OptionalParameterTypes(signature.optionalParameterCount);
+          int index = 0;
+          signature.forEachOptionalParameter((Element parameter) {
+            Constant defaultValue = builder.compileVariable(parameter);
+            HType type = HGraph.mapConstantTypeToSsaType(defaultValue);
+            defaultValueTypes.update(index, parameter.name, type);
+            index++;
+          });
+        } else {
+          // TODO(ahe): I have disabled type optimizations for
+          // optional arguments as the types are stored in the wrong
+          // order.
+          HTypeList parameterTypes =
+              backend.optimisticParameterTypes(element.declaration,
+                                               defaultValueTypes);
+          if (!parameterTypes.allUnknown) {
+            int i = 0;
+            signature.forEachParameter((Element param) {
+              builder.parameters[param].guaranteedType = parameterTypes[i++];
+            });
+          }
+          backend.registerParameterTypesOptimization(
+              element.declaration, parameterTypes, defaultValueTypes);
+        }
+      }
+
+      if (compiler.tracer.enabled) {
+        String name;
+        if (element.isMember()) {
+          String className = element.getEnclosingClass().name.slowToString();
+          String memberName = element.name.slowToString();
+          name = "$className.$memberName";
+          if (element.isGenerativeConstructorBody()) {
+            name = "$name (body)";
+          }
+        } else {
+          name = "${element.name.slowToString()}";
+        }
+        compiler.tracer.traceCompilation(name, work.compilationContext);
+        compiler.tracer.traceGraph('builder', graph);
+      }
+      return graph;
+    });
+  }
+
+  HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) {
+    // The body of the constructor will be generated in a separate function.
+    final ClassElement classElement = work.element.getEnclosingClass();
+    return builder.buildFactory(classElement.implementation,
+                                work.element.implementation);
+  }
+}
+
+/**
+ * Keeps track of locals (including parameters and phis) when building. The
+ * 'this' reference is treated as parameter and hence handled by this class,
+ * too.
+ */
+class LocalsHandler {
+  /**
+   * The values of locals that can be directly accessed (without redirections
+   * to boxes or closure-fields).
+   *
+   * [directLocals] is iterated, so it is a [LinkedHashMap] to make the
+   * iteration order a function only of insertions and not a function of
+   * e.g. Element hash codes.  I'd prefer to use a SortedMap but some elements
+   * don't have source locations for [Elements.compareByPosition].
+   */
+  LinkedHashMap<Element, HInstruction> directLocals;
+  Map<Element, Element> redirectionMapping;
+  SsaBuilder builder;
+  ClosureClassMap closureData;
+
+  LocalsHandler(this.builder)
+      : directLocals = new LinkedHashMap<Element, HInstruction>(),
+        redirectionMapping = new Map<Element, Element>();
+
+  get typesTask => builder.compiler.typesTask;
+
+  /**
+   * Creates a new [LocalsHandler] based on [other]. We only need to
+   * copy the [directLocals], since the other fields can be shared
+   * throughout the AST visit.
+   */
+  LocalsHandler.from(LocalsHandler other)
+      : directLocals =
+            new LinkedHashMap<Element, HInstruction>.from(other.directLocals),
+        redirectionMapping = other.redirectionMapping,
+        builder = other.builder,
+        closureData = other.closureData;
+
+  /**
+   * Redirects accesses from element [from] to element [to]. The [to] element
+   * must be a boxed variable or a variable that is stored in a closure-field.
+   */
+  void redirectElement(Element from, Element to) {
+    assert(redirectionMapping[from] == null);
+    redirectionMapping[from] = to;
+    assert(isStoredInClosureField(from) || isBoxed(from));
+  }
+
+  HInstruction createBox() {
+    // TODO(floitsch): Clean up this hack. Should we create a box-object by
+    // just creating an empty object literal?
+    HInstruction box = new HForeign(const LiteralDartString("{}"),
+                                    HType.UNKNOWN,
+                                    <HInstruction>[]);
+    builder.add(box);
+    return box;
+  }
+
+  /**
+   * If the scope (function or loop) [node] has captured variables then this
+   * method creates a box and sets up the redirections.
+   */
+  void enterScope(Node node, Element element) {
+    // See if any variable in the top-scope of the function is captured. If yes
+    // we need to create a box-object.
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData != null) {
+      HInstruction box;
+      // The scope has captured variables.
+      if (element != null && element.isGenerativeConstructorBody()) {
+        // The box is passed as a parameter to a generative
+        // constructor body.
+        box = builder.addParameter(scopeData.boxElement);
+      } else {
+        box = createBox();
+      }
+      // Add the box to the known locals.
+      directLocals[scopeData.boxElement] = box;
+      // Make sure that accesses to the boxed locals go into the box. We also
+      // need to make sure that parameters are copied into the box if necessary.
+      scopeData.capturedVariableMapping.forEach((Element from, Element to) {
+        // The [from] can only be a parameter for function-scopes and not
+        // loop scopes.
+        if (from.isParameter() && !element.isGenerativeConstructorBody()) {
+          // Now that the redirection is set up, the update to the local will
+          // write the parameter value into the box.
+          // Store the captured parameter in the box. Get the current value
+          // before we put the redirection in place.
+          // We don't need to update the local for a generative
+          // constructor body, because it receives a box that already
+          // contains the updates as the last parameter.
+          HInstruction instruction = readLocal(from);
+          redirectElement(from, to);
+          updateLocal(from, instruction);
+        } else {
+          redirectElement(from, to);
+        }
+      });
+    }
+  }
+
+  /**
+   * Replaces the current box with a new box and copies over the given list
+   * of elements from the old box into the new box.
+   */
+  void updateCaptureBox(Element boxElement, List<Element> toBeCopiedElements) {
+    // Create a new box and copy over the values from the old box into the
+    // new one.
+    HInstruction oldBox = readLocal(boxElement);
+    HInstruction newBox = createBox();
+    for (Element boxedVariable in toBeCopiedElements) {
+      // [readLocal] uses the [boxElement] to find its box. By replacing it
+      // behind its back we can still get to the old values.
+      updateLocal(boxElement, oldBox);
+      HInstruction oldValue = readLocal(boxedVariable);
+      updateLocal(boxElement, newBox);
+      updateLocal(boxedVariable, oldValue);
+    }
+    updateLocal(boxElement, newBox);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [function] must be an implementation element.
+   */
+  void startFunction(Element element, Expression node) {
+    assert(invariant(node, element.isImplementation));
+    Compiler compiler = builder.compiler;
+    closureData = compiler.closureToClassMapper.computeClosureToClassMapping(
+            element, node, builder.elements);
+
+    if (element is FunctionElement) {
+      FunctionElement functionElement = element;
+      FunctionSignature params = functionElement.computeSignature(compiler);
+      params.orderedForEachParameter((Element parameterElement) {
+        if (element.isGenerativeConstructorBody()) {
+          ClosureScope scopeData = closureData.capturingScopes[node];
+          if (scopeData != null
+              && scopeData.capturedVariableMapping.containsKey(
+                  parameterElement)) {
+            // The parameter will be a field in the box passed as the
+            // last parameter. So no need to have it.
+            return;
+          }
+        }
+        HInstruction parameter = builder.addParameter(parameterElement);
+        builder.parameters[parameterElement] = parameter;
+        directLocals[parameterElement] = parameter;
+        parameter.guaranteedType =
+            builder.mapInferredType(
+                typesTask.getGuaranteedTypeOfElement(parameterElement));
+      });
+    }
+
+    enterScope(node, element);
+
+    // If the freeVariableMapping is not empty, then this function was a
+    // nested closure that captures variables. Redirect the captured
+    // variables to fields in the closure.
+    closureData.freeVariableMapping.forEach((Element from, Element to) {
+      redirectElement(from, to);
+    });
+    if (closureData.isClosure()) {
+      // Inside closure redirect references to itself to [:this:].
+      HThis thisInstruction = new HThis(closureData.thisElement);
+      builder.graph.thisInstruction = thisInstruction;
+      builder.graph.entry.addAtEntry(thisInstruction);
+      updateLocal(closureData.closureElement, thisInstruction);
+    } else if (element.isInstanceMember()
+               || element.isGenerativeConstructor()) {
+      // Once closures have been mapped to classes their instance members might
+      // not have any thisElement if the closure was created inside a static
+      // context.
+      ClassElement cls = element.getEnclosingClass();
+      DartType type = cls.computeType(builder.compiler);
+      HThis thisInstruction = new HThis(closureData.thisElement,
+                                        new HBoundedType.nonNull(type));
+      builder.graph.thisInstruction = thisInstruction;
+      builder.graph.entry.addAtEntry(thisInstruction);
+      directLocals[closureData.thisElement] = thisInstruction;
+    }
+
+    // If this method is an intercepted method, add the extra
+    // parameter to it, that is the actual receiver.
+    ClassElement cls = element.getEnclosingClass();
+    if (builder.backend.isInterceptorClass(cls)) {
+      HType type = HType.UNKNOWN;
+      if (cls == builder.backend.jsArrayClass) {
+        type = HType.READABLE_ARRAY;
+      } else if (cls == builder.backend.jsStringClass) {
+        type = HType.STRING;
+      } else if (cls == builder.backend.jsNumberClass) {
+        type = HType.NUMBER;
+      } else if (cls == builder.backend.jsIntClass) {
+        type = HType.INTEGER;
+      } else if (cls == builder.backend.jsDoubleClass) {
+        type = HType.DOUBLE;
+      } else if (cls == builder.backend.jsNullClass) {
+        type = HType.NULL;
+      } else if (cls == builder.backend.jsBoolClass) {
+        type = HType.BOOLEAN;
+      }
+      Element parameter = new InterceptedElement(type, element);
+      HParameterValue value = new HParameterValue(parameter);
+      builder.graph.entry.addAfter(
+          directLocals[closureData.thisElement], value);
+      directLocals[closureData.thisElement] = value;
+      value.guaranteedType = type;
+    }
+  }
+
+  bool hasValueForDirectLocal(Element element) {
+    assert(element != null);
+    assert(isAccessedDirectly(element));
+    return directLocals[element] != null;
+  }
+
+  /**
+   * Returns true if the local can be accessed directly. Boxed variables or
+   * captured variables that are stored in the closure-field return [false].
+   */
+  bool isAccessedDirectly(Element element) {
+    assert(element != null);
+    return redirectionMapping[element] == null
+        && !closureData.usedVariablesInTry.contains(element);
+  }
+
+  bool isStoredInClosureField(Element element) {
+    assert(element != null);
+    if (isAccessedDirectly(element)) return false;
+    Element redirectTarget = redirectionMapping[element];
+    if (redirectTarget == null) return false;
+    if (redirectTarget.isMember()) {
+      assert(redirectTarget is ClosureFieldElement);
+      return true;
+    }
+    return false;
+  }
+
+  bool isBoxed(Element element) {
+    if (isAccessedDirectly(element)) return false;
+    if (isStoredInClosureField(element)) return false;
+    return redirectionMapping[element] != null;
+  }
+
+  bool isUsedInTry(Element element) {
+    return closureData.usedVariablesInTry.contains(element);
+  }
+
+  /**
+   * Returns an [HInstruction] for the given element. If the element is
+   * boxed or stored in a closure then the method generates code to retrieve
+   * the value.
+   */
+  HInstruction readLocal(Element element) {
+    if (isAccessedDirectly(element)) {
+      if (directLocals[element] == null) {
+        builder.compiler.internalError("Cannot find value $element",
+                                       element: element);
+      }
+      return directLocals[element];
+    } else if (isStoredInClosureField(element)) {
+      Element redirect = redirectionMapping[element];
+      HInstruction receiver = readLocal(closureData.closureElement);
+      HInstruction fieldGet = new HFieldGet(redirect, receiver);
+      builder.add(fieldGet);
+      return fieldGet;
+    } else if (isBoxed(element)) {
+      Element redirect = redirectionMapping[element];
+      // In the function that declares the captured variable the box is
+      // accessed as direct local. Inside the nested closure the box is
+      // accessed through a closure-field.
+      // Calling [readLocal] makes sure we generate the correct code to get
+      // the box.
+      assert(redirect.enclosingElement.isVariable());
+      HInstruction box = readLocal(redirect.enclosingElement);
+      HInstruction lookup = new HFieldGet(redirect, box);
+      builder.add(lookup);
+      return lookup;
+    } else {
+      assert(isUsedInTry(element));
+      HLocalValue local = getLocal(element);
+      HInstruction variable = new HLocalGet(element, local);
+      builder.add(variable);
+      return variable;
+    }
+  }
+
+  HType cachedTypeOfThis;
+
+  HInstruction readThis() {
+    HInstruction res = readLocal(closureData.thisElement);
+    if (res.guaranteedType == null) {
+      if (cachedTypeOfThis == null) {
+        assert(closureData.isClosure());
+        Element element = closureData.thisElement;
+        ClassElement cls = element.enclosingElement.getEnclosingClass();
+        DartType type = cls.computeType(builder.compiler);
+        cachedTypeOfThis = new HBoundedType.nonNull(type);
+      }
+      res.guaranteedType = cachedTypeOfThis;
+    }
+    return res;
+  }
+
+  HLocalValue getLocal(Element element) {
+    // If the element is a parameter, we already have a
+    // HParameterValue for it. We cannot create another one because
+    // it could then have another name than the real parameter. And
+    // the other one would not know it is just a copy of the real
+    // parameter.
+    if (element.isParameter()) return builder.parameters[element];
+
+    return builder.activationVariables.putIfAbsent(element, () {
+      HLocalValue local = new HLocalValue(element);
+      builder.graph.entry.addAtExit(local);
+      return local;
+    });
+  }
+
+  /**
+   * Sets the [element] to [value]. If the element is boxed or stored in a
+   * closure then the method generates code to set the value.
+   */
+  void updateLocal(Element element, HInstruction value) {
+    assert(!isStoredInClosureField(element));
+    if (isAccessedDirectly(element)) {
+      directLocals[element] = value;
+    } else if (isBoxed(element)) {
+      Element redirect = redirectionMapping[element];
+      // The box itself could be captured, or be local. A local variable that
+      // is captured will be boxed, but the box itself will be a local.
+      // Inside the closure the box is stored in a closure-field and cannot
+      // be accessed directly.
+      assert(redirect.enclosingElement.isVariable());
+      HInstruction box = readLocal(redirect.enclosingElement);
+      builder.add(new HFieldSet(redirect, box, value));
+    } else {
+      assert(isUsedInTry(element));
+      HLocalValue local = getLocal(element);
+      builder.add(new HLocalSet(element, local, value));
+    }
+  }
+
+  /**
+   * This function must be called before visiting any children of the loop. In
+   * particular it needs to be called before executing the initializers.
+   *
+   * The [LocalsHandler] will make the boxes and updates at the right moment.
+   * The builder just needs to call [enterLoopBody] and [enterLoopUpdates] (for
+   * [For] loops) at the correct places. For phi-handling [beginLoopHeader] and
+   * [endLoop] must also be called.
+   *
+   * The correct place for the box depends on the given loop. In most cases
+   * the box will be created when entering the loop-body: while, do-while, and
+   * for-in (assuming the call to [:next:] is inside the body) can always be
+   * constructed this way.
+   *
+   * Things are slightly more complicated for [For] loops. If no declared
+   * loop variable is boxed then the loop-body approach works here too. If a
+   * loop-variable is boxed we need to introduce a new box for the
+   * loop-variable before we enter the initializer so that the initializer
+   * writes the values into the box. In any case we need to create the box
+   * before the condition since the condition could box the variable.
+   * Since the first box is created outside the actual loop we have a second
+   * location where a box is created: just before the updates. This is
+   * necessary since updates are considered to be part of the next iteration
+   * (and can again capture variables).
+   *
+   * For example the following Dart code prints 1 3 -- 3 4.
+   *
+   *     var fs = [];
+   *     for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) {
+   *       i++;
+   *     }
+   *     print("--");
+   *     for (var i = 0; i < 2; i++) fs[i]();
+   *
+   * We solve this by emitting the following code (only for [For] loops):
+   *  <Create box>    <== move the first box creation outside the loop.
+   *  <initializer>;
+   *  loop-entry:
+   *    if (!<condition>) goto loop-exit;
+   *    <body>
+   *    <update box>  // create a new box and copy the captured loop-variables.
+   *    <updates>
+   *    goto loop-entry;
+   *  loop-exit:
+   */
+  void startLoop(Node node) {
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    if (scopeData.hasBoxedLoopVariables()) {
+      // If there are boxed loop variables then we set up the box and
+      // redirections already now. This way the initializer can write its
+      // values into the box.
+      // For other loops the box will be created when entering the body.
+      enterScope(node, null);
+    }
+  }
+
+  void beginLoopHeader(Node node, HBasicBlock loopEntry) {
+    // Create a copy because we modify the map while iterating over it.
+    Map<Element, HInstruction> saved =
+        new LinkedHashMap<Element, HInstruction>.from(directLocals);
+
+    // Create phis for all elements in the definitions environment.
+    saved.forEach((Element element, HInstruction instruction) {
+      if (isAccessedDirectly(element)) {
+        // We know 'this' cannot be modified.
+        if (!identical(element, closureData.thisElement)) {
+          HPhi phi = new HPhi.singleInput(element, instruction);
+          loopEntry.addPhi(phi);
+          directLocals[element] = phi;
+        } else {
+          directLocals[element] = instruction;
+        }
+      }
+    });
+  }
+
+  void enterLoopBody(Node node) {
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    // If there are no declared boxed loop variables then we did not create the
+    // box before the initializer and we have to create the box now.
+    if (!scopeData.hasBoxedLoopVariables()) {
+      enterScope(node, null);
+    }
+  }
+
+  void enterLoopUpdates(Loop node) {
+    // If there are declared boxed loop variables then the updates might have
+    // access to the box and we must switch to a new box before executing the
+    // updates.
+    // In all other cases a new box will be created when entering the body of
+    // the next iteration.
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    if (scopeData.hasBoxedLoopVariables()) {
+      updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables);
+    }
+  }
+
+  void endLoop(HBasicBlock loopEntry) {
+    // If the loop has an aborting body, we don't update the loop
+    // phis.
+    if (loopEntry.predecessors.length == 1) return;
+    loopEntry.forEachPhi((HPhi phi) {
+      Element element = phi.sourceElement;
+      HInstruction postLoopDefinition = directLocals[element];
+      phi.addInput(postLoopDefinition);
+    });
+  }
+
+  /**
+   * Merge [otherLocals] into this locals handler, creating phi-nodes when
+   * there is a conflict.
+   * If a phi node is necessary, it will use this handler's instruction as the
+   * first input, and the otherLocals instruction as the second.
+   */
+  void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) {
+    // If an element is in one map but not the other we can safely
+    // ignore it. It means that a variable was declared in the
+    // block. Since variable declarations are scoped the declared
+    // variable cannot be alive outside the block. Note: this is only
+    // true for nodes where we do joins.
+    Map<Element, HInstruction> joinedLocals =
+        new LinkedHashMap<Element, HInstruction>();
+    otherLocals.directLocals.forEach((element, instruction) {
+      // We know 'this' cannot be modified.
+      if (identical(element, closureData.thisElement)) {
+        assert(directLocals[element] == instruction);
+        joinedLocals[element] = instruction;
+      } else {
+        HInstruction mine = directLocals[element];
+        if (mine == null) return;
+        if (identical(instruction, mine)) {
+          joinedLocals[element] = instruction;
+        } else {
+          HInstruction phi =
+              new HPhi.manyInputs(element, <HInstruction>[mine, instruction]);
+          joinBlock.addPhi(phi);
+          joinedLocals[element] = phi;
+        }
+      }
+    });
+    directLocals = joinedLocals;
+  }
+
+  /**
+   * The current localsHandler is not used for its values, only for its
+   * declared variables. This is a way to exclude local values from the
+   * result when they are no longer in scope.
+   * Returns the new LocalsHandler to use (may not be [this]).
+   */
+  LocalsHandler mergeMultiple(List<LocalsHandler> locals,
+                              HBasicBlock joinBlock) {
+    assert(locals.length > 0);
+    if (locals.length == 1) return locals[0];
+    Map<Element, HInstruction> joinedLocals =
+        new LinkedHashMap<Element,HInstruction>();
+    HInstruction thisValue = null;
+    directLocals.forEach((Element element, HInstruction instruction) {
+      if (!identical(element, closureData.thisElement)) {
+        HPhi phi = new HPhi.noInputs(element);
+        joinedLocals[element] = phi;
+        joinBlock.addPhi(phi);
+      } else {
+        // We know that "this" never changes, if it's there.
+        // Save it for later. While merging, there is no phi for "this",
+        // so we don't have to special case it in the merge loop.
+        thisValue = instruction;
+      }
+    });
+    for (LocalsHandler local in locals) {
+      local.directLocals.forEach((Element element, HInstruction instruction) {
+        HPhi phi = joinedLocals[element];
+        if (phi != null) {
+          phi.addInput(instruction);
+        }
+      });
+    }
+    if (thisValue != null) {
+      // If there was a "this" for the scope, add it to the new locals.
+      joinedLocals[closureData.thisElement] = thisValue;
+    }
+    directLocals = joinedLocals;
+    return this;
+  }
+}
+
+
+// Represents a single break/continue instruction.
+class JumpHandlerEntry {
+  final HJump jumpInstruction;
+  final LocalsHandler locals;
+  bool isBreak() => jumpInstruction is HBreak;
+  bool isContinue() => jumpInstruction is HContinue;
+  JumpHandlerEntry(this.jumpInstruction, this.locals);
+}
+
+
+abstract class JumpHandler {
+  factory JumpHandler(SsaBuilder builder, TargetElement target) {
+    return new TargetJumpHandler(builder, target);
+  }
+  void generateBreak([LabelElement label]);
+  void generateContinue([LabelElement label]);
+  void forEachBreak(void action(HBreak instruction, LocalsHandler locals));
+  void forEachContinue(void action(HContinue instruction,
+                                   LocalsHandler locals));
+  bool hasAnyContinue();
+  bool hasAnyBreak();
+  void close();
+  final TargetElement target;
+  List<LabelElement> labels();
+}
+
+// Insert break handler used to avoid null checks when a target isn't
+// used as the target of a break, and therefore doesn't need a break
+// handler associated with it.
+class NullJumpHandler implements JumpHandler {
+  final Compiler compiler;
+
+  NullJumpHandler(this.compiler);
+
+  void generateBreak([LabelElement label]) {
+    compiler.internalError('generateBreak should not be called');
+  }
+
+  void generateContinue([LabelElement label]) {
+    compiler.internalError('generateContinue should not be called');
+  }
+
+  void forEachBreak(Function ignored) { }
+  void forEachContinue(Function ignored) { }
+  void close() { }
+  bool hasAnyContinue() => false;
+  bool hasAnyBreak() => false;
+
+  List<LabelElement> labels() => const <LabelElement>[];
+  TargetElement get target => null;
+}
+
+// Records breaks until a target block is available.
+// Breaks are always forward jumps.
+// Continues in loops are implemented as breaks of the body.
+// Continues in switches is currently not handled.
+class TargetJumpHandler implements JumpHandler {
+  final SsaBuilder builder;
+  final TargetElement target;
+  final List<JumpHandlerEntry> jumps;
+
+  TargetJumpHandler(SsaBuilder builder, this.target)
+      : this.builder = builder,
+        jumps = <JumpHandlerEntry>[] {
+    assert(builder.jumpTargets[target] == null);
+    builder.jumpTargets[target] = this;
+  }
+
+  void generateBreak([LabelElement label]) {
+    HInstruction breakInstruction;
+    if (label == null) {
+      breakInstruction = new HBreak(target);
+    } else {
+      breakInstruction = new HBreak.toLabel(label);
+    }
+    LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
+    builder.close(breakInstruction);
+    jumps.add(new JumpHandlerEntry(breakInstruction, locals));
+  }
+
+  void generateContinue([LabelElement label]) {
+    HInstruction continueInstruction;
+    if (label == null) {
+      continueInstruction = new HContinue(target);
+    } else {
+      continueInstruction = new HContinue.toLabel(label);
+    }
+    LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
+    builder.close(continueInstruction);
+    jumps.add(new JumpHandlerEntry(continueInstruction, locals));
+  }
+
+  void forEachBreak(Function action) {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isBreak()) action(entry.jumpInstruction, entry.locals);
+    }
+  }
+
+  void forEachContinue(Function action) {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isContinue()) action(entry.jumpInstruction, entry.locals);
+    }
+  }
+
+  bool hasAnyContinue() {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isContinue()) return true;
+    }
+    return false;
+  }
+
+  bool hasAnyBreak() {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isBreak()) return true;
+    }
+    return false;
+  }
+
+  void close() {
+    // The mapping from TargetElement to JumpHandler is no longer needed.
+    builder.jumpTargets.remove(target);
+  }
+
+  List<LabelElement> labels() {
+    List<LabelElement> result = null;
+    for (LabelElement element in target.labels) {
+      if (result == null) result = <LabelElement>[];
+      result.add(element);
+    }
+    return (result == null) ? const <LabelElement>[] : result;
+  }
+}
+
+class SsaBuilder extends ResolvedVisitor implements Visitor {
+  final SsaBuilderTask builder;
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final ConstantSystem constantSystem;
+  HGraph graph;
+  LocalsHandler localsHandler;
+  HInstruction rethrowableException;
+  Map<Element, HInstruction> parameters;
+  final RuntimeTypeInformation rti;
+  HParameterValue lastAddedParameter;
+
+  Map<TargetElement, JumpHandler> jumpTargets;
+
+  /**
+   * Variables stored in the current activation. These variables are
+   * being updated in try/catch blocks, and should be
+   * accessed indirectly through [HLocalGet] and [HLocalSet].
+   */
+  Map<Element, HLocalValue> activationVariables;
+
+  // We build the Ssa graph by simulating a stack machine.
+  List<HInstruction> stack;
+
+  // The current block to add instructions to. Might be null, if we are
+  // visiting dead code.
+  HBasicBlock current;
+  // The most recently opened block. Has the same value as [current] while
+  // the block is open, but unlike [current], it isn't cleared when the current
+  // block is closed.
+  HBasicBlock lastOpenedBlock;
+
+  final List<Element> sourceElementStack;
+
+  Element get currentElement => sourceElementStack.last.declaration;
+  Compiler get compiler => builder.compiler;
+  CodeEmitterTask get emitter => builder.emitter;
+
+  SsaBuilder(this.constantSystem, SsaBuilderTask builder, CodegenWorkItem work)
+    : this.builder = builder,
+      this.backend = builder.backend,
+      this.work = work,
+      graph = new HGraph(),
+      stack = new List<HInstruction>(),
+      activationVariables = new Map<Element, HLocalValue>(),
+      jumpTargets = new Map<TargetElement, JumpHandler>(),
+      parameters = new Map<Element, HInstruction>(),
+      sourceElementStack = <Element>[work.element],
+      inliningStack = <InliningState>[],
+      rti = builder.backend.rti,
+      super(work.resolutionTree) {
+    localsHandler = new LocalsHandler(this);
+  }
+
+  static const MAX_INLINING_DEPTH = 3;
+  static const MAX_INLINING_SOURCE_SIZE = 128;
+  List<InliningState> inliningStack;
+  Element returnElement;
+  DartType returnType;
+  bool inTryStatement = false;
+
+  /**
+   * Compiles compile-time constants. Never returns [:null:]. If the
+   * initial value is not a compile-time constants, it reports an
+   * internal error.
+   */
+  Constant compileConstant(VariableElement element) {
+    return compiler.constantHandler.compileConstant(element);
+  }
+
+  Constant compileVariable(VariableElement element) {
+    return compiler.constantHandler.compileVariable(element);
+  }
+
+  bool isLazilyInitialized(VariableElement element) {
+    Constant initialValue = compileVariable(element);
+    return initialValue == null;
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [functionElement] must be an implementation element.
+   */
+  HGraph buildMethod(FunctionElement functionElement) {
+    assert(invariant(functionElement, functionElement.isImplementation));
+    FunctionExpression function = functionElement.parseNode(compiler);
+    assert(function != null);
+    assert(!function.modifiers.isExternal());
+    assert(elements[function] != null);
+    openFunction(functionElement, function);
+    SourceString name = functionElement.name;
+    // If [functionElement] is operator== we explicitely add a null
+    // check at the beginning of the method. This is to avoid having
+    // call sites do the null check.
+    if (name == const SourceString('==')) {
+      handleIf(
+          function,
+          () {
+            HParameterValue parameter = parameters.values.first;
+            push(new HIdentity(
+                parameter, graph.addConstantNull(constantSystem)));
+          },
+          () {
+            HReturn ret = new HReturn(
+                graph.addConstantBool(false, constantSystem));
+            close(ret).addSuccessor(graph.exit);
+          },
+          null);
+    }
+    function.body.accept(this);
+    return closeFunction();
+  }
+
+  HGraph buildLazyInitializer(VariableElement variable) {
+    SendSet node = variable.parseNode(compiler);
+    openFunction(variable, node);
+    Link<Node> link = node.arguments;
+    assert(!link.isEmpty && link.tail.isEmpty);
+    visit(link.head);
+    HInstruction value = pop();
+    value = potentiallyCheckType(value, variable.computeType(compiler));
+    close(new HReturn(value)).addSuccessor(graph.exit);
+    return closeFunction();
+  }
+
+  /**
+   * Returns the constructor body associated with the given constructor or
+   * creates a new constructor body, if none can be found.
+   *
+   * Returns [:null:] if the constructor does not have a body.
+   */
+  ConstructorBodyElement getConstructorBody(FunctionElement constructor) {
+    assert(constructor.isGenerativeConstructor());
+    assert(invariant(constructor, constructor.isImplementation));
+    if (constructor.isSynthesized) return null;
+    FunctionExpression node = constructor.parseNode(compiler);
+    // If we know the body doesn't have any code, we don't generate it.
+    if (!node.hasBody()) return null;
+    if (node.hasEmptyBody()) return null;
+    ClassElement classElement = constructor.getEnclosingClass();
+    ConstructorBodyElement bodyElement;
+    classElement.forEachBackendMember((Element backendMember) {
+      if (backendMember.isGenerativeConstructorBody()) {
+        ConstructorBodyElement body = backendMember;
+        if (body.constructor == constructor) {
+          // TODO(kasperl): Find a way of stopping the iteration
+          // through the backend members.
+          bodyElement = backendMember;
+        }
+      }
+    });
+    if (bodyElement == null) {
+      bodyElement = new ConstructorBodyElementX(constructor);
+      // [:resolveMethodElement:] require the passed element to be a
+      // declaration.
+      TreeElements treeElements =
+          compiler.enqueuer.resolution.getCachedElements(
+              constructor.declaration);
+      classElement.addBackendMember(bodyElement);
+
+      if (constructor.isPatch) {
+        // Create origin body element for patched constructors.
+        bodyElement.origin = new ConstructorBodyElementX(constructor.origin);
+        bodyElement.origin.patch = bodyElement;
+        classElement.origin.addBackendMember(bodyElement.origin);
+      }
+      compiler.enqueuer.codegen.addToWorkList(bodyElement.declaration,
+                                              treeElements);
+    }
+    assert(bodyElement.isGenerativeConstructorBody());
+    return bodyElement;
+  }
+
+  HParameterValue addParameter(Element element) {
+    HParameterValue result = new HParameterValue(element);
+    if (lastAddedParameter == null) {
+      graph.entry.addBefore(graph.entry.first, result);
+    } else {
+      graph.entry.addAfter(lastAddedParameter, result);
+    }
+    lastAddedParameter = result;
+    return result;
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [function] must be an implementation element.
+   */
+  InliningState enterInlinedMethod(PartialFunctionElement function,
+                                   Selector selector,
+                                   Link<Node> arguments,
+                                   Node currentNode) {
+    assert(invariant(function, function.isImplementation));
+
+    // Once we start to compile the arguments we must be sure that we don't
+    // abort.
+    List<HInstruction> compiledArguments = new List<HInstruction>();
+    bool succeeded = addStaticSendArgumentsToList(selector,
+                                                  arguments,
+                                                  function,
+                                                  compiledArguments);
+    assert(succeeded);
+
+    FunctionSignature signature = function.computeSignature(compiler);
+    int index = 0;
+    signature.orderedForEachParameter((Element parameter) {
+      HInstruction argument = compiledArguments[index++];
+      localsHandler.updateLocal(parameter, argument);
+      potentiallyCheckType(argument, parameter.computeType(compiler));
+    });
+
+    if (function.isConstructor()) {
+      ClassElement enclosing = function.getEnclosingClass();
+      if (compiler.world.needsRti(enclosing)) {
+        assert(currentNode is NewExpression);
+        InterfaceType type = elements.getType(currentNode);
+        Link<DartType> typeVariable = enclosing.typeVariables;
+        type.typeArguments.forEach((DartType argument) {
+          HInstruction instruction =
+              analyzeTypeArgument(argument, currentNode);
+          localsHandler.updateLocal(typeVariable.head.element, instruction);
+          typeVariable = typeVariable.tail;
+        });
+        while (!typeVariable.isEmpty) {
+          localsHandler.updateLocal(typeVariable.head.element,
+                                    graph.addConstantNull(constantSystem));
+          typeVariable = typeVariable.tail;
+        }
+      }
+    }
+    InliningState state =
+        new InliningState(function, returnElement, returnType, elements, stack);
+
+    // TODO(kasperl): Bad smell. We shouldn't be constructing elements here.
+    returnElement = new ElementX(const SourceString("result"),
+                                 ElementKind.VARIABLE,
+                                 function);
+    localsHandler.updateLocal(returnElement,
+                              graph.addConstantNull(constantSystem));
+    elements = compiler.enqueuer.resolution.getCachedElements(function);
+    assert(elements != null);
+    returnType = signature.returnType;
+    stack = <HInstruction>[];
+    inliningStack.add(state);
+    return state;
+  }
+
+  void leaveInlinedMethod(InliningState state) {
+    InliningState poppedState = inliningStack.removeLast();
+    assert(state == poppedState);
+    elements = state.oldElements;
+    stack.add(localsHandler.readLocal(returnElement));
+    returnElement = state.oldReturnElement;
+    returnType = state.oldReturnType;
+    assert(stack.length == 1);
+    state.oldStack.add(stack[0]);
+    stack = state.oldStack;
+  }
+
+  /**
+   * Try to inline [element] within the currect context of the
+   * builder. The insertion point is the state of the builder.
+   */
+  bool tryInlineMethod(Element element,
+                       Selector selector,
+                       Link<Node> arguments,
+                       Node currentNode) {
+    if (compiler.disableInlining) return false;
+    // Ensure that [element] is an implementation element.
+    element = element.implementation;
+    // TODO(floitsch): we should be able to inline inside lazy initializers.
+    if (!currentElement.isFunction()) return false;
+    // TODO(floitsch): find a cleaner way to know if the element is a function
+    // containing nodes.
+    // [PartialFunctionElement]s are [FunctionElement]s that have [Node]s.
+    if (element is !PartialFunctionElement) return false;
+    // TODO(ngeoffray): try to inline generative constructors. They
+    // don't have any body, which make it more difficult.
+    if (element.isGenerativeConstructor()) return false;
+    if (inliningStack.length > MAX_INLINING_DEPTH) return false;
+    // Don't inline recursive calls. We use the same elements for the inlined
+    // functions and would thus clobber our local variables.
+    // Use [:element.declaration:] since [work.element] is always a declaration.
+    if (currentElement == element.declaration) return false;
+    for (int i = 0; i < inliningStack.length; i++) {
+      if (inliningStack[i].function == element) return false;
+    }
+    PartialFunctionElement function = element;
+    int sourceSize =
+        function.endToken.charOffset - function.beginToken.charOffset;
+    if (sourceSize > MAX_INLINING_SOURCE_SIZE) return false;
+    if (!selector.applies(function, compiler)) return false;
+    FunctionExpression functionExpression = function.parseNode(compiler);
+    TreeElements newElements =
+        compiler.enqueuer.resolution.getCachedElements(function);
+    if (newElements == null) {
+      compiler.internalError("Element not resolved: $function");
+    }
+    if (!InlineWeeder.canBeInlined(functionExpression, newElements)) {
+      return false;
+    }
+
+    InliningState state = enterInlinedMethod(
+        function, selector, arguments, currentNode);
+    inlinedFrom(element, () {
+      functionExpression.body.accept(this);
+    });
+    leaveInlinedMethod(state);
+    return true;
+  }
+
+  inlinedFrom(Element element, f()) {
+    return compiler.withCurrentElement(element, () {
+      sourceElementStack.add(element);
+      var result = f();
+      sourceElementStack.removeLast();
+      return result;
+    });
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [constructor] and [constructors] must all be implementation
+   * elements.
+   */
+  void inlineSuperOrRedirect(FunctionElement constructor,
+                             Selector selector,
+                             Link<Node> arguments,
+                             List<FunctionElement> constructors,
+                             Map<Element, HInstruction> fieldValues,
+                             FunctionElement inlinedFromElement) {
+    compiler.withCurrentElement(constructor, () {
+      assert(invariant(constructor, constructor.isImplementation));
+      constructors.addLast(constructor);
+
+      List<HInstruction> compiledArguments = new List<HInstruction>();
+      bool succeeded =
+          inlinedFrom(inlinedFromElement,
+                       () => addStaticSendArgumentsToList(selector,
+                                                          arguments,
+                                                          constructor,
+                                                          compiledArguments));
+      if (!succeeded) {
+        // Non-matching super and redirects are compile-time errors and thus
+        // checked by the resolver.
+        compiler.internalError(
+            "Parameters and arguments didn't match for super/redirect call",
+            element: constructor);
+      }
+
+      sourceElementStack.add(constructor.enclosingElement);
+      buildFieldInitializers(constructor.enclosingElement.implementation,
+                             fieldValues);
+      sourceElementStack.removeLast();
+
+      int index = 0;
+      FunctionSignature params = constructor.computeSignature(compiler);
+      params.orderedForEachParameter((Element parameter) {
+        HInstruction argument = compiledArguments[index++];
+        // Because we are inlining the initializer, we must update
+        // what was given as parameter. This will be used in case
+        // there is a parameter check expression in the initializer.
+        parameters[parameter] = argument;
+        localsHandler.updateLocal(parameter, argument);
+        // Don't forget to update the field, if the parameter is of the
+        // form [:this.x:].
+        if (parameter.kind == ElementKind.FIELD_PARAMETER) {
+          FieldParameterElement fieldParameterElement = parameter;
+          fieldValues[fieldParameterElement.fieldElement] = argument;
+        }
+      });
+
+      // Build the initializers in the context of the new constructor.
+      TreeElements oldElements = elements;
+      elements =
+          compiler.enqueuer.resolution.getCachedElements(constructor);
+
+      ClosureClassMap oldClosureData = localsHandler.closureData;
+      Node node = constructor.parseNode(compiler);
+      ClosureClassMap newClosureData =
+          compiler.closureToClassMapper.computeClosureToClassMapping(
+              constructor, node, elements);
+      // The [:this:] element now refers to the one in the new closure
+      // data, that is the [:this:] of the super constructor. We
+      // update the element to refer to the current [:this:].
+      localsHandler.updateLocal(newClosureData.thisElement,
+                                localsHandler.readThis());
+      localsHandler.closureData = newClosureData;
+
+      params.orderedForEachParameter((Element parameterElement) {
+        if (elements.isParameterChecked(parameterElement)) {
+          addParameterCheckInstruction(parameterElement);
+        }
+      });
+      localsHandler.enterScope(node, constructor);
+      buildInitializers(constructor, constructors, fieldValues);
+      localsHandler.closureData = oldClosureData;
+      elements = oldElements;
+    });
+  }
+
+  /**
+   * Run through the initializers and inline all field initializers. Recursively
+   * inlines super initializers.
+   *
+   * The constructors of the inlined initializers is added to [constructors]
+   * with sub constructors having a lower index than super constructors.
+   *
+   * Invariant: The [constructor] and elements in [constructors] must all be
+   * implementation elements.
+   */
+  void buildInitializers(FunctionElement constructor,
+                         List<FunctionElement> constructors,
+                         Map<Element, HInstruction> fieldValues) {
+    assert(invariant(constructor, constructor.isImplementation));
+    FunctionExpression functionNode = constructor.parseNode(compiler);
+
+    bool foundSuperOrRedirect = false;
+
+    if (functionNode.initializers != null) {
+      Link<Node> initializers = functionNode.initializers.nodes;
+      for (Link<Node> link = initializers; !link.isEmpty; link = link.tail) {
+        assert(link.head is Send);
+        if (link.head is !SendSet) {
+          // A super initializer or constructor redirection.
+          Send call = link.head;
+          assert(Initializers.isSuperConstructorCall(call) ||
+                 Initializers.isConstructorRedirect(call));
+          FunctionElement target = elements[call];
+          Selector selector = elements.getSelector(call);
+          Link<Node> arguments = call.arguments;
+          inlineSuperOrRedirect(target, selector, arguments, constructors,
+                                fieldValues, constructor);
+          foundSuperOrRedirect = true;
+        } else {
+          // A field initializer.
+          SendSet init = link.head;
+          Link<Node> arguments = init.arguments;
+          assert(!arguments.isEmpty && arguments.tail.isEmpty);
+          sourceElementStack.add(constructor);
+          visit(arguments.head);
+          sourceElementStack.removeLast();
+          fieldValues[elements[init]] = pop();
+        }
+      }
+    }
+
+    if (!foundSuperOrRedirect) {
+      // No super initializer found. Try to find the default constructor if
+      // the class is not Object.
+      ClassElement enclosingClass = constructor.getEnclosingClass();
+      ClassElement superClass = enclosingClass.superclass;
+      if (!enclosingClass.isObject(compiler)) {
+        assert(superClass != null);
+        assert(superClass.resolutionState == STATE_DONE);
+        Selector selector =
+            new Selector.callDefaultConstructor(enclosingClass.getLibrary());
+        // TODO(johnniwinther): Should we find injected constructors as well?
+        FunctionElement target = superClass.lookupConstructor(selector);
+        if (target == null) {
+          compiler.internalError("no default constructor available");
+        }
+        inlineSuperOrRedirect(target.implementation,
+                              selector,
+                              const Link<Node>(),
+                              constructors,
+                              fieldValues,
+                              constructor);
+      }
+    }
+  }
+
+  /**
+   * Run through the fields of [cls] and add their potential
+   * initializers.
+   *
+   * Invariant: [classElement] must be an implementation element.
+   */
+  void buildFieldInitializers(ClassElement classElement,
+                              Map<Element, HInstruction> fieldValues) {
+    assert(invariant(classElement, classElement.isImplementation));
+    classElement.forEachInstanceField(
+        (ClassElement enclosingClass, Element member) {
+          TreeElements definitions = compiler.analyzeElement(member);
+          Node node = member.parseNode(compiler);
+          SendSet assignment = node.asSendSet();
+          HInstruction value;
+          if (assignment == null) {
+            value = graph.addConstantNull(constantSystem);
+          } else {
+            Node right = assignment.arguments.head;
+            TreeElements savedElements = elements;
+            elements = definitions;
+            right.accept(this);
+            elements = savedElements;
+            value = pop();
+          }
+          fieldValues[member] = value;
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: false);
+  }
+
+
+  /**
+   * Build the factory function corresponding to the constructor
+   * [functionElement]:
+   *  - Initialize fields with the values of the field initializers of the
+   *    current constructor and super constructors or constructors redirected
+   *    to, starting from the current constructor.
+   *  - Call the the constructor bodies, starting from the constructor(s) in the
+   *    super class(es).
+   *
+   * Invariant: Both [classElement] and [functionElement] must be
+   * implementation elements.
+   */
+  HGraph buildFactory(ClassElement classElement,
+                      FunctionElement functionElement) {
+    assert(invariant(classElement, classElement.isImplementation));
+    assert(invariant(functionElement, functionElement.isImplementation));
+    FunctionExpression function = functionElement.parseNode(compiler);
+    // Note that constructors (like any other static function) do not need
+    // to deal with optional arguments. It is the callers job to provide all
+    // arguments as if they were positional.
+
+    // The initializer list could contain closures.
+    openFunction(functionElement, function);
+
+    Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>();
+
+    // Compile the possible initialization code for local fields and
+    // super fields.
+    buildFieldInitializers(classElement, fieldValues);
+
+    // Compile field-parameters such as [:this.x:].
+    FunctionSignature params = functionElement.computeSignature(compiler);
+    params.orderedForEachParameter((Element element) {
+      if (element.kind == ElementKind.FIELD_PARAMETER) {
+        // If the [element] is a field-parameter then
+        // initialize the field element with its value.
+        FieldParameterElement fieldParameterElement = element;
+        HInstruction parameterValue = localsHandler.readLocal(element);
+        fieldValues[fieldParameterElement.fieldElement] = parameterValue;
+      }
+    });
+
+    // Analyze the constructor and all referenced constructors and collect
+    // initializers and constructor bodies.
+    List<FunctionElement> constructors = <FunctionElement>[functionElement];
+    buildInitializers(functionElement, constructors, fieldValues);
+
+    // Call the JavaScript constructor with the fields as argument.
+    List<HInstruction> constructorArguments = <HInstruction>[];
+    classElement.forEachInstanceField(
+        (ClassElement enclosingClass, Element member) {
+          constructorArguments.add(potentiallyCheckType(
+              fieldValues[member], member.computeType(compiler)));
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: true);
+
+    InterfaceType type = classElement.computeType(compiler);
+    HType ssaType = new HBoundedType.exact(type);
+    HForeignNew newObject = new HForeignNew(classElement,
+                                            ssaType,
+                                            constructorArguments);
+    add(newObject);
+
+    // Create the runtime type information, if needed.
+    List<HInstruction> inputs = <HInstruction>[];
+    if (compiler.world.needsRti(classElement)) {
+      classElement.typeVariables.forEach((TypeVariableType typeVariable) {
+        inputs.add(localsHandler.directLocals[typeVariable.element]);
+      });
+      callSetRuntimeTypeInfo(classElement, inputs, newObject);
+    }
+
+    // Generate calls to the constructor bodies.
+    for (int index = constructors.length - 1; index >= 0; index--) {
+      FunctionElement constructor = constructors[index];
+      assert(invariant(functionElement, constructor.isImplementation));
+      ConstructorBodyElement body = getConstructorBody(constructor);
+      if (body == null) continue;
+      List bodyCallInputs = <HInstruction>[];
+      bodyCallInputs.add(newObject);
+      FunctionSignature functionSignature = body.computeSignature(compiler);
+      functionSignature.orderedForEachParameter((parameter) {
+        if (!localsHandler.isBoxed(parameter)) {
+          // The parameter will be a field in the box passed as the
+          // last parameter. So no need to pass it.
+          bodyCallInputs.add(localsHandler.readLocal(parameter));
+        }
+      });
+
+      // If parameters are checked, we pass the already computed
+      // boolean to the constructor body.
+      TreeElements elements =
+          compiler.enqueuer.resolution.getCachedElements(constructor);
+      Node node = constructor.parseNode(compiler);
+      ClosureClassMap parameterClosureData =
+          compiler.closureToClassMapper.getMappingForNestedFunction(node);
+      functionSignature.orderedForEachParameter((parameter) {
+        if (elements.isParameterChecked(parameter)) {
+          Element fieldCheck =
+              parameterClosureData.parametersWithSentinel[parameter];
+          bodyCallInputs.add(localsHandler.readLocal(fieldCheck));
+        }
+      });
+
+      // If there are locals that escape (ie used in closures), we
+      // pass the box to the constructor.
+      ClosureScope scopeData = parameterClosureData.capturingScopes[node];
+      if (scopeData != null) {
+        bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement));
+      }
+
+      // TODO(ahe): The constructor name is statically resolved. See
+      // SsaCodeGenerator.visitInvokeDynamicMethod. Is there a cleaner
+      // way to do this?
+      SourceString name =
+          new SourceString(backend.namer.getName(body.declaration));
+      // TODO(kasperl): This seems fishy. We shouldn't be inventing all
+      // these selectors. Maybe the resolver can do more of the work
+      // for us here?
+      LibraryElement library = body.getLibrary();
+      Selector selector = new Selector.call(
+          name, library, bodyCallInputs.length - 1);
+      HInvokeDynamic invoke =
+          new HInvokeDynamicMethod(selector, bodyCallInputs);
+      invoke.element = body;
+      add(invoke);
+    }
+    close(new HReturn(newObject)).addSuccessor(graph.exit);
+    return closeFunction();
+  }
+
+  void addParameterCheckInstruction(Element element) {
+    HInstruction check;
+    Element checkResultElement =
+        localsHandler.closureData.parametersWithSentinel[element];
+    if (currentElement.isGenerativeConstructorBody()) {
+      // A generative constructor body receives extra parameters that
+      // indicate if a parameter was passed to the factory.
+      check = addParameter(checkResultElement);
+    } else {
+      // This is the code we emit for a parameter that is being checked
+      // on whether it was given at value at the call site:
+      //
+      // foo([a = 42]) {
+      //   if (?a) print('parameter passed $a');
+      // }
+      //
+      // foo([a = 42]) {
+      //   var t1 = identical(a, sentinel);
+      //   if (t1) a = 42;
+      //   if (!t1) print('parameter passed ' + a);
+      // }
+
+      // Fetch the original default value of [element];
+      Constant constant = compileVariable(element);
+      HConstant defaultValue = constant == null
+          ? graph.addConstantNull(constantSystem)
+          : graph.addConstant(constant);
+
+      // Emit the equality check with the sentinel.
+      HConstant sentinel = graph.addConstant(SentinelConstant.SENTINEL);
+      HInstruction operand = parameters[element];
+      check = new HIdentity(sentinel, operand);
+      add(check);
+
+      // If the check succeeds, we must update the parameter with the
+      // default value.
+      handleIf(element.parseNode(compiler),
+               () => stack.add(check),
+               () => localsHandler.updateLocal(element, defaultValue),
+               null);
+
+      // Create the instruction that parameter checks will use.
+      check = new HNot(check);
+      add(check);
+    }
+
+    localsHandler.updateLocal(checkResultElement, check);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [functionElement] must be the implementation element.
+   */
+  void openFunction(Element element, Expression node) {
+    assert(invariant(element, element.isImplementation));
+    HBasicBlock block = graph.addNewBlock();
+    open(graph.entry);
+
+    localsHandler.startFunction(element, node);
+    close(new HGoto()).addSuccessor(block);
+
+    open(block);
+
+    if (element is FunctionElement) {
+      FunctionElement functionElement = element;
+      FunctionSignature signature = functionElement.computeSignature(compiler);
+      signature.orderedForEachParameter((Element parameterElement) {
+        if (elements.isParameterChecked(parameterElement)) {
+          addParameterCheckInstruction(parameterElement);
+        }
+      });
+
+      // Put the type checks in the first successor of the entry,
+      // because that is where the type guards will also be inserted.
+      // This way we ensure that a type guard will dominate the type
+      // check.
+      signature.orderedForEachParameter((Element parameterElement) {
+        if (element.isGenerativeConstructorBody()) {
+          ClosureScope scopeData =
+              localsHandler.closureData.capturingScopes[node];
+          if (scopeData != null
+              && scopeData.capturedVariableMapping.containsKey(
+                  parameterElement)) {
+            // The parameter will be a field in the box passed as the
+            // last parameter. So no need to have it.
+            return;
+          }
+        }
+        HInstruction newParameter = potentiallyCheckType(
+            localsHandler.directLocals[parameterElement],
+            parameterElement.computeType(compiler));
+        localsHandler.directLocals[parameterElement] = newParameter;
+      });
+
+      returnType = signature.returnType;
+    } else {
+      // Otherwise it is a lazy initializer which does not have parameters.
+      assert(element is VariableElement);
+    }
+
+    // Add the type parameters of the class as parameters of this
+    // method.
+    var enclosing = element.enclosingElement;
+    if (element.isConstructor() && compiler.world.needsRti(enclosing)) {
+      enclosing.typeVariables.forEach((TypeVariableType typeVariable) {
+        HParameterValue param = addParameter(typeVariable.element);
+        localsHandler.directLocals[typeVariable.element] = param;
+      });
+    }
+  }
+
+  HInstruction potentiallyCheckType(
+      HInstruction original, DartType type,
+      { int kind: HTypeConversion.CHECKED_MODE_CHECK }) {
+    if (!compiler.enableTypeAssertions) return original;
+    HInstruction other = original.convertType(compiler, type, kind);
+    if (other != original) add(other);
+    return other;
+  }
+
+  HGraph closeFunction() {
+    // TODO(kasperl): Make this goto an implicit return.
+    if (!isAborted()) close(new HGoto()).addSuccessor(graph.exit);
+    graph.finalize();
+    return graph;
+  }
+
+  HBasicBlock addNewBlock() {
+    HBasicBlock block = graph.addNewBlock();
+    // If adding a new block during building of an expression, it is due to
+    // conditional expressions or short-circuit logical operators.
+    return block;
+  }
+
+  void open(HBasicBlock block) {
+    block.open();
+    current = block;
+    lastOpenedBlock = block;
+  }
+
+  HBasicBlock close(HControlFlow end) {
+    HBasicBlock result = current;
+    current.close(end);
+    current = null;
+    return result;
+  }
+
+  void goto(HBasicBlock from, HBasicBlock to) {
+    from.close(new HGoto());
+    from.addSuccessor(to);
+  }
+
+  bool isAborted() {
+    return current == null;
+  }
+
+  /**
+   * Creates a new block, transitions to it from any current block, and
+   * opens the new block.
+   */
+  HBasicBlock openNewBlock() {
+    HBasicBlock newBlock = addNewBlock();
+    if (!isAborted()) goto(current, newBlock);
+    open(newBlock);
+    return newBlock;
+  }
+
+  void add(HInstruction instruction) {
+    current.add(instruction);
+  }
+
+  void addWithPosition(HInstruction instruction, Node node) {
+    add(attachPosition(instruction, node));
+  }
+
+  void push(HInstruction instruction) {
+    add(instruction);
+    stack.add(instruction);
+  }
+
+  void pushWithPosition(HInstruction instruction, Node node) {
+    push(attachPosition(instruction, node));
+  }
+
+  HInstruction pop() {
+    return stack.removeLast();
+  }
+
+  void dup() {
+    stack.add(stack.last);
+  }
+
+  HInstruction popBoolified() {
+    HInstruction value = pop();
+    if (compiler.enableTypeAssertions) {
+      return potentiallyCheckType(
+          value,
+          compiler.boolClass.computeType(compiler),
+          kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK);
+    }
+    HInstruction result = new HBoolify(value);
+    add(result);
+    return result;
+  }
+
+  HInstruction attachPosition(HInstruction target, Node node) {
+    target.sourcePosition = sourceFileLocationForBeginToken(node);
+    return target;
+  }
+
+  SourceFileLocation sourceFileLocationForBeginToken(Node node) =>
+      sourceFileLocationForToken(node, node.getBeginToken());
+
+  SourceFileLocation sourceFileLocationForEndToken(Node node) =>
+      sourceFileLocationForToken(node, node.getEndToken());
+
+  SourceFileLocation sourceFileLocationForToken(Node node, Token token) {
+    Element element = sourceElementStack.last;
+    // TODO(johnniwinther): remove the 'element.patch' hack.
+    if (element is FunctionElement) {
+      FunctionElement functionElement = element;
+      if (functionElement.patch != null) element = functionElement.patch;
+    }
+    Script script = element.getCompilationUnit().script;
+    SourceFile sourceFile = script.file;
+    SourceFileLocation location = new SourceFileLocation(sourceFile, token);
+    if (!location.isValid()) {
+      throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message(
+          {'offset': token.charOffset,
+           'fileName': sourceFile.filename,
+           'length': sourceFile.text.length});
+    }
+    return location;
+}
+
+  void visit(Node node) {
+    if (node != null) node.accept(this);
+  }
+
+  visitBlock(Block node) {
+    for (Link<Node> link = node.statements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      visit(link.head);
+      if (isAborted()) {
+        // The block has been aborted by a return or a throw.
+        if (!stack.isEmpty) compiler.cancel('non-empty instruction stack');
+        return;
+      }
+    }
+    assert(!current.isClosed());
+    if (!stack.isEmpty) compiler.cancel('non-empty instruction stack');
+  }
+
+  visitClassNode(ClassNode node) {
+    compiler.internalError('visitClassNode should not be called', node: node);
+  }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    visit(node.expression);
+    pop();
+  }
+
+  /**
+   * Creates a new loop-header block. The previous [current] block
+   * is closed with an [HGoto] and replaced by the newly created block.
+   * Also notifies the locals handler that we're entering a loop.
+   */
+  JumpHandler beginLoopHeader(Node node) {
+    assert(!isAborted());
+    HBasicBlock previousBlock = close(new HGoto());
+
+    JumpHandler jumpHandler = createJumpHandler(node);
+    HBasicBlock loopEntry = graph.addNewLoopHeaderBlock(
+        jumpHandler.target,
+        jumpHandler.labels());
+    previousBlock.addSuccessor(loopEntry);
+    open(loopEntry);
+
+    localsHandler.beginLoopHeader(node, loopEntry);
+    return jumpHandler;
+  }
+
+  /**
+   * Ends the loop:
+   * - creates a new block and adds it as successor to the [branchBlock].
+   * - opens the new block (setting as [current]).
+   * - notifies the locals handler that we're exiting a loop.
+   */
+  void endLoop(HBasicBlock loopEntry,
+               HBasicBlock branchBlock,
+               JumpHandler jumpHandler,
+               LocalsHandler savedLocals) {
+    if (branchBlock == null && !jumpHandler.hasAnyBreak()) return;
+
+    HBasicBlock loopExitBlock = addNewBlock();
+    assert(branchBlock == null || branchBlock.successors.length == 1);
+    List<LocalsHandler> breakLocals = <LocalsHandler>[];
+    jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
+      breakInstruction.block.addSuccessor(loopExitBlock);
+      breakLocals.add(locals);
+    });
+    if (branchBlock != null) {
+      branchBlock.addSuccessor(loopExitBlock);
+    }
+    open(loopExitBlock);
+    localsHandler.endLoop(loopEntry);
+    if (!breakLocals.isEmpty) {
+      breakLocals.add(savedLocals);
+      localsHandler = savedLocals.mergeMultiple(breakLocals, loopExitBlock);
+    } else {
+      localsHandler = savedLocals;
+    }
+  }
+
+  HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) {
+    if (statements == null) return null;
+    return new HSubGraphBlockInformation(statements);
+  }
+
+  HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) {
+    if (expression == null) return null;
+    return new HSubExpressionBlockInformation(expression);
+  }
+
+  // For while loops, initializer and update are null.
+  // The condition function must return a boolean result.
+  // None of the functions must leave anything on the stack.
+  void handleLoop(Node loop,
+                  void initialize(),
+                  HInstruction condition(),
+                  void update(),
+                  void body()) {
+    // Generate:
+    //  <initializer>
+    //  loop-entry:
+    //    if (!<condition>) goto loop-exit;
+    //    <body>
+    //    <updates>
+    //    goto loop-entry;
+    //  loop-exit:
+
+    localsHandler.startLoop(loop);
+
+    // The initializer.
+    SubExpression initializerGraph = null;
+    HBasicBlock startBlock;
+    if (initialize != null) {
+      HBasicBlock initializerBlock = openNewBlock();
+      startBlock = initializerBlock;
+      initialize();
+      assert(!isAborted());
+      initializerGraph =
+          new SubExpression(initializerBlock, current);
+    }
+
+    JumpHandler jumpHandler = beginLoopHeader(loop);
+    HLoopInformation loopInfo = current.loopInformation;
+    HBasicBlock conditionBlock = current;
+    if (startBlock == null) startBlock = conditionBlock;
+
+    HInstruction conditionInstruction = condition();
+    HBasicBlock conditionExitBlock =
+        close(new HLoopBranch(conditionInstruction));
+    SubExpression conditionExpression =
+        new SubExpression(conditionBlock, conditionExitBlock);
+
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+
+    // The body.
+    HBasicBlock beginBodyBlock = addNewBlock();
+    conditionExitBlock.addSuccessor(beginBodyBlock);
+    open(beginBodyBlock);
+
+    localsHandler.enterLoopBody(loop);
+    body();
+
+    SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock);
+    HBasicBlock bodyBlock = current;
+    if (current != null) close(new HGoto());
+
+    SubExpression updateGraph;
+
+    // Check that the loop has at least one back-edge.
+    if (jumpHandler.hasAnyContinue() || bodyBlock != null) {
+      // Update.
+      // We create an update block, even when we are in a while loop. There the
+      // update block is the jump-target for continue statements. We could avoid
+      // the creation if there is no continue, but for now we always create it.
+      HBasicBlock updateBlock = addNewBlock();
+
+      List<LocalsHandler> continueLocals = <LocalsHandler>[];
+      jumpHandler.forEachContinue((HContinue instruction,
+                                   LocalsHandler locals) {
+        instruction.block.addSuccessor(updateBlock);
+        continueLocals.add(locals);
+      });
+
+
+      if (bodyBlock != null) {
+        continueLocals.add(localsHandler);
+        bodyBlock.addSuccessor(updateBlock);
+      }
+
+      open(updateBlock);
+      localsHandler =
+          continueLocals[0].mergeMultiple(continueLocals, updateBlock);
+
+      HLabeledBlockInformation labelInfo;
+      List<LabelElement> labels = jumpHandler.labels();
+      TargetElement target = elements[loop];
+      if (!labels.isEmpty) {
+        beginBodyBlock.setBlockFlow(
+            new HLabeledBlockInformation(
+                new HSubGraphBlockInformation(bodyGraph),
+                jumpHandler.labels(),
+                isContinue: true),
+            updateBlock);
+      } else if (target != null && target.isContinueTarget) {
+        beginBodyBlock.setBlockFlow(
+            new HLabeledBlockInformation.implicit(
+                new HSubGraphBlockInformation(bodyGraph),
+                target,
+                isContinue: true),
+            updateBlock);
+      }
+
+      localsHandler.enterLoopUpdates(loop);
+
+      update();
+
+      HBasicBlock updateEndBlock = close(new HGoto());
+      // The back-edge completing the cycle.
+      updateEndBlock.addSuccessor(conditionBlock);
+      updateGraph = new SubExpression(updateBlock, updateEndBlock);
+    }
+
+    if (jumpHandler.hasAnyContinue() || bodyBlock != null) {
+      endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals);
+      conditionBlock.postProcessLoopHeader();
+      HLoopBlockInformation info =
+          new HLoopBlockInformation(
+              HLoopBlockInformation.loopType(loop),
+              wrapExpressionGraph(initializerGraph),
+              wrapExpressionGraph(conditionExpression),
+              wrapStatementGraph(bodyGraph),
+              wrapExpressionGraph(updateGraph),
+              conditionBlock.loopInformation.target,
+              conditionBlock.loopInformation.labels,
+              sourceFileLocationForBeginToken(loop),
+              sourceFileLocationForEndToken(loop));
+
+      startBlock.setBlockFlow(info, current);
+      loopInfo.loopBlockInformation = info;
+    } else {
+      // There is no back edge for the loop, so we turn the code into:
+      // if (condition) {
+      //   body;
+      // } else {
+      //   // We always create an empty else block to avoid critical edges.
+      // }
+      //
+      // If there is any break in the body, we attach a synthetic
+      // label to the if.
+      HBasicBlock elseBlock = addNewBlock();
+      open(elseBlock);
+      close(new HGoto());
+      endLoop(conditionBlock, null, jumpHandler, savedLocals);
+
+      // [endLoop] will not create an exit block if there are no
+      // breaks.
+      if (current == null) open(addNewBlock());
+      elseBlock.addSuccessor(current);
+      SubGraph elseGraph = new SubGraph(elseBlock, elseBlock);
+      // Remove the loop information attached to the header.
+      conditionBlock.loopInformation = null;
+
+      // Remove the [HLoopBranch] instruction and replace it with
+      // [HIf].
+      HInstruction condition = conditionExitBlock.last.inputs[0];
+      conditionExitBlock.addAtExit(new HIf(condition));
+      conditionExitBlock.addSuccessor(elseBlock);
+      conditionExitBlock.remove(conditionExitBlock.last);
+      HIfBlockInformation info =
+          new HIfBlockInformation(
+            wrapExpressionGraph(conditionExpression),
+            wrapStatementGraph(bodyGraph),
+            wrapStatementGraph(elseGraph));
+
+      conditionBlock.setBlockFlow(info, current);
+      HIf ifBlock = conditionBlock.last;
+      ifBlock.blockInformation = conditionBlock.blockFlow;
+
+      // If the body has any break, attach a synthesized label to the
+      // if block.
+      if (jumpHandler.hasAnyBreak()) {
+        TargetElement target = elements[loop];
+        LabelElement label = target.addLabel(null, 'loop');
+        label.setBreakTarget();
+        SubGraph labelGraph = new SubGraph(conditionBlock, current);
+        HLabeledBlockInformation labelInfo = new HLabeledBlockInformation(
+                new HSubGraphBlockInformation(labelGraph),
+                <LabelElement>[label]);
+
+        conditionBlock.setBlockFlow(labelInfo, current);
+
+        jumpHandler.forEachBreak((HBreak breakInstruction, _) {
+          HBasicBlock block = breakInstruction.block;
+          block.addAtExit(new HBreak.toLabel(label));
+          block.remove(breakInstruction);
+        });
+      }
+    }
+    jumpHandler.close();
+  }
+
+  visitFor(For node) {
+    assert(node.body != null);
+    void buildInitializer() {
+      if (node.initializer == null) return;
+      Node initializer = node.initializer;
+      if (initializer != null) {
+        visit(initializer);
+        if (initializer.asExpression() != null) {
+          pop();
+        }
+      }
+    }
+    HInstruction buildCondition() {
+      if (node.condition == null) {
+        return graph.addConstantBool(true, constantSystem);
+      }
+      visit(node.condition);
+      return popBoolified();
+    }
+    void buildUpdate() {
+      for (Expression expression in node.update) {
+        visit(expression);
+        assert(!isAborted());
+        // The result of the update instruction isn't used, and can just
+        // be dropped.
+        HInstruction updateInstruction = pop();
+      }
+    }
+    void buildBody() {
+      visit(node.body);
+    }
+    handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody);
+  }
+
+  visitWhile(While node) {
+    HInstruction buildCondition() {
+      visit(node.condition);
+      return popBoolified();
+    }
+    handleLoop(node,
+               () {},
+               buildCondition,
+               () {},
+               () { visit(node.body); });
+  }
+
+  visitDoWhile(DoWhile node) {
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+    localsHandler.startLoop(node);
+    JumpHandler jumpHandler = beginLoopHeader(node);
+    HLoopInformation loopInfo = current.loopInformation;
+    HBasicBlock loopEntryBlock = current;
+    HBasicBlock bodyEntryBlock = current;
+    TargetElement target = elements[node];
+    bool hasContinues = target != null && target.isContinueTarget;
+    if (hasContinues) {
+      // Add extra block to hang labels on.
+      // It doesn't currently work if they are on the same block as the
+      // HLoopInfo. The handling of HLabeledBlockInformation will visit a
+      // SubGraph that starts at the same block again, so the HLoopInfo is
+      // either handled twice, or it's handled after the labeled block info,
+      // both of which generate the wrong code.
+      // Using a separate block is just a simple workaround.
+      bodyEntryBlock = openNewBlock();
+    }
+    localsHandler.enterLoopBody(node);
+    visit(node.body);
+
+    // If there are no continues we could avoid the creation of the condition
+    // block. This could also lead to a block having multiple entries and exits.
+    HBasicBlock bodyExitBlock;
+    bool isAbortingBody = false;
+    if (current != null) {
+      bodyExitBlock = close(new HGoto());
+    } else {
+      isAbortingBody = true;
+      bodyExitBlock = lastOpenedBlock;
+    }
+
+    SubExpression conditionExpression;
+    HBasicBlock conditionEndBlock;
+    if (!isAbortingBody || hasContinues) {
+      HBasicBlock conditionBlock = addNewBlock();
+
+      List<LocalsHandler> continueLocals = <LocalsHandler>[];
+      jumpHandler.forEachContinue((HContinue instruction,
+                                   LocalsHandler locals) {
+        instruction.block.addSuccessor(conditionBlock);
+        continueLocals.add(locals);
+      });
+
+      if (!isAbortingBody) {
+        bodyExitBlock.addSuccessor(conditionBlock);
+      }
+
+      if (!continueLocals.isEmpty) {
+        if (!isAbortingBody) continueLocals.add(localsHandler);
+        localsHandler =
+            savedLocals.mergeMultiple(continueLocals, conditionBlock);
+        SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
+        List<LabelElement> labels = jumpHandler.labels();
+        HSubGraphBlockInformation bodyInfo =
+            new HSubGraphBlockInformation(bodyGraph);
+        HLabeledBlockInformation info;
+        if (!labels.isEmpty) {
+          info = new HLabeledBlockInformation(bodyInfo, labels,
+                                              isContinue: true);
+        } else {
+          info = new HLabeledBlockInformation.implicit(bodyInfo, target,
+                                                       isContinue: true);
+        }
+        bodyEntryBlock.setBlockFlow(info, conditionBlock);
+      }
+      open(conditionBlock);
+
+      visit(node.condition);
+      assert(!isAborted());
+      HInstruction conditionInstruction = popBoolified();
+      conditionEndBlock = close(
+          new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP));
+
+      HBasicBlock avoidCriticalEdge = addNewBlock();
+      conditionEndBlock.addSuccessor(avoidCriticalEdge);
+      open(avoidCriticalEdge);
+      close(new HGoto());
+      avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
+
+      conditionExpression =
+          new SubExpression(conditionBlock, conditionEndBlock);
+    }
+
+    endLoop(loopEntryBlock, conditionEndBlock, jumpHandler, localsHandler);
+    if (!isAbortingBody || hasContinues) {
+      loopEntryBlock.postProcessLoopHeader();
+      SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock);
+      HLoopBlockInformation loopBlockInfo =
+          new HLoopBlockInformation(
+              HLoopBlockInformation.DO_WHILE_LOOP,
+              null,
+              wrapExpressionGraph(conditionExpression),
+              wrapStatementGraph(bodyGraph),
+              null,
+              loopEntryBlock.loopInformation.target,
+              loopEntryBlock.loopInformation.labels,
+              sourceFileLocationForBeginToken(node),
+              sourceFileLocationForEndToken(node));
+      loopEntryBlock.setBlockFlow(loopBlockInfo, current);
+      loopInfo.loopBlockInformation = loopBlockInfo;
+    } else {
+      // If the loop has no back edge, we remove the loop information
+      // on the header.
+      loopEntryBlock.loopInformation = null;
+
+      // If the body of the loop has any break, we attach a
+      // synthesized label to the body.
+      if (jumpHandler.hasAnyBreak()) {
+        SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
+        TargetElement target = elements[node];
+        LabelElement label = target.addLabel(null, 'loop');
+        label.setBreakTarget();
+        HLabeledBlockInformation info = new HLabeledBlockInformation(
+            new HSubGraphBlockInformation(bodyGraph), <LabelElement>[label]);
+        loopEntryBlock.setBlockFlow(info, current);
+        jumpHandler.forEachBreak((HBreak breakInstruction, _) {
+          HBasicBlock block = breakInstruction.block;
+          block.addAtExit(new HBreak.toLabel(label));
+          block.remove(breakInstruction);
+        });
+      }
+    }
+    jumpHandler.close();
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    ClosureClassMap nestedClosureData =
+        compiler.closureToClassMapper.getMappingForNestedFunction(node);
+    assert(nestedClosureData != null);
+    assert(nestedClosureData.closureClassElement != null);
+    ClassElement closureClassElement =
+        nestedClosureData.closureClassElement;
+    FunctionElement callElement = nestedClosureData.callElement;
+    // TODO(ahe): This should be registered in codegen, not here.
+    compiler.enqueuer.codegen.addToWorkList(callElement, elements);
+    // TODO(ahe): This should be registered in codegen, not here.
+    compiler.enqueuer.codegen.registerInstantiatedClass(closureClassElement);
+    assert(!closureClassElement.hasLocalScopeMembers);
+
+    List<HInstruction> capturedVariables = <HInstruction>[];
+    closureClassElement.forEachBackendMember((Element member) {
+      // The backendMembers also contains the call method(s). We are only
+      // interested in the fields.
+      if (member.isField()) {
+        Element capturedLocal = nestedClosureData.capturedFieldMapping[member];
+        assert(capturedLocal != null);
+        capturedVariables.add(localsHandler.readLocal(capturedLocal));
+      }
+    });
+
+    HType type = new HBoundedType.exact(
+        compiler.functionClass.computeType(compiler));
+    push(new HForeignNew(closureClassElement, type, capturedVariables));
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    visit(node.function);
+    localsHandler.updateLocal(elements[node], pop());
+  }
+
+  visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      stack.add(localsHandler.readThis());
+    } else {
+      compiler.internalError("SsaBuilder.visitIdentifier on non-this",
+                             node: node);
+    }
+  }
+
+  visitIf(If node) {
+    handleIf(node,
+             () => visit(node.condition),
+             () => visit(node.thenPart),
+             node.elsePart != null ? () => visit(node.elsePart) : null);
+  }
+
+  void handleIf(Node diagnosticNode,
+                void visitCondition(), void visitThen(), void visitElse()) {
+    SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode);
+    branchBuilder.handleIf(visitCondition, visitThen, visitElse);
+  }
+
+  void visitLogicalAndOr(Send node, Operator op) {
+    SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
+    branchBuilder.handleLogicalAndOrWithLeftNode(
+        node.receiver,
+        () { visit(node.argumentsNode); },
+        isAnd: (const SourceString("&&") == op.source));
+  }
+
+
+  void visitLogicalNot(Send node) {
+    assert(node.argumentsNode is Prefix);
+    visit(node.receiver);
+    HNot not = new HNot(popBoolified());
+    pushWithPosition(not, node);
+  }
+
+  void visitUnary(Send node, Operator op) {
+    if (node.isParameterCheck) {
+      Element element = elements[node.receiver];
+      Node function = element.enclosingElement.parseNode(compiler);
+      ClosureClassMap parameterClosureData =
+          compiler.closureToClassMapper.getMappingForNestedFunction(function);
+      Element fieldCheck =
+          parameterClosureData.parametersWithSentinel[element];
+      stack.add(localsHandler.readLocal(fieldCheck));
+      return;
+    }
+    assert(node.argumentsNode is Prefix);
+    visit(node.receiver);
+    assert(!identical(op.token.kind, PLUS_TOKEN));
+    HInstruction operand = pop();
+
+    // See if we can constant-fold right away. This avoids rewrites later on.
+    if (operand is HConstant) {
+      UnaryOperation operation = constantSystem.lookupUnary(op.source);
+      HConstant constant = operand;
+      Constant folded = operation.fold(constant.constant);
+      if (folded != null) {
+        stack.add(graph.addConstant(folded));
+        return;
+      }
+    }
+
+    HInvokeDynamicMethod result =
+        buildInvokeDynamic(node, elements.getSelector(node), operand, []);
+    pushWithPosition(result, node);
+  }
+
+  void visitBinary(
+      HInstruction left, Operator op, HInstruction right, Send send) {
+    Selector selector = null;
+    // TODO(ngeoffray): The resolver creates these selectors already
+    // but does not put them on the [send] instruction.
+    switch (op.source.stringValue) {
+      case "+":
+      case "+=":
+      case "++":
+        selector = new Selector.binaryOperator(const SourceString('+'));
+        break;
+      case "-":
+      case "-=":
+      case "--":
+        selector = new Selector.binaryOperator(const SourceString('-'));
+        break;
+      case "*":
+      case "*=":
+        selector = new Selector.binaryOperator(const SourceString('*'));
+        break;
+      case "/":
+      case "/=":
+        selector = new Selector.binaryOperator(const SourceString('/'));
+        break;
+      case "~/":
+      case "~/=":
+        selector = new Selector.binaryOperator(const SourceString('~/'));
+        break;
+      case "%":
+      case "%=":
+        selector = new Selector.binaryOperator(const SourceString('%'));
+        break;
+      case "<<":
+      case "<<=":
+        selector = new Selector.binaryOperator(const SourceString('<<'));
+        break;
+      case ">>":
+      case ">>=":
+        selector = new Selector.binaryOperator(const SourceString('>>'));
+        break;
+      case "|":
+      case "|=":
+        selector = new Selector.binaryOperator(const SourceString('|'));
+        break;
+      case "&":
+      case "&=":
+        selector = new Selector.binaryOperator(const SourceString('&'));
+        break;
+      case "^":
+      case "^=":
+        selector = new Selector.binaryOperator(const SourceString('^'));
+        break;
+      case "==":
+      case "!=":
+        selector = new Selector.binaryOperator(const SourceString('=='));
+        break;
+      case "<":
+        selector = new Selector.binaryOperator(const SourceString('<'));
+        break;
+      case "<=":
+        selector = new Selector.binaryOperator(const SourceString('<='));
+        break;
+      case ">":
+        selector = new Selector.binaryOperator(const SourceString('>'));
+        break;
+      case ">=":
+        selector = new Selector.binaryOperator(const SourceString('>='));
+        break;
+      case "===":
+        pushWithPosition(new HIdentity(left, right), op);
+        return;
+      case "!==":
+        HIdentity eq = new HIdentity(left, right);
+        add(eq);
+        pushWithPosition(new HNot(eq), op);
+        return;
+      default:
+        compiler.internalError("Unexpected operator $op", node: op);
+        break;
+    }
+
+    pushWithPosition(
+          buildInvokeDynamic(send, selector, left, [right]),
+          op);
+    if (op.source.stringValue == '!=') {
+      HBoolify bl = new HBoolify(pop());
+      add(bl);
+      pushWithPosition(new HNot(bl), op);
+    }
+  }
+
+  HInstruction generateInstanceSendReceiver(Send send) {
+    assert(Elements.isInstanceSend(send, elements));
+    if (send.receiver == null) {
+      return localsHandler.readThis();
+    }
+    visit(send.receiver);
+    return pop();
+  }
+
+  String getTargetName(ErroneousElement error, [String prefix]) {
+    String result = error.name.slowToString();
+    if (?prefix) {
+      result = '$prefix $result';
+    }
+    return result;
+  }
+
+  /**
+   * Returns a set of interceptor classes that contain a member whose
+   * signature matches the given [selector].
+   */
+  Set<ClassElement> getInterceptedClassesOn(Selector selector) {
+    return backend.getInterceptedClassesOn(selector);
+  }
+
+  void generateInstanceGetterWithCompiledReceiver(Send send,
+                                                  HInstruction receiver) {
+    assert(Elements.isInstanceSend(send, elements));
+    // TODO(kasperl): This is a convoluted way of checking if we're
+    // generating code for a compound assignment. If we are, we need
+    // to get the selector from the mapping for the AST selector node.
+    Selector selector = (send.asSendSet() == null)
+        ? elements.getSelector(send)
+        : elements.getSelector(send.selector);
+    assert(selector.isGetter());
+    SourceString getterName = selector.name;
+    Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+
+    bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
+    if (interceptedClasses != null) {
+      // If we're using an interceptor class, emit a call to the
+      // interceptor method and then the actual dynamic call on the
+      // interceptor object.
+      HInstruction instruction =
+          invokeInterceptor(interceptedClasses, receiver, send);
+      instruction = new HInvokeDynamicGetter(
+          selector, null, instruction, !hasGetter);
+      // Add the receiver as an argument to the getter call on the
+      // interceptor.
+      instruction.inputs.add(receiver);
+      pushWithPosition(instruction, send);
+    } else {
+      pushWithPosition(
+          new HInvokeDynamicGetter(selector, null, receiver, !hasGetter), send);
+    }
+  }
+
+  void generateGetter(Send send, Element element) {
+    if (Elements.isStaticOrTopLevelField(element)) {
+      Constant value;
+      if (element.isField() && !element.isAssignable()) {
+        // A static final or const. Get its constant value and inline it if
+        // the value can be compiled eagerly.
+        value = compileVariable(element);
+      }
+      if (value != null) {
+        stack.add(graph.addConstant(value));
+      } else if (element.isField() && isLazilyInitialized(element)) {
+        push(new HLazyStatic(element));
+      } else {
+        if (element.isGetter()) {
+          Selector selector = elements.getSelector(send);
+          if (tryInlineMethod(element, selector, const Link<Node>(), send)) {
+            return;
+          }
+        }
+        // TODO(5346): Try to avoid the need for calling [declaration] before
+        // creating an [HStatic].
+        push(new HStatic(element.declaration));
+        if (element.isGetter()) {
+          push(new HInvokeStatic(<HInstruction>[pop()], HType.UNKNOWN));
+        }
+      }
+    } else if (Elements.isInstanceSend(send, elements)) {
+      HInstruction receiver = generateInstanceSendReceiver(send);
+      generateInstanceGetterWithCompiledReceiver(send, receiver);
+    } else if (Elements.isStaticOrTopLevelFunction(element)) {
+      // TODO(5346): Try to avoid the need for calling [declaration] before
+      // creating an [HStatic].
+      push(new HStatic(element.declaration));
+      // TODO(ahe): This should be registered in codegen.
+      compiler.enqueuer.codegen.registerGetOfStaticFunction(element);
+    } else if (Elements.isErroneousElement(element)) {
+      // An erroneous element indicates an unresolved static getter.
+      generateThrowNoSuchMethod(send,
+                                getTargetName(element, 'get'),
+                                argumentNodes: const Link<Node>());
+    } else {
+      stack.add(localsHandler.readLocal(element));
+    }
+  }
+
+  void generateInstanceSetterWithCompiledReceiver(Send send,
+                                                  HInstruction receiver,
+                                                  HInstruction value) {
+    assert(Elements.isInstanceSend(send, elements));
+    Selector selector = elements.getSelector(send);
+    assert(selector.isSetter());
+    SourceString setterName = selector.name;
+    bool hasSetter = compiler.world.hasAnyUserDefinedSetter(selector);
+    Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+    if (interceptedClasses != null) {
+      // If we're using an interceptor class, emit a call to the
+      // getInterceptor method and then the actual dynamic call on the
+      // interceptor object.
+      HInstruction instruction =
+          invokeInterceptor(interceptedClasses, receiver, send);
+      instruction = new HInvokeDynamicSetter(
+          selector, null, instruction, receiver, !hasSetter);
+      // Add the value as an argument to the setter call on the
+      // interceptor.
+      instruction.inputs.add(value);
+      addWithPosition(instruction, send);
+    } else {
+      addWithPosition(
+          new HInvokeDynamicSetter(selector, null, receiver, value, !hasSetter),
+          send);
+    }
+    stack.add(value);
+  }
+
+  void generateSetter(SendSet send, Element element, HInstruction value) {
+    if (Elements.isStaticOrTopLevelField(element)) {
+      if (element.isSetter()) {
+        HStatic target = new HStatic(element);
+        add(target);
+        addWithPosition(
+            new HInvokeStatic(<HInstruction>[target, value], HType.UNKNOWN),
+            send);
+      } else {
+        value = potentiallyCheckType(value, element.computeType(compiler));
+        addWithPosition(new HStaticStore(element, value), send);
+      }
+      stack.add(value);
+    } else if (element == null || Elements.isInstanceField(element)) {
+      HInstruction receiver = generateInstanceSendReceiver(send);
+      generateInstanceSetterWithCompiledReceiver(send, receiver, value);
+    } else if (Elements.isErroneousElement(element)) {
+      // An erroneous element indicates an unresolved static setter.
+      generateThrowNoSuchMethod(send,
+                                getTargetName(element, 'set'),
+                                argumentNodes: send.arguments);
+    } else {
+      stack.add(value);
+      // If the value does not already have a name, give it here.
+      if (value.sourceElement == null) {
+        value.sourceElement = element;
+      }
+      HInstruction checked = potentiallyCheckType(
+          value, element.computeType(compiler));
+      if (!identical(checked, value)) {
+        pop();
+        stack.add(checked);
+      }
+      localsHandler.updateLocal(element, checked);
+    }
+  }
+
+  HInstruction invokeInterceptor(Set<ClassElement> intercepted,
+                                 HInstruction receiver,
+                                 Send send) {
+    HInterceptor interceptor = new HInterceptor(intercepted, receiver);
+    add(interceptor);
+    return interceptor;
+  }
+
+  void pushInvokeHelper0(Element helper, HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper1(Element helper, HInstruction a0, HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper2(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper3(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HInstruction a2,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper4(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HInstruction a2,
+                         HInstruction a3,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper5(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HInstruction a2,
+                         HInstruction a3,
+                         HInstruction a4,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3, a4];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  HForeign createForeign(String code, HType type, List<HInstruction> inputs) {
+    return new HForeign(new LiteralDartString(code), type, inputs);
+  }
+
+  HInstruction getRuntimeTypeInfo(HInstruction target) {
+    pushInvokeHelper1(backend.getGetRuntimeTypeInfo(), target, HType.UNKNOWN);
+    return pop();
+  }
+
+  // TODO(karlklose): change construction of the representations to be GVN'able
+  // (dartbug.com/7182).
+  List<HInstruction> buildTypeArgumentRepresentations(DartType type) {
+    HInstruction createForeignArray(String code, inputs) {
+      return createForeign(code, HType.READABLE_ARRAY, inputs);
+    }
+    HInstruction typeInfo;
+
+    /// Helper to create an instruction that contains the runtime value of
+    /// the type variable [variable].
+    HInstruction getTypeArgument(TypeVariableType variable) {
+      if (typeInfo == null) {
+        typeInfo = getRuntimeTypeInfo(localsHandler.readThis());
+      }
+      int intIndex = RuntimeTypeInformation.getTypeVariableIndex(variable);
+      HInstruction index = graph.addConstantInt(intIndex, constantSystem);
+      return createForeignArray('#[#]', <HInstruction>[typeInfo, index]);
+    }
+
+    // Compute the representation of the type arguments, including access
+    // to the runtime type information for type variables as instructions.
+    HInstruction representations;
+    if (type.element.isTypeVariable()) {
+      return <HInstruction>[getTypeArgument(type)];
+    } else {
+      assert(type.element.isClass());
+      List<HInstruction> arguments = <HInstruction>[];
+      InterfaceType interface = type;
+      for (DartType argument in interface.typeArguments) {
+        List<HInstruction> inputs = <HInstruction>[];
+        String template = rti.getTypeRepresentation(argument, (variable) {
+          HInstruction runtimeType = getTypeArgument(variable);
+          add(runtimeType);
+          inputs.add(runtimeType);
+        });
+        HInstruction representation = createForeignArray(template, inputs);
+        add(representation);
+        arguments.add(representation);
+      }
+      return arguments;
+    }
+  }
+
+  visitOperatorSend(node) {
+    Operator op = node.selector;
+    if (const SourceString("[]") == op.source) {
+      visitDynamicSend(node);
+    } else if (const SourceString("&&") == op.source ||
+               const SourceString("||") == op.source) {
+      visitLogicalAndOr(node, op);
+    } else if (const SourceString("!") == op.source) {
+      visitLogicalNot(node);
+    } else if (node.argumentsNode is Prefix) {
+      visitUnary(node, op);
+    } else if (const SourceString("is") == op.source) {
+      visit(node.receiver);
+      HInstruction expression = pop();
+      Node argument = node.arguments.head;
+      TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
+      bool isNot = false;
+      // TODO(ngeoffray): Duplicating pattern in resolver. We should
+      // add a new kind of node.
+      if (typeAnnotation == null) {
+        typeAnnotation = argument.asSend().receiver;
+        isNot = true;
+      }
+      DartType type = elements.getType(typeAnnotation);
+      if (type.isMalformed) {
+        String reasons = Types.fetchReasonsFromMalformedType(type);
+        if (compiler.enableTypeAssertions) {
+          generateMalformedSubtypeError(node, expression, type, reasons);
+        } else {
+          generateRuntimeError(node, '$type is malformed: $reasons');
+        }
+        return;
+      }
+      if (type.element.isTypeVariable()) {
+        // TODO(karlklose): remove this check when the backend can deal with
+        // checks of the form [:o is T:] where [:T:] is a type variable.
+        stack.add(graph.addConstantBool(true, constantSystem));
+        return;
+      }
+
+      HInstruction instruction;
+      if (type.element.isTypeVariable() ||
+          RuntimeTypeInformation.hasTypeArguments(type)) {
+        HInstruction typeInfo = getRuntimeTypeInfo(expression);
+        // TODO(karlklose): make isSubtype a HInstruction to enable
+        // optimizations?
+        Element helper = compiler.findHelper(const SourceString('isSubtype'));
+        HInstruction isSubtype = new HStatic(helper);
+        add(isSubtype);
+        // Build a list of representations for the type arguments.
+        List<HInstruction> representations =
+            buildTypeArgumentRepresentations(type);
+        // For each type argument, build a call to isSubtype, with the type
+        // argument as first and the representation of the tested type as
+        // second argument.
+        List<HInstruction> checks = <HInstruction>[];
+        int index = 0;
+        representations.forEach((HInstruction representation) {
+          HInstruction position = graph.addConstantInt(index, constantSystem);
+          // Get the index'th type argument from the runtime type information.
+          HInstruction typeArgument =
+              createForeign('#[#]', HType.UNKNOWN, [typeInfo, position]);
+          add(typeArgument);
+          // Create the call to isSubtype.
+          List<HInstruction> inputs =
+              <HInstruction>[isSubtype, typeArgument, representation];
+          HInstruction call = new HInvokeStatic(inputs, HType.BOOLEAN);
+          add(call);
+          checks.add(call);
+          index++;
+        });
+        instruction = new HIs(type, <HInstruction>[expression]..addAll(checks));
+      } else {
+        instruction = new HIs(type, <HInstruction>[expression]);
+      }
+      if (isNot) {
+        add(instruction);
+        instruction = new HNot(instruction);
+      }
+      push(instruction);
+    } else if (const SourceString("as") == op.source) {
+      visit(node.receiver);
+      HInstruction expression = pop();
+      Node argument = node.arguments.head;
+      TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
+      DartType type = elements.getType(typeAnnotation);
+      HInstruction converted = expression.convertType(
+          compiler, type, HTypeConversion.CAST_TYPE_CHECK);
+      if (converted != expression) add(converted);
+      stack.add(converted);
+    } else {
+      visit(node.receiver);
+      visit(node.argumentsNode);
+      var right = pop();
+      var left = pop();
+      visitBinary(left, op, right, node);
+    }
+  }
+
+  void addDynamicSendArgumentsToList(Send node, List<HInstruction> list) {
+    Selector selector = elements.getSelector(node);
+    if (selector.namedArgumentCount == 0) {
+      addGenericSendArgumentsToList(node.arguments, list);
+    } else {
+      // Visit positional arguments and add them to the list.
+      Link<Node> arguments = node.arguments;
+      int positionalArgumentCount = selector.positionalArgumentCount;
+      for (int i = 0;
+           i < positionalArgumentCount;
+           arguments = arguments.tail, i++) {
+        visit(arguments.head);
+        list.add(pop());
+      }
+
+      // Visit named arguments and add them into a temporary map.
+      Map<SourceString, HInstruction> instructions =
+          new Map<SourceString, HInstruction>();
+      List<SourceString> namedArguments = selector.namedArguments;
+      int nameIndex = 0;
+      for (; !arguments.isEmpty; arguments = arguments.tail) {
+        visit(arguments.head);
+        instructions[namedArguments[nameIndex++]] = pop();
+      }
+
+      // Iterate through the named arguments to add them to the list
+      // of instructions, in an order that can be shared with
+      // selectors with the same named arguments.
+      List<SourceString> orderedNames = selector.getOrderedNamedArguments();
+      for (SourceString name in orderedNames) {
+        list.add(instructions[name]);
+      }
+    }
+  }
+
+  /**
+   * Returns true if the arguments were compatible with the function signature.
+   *
+   * Invariant: [element] must be an implementation element.
+   */
+  bool addStaticSendArgumentsToList(Selector selector,
+                                    Link<Node> arguments,
+                                    FunctionElement element,
+                                    List<HInstruction> list) {
+    assert(invariant(element, element.isImplementation));
+
+    HInstruction compileArgument(Node argument) {
+      visit(argument);
+      return pop();
+    }
+
+    HInstruction handleConstant(Element parameter) {
+      Constant constant;
+      TreeElements calleeElements =
+          compiler.enqueuer.resolution.getCachedElements(element);
+      if (calleeElements.isParameterChecked(parameter)) {
+        constant = SentinelConstant.SENTINEL;
+      } else {
+        constant = compileConstant(parameter);
+      }
+      return graph.addConstant(constant);
+    }
+
+    return selector.addArgumentsToList(arguments,
+                                       list,
+                                       element,
+                                       compileArgument,
+                                       handleConstant,
+                                       compiler);
+  }
+
+  void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) {
+    for (; !link.isEmpty; link = link.tail) {
+      visit(link.head);
+      list.add(pop());
+    }
+  }
+
+  visitDynamicSend(Send node) {
+    Selector selector = elements.getSelector(node);
+
+    SourceString dartMethodName;
+    bool isNotEquals = false;
+    if (node.isIndex && !node.arguments.tail.isEmpty) {
+      dartMethodName = Elements.constructOperatorName(
+          const SourceString('[]='), false);
+    } else if (node.selector.asOperator() != null) {
+      SourceString name = node.selector.asIdentifier().source;
+      isNotEquals = identical(name.stringValue, '!=');
+      dartMethodName = Elements.constructOperatorName(
+          name, node.argumentsNode is Prefix);
+    } else {
+      dartMethodName = node.selector.asIdentifier().source;
+    }
+
+    Element element = elements[node];
+    bool isClosureCall = false;
+    if (element != null && compiler.world.hasNoOverridingMember(element)) {
+      if (tryInlineMethod(element, selector, node.arguments, node)) {
+        if (element.isGetter()) {
+          // If the element is a getter, we are doing a closure call
+          // on what this getter returns.
+          assert(selector.isCall());
+          isClosureCall = true;
+        } else {
+          return;
+        }
+      }
+    }
+
+    List<HInstruction> inputs = <HInstruction>[];
+    if (isClosureCall) inputs.add(pop());
+
+    HInstruction receiver;
+    if (!isClosureCall) {
+      if (node.receiver == null) {
+        receiver = localsHandler.readThis();
+      } else {
+        visit(node.receiver);
+        receiver = pop();
+      }
+    }
+
+    addDynamicSendArgumentsToList(node, inputs);
+
+    HInstruction invoke;
+    if (isClosureCall) {
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      invoke = new HInvokeClosure(closureSelector, inputs);
+    } else {
+      invoke = buildInvokeDynamic(node, selector, receiver, inputs);
+    }
+
+    pushWithPosition(invoke, node);
+
+    if (isNotEquals) {
+      HNot not = new HNot(popBoolified());
+      push(not);
+    }
+  }
+
+  visitClosureSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    assert(node.receiver == null);
+    Element element = elements[node];
+    HInstruction closureTarget;
+    if (element == null) {
+      visit(node.selector);
+      closureTarget = pop();
+    } else {
+      assert(Elements.isLocal(element));
+      closureTarget = localsHandler.readLocal(element);
+    }
+    var inputs = <HInstruction>[];
+    inputs.add(closureTarget);
+    addDynamicSendArgumentsToList(node, inputs);
+    Selector closureSelector = new Selector.callClosureFrom(selector);
+    pushWithPosition(new HInvokeClosure(closureSelector, inputs), node);
+  }
+
+  void handleForeignJs(Send node) {
+    Link<Node> link = node.arguments;
+    // If the invoke is on foreign code, don't visit the first
+    // argument, which is the type, and the second argument,
+    // which is the foreign code.
+    if (link.isEmpty || link.tail.isEmpty) {
+      compiler.cancel('At least two arguments expected',
+                      node: node.argumentsNode);
+    }
+    List<HInstruction> inputs = <HInstruction>[];
+    Node type = link.head;
+    Node code = link.tail.head;
+    addGenericSendArgumentsToList(link.tail.tail, inputs);
+
+    native.NativeBehavior nativeBehavior =
+        compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node);
+    HType ssaType = mapNativeBehaviorType(nativeBehavior);
+    if (code is StringNode) {
+      StringNode codeString = code;
+      if (!codeString.isInterpolation) {
+        // codeString may not be an interpolation, but may be a juxtaposition.
+        push(new HForeign(codeString.dartString, ssaType, inputs));
+        return;
+      }
+    }
+    compiler.cancel('JS code must be a string literal', node: code);
+  }
+
+  void handleForeignJsCurrentIsolate(Send node) {
+    if (!node.arguments.isEmpty) {
+      compiler.cancel(
+          'Too many arguments to JS_CURRENT_ISOLATE', node: node);
+    }
+
+    if (!compiler.hasIsolateSupport()) {
+      // If the isolate library is not used, we just generate code
+      // to fetch the Leg's current isolate.
+      String name = backend.namer.CURRENT_ISOLATE;
+      push(new HForeign(new DartString.literal(name),
+                        HType.UNKNOWN,
+                        <HInstruction>[]));
+    } else {
+      // Call a helper method from the isolate library. The isolate
+      // library uses its own isolate structure, that encapsulates
+      // Leg's isolate.
+      Element element = compiler.isolateHelperLibrary.find(
+          const SourceString('_currentIsolate'));
+      if (element == null) {
+        compiler.cancel(
+            'Isolate library and compiler mismatch', node: node);
+      }
+      pushInvokeHelper0(element, HType.UNKNOWN);
+    }
+  }
+
+  void handleForeignJsCallInIsolate(Send node) {
+    Link<Node> link = node.arguments;
+    if (!compiler.hasIsolateSupport()) {
+      // If the isolate library is not used, we just invoke the
+      // closure.
+      visit(link.tail.head);
+      Selector selector = new Selector.callClosure(0);
+      push(new HInvokeClosure(selector, <HInstruction>[pop()]));
+    } else {
+      // Call a helper method from the isolate library.
+      Element element = compiler.isolateHelperLibrary.find(
+          const SourceString('_callInIsolate'));
+      if (element == null) {
+        compiler.cancel(
+            'Isolate library and compiler mismatch', node: node);
+      }
+      HStatic target = new HStatic(element);
+      add(target);
+      List<HInstruction> inputs = <HInstruction>[target];
+      addGenericSendArgumentsToList(link, inputs);
+      push(new HInvokeStatic(inputs, HType.UNKNOWN));
+    }
+  }
+
+  FunctionSignature handleForeignRawFunctionRef(Send node, String name) {
+    if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
+      compiler.cancel('"$name" requires exactly one argument',
+                      node: node.argumentsNode);
+    }
+    Node closure = node.arguments.head;
+    Element element = elements[closure];
+    if (!Elements.isStaticOrTopLevelFunction(element)) {
+      compiler.cancel(
+          '"$name" requires a static or top-level method',
+          node: closure);
+    }
+    FunctionElement function = element;
+    // TODO(johnniwinther): Try to eliminate the need to distinguish declaration
+    // and implementation signatures. Currently it is need because the
+    // signatures have different elements for parameters.
+    FunctionElement implementation = function.implementation;
+    FunctionSignature params = implementation.computeSignature(compiler);
+    if (params.optionalParameterCount != 0) {
+      compiler.cancel(
+          '"$name" does not handle closure with optional parameters',
+          node: closure);
+    }
+    visit(closure);
+    return params;
+  }
+
+  void handleForeignDartClosureToJs(Send node, String name) {
+    FunctionSignature params = handleForeignRawFunctionRef(node, name);
+    List<HInstruction> inputs = <HInstruction>[pop()];
+    String invocationName = backend.namer.invocationName(
+        new Selector.callClosure(params.requiredParameterCount));
+    push(new HForeign(new DartString.literal('#.$invocationName'),
+                      HType.UNKNOWN,
+                      inputs));
+  }
+
+  void handleForeignSetCurrentIsolate(Send node) {
+    if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
+      compiler.cancel('Exactly one argument required',
+                      node: node.argumentsNode);
+    }
+    visit(node.arguments.head);
+    String isolateName = backend.namer.CURRENT_ISOLATE;
+    push(new HForeign(new DartString.literal("$isolateName = #"),
+                      HType.UNKNOWN,
+                      <HInstruction>[pop()]));
+  }
+
+  void handleForeignCreateIsolate(Send node) {
+    if (!node.arguments.isEmpty) {
+      compiler.cancel('Too many arguments',
+                      node: node.argumentsNode);
+    }
+    String constructorName = backend.namer.isolateName;
+    push(new HForeign(new DartString.literal("new $constructorName"),
+                      HType.UNKNOWN,
+                      <HInstruction>[]));
+  }
+
+  visitForeignSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    SourceString name = selector.name;
+    if (name == const SourceString('JS')) {
+      handleForeignJs(node);
+    } else if (name == const SourceString('JS_CURRENT_ISOLATE')) {
+      handleForeignJsCurrentIsolate(node);
+    } else if (name == const SourceString('JS_CALL_IN_ISOLATE')) {
+      handleForeignJsCallInIsolate(node);
+    } else if (name == const SourceString('DART_CLOSURE_TO_JS')) {
+      handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS');
+    } else if (name == const SourceString('RAW_DART_FUNCTION_REF')) {
+      handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF');
+    } else if (name == const SourceString('JS_SET_CURRENT_ISOLATE')) {
+      handleForeignSetCurrentIsolate(node);
+    } else if (name == const SourceString('JS_CREATE_ISOLATE')) {
+      handleForeignCreateIsolate(node);
+    } else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) {
+      stack.add(addConstantString(node, backend.namer.operatorIsPrefix()));
+    } else {
+      throw "Unknown foreign: ${selector}";
+    }
+  }
+
+  generateSuperNoSuchMethodSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    SourceString name = selector.name;
+
+    ClassElement cls = currentElement.getEnclosingClass();
+    Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD);
+    if (element.enclosingElement.declaration != compiler.objectClass) {
+      // Register the call as dynamic if [:noSuchMethod:] on the super class
+      // is _not_ the default implementation from [:Object:].
+      compiler.enqueuer.codegen.registerDynamicInvocation(name, selector);
+    }
+    HStatic target = new HStatic(element);
+    add(target);
+    HInstruction self = localsHandler.readThis();
+    Constant nameConstant = constantSystem.createString(
+        new DartString.literal(name.slowToString()), node);
+
+    String internalName = backend.namer.invocationName(selector);
+    Constant internalNameConstant =
+        constantSystem.createString(new DartString.literal(internalName), node);
+
+    Element createInvocationMirror =
+        compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR);
+
+    var arguments = new List<HInstruction>();
+    if (node.argumentsNode != null) {
+      addGenericSendArgumentsToList(node.arguments, arguments);
+    }
+    var argumentsInstruction = new HLiteralList(arguments);
+    add(argumentsInstruction);
+
+    var argumentNames = new List<HInstruction>();
+    for (SourceString argumentName in selector.namedArguments) {
+      Constant argumentNameConstant =
+          constantSystem.createString(new DartString.literal(
+              argumentName.slowToString()), node);
+      argumentNames.add(graph.addConstant(argumentNameConstant));
+    }
+    var argumentNamesInstruction = new HLiteralList(argumentNames);
+    add(argumentNamesInstruction);
+
+    Constant kindConstant =
+        constantSystem.createInt(selector.invocationMirrorKind);
+
+    pushInvokeHelper5(createInvocationMirror,
+                      graph.addConstant(nameConstant),
+                      graph.addConstant(internalNameConstant),
+                      graph.addConstant(kindConstant),
+                      argumentsInstruction,
+                      argumentNamesInstruction,
+                      HType.UNKNOWN);
+
+    var inputs = <HInstruction>[
+        target,
+        self,
+        pop()];
+    push(new HInvokeSuper(inputs));
+  }
+
+  visitSend(Send node) {
+    Element element = elements[node];
+    if (element != null && identical(element, currentElement)) {
+      graph.isRecursiveMethod = true;
+    }
+    super.visitSend(node);
+  }
+
+  visitSuperSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    Element element = elements[node];
+    if (element == null) return generateSuperNoSuchMethodSend(node);
+    // TODO(5346): Try to avoid the need for calling [declaration] before
+    // creating an [HStatic].
+    HInstruction target = new HStatic(element.declaration);
+    HInstruction context = localsHandler.readThis();
+    add(target);
+    var inputs = <HInstruction>[target, context];
+    if (node.isPropertyAccess) {
+      push(new HInvokeSuper(inputs));
+    } else if (element.isFunction() || element.isGenerativeConstructor()) {
+      // TODO(5347): Try to avoid the need for calling [implementation] before
+      // calling [addStaticSendArgumentsToList].
+      FunctionElement function = element.implementation;
+      bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
+                                                    function, inputs);
+      if (!succeeded) {
+        generateWrongArgumentCountError(node, element, node.arguments);
+      } else {
+        push(new HInvokeSuper(inputs));
+      }
+    } else {
+      target = new HInvokeSuper(inputs);
+      add(target);
+      inputs = <HInstruction>[target];
+      addDynamicSendArgumentsToList(node, inputs);
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      push(new HInvokeClosure(closureSelector, inputs));
+    }
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [argument] must not be malformed in checked mode.
+   */
+  HInstruction analyzeTypeArgument(DartType argument, Node currentNode) {
+    assert(invariant(currentNode,
+                     !compiler.enableTypeAssertions || !argument.isMalformed,
+                     message: '$argument is malformed in checked mode'));
+    if (argument == compiler.types.dynamicType || argument.isMalformed) {
+      // Represent [dynamic] as [null].
+      return graph.addConstantNull(constantSystem);
+    }
+
+    // These variables are shared between invocations of the helper.
+    HInstruction typeInfo;
+    List<HInstruction> inputs = <HInstruction>[];
+
+    /**
+     * Helper to create an instruction that gets the value of a type variable.
+     */
+    void addTypeVariableReference(TypeVariableType type) {
+      Element member = currentElement;
+      if (member.enclosingElement.isClosure()) {
+        ClosureClassElement closureClass = member.enclosingElement;
+        member = closureClass.methodElement;
+        member = member.getOutermostEnclosingMemberOrTopLevel();
+      }
+      if (member.isFactoryConstructor()) {
+        // The type variable is stored in a parameter of the factory.
+        inputs.add(localsHandler.readLocal(type.element));
+      } else if (member.isInstanceMember()
+                 || member.isGenerativeConstructor()) {
+        // The type variable is stored in [this].
+        if (typeInfo == null) {
+          pushInvokeHelper1(backend.getGetRuntimeTypeInfo(),
+                            localsHandler.readThis(),
+                            HType.UNKNOWN);
+          typeInfo = pop();
+        }
+        int index = RuntimeTypeInformation.getTypeVariableIndex(type);
+        HInstruction foreign = createForeign('#[$index]', HType.STRING,
+                                             <HInstruction>[typeInfo]);
+        add(foreign);
+        inputs.add(foreign);
+      } else {
+        // TODO(ngeoffray): Match the VM behavior and throw an
+        // exception at runtime.
+        compiler.cancel('Unimplemented unresolved type variable',
+                        node: currentNode);
+      }
+    }
+
+    String template = rti.getTypeRepresentation(argument,
+                                                addTypeVariableReference);
+    HInstruction result = createForeign(template, HType.STRING, inputs);
+    add(result);
+    return result;
+  }
+
+  void handleListConstructor(InterfaceType type,
+                             Node currentNode,
+                             HInstruction newObject) {
+    if (!compiler.world.needsRti(type.element)) return;
+    List<HInstruction> inputs = <HInstruction>[];
+    if (!type.isRaw) {
+      type.typeArguments.forEach((DartType argument) {
+        inputs.add(analyzeTypeArgument(argument, currentNode));
+      });
+    }
+    callSetRuntimeTypeInfo(type.element, inputs, newObject);
+  }
+
+  void callSetRuntimeTypeInfo(ClassElement element,
+                              List<HInstruction> rtiInputs,
+                              HInstruction newObject) {
+    if (!compiler.world.needsRti(element) || element.typeVariables.isEmpty) {
+      return;
+    }
+
+    HInstruction typeInfo = new HLiteralList(rtiInputs);
+    add(typeInfo);
+
+    // Set the runtime type information on the object.
+    Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo();
+    HInstruction typeInfoSetter = new HStatic(typeInfoSetterElement);
+    add(typeInfoSetter);
+    add(new HInvokeStatic(
+        <HInstruction>[typeInfoSetter, newObject, typeInfo], HType.UNKNOWN));
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [type] must not be malformed in checked mode.
+   */
+  visitNewSend(Send node, InterfaceType type) {
+    assert(invariant(node,
+                     !compiler.enableTypeAssertions || !type.isMalformed,
+                     message: '$type is malformed in checked mode'));
+    bool isListConstructor = false;
+    computeType(element) {
+      Element originalElement = elements[node];
+      if (identical(originalElement.getEnclosingClass(), compiler.listClass)) {
+        isListConstructor = true;
+        if (node.arguments.isEmpty) {
+          return HType.EXTENDABLE_ARRAY;
+        } else {
+          return HType.MUTABLE_ARRAY;
+        }
+      } else if (element.isGenerativeConstructor()) {
+        ClassElement cls = element.getEnclosingClass();
+        return new HBoundedType.exact(cls.thisType);
+      } else {
+        return HType.UNKNOWN;
+      }
+    }
+
+    Element constructor = elements[node];
+    Selector selector = elements.getSelector(node);
+    if (compiler.enqueuer.resolution.getCachedElements(constructor) == null) {
+      compiler.internalError("Unresolved element: $constructor", node: node);
+    }
+    FunctionElement functionElement = constructor;
+    constructor = functionElement.redirectionTarget;
+    // TODO(5346): Try to avoid the need for calling [declaration] before
+    // creating an [HStatic].
+    HInstruction target = new HStatic(constructor.declaration);
+    add(target);
+    var inputs = <HInstruction>[];
+    inputs.add(target);
+    // TODO(5347): Try to avoid the need for calling [implementation] before
+    // calling [addStaticSendArgumentsToList].
+    bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
+                                                  constructor.implementation,
+                                                  inputs);
+    if (!succeeded) {
+      generateWrongArgumentCountError(node, constructor, node.arguments);
+      return;
+    }
+
+    ClassElement cls = constructor.getEnclosingClass();
+    if (cls.isAbstract(compiler) && constructor.isGenerativeConstructor()) {
+      generateAbstractClassInstantiationError(node, cls.name.slowToString());
+      return;
+    }
+    if (compiler.world.needsRti(cls)) {
+      Link<DartType> typeVariable = cls.typeVariables;
+      type.typeArguments.forEach((DartType argument) {
+        inputs.add(analyzeTypeArgument(argument, node));
+        typeVariable = typeVariable.tail;
+      });
+      // Also add null to non-provided type variables to call the
+      // constructor with the right number of arguments.
+      while (!typeVariable.isEmpty) {
+        inputs.add(graph.addConstantNull(constantSystem));
+        typeVariable = typeVariable.tail;
+      }
+    }
+
+    HType elementType = computeType(constructor);
+    HInstruction newInstance = new HInvokeStatic(inputs, elementType);
+    pushWithPosition(newInstance, node);
+
+    // The List constructor forwards to a Dart static method that does
+    // not know about the type argument. Therefore we special case
+    // this constructor to have the setRuntimeTypeInfo called where
+    // the 'new' is done.
+    if (isListConstructor && compiler.world.needsRti(compiler.listClass)) {
+      handleListConstructor(type, node, newInstance);
+    }
+  }
+
+  visitStaticSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    Element element = elements[node];
+    if (element.isForeign(compiler)) {
+      visitForeignSend(node);
+      return;
+    }
+    if (element.isErroneous()) {
+      generateThrowNoSuchMethod(node,
+                                getTargetName(element),
+                                argumentNodes: node.arguments);
+      return;
+    }
+    if (identical(element, compiler.assertMethod)
+        && !compiler.enableUserAssertions) {
+      stack.add(graph.addConstantNull(constantSystem));
+      return;
+    }
+    compiler.ensure(!element.isGenerativeConstructor());
+    if (element.isFunction()) {
+      bool isIdenticalFunction = element == compiler.identicalFunction;
+
+      if (!isIdenticalFunction
+          && tryInlineMethod(element, selector, node.arguments, node)) {
+        return;
+      }
+
+      HInstruction target = new HStatic(element);
+      add(target);
+      var inputs = <HInstruction>[target];
+      // TODO(5347): Try to avoid the need for calling [implementation] before
+      // calling [addStaticSendArgumentsToList].
+      bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
+                                                    element.implementation,
+                                                    inputs);
+      if (!succeeded) {
+        generateWrongArgumentCountError(node, element, node.arguments);
+        return;
+      }
+
+      if (isIdenticalFunction) {
+        pushWithPosition(new HIdentity(inputs[1], inputs[2]), node);
+        return;
+      }
+
+      HInvokeStatic instruction = new HInvokeStatic(inputs, HType.UNKNOWN);
+      // TODO(ngeoffray): Only do this if knowing the return type is
+      // useful.
+      HType returnType =
+          builder.backend.optimisticReturnTypesWithRecompilationOnTypeChange(
+              currentElement, element);
+      if (returnType != null) instruction.guaranteedType = returnType;
+      pushWithPosition(instruction, node);
+    } else {
+      generateGetter(node, element);
+      List<HInstruction> inputs = <HInstruction>[pop()];
+      addDynamicSendArgumentsToList(node, inputs);
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      pushWithPosition(new HInvokeClosure(closureSelector, inputs), node);
+    }
+  }
+
+  HConstant addConstantString(Node node, String string) {
+    DartString dartString = new DartString.literal(string);
+    Constant constant = constantSystem.createString(dartString, node);
+    return graph.addConstant(constant);
+  }
+
+  visitTypeReferenceSend(Send node) {
+    Element element = elements[node];
+    if (element.isClass() || element.isTypedef()) {
+      // TODO(karlklose): add type representation
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+    } else if (element.isTypeVariable()) {
+      // TODO(6248): implement support for type variables.
+      compiler.unimplemented('first class type for type variable', node: node);
+    } else {
+      internalError('unexpected element kind $element', node: node);
+    }
+    if (node.isCall) {
+      // This send is of the form 'e(...)', where e is resolved to a type
+      // reference. We create a regular closure call on the result of the type
+      // reference instead of creating a NoSuchMethodError to avoid pulling it
+      // in if it is not used (e.g., in a try/catch).
+      HInstruction target = pop();
+      Selector selector = elements.getSelector(node);
+      List<HInstruction> inputs = <HInstruction>[target];
+      addDynamicSendArgumentsToList(node, inputs);
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      push(new HInvokeClosure(closureSelector, inputs));
+    }
+  }
+
+  visitGetterSend(Send node) {
+    generateGetter(node, elements[node]);
+  }
+
+  // TODO(antonm): migrate rest of SsaBuilder to internalError.
+  internalError(String reason, {Node node}) {
+    compiler.internalError(reason, node: node);
+  }
+
+  void generateError(Node node, String message, Element helper) {
+    HInstruction errorMessage = addConstantString(node, message);
+    pushInvokeHelper1(helper, errorMessage, HType.UNKNOWN);
+  }
+
+  void generateRuntimeError(Node node, String message) {
+    generateError(node, message, backend.getThrowRuntimeError());
+  }
+
+  void generateAbstractClassInstantiationError(Node node, String message) {
+    generateError(node,
+                  message,
+                  backend.getThrowAbstractClassInstantiationError());
+  }
+
+  void generateThrowNoSuchMethod(Node diagnosticNode,
+                                 String methodName,
+                                 {Link<Node> argumentNodes,
+                                  List<HInstruction> argumentValues,
+                                  List<String> existingArguments}) {
+    Element helper =
+        compiler.findHelper(const SourceString('throwNoSuchMethod'));
+    Constant receiverConstant =
+        constantSystem.createString(new DartString.empty(), diagnosticNode);
+    HInstruction receiver = graph.addConstant(receiverConstant);
+    DartString dartString = new DartString.literal(methodName);
+    Constant nameConstant =
+        constantSystem.createString(dartString, diagnosticNode);
+    HInstruction name = graph.addConstant(nameConstant);
+    if (argumentValues == null) {
+      argumentValues = <HInstruction>[];
+      argumentNodes.forEach((argumentNode) {
+        visit(argumentNode);
+        HInstruction value = pop();
+        argumentValues.add(value);
+      });
+    }
+    HInstruction arguments = new HLiteralList(argumentValues);
+    add(arguments);
+    HInstruction existingNamesList;
+    if (existingArguments != null) {
+      List<HInstruction> existingNames = <HInstruction>[];
+      for (String name in existingArguments) {
+        HInstruction nameConstant =
+            graph.addConstantString(new DartString.literal(name),
+                                    diagnosticNode, constantSystem);
+        existingNames.add(nameConstant);
+      }
+      existingNamesList = new HLiteralList(existingNames);
+      add(existingNamesList);
+    } else {
+      existingNamesList = graph.addConstantNull(constantSystem);
+    }
+    pushInvokeHelper4(
+        helper, receiver, name, arguments, existingNamesList, HType.UNKNOWN);
+  }
+
+  /**
+   * Generate code to throw a [NoSuchMethodError] exception for calling a
+   * method with a wrong number of arguments or mismatching named optional
+   * arguments.
+   */
+  void generateWrongArgumentCountError(Node diagnosticNode,
+                                       FunctionElement function,
+                                       Link<Node> argumentNodes) {
+    List<String> existingArguments = <String>[];
+    FunctionSignature signature = function.computeSignature(compiler);
+    signature.forEachParameter((Element parameter) {
+      existingArguments.add(parameter.name.slowToString());
+    });
+    generateThrowNoSuchMethod(diagnosticNode,
+                              function.name.slowToString(),
+                              argumentNodes: argumentNodes,
+                              existingArguments: existingArguments);
+  }
+
+  void generateMalformedSubtypeError(Node node, HInstruction value,
+                                     DartType type, String reasons) {
+    HInstruction typeString = addConstantString(node, type.toString());
+    HInstruction reasonsString = addConstantString(node, reasons);
+    Element helper = backend.getThrowMalformedSubtypeError();
+    pushInvokeHelper3(helper, value, typeString, reasonsString, HType.UNKNOWN);
+  }
+
+  visitNewExpression(NewExpression node) {
+    Element element = elements[node.send];
+    if (!Elements.isErroneousElement(element)) {
+      FunctionElement function = element;
+      element = function.redirectionTarget;
+    }
+    if (Elements.isErroneousElement(element)) {
+      ErroneousElement error = element;
+      if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) {
+        generateThrowNoSuchMethod(node.send,
+                                  getTargetName(error, 'constructor'),
+                                  argumentNodes: node.send.arguments);
+      } else {
+        Message message = error.messageKind.message(error.messageArguments);
+        generateRuntimeError(node.send, message.toString());
+      }
+    } else if (node.isConst()) {
+      // TODO(karlklose): add type representation
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+    } else {
+      DartType type = elements.getType(node);
+      if (compiler.enableTypeAssertions && type.isMalformed) {
+        String reasons = Types.fetchReasonsFromMalformedType(type);
+        // TODO(johnniwinther): Change to resemble type errors from bounds check
+        // on type arguments.
+        generateRuntimeError(node, '$type is malformed: $reasons');
+      } else {
+        // TODO(karlklose): move this type registration to the codegen.
+        compiler.codegenWorld.instantiatedTypes.add(type);
+        Send send = node.send;
+        Element constructor = elements[send];
+        Selector selector = elements.getSelector(send);
+        if (!tryInlineMethod(constructor, selector, send.arguments, node)) {
+          visitNewSend(send, type);
+        }
+      }
+    }
+  }
+
+  HInvokeDynamicMethod buildInvokeDynamic(Node node,
+                                          Selector selector,
+                                          HInstruction receiver,
+                                          List<HInstruction> arguments) {
+    Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+    List<HInstruction> inputs = <HInstruction>[];
+    bool isIntercepted = interceptedClasses != null;
+    if (isIntercepted) {
+      assert(!interceptedClasses.isEmpty);
+      inputs.add(invokeInterceptor(interceptedClasses, receiver, node));
+    }
+    inputs.add(receiver);
+    inputs.addAll(arguments);
+    return new HInvokeDynamicMethod(selector, inputs, isIntercepted);
+  }
+
+  visitSendSet(SendSet node) {
+    Element element = elements[node];
+    if (!Elements.isUnresolved(element) && element.impliesType()) {
+      Identifier selector = node.selector;
+      generateThrowNoSuchMethod(node, selector.source.slowToString(),
+                                argumentNodes: node.arguments);
+      return;
+    }
+    Operator op = node.assignmentOperator;
+    if (node.isSuperCall) {
+      if (element == null) return generateSuperNoSuchMethodSend(node);
+      HInstruction target = new HStatic(element);
+      HInstruction context = localsHandler.readThis();
+      add(target);
+      var inputs = <HInstruction>[target, context];
+      addDynamicSendArgumentsToList(node, inputs);
+      if (!identical(node.assignmentOperator.source.stringValue, '=')) {
+        compiler.unimplemented('complex super assignment',
+                               node: node.assignmentOperator);
+      }
+      push(new HInvokeSuper(inputs, isSetter: true));
+    } else if (node.isIndex) {
+      if (const SourceString("=") == op.source) {
+        visitDynamicSend(node);
+        HInvokeDynamicMethod method = pop();
+        // Push the value.
+        stack.add(method.inputs.last);
+      } else {
+        visit(node.receiver);
+        HInstruction receiver = pop();
+        visit(node.argumentsNode);
+        HInstruction value;
+        HInstruction index;
+        // Compound assignments are considered as being prefix.
+        bool isCompoundAssignment = op.source.stringValue.endsWith('=');
+        bool isPrefix = !node.isPostfix;
+        Element getter = elements[node.selector];
+        if (isCompoundAssignment) {
+          value = pop();
+          index = pop();
+        } else {
+          index = pop();
+          value = graph.addConstantInt(1, constantSystem);
+        }
+
+        HInvokeDynamicMethod left = buildInvokeDynamic(
+            node, new Selector.index(), receiver, [index]);
+        add(left);
+        visitBinary(left, op, value, node);
+        value = pop();
+        HInvokeDynamicMethod assign = buildInvokeDynamic(
+            node, new Selector.indexSet(), receiver, [index, value]);
+        add(assign);
+        if (isPrefix) {
+          stack.add(value);
+        } else {
+          stack.add(left);
+        }
+      }
+    } else if (const SourceString("=") == op.source) {
+      Element element = elements[node];
+      Link<Node> link = node.arguments;
+      assert(!link.isEmpty && link.tail.isEmpty);
+      visit(link.head);
+      HInstruction value = pop();
+      generateSetter(node, element, value);
+    } else if (identical(op.source.stringValue, "is")) {
+      compiler.internalError("is-operator as SendSet", node: op);
+    } else {
+      assert(const SourceString("++") == op.source ||
+             const SourceString("--") == op.source ||
+             node.assignmentOperator.source.stringValue.endsWith("="));
+      Element element = elements[node];
+      bool isCompoundAssignment = !node.arguments.isEmpty;
+      bool isPrefix = !node.isPostfix;  // Compound assignments are prefix.
+
+      // [receiver] is only used if the node is an instance send.
+      HInstruction receiver = null;
+      Element selectorElement = elements[node];
+      if (Elements.isInstanceSend(node, elements)) {
+        receiver = generateInstanceSendReceiver(node);
+        generateInstanceGetterWithCompiledReceiver(node, receiver);
+      } else {
+        generateGetter(node, elements[node.selector]);
+      }
+      HInstruction left = pop();
+      HInstruction right;
+      if (isCompoundAssignment) {
+        visit(node.argumentsNode);
+        right = pop();
+      } else {
+        right = graph.addConstantInt(1, constantSystem);
+      }
+      visitBinary(left, op, right, node);
+      HInstruction operation = pop();
+      assert(operation != null);
+      if (Elements.isInstanceSend(node, elements)) {
+        assert(receiver != null);
+        generateInstanceSetterWithCompiledReceiver(node, receiver, operation);
+      } else {
+        assert(receiver == null);
+        generateSetter(node, element, operation);
+      }
+      if (!isPrefix) {
+        pop();
+        stack.add(left);
+      }
+    }
+  }
+
+  void visitLiteralInt(LiteralInt node) {
+    stack.add(graph.addConstantInt(node.value, constantSystem));
+  }
+
+  void visitLiteralDouble(LiteralDouble node) {
+    stack.add(graph.addConstantDouble(node.value, constantSystem));
+  }
+
+  void visitLiteralBool(LiteralBool node) {
+    stack.add(graph.addConstantBool(node.value, constantSystem));
+  }
+
+  void visitLiteralString(LiteralString node) {
+    stack.add(graph.addConstantString(node.dartString, node, constantSystem));
+  }
+
+  void visitStringJuxtaposition(StringJuxtaposition node) {
+    if (!node.isInterpolation) {
+      // This is a simple string with no interpolations.
+      stack.add(graph.addConstantString(node.dartString, node, constantSystem));
+      return;
+    }
+    StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
+    stringBuilder.visit(node);
+    stack.add(stringBuilder.result);
+  }
+
+  void visitLiteralNull(LiteralNull node) {
+    stack.add(graph.addConstantNull(constantSystem));
+  }
+
+  visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      if (isAborted()) {
+        compiler.reportWarning(link.head, 'dead code');
+      } else {
+        visit(link.head);
+      }
+    }
+  }
+
+  void visitParenthesizedExpression(ParenthesizedExpression node) {
+    visit(node.expression);
+  }
+
+  visitOperator(Operator node) {
+    // Operators are intercepted in their surrounding Send nodes.
+    compiler.internalError('visitOperator should not be called', node: node);
+  }
+
+  visitCascade(Cascade node) {
+    visit(node.expression);
+    // Remove the result and reveal the duplicated receiver on the stack.
+    pop();
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visit(node.expression);
+    dup();
+  }
+
+  void handleInTryStatement() {
+    if (!inTryStatement) return;
+    HBasicBlock block = close(new HExitTry());
+    HBasicBlock newBlock = graph.addNewBlock();
+    block.addSuccessor(newBlock);
+    open(newBlock);
+  }
+
+  visitReturn(Return node) {
+    if (identical(node.getBeginToken().stringValue, 'native')) {
+      native.handleSsaNative(this, node.expression);
+      return;
+    }
+    assert(invariant(node, !node.isRedirectingFactoryBody));
+    HInstruction value;
+    if (node.expression == null) {
+      value = graph.addConstantNull(constantSystem);
+    } else {
+      visit(node.expression);
+      value = pop();
+      value = potentiallyCheckType(value, returnType);
+    }
+
+    handleInTryStatement();
+
+    if (!inliningStack.isEmpty) {
+      localsHandler.updateLocal(returnElement, value);
+    } else {
+      close(attachPosition(new HReturn(value), node)).addSuccessor(graph.exit);
+    }
+  }
+
+  visitThrow(Throw node) {
+    if (node.expression == null) {
+      HInstruction exception = rethrowableException;
+      if (exception == null) {
+        exception = graph.addConstantNull(constantSystem);
+        compiler.internalError(
+            'rethrowableException should not be null', node: node);
+      }
+      close(new HThrow(exception, isRethrow: true));
+    } else {
+      visit(node.expression);
+      close(new HThrow(pop()));
+    }
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    compiler.internalError('visiting type annotation in SSA builder',
+                           node: node);
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    for (Link<Node> link = node.definitions.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      Node definition = link.head;
+      if (definition is Identifier) {
+        HInstruction initialValue = graph.addConstantNull(constantSystem);
+        localsHandler.updateLocal(elements[definition], initialValue);
+      } else {
+        assert(definition is SendSet);
+        visitSendSet(definition);
+        pop();  // Discard value.
+      }
+    }
+  }
+
+  visitLiteralList(LiteralList node) {
+    if (node.isConst()) {
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+      return;
+    }
+
+    List<HInstruction> inputs = <HInstruction>[];
+    for (Link<Node> link = node.elements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      visit(link.head);
+      inputs.add(pop());
+    }
+    push(new HLiteralList(inputs));
+  }
+
+  visitConditional(Conditional node) {
+    SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
+    brancher.handleConditional(() => visit(node.condition),
+                               () => visit(node.thenExpression),
+                               () => visit(node.elseExpression));
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
+    stringBuilder.visit(node);
+    stack.add(stringBuilder.result);
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    // The parts are iterated in visitStringInterpolation.
+    compiler.internalError('visitStringInterpolation should not be called',
+                           node: node);
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    // Do nothing, empty statement.
+  }
+
+  visitModifiers(Modifiers node) {
+    compiler.unimplemented('SsaBuilder.visitModifiers', node: node);
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    assert(!isAborted());
+    handleInTryStatement();
+    TargetElement target = elements[node];
+    assert(target != null);
+    JumpHandler handler = jumpTargets[target];
+    assert(handler != null);
+    if (node.target == null) {
+      handler.generateBreak();
+    } else {
+      LabelElement label = elements[node.target];
+      handler.generateBreak(label);
+    }
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    handleInTryStatement();
+    TargetElement target = elements[node];
+    assert(target != null);
+    JumpHandler handler = jumpTargets[target];
+    assert(handler != null);
+    if (node.target == null) {
+      handler.generateContinue();
+    } else {
+      LabelElement label = elements[node.target];
+      assert(label != null);
+      handler.generateContinue(label);
+    }
+  }
+
+  /**
+   * Creates a [JumpHandler] for a statement. The node must be a jump
+   * target. If there are no breaks or continues targeting the statement,
+   * a special "null handler" is returned.
+   */
+  JumpHandler createJumpHandler(Statement node) {
+    TargetElement element = elements[node];
+    if (element == null || !identical(element.statement, node)) {
+      // No breaks or continues to this node.
+      return new NullJumpHandler(compiler);
+    }
+    return new JumpHandler(this, element);
+  }
+
+  visitForIn(ForIn node) {
+    // Generate a structure equivalent to:
+    //   Iterator<E> $iter = <iterable>.iterator;
+    //   while ($iter.moveNext()) {
+    //     E <declaredIdentifier> = $iter.current;
+    //     <body>
+    //   }
+
+    // The iterator is shared between initializer, condition and body.
+    HInstruction iterator;
+    void buildInitializer() {
+      SourceString iteratorName = const SourceString("iterator");
+      Selector selector =
+          new Selector.getter(iteratorName, currentElement.getLibrary());
+      Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+      visit(node.expression);
+      HInstruction receiver = pop();
+      bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
+      if (interceptedClasses == null) {
+        iterator =
+            new HInvokeDynamicGetter(selector, null, receiver, hasGetter);
+      } else {
+        HInterceptor interceptor =
+            invokeInterceptor(interceptedClasses, receiver, null);
+        iterator =
+            new HInvokeDynamicGetter(selector, null, interceptor, hasGetter);
+        // Add the receiver as an argument to the getter call on the
+        // interceptor.
+        iterator.inputs.add(receiver);
+      }
+      add(iterator);
+    }
+    HInstruction buildCondition() {
+      SourceString name = const SourceString('moveNext');
+      Selector selector = new Selector.call(
+          name, currentElement.getLibrary(), 0);
+      bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
+      push(new HInvokeDynamicMethod(selector, <HInstruction>[iterator]));
+      return popBoolified();
+    }
+    void buildBody() {
+      SourceString name = const SourceString('current');
+      Selector call = new Selector.getter(name, currentElement.getLibrary());
+      bool hasGetter = compiler.world.hasAnyUserDefinedGetter(call);
+      push(new HInvokeDynamicGetter(call, null, iterator, hasGetter));
+
+      Element variable;
+      if (node.declaredIdentifier.asSend() != null) {
+        variable = elements[node.declaredIdentifier];
+      } else {
+        assert(node.declaredIdentifier.asVariableDefinitions() != null);
+        VariableDefinitions variableDefinitions = node.declaredIdentifier;
+        variable = elements[variableDefinitions.definitions.nodes.head];
+      }
+      HInstruction oldVariable = pop();
+      if (variable.isErroneous()) {
+        generateThrowNoSuchMethod(node,
+                                  getTargetName(variable, 'set'),
+                                  argumentValues: <HInstruction>[oldVariable]);
+        pop();
+      } else {
+        localsHandler.updateLocal(variable, oldVariable);
+      }
+
+      visit(node.body);
+    }
+    handleLoop(node, buildInitializer, buildCondition, () {}, buildBody);
+  }
+
+  visitLabel(Label node) {
+    compiler.internalError('SsaBuilder.visitLabel', node: node);
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    Statement body = node.statement;
+    if (body is Loop || body is SwitchStatement) {
+      // Loops and switches handle their own labels.
+      visit(body);
+      return;
+    }
+    // Non-loop statements can only be break targets, not continue targets.
+    TargetElement targetElement = elements[body];
+    if (targetElement == null || !identical(targetElement.statement, body)) {
+      // Labeled statements with no element on the body have no breaks.
+      // A different target statement only happens if the body is itself
+      // a break or continue for a different target. In that case, this
+      // label is also always unused.
+      visit(body);
+      return;
+    }
+    LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler);
+    assert(targetElement.isBreakTarget);
+    JumpHandler handler = new JumpHandler(this, targetElement);
+    // Introduce a new basic block.
+    HBasicBlock entryBlock = openNewBlock();
+    visit(body);
+    SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock);
+
+    HBasicBlock joinBlock = graph.addNewBlock();
+    List<LocalsHandler> breakLocals = <LocalsHandler>[];
+    handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
+      breakInstruction.block.addSuccessor(joinBlock);
+      breakLocals.add(locals);
+    });
+    bool hasBreak = breakLocals.length > 0;
+    if (!isAborted()) {
+      goto(current, joinBlock);
+      breakLocals.add(localsHandler);
+    }
+    open(joinBlock);
+    localsHandler = beforeLocals.mergeMultiple(breakLocals, joinBlock);
+
+    if (hasBreak) {
+      // There was at least one reachable break, so the label is needed.
+      entryBlock.setBlockFlow(
+          new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph),
+                                       handler.labels()),
+          joinBlock);
+    }
+    handler.close();
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    if (node.isConst()) {
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+      return;
+    }
+    List<HInstruction> inputs = <HInstruction>[];
+    for (Link<Node> link = node.entries.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      visit(link.head);
+      inputs.addLast(pop());
+      inputs.addLast(pop());
+    }
+    HLiteralList keyValuePairs = new HLiteralList(inputs);
+    add(keyValuePairs);
+    pushInvokeHelper1(backend.getMapMaker(), keyValuePairs,
+        new HType.fromBoundedType(compiler.mapClass.computeType(compiler),
+                                  compiler,
+                                  false));
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    visit(node.value);
+    visit(node.key);
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visit(node.expression);
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    if (tryBuildConstantSwitch(node)) return;
+
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+    HBasicBlock startBlock = openNewBlock();
+    visit(node.expression);
+    HInstruction expression = pop();
+    if (node.cases.isEmpty) {
+      return;
+    }
+
+    Link<Node> cases = node.cases.nodes;
+    JumpHandler jumpHandler = createJumpHandler(node);
+
+    buildSwitchCases(cases, expression);
+
+    HBasicBlock lastBlock = lastOpenedBlock;
+
+    // Create merge block for break targets.
+    HBasicBlock joinBlock = new HBasicBlock();
+    List<LocalsHandler> caseLocals = <LocalsHandler>[];
+    jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
+      instruction.block.addSuccessor(joinBlock);
+      caseLocals.add(locals);
+    });
+    if (!isAborted()) {
+      // The current flow is only aborted if the switch has a default that
+      // aborts (all previous cases must abort, and if there is no default,
+      // it's possible to miss all the cases).
+      caseLocals.add(localsHandler);
+      goto(current, joinBlock);
+    }
+    if (caseLocals.length != 0) {
+      graph.addBlock(joinBlock);
+      open(joinBlock);
+      if (caseLocals.length == 1) {
+        localsHandler = caseLocals[0];
+      } else {
+        localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock);
+      }
+    } else {
+      // The joinblock is not used.
+      joinBlock = null;
+    }
+    startBlock.setBlockFlow(
+        new HLabeledBlockInformation.implicit(
+            new HSubGraphBlockInformation(new SubGraph(startBlock, lastBlock)),
+            elements[node]),
+        joinBlock);
+    jumpHandler.close();
+  }
+
+  bool tryBuildConstantSwitch(SwitchStatement node) {
+    Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>();
+    // First check whether all case expressions are compile-time constants,
+    // and all have the same type that doesn't override operator==.
+    // TODO(lrn): Move the constant resolution to the resolver, so
+    // we can report an error before reaching the backend.
+    DartType firstConstantType = null;
+    bool failure = false;
+    for (SwitchCase switchCase in node.cases) {
+      for (Node labelOrCase in switchCase.labelsAndCases) {
+        if (labelOrCase is CaseMatch) {
+          CaseMatch match = labelOrCase;
+          Constant constant =
+            compiler.constantHandler.tryCompileNodeWithDefinitions(
+                match.expression, elements);
+          if (constant == null) {
+            compiler.reportWarning(match.expression,
+                MessageKind.NOT_A_COMPILE_TIME_CONSTANT.error());
+            failure = true;
+            continue;
+          }
+          if (firstConstantType == null) {
+            firstConstantType = constant.computeType(compiler);
+            if (nonPrimitiveTypeOverridesEquals(constant)) {
+              compiler.reportWarning(match.expression,
+                  MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS.error());
+              failure = true;
+            }
+          } else {
+            DartType constantType =
+                constant.computeType(compiler);
+            if (constantType != firstConstantType) {
+              compiler.reportWarning(match.expression,
+                  MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL.error());
+              failure = true;
+            }
+          }
+          constants[labelOrCase] = constant;
+        } else {
+          compiler.reportWarning(node, "Unsupported: Labels on cases");
+          failure = true;
+        }
+      }
+    }
+    if (failure) {
+      return false;
+    }
+
+    // TODO(ngeoffray): Handle switch-instruction in bailout code.
+    work.allowSpeculativeOptimization = false;
+    // Then build a switch structure.
+    HBasicBlock expressionStart = openNewBlock();
+    visit(node.expression);
+    HInstruction expression = pop();
+    if (node.cases.isEmpty) {
+      return true;
+    }
+    HBasicBlock expressionEnd = current;
+
+    HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]);
+    HBasicBlock expressionBlock = close(switchInstruction);
+    JumpHandler jumpHandler = createJumpHandler(node);
+    LocalsHandler savedLocals = localsHandler;
+
+    List<List<Constant>> matchExpressions = <List<Constant>>[];
+    List<HStatementInformation> statements = <HStatementInformation>[];
+    bool hasDefault = false;
+    Element getFallThroughErrorElement =
+        compiler.findHelper(const SourceString("getFallThroughError"));
+    HasNextIterator<Node> caseIterator =
+        new HasNextIterator<Node>(node.cases.iterator);
+    while (caseIterator.hasNext) {
+      SwitchCase switchCase = caseIterator.next();
+      List<Constant> caseConstants = <Constant>[];
+      HBasicBlock block = graph.addNewBlock();
+      for (Node labelOrCase in switchCase.labelsAndCases) {
+        if (labelOrCase is CaseMatch) {
+          Constant constant = constants[labelOrCase];
+          caseConstants.add(constant);
+          HConstant hConstant = graph.addConstant(constant);
+          switchInstruction.inputs.add(hConstant);
+          hConstant.usedBy.add(switchInstruction);
+          expressionBlock.addSuccessor(block);
+        }
+      }
+      matchExpressions.add(caseConstants);
+
+      if (switchCase.isDefaultCase) {
+        // An HSwitch has n inputs and n+1 successors, the last being the
+        // default case.
+        expressionBlock.addSuccessor(block);
+        hasDefault = true;
+      }
+      open(block);
+      localsHandler = new LocalsHandler.from(savedLocals);
+      visit(switchCase.statements);
+      if (!isAborted() && caseIterator.hasNext) {
+        pushInvokeHelper0(getFallThroughErrorElement, HType.UNKNOWN);
+        HInstruction error = pop();
+        close(new HThrow(error));
+      }
+      statements.add(
+          new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock)));
+    }
+
+    // Add a join-block if necessary.
+    // We create [joinBlock] early, and then go through the cases that might
+    // want to jump to it. In each case, if we add [joinBlock] as a successor
+    // of another block, we also add an element to [caseLocals] that is used
+    // to create the phis in [joinBlock].
+    // If we never jump to the join block, [caseLocals] will stay empty, and
+    // the join block is never added to the graph.
+    HBasicBlock joinBlock = new HBasicBlock();
+    List<LocalsHandler> caseLocals = <LocalsHandler>[];
+    jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
+      instruction.block.addSuccessor(joinBlock);
+      caseLocals.add(locals);
+    });
+    if (!isAborted()) {
+      current.close(new HGoto());
+      lastOpenedBlock.addSuccessor(joinBlock);
+      caseLocals.add(localsHandler);
+    }
+    if (!hasDefault) {
+      // The current flow is only aborted if the switch has a default that
+      // aborts (all previous cases must abort, and if there is no default,
+      // it's possible to miss all the cases).
+      expressionEnd.addSuccessor(joinBlock);
+      caseLocals.add(savedLocals);
+    }
+    assert(caseLocals.length == joinBlock.predecessors.length);
+    if (caseLocals.length != 0) {
+      graph.addBlock(joinBlock);
+      open(joinBlock);
+      if (caseLocals.length == 1) {
+        localsHandler = caseLocals[0];
+      } else {
+        localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock);
+      }
+    } else {
+      // The joinblock is not used.
+      joinBlock = null;
+    }
+
+    HSubExpressionBlockInformation expressionInfo =
+        new HSubExpressionBlockInformation(new SubExpression(expressionStart,
+                                                             expressionEnd));
+    expressionStart.setBlockFlow(
+        new HSwitchBlockInformation(expressionInfo,
+                                    matchExpressions,
+                                    statements,
+                                    hasDefault,
+                                    jumpHandler.target,
+                                    jumpHandler.labels()),
+        joinBlock);
+
+    jumpHandler.close();
+    return true;
+  }
+
+  bool nonPrimitiveTypeOverridesEquals(Constant constant) {
+    // Function values override equals. Even static ones, since
+    // they inherit from [Function].
+    if (constant.isFunction()) return true;
+
+    // [Map] and [List] do not override equals.
+    // If constant is primitive, just return false. We know
+    // about the equals methods of num/String classes.
+    if (!constant.isConstructedObject()) return false;
+
+    ConstructedConstant constructedConstant = constant;
+    DartType type = constructedConstant.type;
+    assert(type != null);
+    Element element = type.element;
+    // If the type is not a class, we'll just assume it overrides
+    // operator==. Typedefs do, since [Function] does.
+    if (!element.isClass()) return true;
+    ClassElement classElement = element;
+    return typeOverridesObjectEquals(classElement);
+  }
+
+  bool typeOverridesObjectEquals(ClassElement classElement) {
+    Element operatorEq =
+        lookupOperator(classElement, const SourceString('=='));
+    if (operatorEq == null) return false;
+    // If the operator== declaration is in Object, it's not overridden.
+    return (operatorEq.getEnclosingClass() != compiler.objectClass);
+  }
+
+  Element lookupOperator(ClassElement classElement, SourceString operatorName) {
+    SourceString dartMethodName =
+        Elements.constructOperatorName(operatorName, false);
+    return classElement.lookupMember(dartMethodName);
+  }
+
+
+  // Recursively build an if/else structure to match the cases.
+  void buildSwitchCases(Link<Node> cases, HInstruction expression,
+                        [int encounteredCaseTypes = 0]) {
+    final int NO_TYPE = 0;
+    final int INT_TYPE = 1;
+    final int STRING_TYPE = 2;
+    final int CONFLICT_TYPE = 3;
+    int combine(int type1, int type2) => type1 | type2;
+
+    SwitchCase node = cases.head;
+    // Called for the statements on all but the last case block.
+    // Ensures that a user expecting a fallthrough gets an error.
+    void visitStatementsAndAbort() {
+      visit(node.statements);
+      if (!isAborted()) {
+        compiler.reportWarning(node, 'Missing break at end of switch case');
+        Element element =
+            compiler.findHelper(const SourceString("getFallThroughError"));
+        pushInvokeHelper0(element, HType.UNKNOWN);
+        HInstruction error = pop();
+        close(new HThrow(error));
+      }
+    }
+
+    Link<Node> skipLabels(Link<Node> labelsAndCases) {
+      while (!labelsAndCases.isEmpty && labelsAndCases.head is Label) {
+        labelsAndCases = labelsAndCases.tail;
+      }
+      return labelsAndCases;
+    }
+
+    Link<Node> labelsAndCases = skipLabels(node.labelsAndCases.nodes);
+    if (labelsAndCases.isEmpty) {
+      // Default case with no expressions.
+      if (!node.isDefaultCase) {
+        compiler.internalError("Case with no expression and not default",
+                               node: node);
+      }
+      visit(node.statements);
+      // This must be the final case (otherwise "default" would be invalid),
+      // so we don't need to check for fallthrough.
+      return;
+    }
+
+    // Recursively build the test conditions. Leaves the result on the
+    // expression stack.
+    void buildTests(Link<Node> remainingCases) {
+      // Build comparison for one case expression.
+      void left() {
+        CaseMatch match = remainingCases.head;
+        // TODO(lrn): Move the constant resolution to the resolver, so
+        // we can report an error before reaching the backend.
+        Constant constant =
+            compiler.constantHandler.tryCompileNodeWithDefinitions(
+                match.expression, elements);
+        if (constant != null) {
+          stack.add(graph.addConstant(constant));
+        } else {
+          visit(match.expression);
+        }
+        push(new HIdentity(pop(), expression));
+      }
+
+      // If this is the last expression, just return it.
+      Link<Node> tail = skipLabels(remainingCases.tail);
+      if (tail.isEmpty) {
+        left();
+        return;
+      }
+
+      void right() {
+        buildTests(tail);
+      }
+      SsaBranchBuilder branchBuilder =
+          new SsaBranchBuilder(this, remainingCases.head);
+      branchBuilder.handleLogicalAndOr(left, right, isAnd: false);
+    }
+
+    if (node.isDefaultCase) {
+      // Default case must be last.
+      assert(cases.tail.isEmpty);
+      // Perform the tests until one of them match, but then always execute the
+      // statements.
+      // TODO(lrn): Stop performing tests when all expressions are compile-time
+      // constant strings or integers.
+      handleIf(node, () { buildTests(labelsAndCases); }, (){}, null);
+      visit(node.statements);
+    } else {
+      if (cases.tail.isEmpty) {
+        handleIf(node,
+                 () { buildTests(labelsAndCases); },
+                 () { visit(node.statements); },
+                 null);
+      } else {
+        handleIf(node,
+                 () { buildTests(labelsAndCases); },
+                 () { visitStatementsAndAbort(); },
+                 () { buildSwitchCases(cases.tail, expression,
+                                       encounteredCaseTypes); });
+      }
+    }
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    compiler.internalError('SsaBuilder.visitSwitchCase');
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    compiler.internalError('SsaBuilder.visitCaseMatch');
+  }
+
+  visitTryStatement(TryStatement node) {
+    work.allowSpeculativeOptimization = false;
+    // Save the current locals. The catch block and the finally block
+    // must not reuse the existing locals handler. None of the variables
+    // that have been defined in the body-block will be used, but for
+    // loops we will add (unnecessary) phis that will reference the body
+    // variables. This makes it look as if the variables were used
+    // in a non-dominated block.
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+    HBasicBlock enterBlock = openNewBlock();
+    HTry tryInstruction = new HTry();
+    close(tryInstruction);
+    bool oldInTryStatement = inTryStatement;
+    inTryStatement = true;
+
+    HBasicBlock startTryBlock;
+    HBasicBlock endTryBlock;
+    HBasicBlock startCatchBlock;
+    HBasicBlock endCatchBlock;
+    HBasicBlock startFinallyBlock;
+    HBasicBlock endFinallyBlock;
+
+    startTryBlock = graph.addNewBlock();
+    open(startTryBlock);
+    visit(node.tryBlock);
+    if (!isAborted()) endTryBlock = close(new HGoto());
+    SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock);
+    SubGraph catchGraph = null;
+    HLocalValue exception = null;
+
+    if (!node.catchBlocks.isEmpty) {
+      localsHandler = new LocalsHandler.from(savedLocals);
+      startCatchBlock = graph.addNewBlock();
+      open(startCatchBlock);
+      // TODO(kasperl): Bad smell. We shouldn't be constructing elements here.
+      // Note that the name of this element is irrelevant.
+      Element element = new ElementX(const SourceString('exception'),
+                                     ElementKind.PARAMETER,
+                                     currentElement);
+      exception = new HLocalValue(element);
+      add(exception);
+      HInstruction oldRethrowableException = rethrowableException;
+      rethrowableException = exception;
+
+      pushInvokeHelper1(
+          backend.getExceptionUnwrapper(), exception, HType.UNKNOWN);
+      HInvokeStatic unwrappedException = pop();
+      tryInstruction.exception = exception;
+      Link<Node> link = node.catchBlocks.nodes;
+
+      void pushCondition(CatchBlock catchBlock) {
+        if (catchBlock.onKeyword != null) {
+          DartType type = elements.getType(catchBlock.type);
+          if (type == null) {
+            compiler.cancel('On with unresolved type',
+                            node: catchBlock.type);
+          }
+          HInstruction condition =
+              new HIs(type, <HInstruction>[unwrappedException]);
+          push(condition);
+        }
+        else {
+          VariableDefinitions declaration = catchBlock.formals.nodes.head;
+          HInstruction condition = null;
+          if (declaration.type == null) {
+            condition = graph.addConstantBool(true, constantSystem);
+            stack.add(condition);
+          } else {
+            // TODO(aprelev@gmail.com): Once old catch syntax is removed
+            // "if" condition above and this "else" branch should be deleted as
+            // type of declared variable won't matter for the catch
+            // condition.
+            DartType type = elements.getType(declaration.type);
+            if (type == null) {
+              compiler.cancel('Catch with unresolved type', node: catchBlock);
+            }
+            condition =
+                new HIs(type, <HInstruction>[unwrappedException], nullOk: true);
+            push(condition);
+          }
+        }
+      }
+
+      void visitThen() {
+        CatchBlock catchBlock = link.head;
+        link = link.tail;
+        if (catchBlock.exception != null) {
+          localsHandler.updateLocal(elements[catchBlock.exception],
+                                    unwrappedException);
+        }
+        Node trace = catchBlock.trace;
+        if (trace != null) {
+          pushInvokeHelper1(
+              backend.getTraceFromException(), exception, HType.UNKNOWN);
+          HInstruction traceInstruction = pop();
+          localsHandler.updateLocal(elements[trace], traceInstruction);
+        }
+        visit(catchBlock);
+      }
+
+      void visitElse() {
+        if (link.isEmpty) {
+          close(new HThrow(exception, isRethrow: true));
+        } else {
+          CatchBlock newBlock = link.head;
+          handleIf(node,
+                   () { pushCondition(newBlock); },
+                   visitThen, visitElse);
+        }
+      }
+
+      CatchBlock firstBlock = link.head;
+      handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse);
+      if (!isAborted()) endCatchBlock = close(new HGoto());
+
+      rethrowableException = oldRethrowableException;
+      tryInstruction.catchBlock = startCatchBlock;
+      catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock);
+    }
+
+    SubGraph finallyGraph = null;
+    if (node.finallyBlock != null) {
+      localsHandler = new LocalsHandler.from(savedLocals);
+      startFinallyBlock = graph.addNewBlock();
+      open(startFinallyBlock);
+      visit(node.finallyBlock);
+      if (!isAborted()) endFinallyBlock = close(new HGoto());
+      tryInstruction.finallyBlock = startFinallyBlock;
+      finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock);
+    }
+
+    HBasicBlock exitBlock = graph.addNewBlock();
+
+    addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); }
+    addExitTrySuccessor(successor) {
+      if (successor == null) return;
+      // Iterate over all blocks created inside this try/catch, and
+      // attach successor information to blocks that end with
+      // [HExitTry].
+      for (int i = startTryBlock.id; i < successor.id; i++) {
+        HBasicBlock block = graph.blocks[i];
+        var last = block.last;
+        if (last is HExitTry) {
+          block.addSuccessor(successor);
+        } else if (last is HTry) {
+          // Skip all blocks inside this nested try/catch.
+          i = last.joinBlock.id;
+        }
+      }
+    }
+
+    // Setup all successors. The entry block that contains the [HTry]
+    // has 1) the body, 2) the catch, 3) the finally, and 4) the exit
+    // blocks as successors.
+    enterBlock.addSuccessor(startTryBlock);
+    addOptionalSuccessor(enterBlock, startCatchBlock);
+    addOptionalSuccessor(enterBlock, startFinallyBlock);
+    enterBlock.addSuccessor(exitBlock);
+
+    // The body has either the catch or the finally block as successor.
+    if (endTryBlock != null) {
+      assert(startCatchBlock != null || startFinallyBlock != null);
+      endTryBlock.addSuccessor(
+          startCatchBlock != null ? startCatchBlock : startFinallyBlock);
+    }
+
+    // The catch block has either the finally or the exit block as
+    // successor.
+    if (endCatchBlock != null) {
+      endCatchBlock.addSuccessor(
+          startFinallyBlock != null ? startFinallyBlock : exitBlock);
+    }
+
+    // The finally block has the exit block as successor.
+    if (endFinallyBlock != null) {
+      endFinallyBlock.addSuccessor(exitBlock);
+    }
+
+    // If a block inside try/catch aborts (eg with a return statement),
+    // we explicitely mark this block a predecessor of the catch
+    // block and the finally block.
+    addExitTrySuccessor(startCatchBlock);
+    addExitTrySuccessor(startFinallyBlock);
+
+    // Use the locals handler not altered by the catch and finally
+    // blocks.
+    localsHandler = savedLocals;
+    open(exitBlock);
+    enterBlock.setBlockFlow(
+        new HTryBlockInformation(
+          wrapStatementGraph(bodyGraph),
+          exception,
+          wrapStatementGraph(catchGraph),
+          wrapStatementGraph(finallyGraph)),
+        exitBlock);
+    inTryStatement = oldInTryStatement;
+  }
+
+  visitScriptTag(ScriptTag node) {
+    compiler.unimplemented('SsaBuilder.visitScriptTag', node: node);
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    visit(node.block);
+  }
+
+  visitTypedef(Typedef node) {
+    compiler.unimplemented('SsaBuilder.visitTypedef', node: node);
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    compiler.internalError('SsaBuilder.visitTypeVariable');
+  }
+
+  HType mapBaseType(BaseType baseType) {
+    if (!baseType.isClass()) return HType.UNKNOWN;
+    ClassBaseType classBaseType = baseType;
+    return new HType.fromBoundedType(
+        classBaseType.element.computeType(compiler), compiler, false);
+  }
+
+  HType mapInferredType(ConcreteType concreteType) {
+    if (concreteType == null) return HType.UNKNOWN;
+    HType ssaType = HType.CONFLICTING;
+    for (BaseType baseType in concreteType.baseTypes) {
+      ssaType = ssaType.union(mapBaseType(baseType), compiler);
+    }
+    assert(!ssaType.isConflicting());
+    return ssaType;
+  }
+
+  HType mapNativeType(type) {
+    if (type == native.SpecialType.JsObject) {
+      return new HBoundedType.exact(
+          compiler.objectClass.computeType(compiler));
+    } else if (type == native.SpecialType.JsArray) {
+      return HType.READABLE_ARRAY;
+    } else {
+      return new HType.fromBoundedType(type, compiler, false);
+    }
+  }
+
+  HType mapNativeBehaviorType(native.NativeBehavior nativeBehavior) {
+    if (nativeBehavior.typesInstantiated.isEmpty) return HType.UNKNOWN;
+
+    HType ssaType = HType.CONFLICTING;
+    for (final type in nativeBehavior.typesInstantiated) {
+      ssaType = ssaType.union(mapNativeType(type), compiler);
+    }
+    assert(!ssaType.isConflicting());
+    return ssaType;
+  }
+}
+
+/**
+ * Visitor that handles generation of string literals (LiteralString,
+ * StringInterpolation), and otherwise delegates to the given visitor for
+ * non-literal subexpressions.
+ * TODO(lrn): Consider whether to handle compile time constant int/boolean
+ * expressions as well.
+ */
+class StringBuilderVisitor extends Visitor {
+  final SsaBuilder builder;
+  final Node diagnosticNode;
+
+  /**
+   * The string value generated so far.
+   */
+  HInstruction result = null;
+
+  StringBuilderVisitor(this.builder, this.diagnosticNode);
+
+  void visit(Node node) {
+    node.accept(this);
+  }
+
+  visitNode(Node node) {
+    builder.compiler.internalError('unexpected node', node: node);
+  }
+
+  void visitExpression(Node node) {
+    node.accept(builder);
+    HInstruction expression = builder.pop();
+    result = (result == null) ? expression : concat(result, expression);
+  }
+
+  void visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+  }
+
+  void visitStringInterpolationPart(StringInterpolationPart node) {
+    visit(node.expression);
+    visit(node.string);
+  }
+
+  void visitStringJuxtaposition(StringJuxtaposition node) {
+    node.visitChildren(this);
+  }
+
+  void visitNodeList(NodeList node) {
+     node.visitChildren(this);
+  }
+
+  HInstruction concat(HInstruction left, HInstruction right) {
+    HInstruction instruction = new HStringConcat(left, right, diagnosticNode);
+    builder.add(instruction);
+    return instruction;
+  }
+}
+
+/**
+ * This class visits the method that is a candidate for inlining and
+ * finds whether it is too difficult to inline.
+ */
+class InlineWeeder extends Visitor {
+  final TreeElements elements;
+  bool seenReturn = false;
+  bool tooDifficult = false;
+
+  InlineWeeder(this.elements);
+
+  static bool canBeInlined(FunctionExpression functionExpression,
+                           TreeElements elements) {
+    InlineWeeder weeder = new InlineWeeder(elements);
+    weeder.visit(functionExpression.body);
+    if (weeder.tooDifficult) return false;
+    return true;
+  }
+
+  void visit(Node node) {
+    node.accept(this);
+  }
+
+  void visitNode(Node node) {
+    if (seenReturn) {
+      tooDifficult = true;
+    } else {
+      node.visitChildren(this);
+    }
+  }
+
+  void visitFunctionExpression(Node node) {
+    tooDifficult = true;
+  }
+
+  void visitFunctionDeclaration(Node node) {
+    tooDifficult = true;
+  }
+
+  void visitSend(Send node) {
+    if (node.isParameterCheck) {
+      tooDifficult = true;
+      return;
+    }
+    node.visitChildren(this);
+  }
+
+  visitLoop(Node node) {
+    node.visitChildren(this);
+    if (seenReturn) tooDifficult = true;
+  }
+
+  void visitReturn(Return node) {
+    if (seenReturn
+        || identical(node.getBeginToken().stringValue, 'native')
+        || node.isRedirectingFactoryBody) {
+      tooDifficult = true;
+      return;
+    }
+    node.visitChildren(this);
+    seenReturn = true;
+  }
+
+  void visitTryStatement(Node node) {
+    tooDifficult = true;
+  }
+
+  void visitThrow(Node node) {
+    tooDifficult = true;
+  }
+}
+
+class InliningState {
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [function] must be an implementation element.
+   */
+  final PartialFunctionElement function;
+  final Element oldReturnElement;
+  final DartType oldReturnType;
+  final TreeElements oldElements;
+  final List<HInstruction> oldStack;
+
+  InliningState(this.function,
+                this.oldReturnElement,
+                this.oldReturnType,
+                this.oldElements,
+                this.oldStack) {
+    assert(function.isImplementation);
+  }
+}
+
+class SsaBranch {
+  final SsaBranchBuilder branchBuilder;
+  final HBasicBlock block;
+  LocalsHandler startLocals;
+  LocalsHandler exitLocals;
+  SubGraph graph;
+
+  SsaBranch(this.branchBuilder) : block = new HBasicBlock();
+}
+
+class SsaBranchBuilder {
+  final SsaBuilder builder;
+  final Node diagnosticNode;
+
+  SsaBranchBuilder(this.builder, [this.diagnosticNode]);
+
+  Compiler get compiler => builder.compiler;
+
+  void checkNotAborted() {
+    if (builder.isAborted()) {
+      compiler.unimplemented("aborted control flow", node: diagnosticNode);
+    }
+  }
+
+  void buildCondition(void visitCondition(),
+                      SsaBranch conditionBranch,
+                      SsaBranch thenBranch,
+                      SsaBranch elseBranch) {
+    startBranch(conditionBranch);
+    visitCondition();
+    checkNotAborted();
+    assert(identical(builder.current, builder.lastOpenedBlock));
+    HInstruction conditionValue = builder.popBoolified();
+    HIf branch = new HIf(conditionValue);
+    HBasicBlock conditionExitBlock = builder.current;
+    builder.close(branch);
+    conditionBranch.exitLocals = builder.localsHandler;
+    conditionExitBlock.addSuccessor(thenBranch.block);
+    conditionExitBlock.addSuccessor(elseBranch.block);
+    bool conditionBranchLocalsCanBeReused =
+        mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true);
+    mergeLocals(conditionBranch, elseBranch,
+                mayReuseFromLocals: conditionBranchLocalsCanBeReused);
+
+    conditionBranch.graph =
+        new SubExpression(conditionBranch.block, conditionExitBlock);
+  }
+
+  /**
+   * Returns true if the locals of the [fromBranch] may be reused. A [:true:]
+   * return value implies that [mayReuseFromLocals] was set to [:true:].
+   */
+  bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch,
+                   {bool mayReuseFromLocals}) {
+    LocalsHandler fromLocals = fromBranch.exitLocals;
+    if (toBranch.startLocals == null) {
+      if (mayReuseFromLocals) {
+        toBranch.startLocals = fromLocals;
+        return false;
+      } else {
+        toBranch.startLocals = new LocalsHandler.from(fromLocals);
+        return true;
+      }
+    } else {
+      toBranch.startLocals.mergeWith(fromLocals, toBranch.block);
+      return true;
+    }
+  }
+
+  void startBranch(SsaBranch branch) {
+    builder.graph.addBlock(branch.block);
+    builder.localsHandler = branch.startLocals;
+    builder.open(branch.block);
+  }
+
+  HInstruction buildBranch(SsaBranch branch,
+                           void visitBranch(),
+                           SsaBranch joinBranch,
+                           bool isExpression) {
+    startBranch(branch);
+    visitBranch();
+    branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock);
+    branch.exitLocals = builder.localsHandler;
+    if (!builder.isAborted()) {
+      builder.goto(builder.current, joinBranch.block);
+      mergeLocals(branch, joinBranch, mayReuseFromLocals: true);
+    }
+    if (isExpression) {
+      checkNotAborted();
+      return builder.pop();
+    }
+    return null;
+  }
+
+  handleIf(void visitCondition(), void visitThen(), void visitElse()) {
+    if (visitElse == null) {
+      // Make sure to have an else part to avoid a critical edge. A
+      // critical edge is an edge that connects a block with multiple
+      // successors to a block with multiple predecessors. We avoid
+      // such edges because they prevent inserting copies during code
+      // generation of phi instructions.
+      visitElse = () {};
+    }
+
+    _handleDiamondBranch(visitCondition, visitThen, visitElse, false);
+  }
+
+  handleConditional(void visitCondition(), void visitThen(), void visitElse()) {
+    assert(visitElse != null);
+    _handleDiamondBranch(visitCondition, visitThen, visitElse, true);
+  }
+
+  void handleLogicalAndOr(void left(), void right(), {bool isAnd}) {
+    // x && y is transformed into:
+    //   t0 = boolify(x);
+    //   if (t0) {
+    //     t1 = boolify(y);
+    //   }
+    //   result = phi(t1, false);
+    //
+    // x || y is transformed into:
+    //   t0 = boolify(x);
+    //   if (not(t0)) {
+    //     t1 = boolify(y);
+    //   }
+    //   result = phi(t1, true);
+    HInstruction boolifiedLeft;
+    HInstruction boolifiedRight;
+
+    void visitCondition() {
+      left();
+      boolifiedLeft = builder.popBoolified();
+      builder.stack.add(boolifiedLeft);
+      if (!isAnd) {
+        builder.push(new HNot(builder.pop()));
+      }
+    }
+
+    void visitThen() {
+      right();
+      boolifiedRight = builder.popBoolified();
+    }
+
+    handleIf(visitCondition, visitThen, null);
+    HConstant notIsAnd =
+        builder.graph.addConstantBool(!isAnd, builder.constantSystem);
+    HPhi result = new HPhi.manyInputs(null,
+                                      <HInstruction>[boolifiedRight, notIsAnd]);
+    builder.current.addPhi(result);
+    builder.stack.add(result);
+  }
+
+  void handleLogicalAndOrWithLeftNode(Node left,
+                                      void visitRight(),
+                                      {bool isAnd}) {
+    // This method is similar to [handleLogicalAndOr] but optimizes the case
+    // where left is a logical "and" or logical "or".
+    //
+    // For example (x && y) && z is transformed into x && (y && z):
+    //   t0 = boolify(x);
+    //   if (t0) {
+    //     t1 = boolify(y);
+    //     if (t1) {
+    //       t2 = boolify(z);
+    //     }
+    //     t3 = phi(t2, false);
+    //   }
+    //   result = phi(t3, false);
+
+    Send send = left.asSend();
+    if (send != null &&
+        (isAnd ? send.isLogicalAnd : send.isLogicalOr)) {
+      Node newLeft = send.receiver;
+      Link<Node> link = send.argumentsNode.nodes;
+      assert(link.tail.isEmpty);
+      Node middle = link.head;
+      handleLogicalAndOrWithLeftNode(
+          newLeft,
+          () => handleLogicalAndOrWithLeftNode(middle, visitRight,
+                                               isAnd: isAnd),
+          isAnd: isAnd);
+    } else {
+      handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd);
+    }
+  }
+
+  void _handleDiamondBranch(void visitCondition(),
+                            void visitThen(),
+                            void visitElse(),
+                            bool isExpression) {
+    SsaBranch conditionBranch = new SsaBranch(this);
+    SsaBranch thenBranch = new SsaBranch(this);
+    SsaBranch elseBranch = new SsaBranch(this);
+    SsaBranch joinBranch = new SsaBranch(this);
+
+    conditionBranch.startLocals = builder.localsHandler;
+    builder.goto(builder.current, conditionBranch.block);
+
+    buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch);
+    HInstruction thenValue =
+        buildBranch(thenBranch, visitThen, joinBranch, isExpression);
+    HInstruction elseValue =
+        buildBranch(elseBranch, visitElse, joinBranch, isExpression);
+
+    if (isExpression) {
+      assert(thenValue != null && elseValue != null);
+      HPhi phi =
+          new HPhi.manyInputs(null, <HInstruction>[thenValue, elseValue]);
+      joinBranch.block.addPhi(phi);
+      builder.stack.add(phi);
+    }
+
+    HBasicBlock thenBlock = thenBranch.block;
+    HBasicBlock elseBlock = elseBranch.block;
+    HBasicBlock joinBlock;
+    // If at least one branch did not abort, open the joinBranch.
+    if (!joinBranch.block.predecessors.isEmpty) {
+      startBranch(joinBranch);
+      joinBlock = joinBranch.block;
+    }
+
+    HIfBlockInformation info =
+        new HIfBlockInformation(
+          new HSubExpressionBlockInformation(conditionBranch.graph),
+          new HSubGraphBlockInformation(thenBranch.graph),
+          new HSubGraphBlockInformation(elseBranch.graph));
+
+    HBasicBlock conditionStartBlock = conditionBranch.block;
+    conditionStartBlock.setBlockFlow(info, joinBlock);
+    SubGraph conditionGraph = conditionBranch.graph;
+    HIf branch = conditionGraph.end.last;
+    assert(branch is HIf);
+    branch.blockInformation = conditionStartBlock.blockFlow;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart
new file mode 100644
index 0000000..e5caac1
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart
@@ -0,0 +1,3006 @@
+// 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.
+
+part of ssa;
+
+class SsaCodeGeneratorTask extends CompilerTask {
+
+  final JavaScriptBackend backend;
+
+  SsaCodeGeneratorTask(JavaScriptBackend backend)
+      : this.backend = backend,
+        super(backend.compiler);
+  String get name => 'SSA code generator';
+  NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter;
+
+
+  js.Fun buildJavaScriptFunction(FunctionElement element,
+                                 List<js.Parameter> parameters,
+                                 js.Block body) {
+    FunctionExpression expression =
+        element.implementation.parseNode(backend.compiler);
+    js.Fun result = new js.Fun(parameters, body);
+    // TODO(johnniwinther): remove the 'element.patch' hack.
+    Element sourceElement = element.patch == null ? element : element.patch;
+    SourceFile sourceFile = sourceElement.getCompilationUnit().script.file;
+    // TODO(podivilov): find the right sourceFile here and remove offset checks
+    // below.
+    if (expression.getBeginToken().charOffset < sourceFile.text.length) {
+      result.sourcePosition = new SourceFileLocation(
+          sourceFile, expression.getBeginToken());
+    }
+    if (expression.getEndToken().charOffset < sourceFile.text.length) {
+      result.endSourcePosition = new SourceFileLocation(
+          sourceFile, expression.getEndToken());
+    }
+    return result;
+  }
+
+  CodeBuffer prettyPrint(js.Node node) {
+    var code = js.prettyPrint(node, compiler, allowVariableMinification: true);
+    return code;
+  }
+
+  js.Expression generateCode(CodegenWorkItem work, HGraph graph) {
+    if (work.element.isField()) {
+      return generateLazyInitializer(work, graph);
+    } else {
+      return generateMethod(work, graph);
+    }
+  }
+
+  js.Expression generateLazyInitializer(work, graph) {
+    return measure(() {
+      compiler.tracer.traceGraph("codegen", graph);
+      SsaOptimizedCodeGenerator codegen =
+          new SsaOptimizedCodeGenerator(backend, work);
+      codegen.visitGraph(graph);
+      return new js.Fun(codegen.parameters, codegen.body);
+    });
+  }
+
+  js.Expression generateMethod(CodegenWorkItem work, HGraph graph) {
+    return measure(() {
+      compiler.tracer.traceGraph("codegen", graph);
+      SsaOptimizedCodeGenerator codegen =
+          new SsaOptimizedCodeGenerator(backend, work);
+      codegen.visitGraph(graph);
+
+      FunctionElement element = work.element;
+      js.Block body;
+      ClassElement enclosingClass = element.getEnclosingClass();
+
+      if (element.isInstanceMember()
+          && enclosingClass.isNative()
+          && native.isOverriddenMethod(
+              element, enclosingClass, nativeEmitter)) {
+        // Record that this method is overridden. In case of optional
+        // arguments, the emitter will generate stubs to handle them,
+        // and needs to know if the method is overridden.
+        nativeEmitter.overriddenMethods.add(element);
+        StringBuffer buffer = new StringBuffer();
+        body =
+            nativeEmitter.generateMethodBodyWithPrototypeCheckForElement(
+                element, codegen.body, codegen.parameters);
+      } else {
+        body = codegen.body;
+      }
+
+      return buildJavaScriptFunction(element, codegen.parameters, body);
+    });
+  }
+
+  js.Expression generateBailoutMethod(CodegenWorkItem work, HGraph graph) {
+    return measure(() {
+      compiler.tracer.traceGraph("codegen-bailout", graph);
+
+      SsaUnoptimizedCodeGenerator codegen =
+          new SsaUnoptimizedCodeGenerator(backend, work);
+      codegen.visitGraph(graph);
+
+      js.Block body = new js.Block(<js.Statement>[]);
+      body.statements.add(codegen.body);
+      js.Fun fun =
+          buildJavaScriptFunction(work.element, codegen.newParameters, body);
+      return fun;
+    });
+  }
+}
+
+// Stop-gap until the core classes have such a class.
+class OrderedSet<T> {
+  final LinkedHashMap<T, bool> map = new LinkedHashMap<T, bool>();
+
+  void add(T x) {
+    if (!map.containsKey(x)) {
+      map[x] = true;
+    }
+  }
+
+  bool contains(T x) => map.containsKey(x);
+
+  bool remove(T x) => map.remove(x) != null;
+
+  bool get isEmpty => map.isEmpty;
+
+  void forEach(f) => map.keys.forEach(f);
+
+  T get first {
+    var iterator = map.keys.iterator;
+    if (!iterator.moveNext()) throw new StateError("No elements");
+    return iterator.current;
+  }
+
+  get length => map.length;
+}
+
+typedef void ElementAction(Element element);
+
+abstract class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
+  /**
+   * Returned by [expressionType] to tell how code can be generated for
+   * a subgraph.
+   * - [TYPE_STATEMENT] means that the graph must be generated as a statement,
+   * which is always possible.
+   * - [TYPE_EXPRESSION] means that the graph can be generated as an expression,
+   * or possibly several comma-separated expressions.
+   * - [TYPE_DECLARATION] means that the graph can be generated as an
+   * expression, and that it only generates expressions of the form
+   *   variable = expression
+   * which are also valid as parts of a "var" declaration.
+   */
+  static const int TYPE_STATEMENT = 0;
+  static const int TYPE_EXPRESSION = 1;
+  static const int TYPE_DECLARATION = 2;
+
+  /**
+   * Whether we are currently generating expressions instead of statements.
+   * This includes declarations, which are generated as expressions.
+   */
+  bool isGeneratingExpression = false;
+
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final HTypeMap types;
+
+  final Set<HInstruction> generateAtUseSite;
+  final Set<HInstruction> controlFlowOperators;
+  final Map<Element, ElementAction> breakAction;
+  final Map<Element, ElementAction> continueAction;
+  final List<js.Parameter> parameters;
+
+  js.Block currentContainer;
+  js.Block get body => currentContainer;
+  List<js.Expression> expressionStack;
+  List<js.Block> oldContainerStack;
+
+  /**
+   * Contains the names of the instructions, as well as the parallel
+   * copies to perform on block transitioning.
+   */
+  VariableNames variableNames;
+  bool shouldGroupVarDeclarations = false;
+
+  /**
+   * While generating expressions, we can't insert variable declarations.
+   * Instead we declare them at the start of the function.  When minifying
+   * we do this most of the time, because it reduces the size unless there
+   * is only one variable.
+   */
+  final OrderedSet<String> collectedVariableDeclarations;
+
+  /**
+   * Set of variables and parameters that have already been declared.
+   */
+  final Set<String> declaredLocals;
+
+  int indent = 0;
+  HGraph currentGraph;
+
+  // Records a block-information that is being handled specially.
+  // Used to break bad recursion.
+  HBlockInformation currentBlockInformation;
+  // The subgraph is used to delimit traversal for some constructions, e.g.,
+  // if branches.
+  SubGraph subGraph;
+
+  SsaCodeGenerator(this.backend, CodegenWorkItem work)
+    : this.work = work,
+      this.types =
+          (work.compilationContext as JavaScriptItemCompilationContext).types,
+      declaredLocals = new Set<String>(),
+      collectedVariableDeclarations = new OrderedSet<String>(),
+      currentContainer = new js.Block.empty(),
+      parameters = <js.Parameter>[],
+      expressionStack = <js.Expression>[],
+      oldContainerStack = <js.Block>[],
+      generateAtUseSite = new Set<HInstruction>(),
+      controlFlowOperators = new Set<HInstruction>(),
+      breakAction = new Map<Element, ElementAction>(),
+      continueAction = new Map<Element, ElementAction>();
+
+  Compiler get compiler => backend.compiler;
+  NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter;
+  CodegenEnqueuer get world => backend.compiler.enqueuer.codegen;
+
+  bool isGenerateAtUseSite(HInstruction instruction) {
+    return generateAtUseSite.contains(instruction);
+  }
+
+  bool isNonNegativeInt32Constant(HInstruction instruction) {
+    if (instruction.isConstantInteger()) {
+      HConstant constantInstruction = instruction;
+      PrimitiveConstant primitiveConstant = constantInstruction.constant;
+      int value = primitiveConstant.value;
+      if (value >= 0 && value < (1 << 31)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool hasNonBitOpUser(HInstruction instruction, Set<HPhi> phiSet) {
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HPhi) {
+        if (!phiSet.contains(user)) {
+          phiSet.add(user);
+          if (hasNonBitOpUser(user, phiSet)) return true;
+        }
+      } else if (user is! HBitNot && user is! HBinaryBitOp) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // We want the outcome of bit-operations to be positive. However, if
+  // the result of a bit-operation is only used by other bit
+  // operations we do not have to convert to an unsigned
+  // integer. Also, if we are using & with a positive constant we know
+  // that the result is positive already and need no conversion.
+  bool requiresUintConversion(HInstruction instruction) {
+    if (instruction is HBitAnd) {
+      HBitAnd bitAnd = instruction;
+      if (isNonNegativeInt32Constant(bitAnd.left) ||
+          isNonNegativeInt32Constant(bitAnd.right)) {
+        return false;
+      }
+    }
+    return hasNonBitOpUser(instruction, new Set<HPhi>());
+  }
+
+  /**
+   * If the [instruction] is not `null` it will be used to attach the position
+   * to the [statement].
+   */
+  void pushStatement(js.Statement statement, [HInstruction instruction]) {
+    assert(expressionStack.isEmpty);
+    if (instruction != null) {
+      attachLocation(statement, instruction);
+    }
+    currentContainer.statements.add(statement);
+  }
+
+  void insertStatementAtStart(js.Statement statement) {
+    currentContainer.statements.insertRange(0, 1, statement);
+  }
+
+  /**
+   * If the [instruction] is not `null` it will be used to attach the position
+   * to the [expression].
+   */
+  pushExpressionAsStatement(js.Expression expression,
+                            [HInstruction instruction]) {
+    pushStatement(new js.ExpressionStatement(expression), instruction);
+  }
+
+  /**
+   * If the [instruction] is not `null` it will be used to attach the position
+   * to the [expression].
+   */
+  push(js.Expression expression, [HInstruction instruction]) {
+    if (instruction != null) {
+      attachLocation(expression, instruction);
+    }
+    expressionStack.add(expression);
+  }
+
+  js.Expression pop() {
+    return expressionStack.removeLast();
+  }
+
+  attachLocationToLast(HInstruction instruction) {
+    attachLocation(expressionStack.last, instruction);
+  }
+
+  js.Node attachLocation(js.Node jsNode, HInstruction instruction) {
+    jsNode.sourcePosition = instruction.sourcePosition;
+    return jsNode;
+  }
+
+  js.Node attachLocationRange(js.Node jsNode,
+                              SourceFileLocation sourcePosition,
+                              SourceFileLocation endSourcePosition) {
+    jsNode.sourcePosition = sourcePosition;
+    jsNode.endSourcePosition = endSourcePosition;
+    return jsNode;
+  }
+
+  visitTypeGuard(HTypeGuard node);
+  visitBailoutTarget(HBailoutTarget node);
+
+  beginGraph(HGraph graph);
+  endGraph(HGraph graph);
+
+  preLabeledBlock(HLabeledBlockInformation labeledBlockInfo);
+  startLabeledBlock(HLabeledBlockInformation labeledBlockInfo);
+  endLabeledBlock(HLabeledBlockInformation labeledBlockInfo);
+
+  void preGenerateMethod(HGraph graph) {
+    new SsaInstructionMerger(types, generateAtUseSite).visitGraph(graph);
+    new SsaConditionMerger(
+        types, generateAtUseSite, controlFlowOperators).visitGraph(graph);
+    SsaLiveIntervalBuilder intervalBuilder =
+        new SsaLiveIntervalBuilder(compiler, generateAtUseSite);
+    intervalBuilder.visitGraph(graph);
+    SsaVariableAllocator allocator = new SsaVariableAllocator(
+        compiler,
+        intervalBuilder.liveInstructions,
+        intervalBuilder.liveIntervals,
+        generateAtUseSite);
+    allocator.visitGraph(graph);
+    variableNames = allocator.names;
+    shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1;
+
+    // Don't register a return type for lazily initialized variables.
+    if (work.element is! FunctionElement) return;
+
+    // Register return types to the backend.
+    graph.exit.predecessors.forEach((HBasicBlock block) {
+      HInstruction last = block.last;
+      assert(last is HGoto || last is HReturn);
+      if (last is HReturn) {
+        backend.registerReturnType(work.element, types[last.inputs[0]]);
+      } else {
+        backend.registerReturnType(work.element, HType.NULL);
+      }
+    });
+  }
+
+  void handleDelayedVariableDeclarations() {
+    // If we have only one variable declaration and the first statement is an
+    // assignment to that variable then we can merge the two.  We count the
+    // number of variables in the variable allocator to try to avoid this issue,
+    // but it sometimes happens that the variable allocator introduces a
+    // temporary variable that it later eliminates.
+    if (!collectedVariableDeclarations.isEmpty) {
+      if (collectedVariableDeclarations.length == 1 &&
+          currentContainer.statements.length >= 1 &&
+          currentContainer.statements[0] is js.ExpressionStatement) {
+        String name = collectedVariableDeclarations.first;
+        js.ExpressionStatement statement = currentContainer.statements[0];
+        if (statement.expression is js.Assignment) {
+          js.Assignment assignment = statement.expression;
+          if (!assignment.isCompound &&
+              assignment.leftHandSide is js.VariableReference) {
+            js.VariableReference variableReference = assignment.leftHandSide;
+            if (variableReference.name == name) {
+              js.VariableDeclaration decl = new js.VariableDeclaration(name);
+              js.VariableInitialization initialization =
+                  new js.VariableInitialization(decl, assignment.value);
+              currentContainer.statements[0] = new js.ExpressionStatement(
+                  new js.VariableDeclarationList([initialization]));
+              return;
+            }
+          }
+        }
+      }
+      // If we can't merge the declaration with the first assignment then we
+      // just do it with a new var z,y,x; statement.
+      List<js.VariableInitialization> declarations =
+          <js.VariableInitialization>[];
+      collectedVariableDeclarations.forEach((String name) {
+        declarations.add(new js.VariableInitialization(
+            new js.VariableDeclaration(name), null));
+      });
+      var declarationList = new js.VariableDeclarationList(declarations);
+      insertStatementAtStart(new js.ExpressionStatement(declarationList));
+    }
+  }
+
+  visitGraph(HGraph graph) {
+    preGenerateMethod(graph);
+    currentGraph = graph;
+    indent++;  // We are already inside a function.
+    subGraph = new SubGraph(graph.entry, graph.exit);
+    HBasicBlock start = beginGraph(graph);
+    visitBasicBlock(start);
+    handleDelayedVariableDeclarations();
+    endGraph(graph);
+  }
+
+  void visitSubGraph(SubGraph newSubGraph) {
+    SubGraph oldSubGraph = subGraph;
+    subGraph = newSubGraph;
+    visitBasicBlock(subGraph.start);
+    subGraph = oldSubGraph;
+  }
+
+  /**
+   * Check whether a sub-graph can be generated as an expression, or even
+   * as a declaration, or if it has to fall back to being generated as
+   * a statement.
+   * Expressions are anything that doesn't generate control flow constructs.
+   * Declarations must only generate assignments on the form "id = expression",
+   * and not, e.g., expressions where the value isn't assigned, or where it's
+   * assigned to something that's not a simple variable.
+   */
+  int expressionType(HExpressionInformation info) {
+    // The only HExpressionInformation used as part of a HBlockInformation is
+    // current HSubExpressionBlockInformation, so it's the only one reaching
+    // here. If we start using the other HExpressionInformation types too,
+    // this code should be generalized.
+    assert(info is HSubExpressionBlockInformation);
+    HSubExpressionBlockInformation expressionInfo = info;
+    SubGraph limits = expressionInfo.subExpression;
+
+    // Start assuming that we can generate declarations. If we find a
+    // counter-example, we degrade our assumption to either expression or
+    // statement, and in the latter case, we can return immediately since
+    // it can't get any worse. E.g., a function call where the return value
+    // isn't used can't be in a declaration. A bailout can't be in an
+    // expression.
+    int result = TYPE_DECLARATION;
+    HBasicBlock basicBlock = limits.start;
+    do {
+      HInstruction current = basicBlock.first;
+      while (current != basicBlock.last) {
+        // E.g, type guards.
+        if (current.isControlFlow()) {
+          return TYPE_STATEMENT;
+        }
+        // HFieldSet generates code on the form x.y = ..., which isn't
+        // valid in a declaration, but it also always have no uses, so
+        // it's caught by that test too.
+        assert(current is! HFieldSet || current.usedBy.isEmpty);
+        if (current.usedBy.isEmpty) {
+          result = TYPE_EXPRESSION;
+        }
+        current = current.next;
+      }
+      if (current is HGoto) {
+        basicBlock = basicBlock.successors[0];
+      } else if (current is HConditionalBranch) {
+        if (generateAtUseSite.contains(current)) {
+          // Short-circuit control flow operator trickery.
+          // Check the second half, which will continue into the join.
+          // (The first half is [inputs[0]], the second half is [successors[0]],
+          // and [successors[1]] is the join-block).
+          basicBlock = basicBlock.successors[0];
+        } else {
+          // We allow an expression to end on an HIf (a condition expression).
+          return identical(basicBlock, limits.end) ? result : TYPE_STATEMENT;
+        }
+      } else {
+        // Expression-incompatible control flow.
+        return TYPE_STATEMENT;
+      }
+    } while (limits.contains(basicBlock));
+    return result;
+  }
+
+  bool isJSExpression(HExpressionInformation info) {
+    return !identical(expressionType(info), TYPE_STATEMENT);
+  }
+
+  bool isJSDeclaration(HExpressionInformation info) {
+    return identical(expressionType(info), TYPE_DECLARATION);
+  }
+
+  bool isJSCondition(HExpressionInformation info) {
+    HSubExpressionBlockInformation graph = info;
+    SubExpression limits = graph.subExpression;
+    return !identical(expressionType(info), TYPE_STATEMENT) &&
+       (limits.end.last is HConditionalBranch);
+  }
+
+  /**
+   * Generate statements from block information.
+   * If the block information contains expressions, generate only
+   * assignments, and if it ends in a conditional branch, don't generate
+   * the condition.
+   */
+  void generateStatements(HBlockInformation block) {
+    if (block is HStatementInformation) {
+      block.accept(this);
+    } else {
+      HSubExpressionBlockInformation expression = block;
+      visitSubGraph(expression.subExpression);
+    }
+  }
+
+  js.Block generateStatementsInNewBlock(HBlockInformation block) {
+    js.Block result = new js.Block.empty();
+    js.Block oldContainer = currentContainer;
+    currentContainer = result;
+    generateStatements(block);
+    currentContainer = oldContainer;
+    return result;
+  }
+
+  /**
+   * If the [block] only contains one statement returns that statement. If the
+   * that statement itself is a block, recursively calls this method.
+   *
+   * If the block is empty, returns a new instance of [js.NOP].
+   */
+  js.Statement unwrapStatement(js.Block block) {
+    int len = block.statements.length;
+    if (len == 0) return new js.EmptyStatement();
+    if (len == 1) {
+      js.Statement result = block.statements[0];
+      if (result is Block) return unwrapStatement(result);
+      return result;
+    }
+    return block;
+  }
+
+  /**
+   * Generate expressions from block information.
+   */
+  js.Expression generateExpression(HExpressionInformation expression) {
+    // Currently we only handle sub-expression graphs.
+    assert(expression is HSubExpressionBlockInformation);
+
+    bool oldIsGeneratingExpression = isGeneratingExpression;
+    isGeneratingExpression = true;
+    List<js.Expression> oldExpressionStack = expressionStack;
+    List<js.Expression> sequenceElements = <js.Expression>[];
+    expressionStack = sequenceElements;
+    HSubExpressionBlockInformation expressionSubGraph = expression;
+    visitSubGraph(expressionSubGraph.subExpression);
+    expressionStack = oldExpressionStack;
+    isGeneratingExpression = oldIsGeneratingExpression;
+    if (sequenceElements.isEmpty) {
+      // Happens when the initializer, condition or update of a loop is empty.
+      return null;
+    } else if (sequenceElements.length == 1) {
+      return sequenceElements[0];
+    } else {
+      return new js.Sequence(sequenceElements);
+    }
+  }
+
+  /**
+    * Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET].
+    */
+  List<js.Expression> visitArguments(List<HInstruction> inputs) {
+    assert(inputs.length >= HInvoke.ARGUMENTS_OFFSET);
+    List<js.Expression> result = <js.Expression>[];
+    for (int i = HInvoke.ARGUMENTS_OFFSET; i < inputs.length; i++) {
+      use(inputs[i]);
+      result.add(pop());
+    }
+    return result;
+  }
+
+  bool isVariableDeclared(String variableName) {
+    return declaredLocals.contains(variableName) ||
+        collectedVariableDeclarations.contains(variableName);
+  }
+
+  js.Expression generateExpressionAssignment(String variableName,
+                                             js.Expression value) {
+    if (value is js.Binary) {
+      js.Binary binary = value;
+      String op = binary.op;
+      if (op == '+' || op == '-' || op == '/' || op == '*' || op == '%' ||
+          op == '^' || op == '&' || op == '|') {
+        if (binary.left is js.VariableUse &&
+            (binary.left as js.VariableUse).name == variableName) {
+          // We know now, that we can shorten x = x + y into x += y.
+          // Also check for the shortcut where y equals 1: x++ and x--.
+          if ((op == '+' || op == '-') &&
+              binary.right is js.LiteralNumber &&
+              (binary.right as js.LiteralNumber).value == "1") {
+            return new js.Prefix(op == '+' ? '++' : '--', binary.left);
+          }
+          return new js.Assignment.compound(binary.left, op, binary.right);
+        }
+      }
+    }
+    return new js.Assignment(new js.VariableUse(variableName), value);
+  }
+
+  void assignVariable(String variableName, js.Expression value) {
+    if (isGeneratingExpression) {
+      // If we are in an expression then we can't declare the variable here.
+      // We have no choice, but to use it and then declare it separately.
+      if (!isVariableDeclared(variableName)) {
+        collectedVariableDeclarations.add(variableName);
+      }
+      push(generateExpressionAssignment(variableName, value));
+      // Otherwise if we are trying to declare inline and we are in a statement
+      // then we declare (unless it was already declared).
+    } else if (!shouldGroupVarDeclarations &&
+               !declaredLocals.contains(variableName)) {
+      // It may be necessary to remove it from the ones to be declared later.
+      collectedVariableDeclarations.remove(variableName);
+      declaredLocals.add(variableName);
+      js.VariableDeclaration decl = new js.VariableDeclaration(variableName);
+      js.VariableInitialization initialization =
+          new js.VariableInitialization(decl, value);
+
+      pushExpressionAsStatement(new js.VariableDeclarationList(
+          <js.VariableInitialization>[initialization]));
+    } else {
+      // Otherwise we are just going to use it.  If we have not already declared
+      // it then we make sure we will declare it later.
+      if (!declaredLocals.contains(variableName)) {
+        collectedVariableDeclarations.add(variableName);
+      }
+      pushExpressionAsStatement(
+          generateExpressionAssignment(variableName, value));
+    }
+  }
+
+  void define(HInstruction instruction) {
+    // For simple type checks like i = intTypeCheck(i), we don't have to
+    // emit an assignment, because the intTypeCheck just returns its
+    // argument.
+    bool needsAssignment = true;
+    if (instruction is HTypeConversion) {
+      String inputName = variableNames.getName(instruction.checkedInput);
+      if (variableNames.getName(instruction) == inputName) {
+        needsAssignment = false;
+      }
+    }
+    if (instruction is HLocalValue) {
+      needsAssignment = false;
+    }
+
+    if (needsAssignment &&
+        !instruction.isControlFlow() && variableNames.hasName(instruction)) {
+      visitExpression(instruction);
+      assignVariable(variableNames.getName(instruction), pop());
+      return;
+    }
+
+    if (isGeneratingExpression) {
+      visitExpression(instruction);
+    } else {
+      visitStatement(instruction);
+    }
+  }
+
+  void use(HInstruction argument) {
+    if (isGenerateAtUseSite(argument)) {
+      visitExpression(argument);
+    } else if (argument is HCheck && argument.isControlFlow()) {
+      // A [HCheck] that has control flow can never be used as an
+      // expression and may not have a name. Therefore we just use the
+      // checked instruction.
+      HCheck check = argument;
+      use(check.checkedInput);
+    } else {
+      push(new js.VariableUse(variableNames.getName(argument)));
+    }
+  }
+
+  visit(HInstruction node) {
+    node.accept(this);
+  }
+
+  visitExpression(HInstruction node) {
+    bool oldIsGeneratingExpression = isGeneratingExpression;
+    isGeneratingExpression = true;
+    visit(node);
+    isGeneratingExpression = oldIsGeneratingExpression;
+  }
+
+  visitStatement(HInstruction node) {
+    assert(!isGeneratingExpression);
+    visit(node);
+    if (!expressionStack.isEmpty) {
+      assert(expressionStack.length == 1);
+      pushExpressionAsStatement(pop());
+    }
+  }
+
+  void continueAsBreak(LabelElement target) {
+    pushStatement(new js.Break(backend.namer.continueLabelName(target)));
+  }
+
+  void implicitContinueAsBreak(TargetElement target) {
+    pushStatement(new js.Break(
+        backend.namer.implicitContinueLabelName(target)));
+  }
+
+  void implicitBreakWithLabel(TargetElement target) {
+    pushStatement(new js.Break(backend.namer.implicitBreakLabelName(target)));
+  }
+
+  js.Statement wrapIntoLabels(js.Statement result, List<LabelElement> labels) {
+    for (LabelElement label in labels) {
+      if (label.isTarget) {
+        String breakLabelString = backend.namer.breakLabelName(label);
+        result = new js.LabeledStatement(breakLabelString, result);
+      }
+    }
+    return result;
+  }
+
+
+  // The regular [visitIf] method implements the needed logic.
+  bool visitIfInfo(HIfBlockInformation info) => false;
+
+  bool visitSwitchInfo(HSwitchBlockInformation info) {
+    bool isExpression = isJSExpression(info.expression);
+    if (!isExpression) {
+      generateStatements(info.expression);
+    }
+
+    if (isExpression) {
+      push(generateExpression(info.expression));
+    } else {
+      use(info.expression.conditionExpression);
+    }
+    js.Expression key = pop();
+    List<js.SwitchClause> cases = <js.SwitchClause>[];
+
+    js.Block oldContainer = currentContainer;
+    for (int i = 0; i < info.matchExpressions.length; i++) {
+      for (Constant constant in info.matchExpressions[i]) {
+        generateConstant(constant);
+        currentContainer = new js.Block.empty();
+        cases.add(new js.Case(pop(), currentContainer));
+      }
+      if (i == info.matchExpressions.length - 1 && info.hasDefault) {
+        currentContainer = new js.Block.empty();
+        cases.add(new js.Default(currentContainer));
+      }
+      generateStatements(info.statements[i]);
+    }
+    currentContainer = oldContainer;
+
+    js.Statement result = new js.Switch(key, cases);
+    pushStatement(wrapIntoLabels(result, info.labels));
+    return true;
+  }
+
+  bool visitSequenceInfo(HStatementSequenceInformation info) {
+    return false;
+  }
+
+  bool visitSubGraphInfo(HSubGraphBlockInformation info) {
+    visitSubGraph(info.subGraph);
+    return true;
+  }
+
+  bool visitSubExpressionInfo(HSubExpressionBlockInformation info) {
+    return false;
+  }
+
+  bool visitAndOrInfo(HAndOrBlockInformation info) {
+    return false;
+  }
+
+  bool visitTryInfo(HTryBlockInformation info) {
+    js.Block body = generateStatementsInNewBlock(info.body);
+    js.Catch catchPart = null;
+    js.Block finallyPart = null;
+    if (info.catchBlock != null) {
+      HLocalValue exception = info.catchVariable;
+      String name = variableNames.getName(exception);
+      js.VariableDeclaration decl = new js.VariableDeclaration(name);
+      js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock);
+      catchPart = new js.Catch(decl, catchBlock);
+    }
+    if (info.finallyBlock != null) {
+      finallyPart = generateStatementsInNewBlock(info.finallyBlock);
+    }
+    pushStatement(new js.Try(body, catchPart, finallyPart));
+    return true;
+  }
+
+  void visitBodyIgnoreLabels(HLoopBlockInformation info) {
+    if (info.body.start.isLabeledBlock()) {
+      HBlockInformation oldInfo = currentBlockInformation;
+      currentBlockInformation = info.body.start.blockFlow.body;
+      generateStatements(info.body);
+      currentBlockInformation = oldInfo;
+    } else {
+      generateStatements(info.body);
+    }
+  }
+
+  bool visitLoopInfo(HLoopBlockInformation info) {
+    HExpressionInformation condition = info.condition;
+    bool isConditionExpression = isJSCondition(condition);
+
+    js.Loop loop;
+
+    switch (info.kind) {
+      // Treate all three "test-first" loops the same way.
+      case HLoopBlockInformation.FOR_LOOP:
+      case HLoopBlockInformation.WHILE_LOOP:
+      case HLoopBlockInformation.FOR_IN_LOOP:
+        HBlockInformation initialization = info.initializer;
+        int initializationType = TYPE_STATEMENT;
+        if (initialization != null) {
+          initializationType = expressionType(initialization);
+          if (initializationType == TYPE_STATEMENT) {
+            generateStatements(initialization);
+            initialization = null;
+          }
+        }
+        if (isConditionExpression &&
+            info.updates != null && isJSExpression(info.updates)) {
+          // If we have an updates graph, and it's expressible as an
+          // expression, generate a for-loop.
+          js.Expression jsInitialization = null;
+          if (initialization != null) {
+            int delayedVariablesCount = collectedVariableDeclarations.length;
+            jsInitialization = generateExpression(initialization);
+            if (!shouldGroupVarDeclarations &&
+                delayedVariablesCount < collectedVariableDeclarations.length) {
+              // We just added a new delayed variable-declaration. See if we
+              // can put in a 'var' in front of the initialization to make it
+              // go away.
+              List<js.Expression> expressions;
+              if (jsInitialization is js.Sequence) {
+                expressions = jsInitialization.expressions;
+              } else {
+                expressions = <js.Expression>[jsInitialization];
+              }
+              bool canTransformToVariableDeclaration = true;
+              for (js.Expression expression in expressions) {
+                bool expressionIsVariableAssignment = false;
+                if (expression is js.Assignment) {
+                  js.Assignment assignment = expression;
+                  if (assignment.leftHandSide is js.VariableUse &&
+                      assignment.compoundTarget == null) {
+                    expressionIsVariableAssignment = true;
+                  }
+                }
+                if (!expressionIsVariableAssignment) {
+                  canTransformToVariableDeclaration = false;
+                  break;
+                }
+              }
+              if (canTransformToVariableDeclaration) {
+                List<js.VariableInitialization> inits =
+                    <js.VariableInitialization>[];
+                for (js.Assignment assignment in expressions) {
+                  String id = (assignment.leftHandSide as js.VariableUse).name;
+                  js.Node declaration = new js.VariableDeclaration(id);
+                  inits.add(new js.VariableInitialization(declaration,
+                                                          assignment.value));
+                  collectedVariableDeclarations.remove(id);
+                }
+                jsInitialization = new js.VariableDeclarationList(inits);
+              }
+            }
+          }
+          js.Expression jsCondition = generateExpression(condition);
+          js.Expression jsUpdates = generateExpression(info.updates);
+          // The body might be labeled. Ignore this when recursing on the
+          // subgraph.
+          // TODO(lrn): Remove this extra labeling when handling all loops
+          // using subgraphs.
+          js.Block oldContainer = currentContainer;
+          js.Statement body = new js.Block.empty();
+          currentContainer = body;
+          visitBodyIgnoreLabels(info);
+          currentContainer = oldContainer;
+          body = unwrapStatement(body);
+          loop = new js.For(jsInitialization, jsCondition, jsUpdates, body);
+        } else {
+          // We have either no update graph, or it's too complex to
+          // put in an expression.
+          if (initialization != null) {
+            generateStatements(initialization);
+          }
+          js.Expression jsCondition;
+          js.Block oldContainer = currentContainer;
+          js.Statement body = new js.Block.empty();
+          if (isConditionExpression) {
+            jsCondition = generateExpression(condition);
+            currentContainer = body;
+          } else {
+            jsCondition = newLiteralBool(true);
+            currentContainer = body;
+            generateStatements(condition);
+            use(condition.conditionExpression);
+            js.Expression ifTest = new js.Prefix("!", pop());
+            js.Break jsBreak = new js.Break(null);
+            pushStatement(new js.If.noElse(ifTest, jsBreak));
+          }
+          if (info.updates != null) {
+            wrapLoopBodyForContinue(info);
+            generateStatements(info.updates);
+          } else {
+            visitBodyIgnoreLabels(info);
+          }
+          currentContainer = oldContainer;
+          body = unwrapStatement(body);
+          loop = new js.While(jsCondition, body);
+        }
+        break;
+      case HLoopBlockInformation.DO_WHILE_LOOP:
+        if (info.initializer != null) {
+          generateStatements(info.initializer);
+        }
+        js.Block oldContainer = currentContainer;
+        js.Block body = new js.Block.empty();
+        // If there are phi copies in the block that jumps to the
+        // loop entry, we must emit the condition like this:
+        // do {
+        //   body;
+        //   if (condition) {
+        //     phi updates;
+        //     continue;
+        //   } else {
+        //     break;
+        //   }
+        // } while (true);
+        HBasicBlock avoidEdge = info.end.successors[0];
+        js.Block updateBody = new js.Block.empty();
+        currentContainer = updateBody;
+        assignPhisOfSuccessors(avoidEdge);
+        bool hasPhiUpdates = !updateBody.statements.isEmpty;
+        currentContainer = body;
+        visitBodyIgnoreLabels(info);
+        if (info.updates != null) {
+          generateStatements(info.updates);
+        }
+        if (isConditionExpression) {
+          push(generateExpression(condition));
+        } else {
+          generateStatements(condition);
+          use(condition.conditionExpression);
+        }
+        js.Expression jsCondition = pop();
+        if (hasPhiUpdates) {
+          updateBody.statements.add(new js.Continue(null));
+          body.statements.add(
+              new js.If(jsCondition, updateBody, new js.Break(null)));
+          jsCondition = newLiteralBool(true);
+        }
+        loop = new js.Do(unwrapStatement(body), jsCondition);
+        currentContainer = oldContainer;
+        break;
+      default:
+        compiler.internalError(
+          'Unexpected loop kind: ${info.kind}',
+          instruction: condition.conditionExpression);
+    }
+    attachLocationRange(loop, info.sourcePosition, info.endSourcePosition);
+    pushStatement(wrapIntoLabels(loop, info.labels));
+    return true;
+  }
+
+  bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) {
+    preLabeledBlock(labeledBlockInfo);
+    Link<Element> continueOverrides = const Link<Element>();
+
+    js.Block oldContainer = currentContainer;
+    js.Block body = new js.Block.empty();
+    js.Statement result = body;
+
+    currentContainer = body;
+
+    // If [labeledBlockInfo.isContinue], the block is an artificial
+    // block around the body of a loop with an update block, so that
+    // continues of the loop can be written as breaks of the body
+    // block.
+    if (labeledBlockInfo.isContinue) {
+      for (LabelElement label in labeledBlockInfo.labels) {
+        if (label.isContinueTarget) {
+          String labelName = backend.namer.continueLabelName(label);
+          result = new js.LabeledStatement(labelName, result);
+          continueAction[label] = continueAsBreak;
+          continueOverrides = continueOverrides.prepend(label);
+        }
+      }
+      // For handling unlabeled continues from the body of a loop.
+      // TODO(lrn): Consider recording whether the target is in fact
+      // a target of an unlabeled continue, and not generate this if it isn't.
+      TargetElement target = labeledBlockInfo.target;
+      String labelName = backend.namer.implicitContinueLabelName(target);
+      result = new js.LabeledStatement(labelName, result);
+      continueAction[target] = implicitContinueAsBreak;
+      continueOverrides = continueOverrides.prepend(target);
+    } else {
+      for (LabelElement label in labeledBlockInfo.labels) {
+        if (label.isBreakTarget) {
+          String labelName = backend.namer.breakLabelName(label);
+          result = new js.LabeledStatement(labelName, result);
+        }
+      }
+      TargetElement target = labeledBlockInfo.target;
+      if (target.isSwitch) {
+        // This is an extra block around a switch that is generated
+        // as a nested if/else chain. We add an extra break target
+        // so that case code can break.
+        String labelName = backend.namer.implicitBreakLabelName(target);
+        result = new js.LabeledStatement(labelName, result);
+        breakAction[target] = implicitBreakWithLabel;
+      }
+    }
+
+    currentContainer = body;
+    startLabeledBlock(labeledBlockInfo);
+    generateStatements(labeledBlockInfo.body);
+    endLabeledBlock(labeledBlockInfo);
+
+    if (labeledBlockInfo.isContinue) {
+      while (!continueOverrides.isEmpty) {
+        continueAction.remove(continueOverrides.head);
+        continueOverrides = continueOverrides.tail;
+      }
+    } else {
+      breakAction.remove(labeledBlockInfo.target);
+    }
+
+    currentContainer = oldContainer;
+    pushStatement(result);
+    return true;
+  }
+
+  // Wraps a loop body in a block to make continues have a target to break
+  // to (if necessary).
+  void wrapLoopBodyForContinue(HLoopBlockInformation info) {
+    TargetElement target = info.target;
+    if (target != null && target.isContinueTarget) {
+      js.Block oldContainer = currentContainer;
+      js.Block body = new js.Block.empty();
+      currentContainer = body;
+      js.Statement result = body;
+      for (LabelElement label in info.labels) {
+        if (label.isContinueTarget) {
+          String labelName = backend.namer.continueLabelName(label);
+          result = new js.LabeledStatement(labelName, result);
+          continueAction[label] = continueAsBreak;
+        }
+      }
+      String labelName = backend.namer.implicitContinueLabelName(target);
+      result = new js.LabeledStatement(labelName, result);
+      continueAction[info.target] = implicitContinueAsBreak;
+      visitBodyIgnoreLabels(info);
+      continueAction.remove(info.target);
+      for (LabelElement label in info.labels) {
+        if (label.isContinueTarget) {
+          continueAction.remove(label);
+        }
+      }
+      currentContainer = oldContainer;
+      pushStatement(result);
+    } else {
+      // Loop body contains no continues, so we don't need a break target.
+      generateStatements(info.body);
+    }
+  }
+
+  bool handleBlockFlow(HBlockFlow block) {
+    HBlockInformation info = block.body;
+    // If we reach here again while handling the attached information,
+    // e.g., because we call visitSubGraph on a subgraph starting on
+    // the same block, don't handle it again.
+    // When the structure graph is complete, we will be able to have
+    // different structures starting on the same basic block (e.g., an
+    // "if" and its condition).
+    if (identical(info, currentBlockInformation)) return false;
+
+    HBlockInformation oldBlockInformation = currentBlockInformation;
+    currentBlockInformation = info;
+    bool success = info.accept(this);
+    currentBlockInformation = oldBlockInformation;
+    if (success) {
+      HBasicBlock continuation = block.continuation;
+      if (continuation != null) {
+        visitBasicBlock(continuation);
+      }
+    }
+    return success;
+  }
+
+  void visitBasicBlock(HBasicBlock node) {
+    // Abort traversal if we are leaving the currently active sub-graph.
+    if (!subGraph.contains(node)) return;
+
+    // If this node has block-structure based information attached,
+    // try using that to traverse from here.
+    if (node.blockFlow != null && handleBlockFlow(node.blockFlow)) {
+      return;
+    }
+    iterateBasicBlock(node);
+  }
+
+  void emitAssignment(String destination, String source) {
+    assignVariable(destination, new js.VariableUse(source));
+  }
+
+  /**
+   * Sequentialize a list of conceptually parallel copies. Parallel
+   * copies may contain cycles, that this method breaks.
+   */
+  void sequentializeCopies(Iterable<Copy> copies,
+                           String tempName,
+                           void doAssignment(String target, String source)) {
+    // Map to keep track of the current location (ie the variable that
+    // holds the initial value) of a variable.
+    Map<String, String> currentLocation = new Map<String, String>();
+
+    // Map to keep track of the initial value of a variable.
+    Map<String, String> initialValue = new Map<String, String>();
+
+    // List of variables to assign a value.
+    List<String> worklist = <String>[];
+
+    // List of variables that we can assign a value to (ie are not
+    // being used anymore).
+    List<String> ready = <String>[];
+
+    // Prune [copies] by removing self-copies.
+    List<Copy> prunedCopies = <Copy>[];
+    for (Copy copy in copies) {
+      if (copy.source != copy.destination) {
+        prunedCopies.add(copy);
+      }
+    }
+    copies = prunedCopies;
+
+
+    // For each copy, set the current location of the source to
+    // itself, and the initial value of the destination to the source.
+    // Add the destination to the list of copies to make.
+    for (Copy copy in copies) {
+      currentLocation[copy.source] = copy.source;
+      initialValue[copy.destination] = copy.source;
+      worklist.add(copy.destination);
+    }
+
+    // For each copy, if the destination does not have a current
+    // location, then we can safely assign to it.
+    for (Copy copy in copies) {
+      if (currentLocation[copy.destination] == null) {
+        ready.add(copy.destination);
+      }
+    }
+
+    while (!worklist.isEmpty) {
+      while (!ready.isEmpty) {
+        String destination = ready.removeLast();
+        String source = initialValue[destination];
+        // Since [source] might have been updated, use the current
+        // location of [source]
+        String copy = currentLocation[source];
+        doAssignment(destination, copy);
+        // Now [destination] is the current location of [source].
+        currentLocation[source] = destination;
+        // If [source] hasn't been updated and needs to have a value,
+        // add it to the list of variables that can be updated. Copies
+        // of [source] will now use [destination].
+        if (source == copy && initialValue[source] != null) {
+          ready.add(source);
+        }
+      }
+
+      // Check if we have a cycle.
+      String current = worklist.removeLast();
+      // If [current] is used as a source, and the assignment has been
+      // done, we are done with this variable. Otherwise there is a
+      // cycle that we break by using a temporary name.
+      if (currentLocation[current] != null
+          && current != currentLocation[initialValue[current]]) {
+        doAssignment(tempName, current);
+        currentLocation[current] = tempName;
+        // [current] can now be safely updated. Copies of [current]
+        // will now use [tempName].
+        ready.add(current);
+      }
+    }
+  }
+
+  void assignPhisOfSuccessors(HBasicBlock node) {
+    CopyHandler handler = variableNames.getCopyHandler(node);
+    if (handler == null) return;
+
+    // Map the instructions to strings.
+    Iterable<Copy> copies = handler.copies.map((Copy copy) {
+      return new Copy(variableNames.getName(copy.source),
+                      variableNames.getName(copy.destination));
+    });
+
+    sequentializeCopies(copies, variableNames.getSwapTemp(), emitAssignment);
+
+    for (Copy copy in handler.assignments) {
+      String name = variableNames.getName(copy.destination);
+      use(copy.source);
+      assignVariable(name, pop());
+    }
+  }
+
+  void iterateBasicBlock(HBasicBlock node) {
+    HInstruction instruction = node.first;
+    while (!identical(instruction, node.last)) {
+      if (instruction is HTypeGuard || instruction is HBailoutTarget) {
+        visit(instruction);
+      } else if (!isGenerateAtUseSite(instruction)) {
+        define(instruction);
+      }
+      instruction = instruction.next;
+    }
+    assignPhisOfSuccessors(node);
+    visit(instruction);
+  }
+
+  visitInvokeBinary(HInvokeBinary node, String op) {
+    use(node.left);
+    js.Expression jsLeft = pop();
+    use(node.right);
+    push(new js.Binary(op, jsLeft, pop()), node);
+  }
+
+  visitRelational(HRelational node, String op) => visitInvokeBinary(node, op);
+
+  // We want the outcome of bit-operations to be positive. We use the unsigned
+  // shift operator to achieve this.
+  visitBitInvokeBinary(HBinaryBitOp node, String op) {
+    visitInvokeBinary(node, op);
+    if (requiresUintConversion(node)) {
+      push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node);
+    }
+  }
+
+  visitInvokeUnary(HInvokeUnary node, String op) {
+    use(node.operand);
+    push(new js.Prefix(op, pop()), node);
+  }
+
+  // We want the outcome of bit-operations to be positive. We use the unsigned
+  // shift operator to achieve this.
+  visitBitInvokeUnary(HInvokeUnary node, String op) {
+    visitInvokeUnary(node, op);
+    if (requiresUintConversion(node)) {
+      push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node);
+    }
+  }
+
+  void emitIdentityComparison(HInstruction left, HInstruction right) {
+    String op = singleIdentityComparison(left, right, types);
+    if (op != null) {
+      use(left);
+      js.Expression jsLeft = pop();
+      use(right);
+      push(new js.Binary(op, jsLeft, pop()));
+    } else {
+      assert(NullConstant.JsNull == 'null');
+      use(left);
+      js.Binary leftEqualsNull =
+          new js.Binary("==", pop(), new js.LiteralNull());
+      use(right);
+      js.Binary rightEqualsNull =
+          new js.Binary("==", pop(), new js.LiteralNull());
+      use(right);
+      use(left);
+      js.Binary tripleEq = new js.Binary("===", pop(), pop());
+
+      push(new js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq));
+    }
+  }
+
+  visitIdentity(HIdentity node) {
+    emitIdentityComparison(node.left, node.right);
+  }
+
+  visitAdd(HAdd node)               => visitInvokeBinary(node, '+');
+  visitDivide(HDivide node)         => visitInvokeBinary(node, '/');
+  visitMultiply(HMultiply node)     => visitInvokeBinary(node, '*');
+  visitSubtract(HSubtract node)     => visitInvokeBinary(node, '-');
+  visitBitAnd(HBitAnd node)         => visitBitInvokeBinary(node, '&');
+  visitBitNot(HBitNot node)         => visitBitInvokeUnary(node, '~');
+  visitBitOr(HBitOr node)           => visitBitInvokeBinary(node, '|');
+  visitBitXor(HBitXor node)         => visitBitInvokeBinary(node, '^');
+  visitShiftLeft(HShiftLeft node)   => visitBitInvokeBinary(node, '<<');
+
+  visitNegate(HNegate node)         => visitInvokeUnary(node, '-');
+
+  visitLess(HLess node)                 => visitRelational(node, '<');
+  visitLessEqual(HLessEqual node)       => visitRelational(node, '<=');
+  visitGreater(HGreater node)           => visitRelational(node, '>');
+  visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>=');
+
+  visitBoolify(HBoolify node) {
+    assert(node.inputs.length == 1);
+    use(node.inputs[0]);
+    push(new js.Binary('===', pop(), newLiteralBool(true)), node);
+  }
+
+  visitExit(HExit node) {
+    // Don't do anything.
+  }
+
+  visitGoto(HGoto node) {
+    HBasicBlock block = node.block;
+    assert(block.successors.length == 1);
+    List<HBasicBlock> dominated = block.dominatedBlocks;
+    // With the exception of the entry-node which dominates its successor
+    // and the exit node, no block finishing with a 'goto' can have more than
+    // one dominated block (since it has only one successor).
+    // If the successor is dominated by another block, then the other block
+    // is responsible for visiting the successor.
+    if (dominated.isEmpty) return;
+    if (dominated.length > 2) {
+      compiler.internalError('dominated.length = ${dominated.length}',
+                             instruction: node);
+    }
+    if (dominated.length == 2 && block != currentGraph.entry) {
+      compiler.internalError('node.block != currentGraph.entry',
+                             instruction: node);
+    }
+    assert(dominated[0] == block.successors[0]);
+    visitBasicBlock(dominated[0]);
+  }
+
+  visitLoopBranch(HLoopBranch node) {
+    assert(node.block == subGraph.end);
+    // We are generating code for a loop condition.
+    // If we are generating the subgraph as an expression, the
+    // condition will be generated as the expression.
+    // Otherwise, we don't generate the expression, and leave that
+    // to the code that called [visitSubGraph].
+    if (isGeneratingExpression) {
+      use(node.inputs[0]);
+    }
+  }
+
+  /**
+   * Checks if [map] contains an [ElementAction] for [element], and
+   * if so calls that action and returns true.
+   * Otherwise returns false.
+   */
+  bool tryCallAction(Map<Element, ElementAction> map, Element element) {
+    ElementAction action = map[element];
+    if (action == null) return false;
+    action(element);
+    return true;
+  }
+
+  visitBreak(HBreak node) {
+    assert(node.block.successors.length == 1);
+    if (node.label != null) {
+      LabelElement label = node.label;
+      if (!tryCallAction(breakAction, label)) {
+        pushStatement(new js.Break(backend.namer.breakLabelName(label)), node);
+      }
+    } else {
+      TargetElement target = node.target;
+      if (!tryCallAction(breakAction, target)) {
+        pushStatement(new js.Break(null), node);
+      }
+    }
+  }
+
+  visitContinue(HContinue node) {
+    assert(node.block.successors.length == 1);
+    if (node.label != null) {
+      LabelElement label = node.label;
+      if (!tryCallAction(continueAction, label)) {
+        // TODO(floitsch): should this really be the breakLabelName?
+        pushStatement(new js.Continue(backend.namer.breakLabelName(label)),
+                      node);
+      }
+    } else {
+      TargetElement target = node.target;
+      if (!tryCallAction(continueAction, target)) {
+        pushStatement(new js.Continue(null), node);
+      }
+    }
+  }
+
+  visitExitTry(HExitTry node) {
+    // An [HExitTry] is used to represent the control flow graph of a
+    // try/catch block, ie the try body is always a predecessor
+    // of the catch and finally. Here, we continue visiting the try
+    // body by visiting the block that contains the user-level control
+    // flow instruction.
+    visitBasicBlock(node.bodyTrySuccessor);
+  }
+
+  visitTry(HTry node) {
+    // We should never get here. Try/catch/finally is always handled using block
+    // information in [visitTryInfo], or not at all, in the case of the bailout
+    // generator.
+    compiler.internalError('visitTry should not be called', instruction: node);
+  }
+
+  bool tryControlFlowOperation(HIf node) {
+    if (!controlFlowOperators.contains(node)) return false;
+    HPhi phi = node.joinBlock.phis.first;
+    bool atUseSite = isGenerateAtUseSite(phi);
+    // Don't generate a conditional operator in this situation:
+    // i = condition ? bar() : i;
+    // But generate this instead:
+    // if (condition) i = bar();
+    // Usually, the variable name is longer than 'if' and it takes up
+    // more space to duplicate the name.
+    if (!atUseSite
+        && variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) {
+      return false;
+    }
+    if (!atUseSite) define(phi);
+    visitBasicBlock(node.joinBlock);
+    return true;
+  }
+
+  void generateIf(HIf node, HIfBlockInformation info) {
+    use(node.inputs[0]);
+    js.Expression test = pop();
+
+    HStatementInformation thenGraph = info.thenGraph;
+    HStatementInformation elseGraph = info.elseGraph;
+    js.Statement thenPart =
+        unwrapStatement(generateStatementsInNewBlock(thenGraph));
+    js.Statement elsePart =
+        unwrapStatement(generateStatementsInNewBlock(elseGraph));
+
+    pushStatement(new js.If(test, thenPart, elsePart), node);
+  }
+
+  visitIf(HIf node) {
+    if (tryControlFlowOperation(node)) return;
+
+    HInstruction condition = node.inputs[0];
+    HIfBlockInformation info = node.blockInformation.body;
+
+    if (condition.isConstant()) {
+      HConstant constant = condition;
+      if (constant.constant.isTrue()) {
+        generateStatements(info.thenGraph);
+      } else {
+        generateStatements(info.elseGraph);
+      }
+    } else {
+      generateIf(node, info);
+    }
+
+    HBasicBlock joinBlock = node.joinBlock;
+    if (joinBlock != null && !identical(joinBlock.dominator, node.block)) {
+      // The join block is dominated by a block in one of the branches.
+      // The subgraph traversal never reached it, so we visit it here
+      // instead.
+      visitBasicBlock(joinBlock);
+    }
+
+    // Visit all the dominated blocks that are not part of the then or else
+    // branches, and is not the join block.
+    // Depending on how the then/else branches terminate
+    // (e.g., return/throw/break) there can be any number of these.
+    List<HBasicBlock> dominated = node.block.dominatedBlocks;
+    for (int i = 2; i < dominated.length; i++) {
+      visitBasicBlock(dominated[i]);
+    }
+  }
+
+  js.Call jsPropertyCall(js.Expression receiver,
+                         String fieldName,
+                         List<js.Expression> arguments) {
+    return new js.Call(new js.PropertyAccess.field(receiver, fieldName),
+                       arguments);
+  }
+
+  void visitInterceptor(HInterceptor node) {
+    backend.registerSpecializedGetInterceptor(node.interceptedClasses);
+    String name = backend.namer.getInterceptorName(
+        backend.getInterceptorMethod, node.interceptedClasses);
+    var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE);
+    use(node.receiver);
+    List<js.Expression> arguments = <js.Expression>[pop()];
+    push(jsPropertyCall(isolate, name, arguments), node);
+  }
+
+  visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
+    use(node.receiver);
+    js.Expression object = pop();
+    SourceString name = node.selector.name;
+    String methodName;
+    List<js.Expression> arguments = visitArguments(node.inputs);
+    Element target = node.element;
+
+    if (target != null) {
+      // Avoid adding the generative constructor name to the list of
+      // seen selectors.
+      if (target.isGenerativeConstructorBody()) {
+        methodName = name.slowToString();
+      } else if (!node.isInterceptorCall) {
+        if (target == backend.jsArrayAdd) {
+          methodName = 'push';
+        } else if (target == backend.jsArrayRemoveLast) {
+          methodName = 'pop';
+        } else if (target == backend.jsStringSplit) {
+          methodName = 'split';
+          // Split returns a List, so we make sure the backend knows the
+          // list class is instantiated.
+          world.registerInstantiatedClass(compiler.listClass);
+        } else if (target == backend.jsStringConcat) {
+          push(new js.Binary('+', object, arguments[0]), node);
+          return;
+        }
+      }
+    }
+
+    if (methodName == null) {
+      methodName = backend.namer.invocationName(node.selector);
+      registerMethodInvoke(node);
+    }
+    push(jsPropertyCall(object, methodName, arguments), node);
+  }
+
+  void visitOneShotInterceptor(HOneShotInterceptor node) {
+    List<js.Expression> arguments = visitArguments(node.inputs);
+    var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE);
+    Selector selector = node.selector;
+    String methodName = backend.namer.oneShotInterceptorName(selector);
+    push(jsPropertyCall(isolate, methodName, arguments), node);
+    backend.registerSpecializedGetInterceptor(node.interceptedClasses);
+    backend.addOneShotInterceptor(selector);
+    if (selector.isGetter()) {
+      registerGetter(node);
+    } else if (selector.isSetter()) {
+      registerSetter(node);
+    } else {
+      registerMethodInvoke(node);
+    }
+  }
+
+  Selector getOptimizedSelectorFor(HInvokeDynamic node,
+                                   Selector defaultSelector) {
+    // If [JSInvocationMirror.invokeOn] has been called, we must not create a
+    // typed selector based on the receiver type.
+    if (node.element == null && // Invocation is not exact.
+        backend.compiler.enabledInvokeOn) {
+      return defaultSelector;
+    }
+    int receiverIndex = node.isInterceptorCall ? 1 : 0;
+    HType receiverHType = types[node.inputs[receiverIndex]];
+    DartType receiverType = receiverHType.computeType(compiler);
+    if (receiverType != null &&
+        !identical(receiverType.kind, TypeKind.MALFORMED_TYPE)) {
+      return new TypedSelector(receiverType, defaultSelector);
+    } else {
+      return defaultSelector;
+    }
+  }
+
+  void registerInvoke(HInvokeDynamic node) {
+    bool inLoop = node.block.enclosingLoopHeader != null;
+    SourceString name = node.selector.name;
+    if (inLoop) {
+      Element target = node.element;
+      if (target != null) {
+        backend.builder.functionsCalledInLoop.add(target);
+      } else {
+        backend.builder.selectorsCalledInLoop[name] = node.selector;
+      }
+    }
+
+    if (node.isInterceptorCall) {
+      backend.addInterceptedSelector(node.selector);
+    }
+  }
+
+  void registerMethodInvoke(HInvokeDynamic node) {
+    Selector selector = getOptimizedSelectorFor(node, node.selector);
+    // Register this invocation to collect the types used at all call sites.
+    backend.registerDynamicInvocation(node, selector, types);
+
+    // If we don't know what we're calling or if we are calling a getter,
+    // we need to register that fact that we may be calling a closure
+    // with the same arguments.
+    Element target = node.element;
+    if (target == null || target.isGetter()) {
+      // TODO(kasperl): If we have a typed selector for the call, we
+      // may know something about the types of closures that need
+      // the specific closure call method.
+      Selector call = new Selector.callClosureFrom(selector);
+      world.registerDynamicInvocation(call.name, call);
+    }
+
+    if (target != null) {
+      // If we know we're calling a specific method, register that
+      // method only.
+      world.registerDynamicInvocationOf(target, selector);
+    } else {
+      SourceString name = node.selector.name;
+      world.registerDynamicInvocation(name, selector);
+    }
+    registerInvoke(node);
+  }
+
+  void registerSetter(HInvokeDynamic node) {
+    Selector selector = getOptimizedSelectorFor(node, node.selector);
+    world.registerDynamicSetter(selector.name, selector);
+    HType valueType = node.isInterceptorCall
+        ? types[node.inputs[2]]
+        : types[node.inputs[1]];
+    backend.addedDynamicSetter(selector, valueType);
+    registerInvoke(node);
+  }
+
+  void registerGetter(HInvokeDynamic node) {
+    Selector getter = node.selector;
+    world.registerDynamicGetter(
+        getter.name, getOptimizedSelectorFor(node, getter));
+    world.registerInstantiatedClass(compiler.functionClass);
+    registerInvoke(node);
+  }
+
+  visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
+    use(node.receiver);
+    Selector setter = node.selector;
+    String name = backend.namer.invocationName(setter);
+    push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node);
+    registerSetter(node);
+  }
+
+  visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
+    use(node.receiver);
+    Selector getter = node.selector;
+    String name = backend.namer.invocationName(getter);
+    push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node);
+    registerGetter(node);
+  }
+
+  visitInvokeClosure(HInvokeClosure node) {
+    Selector call = new Selector.callClosureFrom(node.selector);
+    use(node.receiver);
+    push(jsPropertyCall(pop(),
+                        backend.namer.invocationName(call),
+                        visitArguments(node.inputs)),
+         node);
+    world.registerDynamicInvocation(call.name, call);
+  }
+
+  visitInvokeStatic(HInvokeStatic node) {
+    if (node.typeCode() == HInstruction.INVOKE_STATIC_TYPECODE) {
+      // Register this invocation to collect the types used at all call sites.
+      backend.registerStaticInvocation(node, types);
+    }
+    use(node.target);
+    push(new js.Call(pop(), visitArguments(node.inputs)), node);
+  }
+
+  visitInvokeSuper(HInvokeSuper node) {
+    Element superMethod = node.element;
+    Element superClass = superMethod.getEnclosingClass();
+    if (superMethod.kind == ElementKind.FIELD) {
+      ClassElement currentClass = work.element.getEnclosingClass();
+      if (currentClass.isClosure()) {
+        ClosureClassElement closure = currentClass;
+        currentClass = closure.methodElement.getEnclosingClass();
+      }
+      String fieldName = currentClass.isShadowedByField(superMethod)
+          ? backend.namer.shadowedFieldName(superMethod)
+          : backend.namer.instanceFieldName(superMethod);
+      use(node.inputs[1]);
+      js.PropertyAccess access =
+          new js.PropertyAccess.field(pop(), fieldName);
+      if (node.isSetter) {
+        use(node.value);
+        push(new js.Assignment(access, pop()), node);
+      } else {
+        push(access, node);
+      }
+    } else {
+      String methodName = backend.namer.getName(superMethod);
+      String className = backend.namer.isolateAccess(superClass);
+      js.VariableUse classReference = new js.VariableUse(className);
+      js.PropertyAccess prototype =
+          new js.PropertyAccess.field(classReference, "prototype");
+      js.PropertyAccess method =
+          new js.PropertyAccess.field(prototype, methodName);
+      push(jsPropertyCall(method, "call", visitArguments(node.inputs)), node);
+    }
+    world.registerStaticUse(superMethod);
+  }
+
+  visitFieldGet(HFieldGet node) {
+    use(node.receiver);
+    if (node.element == backend.jsArrayLength
+        || node.element == backend.jsStringLength) {
+      // We're accessing a native JavaScript property called 'length'
+      // on a JS String or a JS array. Therefore, the name of that
+      // property should not be mangled.
+      push(new js.PropertyAccess.field(pop(), 'length'), node);
+    } else {
+      String name = _fieldPropertyName(node.element);
+      push(new js.PropertyAccess.field(pop(), name), node);
+      HType receiverHType = types[node.receiver];
+      DartType type = receiverHType.computeType(compiler);
+      if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) {
+        world.registerFieldGetter(
+            node.element.name, node.element.getLibrary(), type);
+      }
+    }
+  }
+
+  visitFieldSet(HFieldSet node) {
+    String name = _fieldPropertyName(node.element);
+    DartType type = types[node.receiver].computeType(compiler);
+    if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) {
+      // Field setters in the generative constructor body are handled in a
+      // step "SsaConstructionFieldTypes" in the ssa optimizer.
+      if (!work.element.isGenerativeConstructorBody()) {
+        world.registerFieldSetter(
+            node.element.name, node.element.getLibrary(), type);
+        backend.registerFieldSetter(
+            work.element, node.element, types[node.value]);
+      }
+    }
+    use(node.receiver);
+    js.Expression receiver = pop();
+    use(node.value);
+    push(new js.Assignment(new js.PropertyAccess.field(receiver, name), pop()),
+        node);
+  }
+
+  String _fieldPropertyName(Element element) => element.hasFixedBackendName()
+      ? element.fixedBackendName()
+      : backend.namer.getName(element);
+
+  visitLocalGet(HLocalGet node) {
+    use(node.receiver);
+  }
+
+  visitLocalSet(HLocalSet node) {
+    use(node.value);
+    assignVariable(variableNames.getName(node.receiver), pop());
+  }
+
+  void registerForeignType(HType type) {
+    DartType dartType = type.computeType(compiler);
+    if (dartType == null) {
+      assert(type == HType.UNKNOWN);
+      return;
+    }
+    world.registerInstantiatedClass(dartType.element);
+  }
+
+  visitForeign(HForeign node) {
+    String code = node.code.slowToString();
+    List<HInstruction> inputs = node.inputs;
+    if (node.isJsStatement()) {
+      if (!inputs.isEmpty) {
+        compiler.internalError("foreign statement with inputs: $code",
+                               instruction: node);
+      }
+      pushStatement(new js.LiteralStatement(code), node);
+    } else {
+      List<js.Expression> data = <js.Expression>[];
+      for (int i = 0; i < inputs.length; i++) {
+        use(inputs[i]);
+        data.add(pop());
+      }
+      push(new js.LiteralExpression.withData(code, data), node);
+    }
+    registerForeignType(types[node]);
+    // TODO(sra): Tell world.nativeEnqueuer about the types created here.
+  }
+
+  visitForeignNew(HForeignNew node) {
+    String jsClassReference = backend.namer.isolateAccess(node.element);
+    List<HInstruction> inputs = node.inputs;
+    // We can't use 'visitArguments', since our arguments start at input[0].
+    List<js.Expression> arguments = <js.Expression>[];
+    for (int i = 0; i < inputs.length; i++) {
+      use(inputs[i]);
+      arguments.add(pop());
+    }
+    // TODO(floitsch): jsClassReference is an Access. We shouldn't treat it
+    // as if it was a string.
+    push(new js.New(new js.VariableUse(jsClassReference), arguments), node);
+    registerForeignType(types[node]);
+  }
+
+  js.Expression newLiteralBool(bool value) {
+    if (compiler.enableMinification) {
+      // Use !0 for true, !1 for false.
+      return new js.Prefix("!", new js.LiteralNumber(value ? "0" : "1"));
+    } else {
+      return new js.LiteralBool(value);
+    }
+  }
+
+  void generateConstant(Constant constant) {
+    if (constant.isFunction()) {
+      FunctionConstant function = constant;
+      world.registerStaticUse(function.element);
+    }
+    push(backend.emitter.constantReference(constant));
+  }
+
+  visitConstant(HConstant node) {
+    assert(isGenerateAtUseSite(node));
+    generateConstant(node.constant);
+    DartType type = node.constant.computeType(compiler);
+    if (node.constant is ConstructedConstant) {
+      ConstantHandler handler = compiler.constantHandler;
+      handler.registerCompileTimeConstant(node.constant);
+    }
+    world.registerInstantiatedClass(type.element);
+  }
+
+  visitNot(HNot node) {
+    assert(node.inputs.length == 1);
+    generateNot(node.inputs[0]);
+    attachLocationToLast(node);
+  }
+
+  void generateNot(HInstruction input) {
+    bool canGenerateOptimizedComparison(HInstruction instruction) {
+      if (instruction is !HRelational) return false;
+      HRelational relational = instruction;
+      HInstruction left = relational.left;
+      HInstruction right = relational.right;
+      // This optimization doesn't work for NaN, so we only do it if the
+      // type is known to be an integer.
+      return types[left].isUseful() && left.isInteger(types)
+          && types[right].isUseful() && right.isInteger(types);
+    }
+
+    if (input is HBoolify && isGenerateAtUseSite(input)) {
+      use(input.inputs[0]);
+      push(new js.Binary("!==", pop(), newLiteralBool(true)), input);
+    } else if (canGenerateOptimizedComparison(input) &&
+               isGenerateAtUseSite(input)) {
+      Map<String, String> inverseOperator = const <String, String>{
+        "==" : "!=",
+        "!=" : "==",
+        "===": "!==",
+        "!==": "===",
+        "<"  : ">=",
+        "<=" : ">",
+        ">"  : "<=",
+        ">=" : "<"
+      };
+      HRelational relational = input;
+      BinaryOperation operation = relational.operation(backend.constantSystem);
+      visitRelational(input, inverseOperator[operation.name.stringValue]);
+    } else {
+      use(input);
+      push(new js.Prefix("!", pop()));
+    }
+  }
+
+  visitParameterValue(HParameterValue node) {
+    assert(!isGenerateAtUseSite(node));
+    String name = variableNames.getName(node);
+    parameters.add(new js.Parameter(name));
+    declaredLocals.add(name);
+  }
+
+  visitLocalValue(HLocalValue node) {
+    assert(!isGenerateAtUseSite(node));
+    String name = variableNames.getName(node);
+    collectedVariableDeclarations.add(name);
+  }
+
+  visitPhi(HPhi node) {
+    // This method is only called for phis that are generated at use
+    // site. A phi can be generated at use site only if it is the
+    // result of a control flow operation.
+    HBasicBlock ifBlock = node.block.dominator;
+    assert(controlFlowOperators.contains(ifBlock.last));
+    HInstruction input = ifBlock.last.inputs[0];
+    if (input.isConstantFalse()) {
+      use(node.inputs[1]);
+    } else if (input.isConstantTrue()) {
+      use(node.inputs[0]);
+    } else if (node.inputs[1].isConstantBoolean()) {
+      String operation = node.inputs[1].isConstantFalse() ? '&&' : '||';
+      if (operation == '||') {
+        if (input is HNot) {
+          use(input.inputs[0]);
+        } else {
+          generateNot(input);
+        }
+      } else {
+        use(input);
+      }
+      js.Expression left = pop();
+      use(node.inputs[0]);
+      push(new js.Binary(operation, left, pop()));
+    } else {
+      use(input);
+      js.Expression test = pop();
+      use(node.inputs[0]);
+      js.Expression then = pop();
+      use(node.inputs[1]);
+      push(new js.Conditional(test, then, pop()));
+    }
+  }
+
+  visitReturn(HReturn node) {
+    assert(node.inputs.length == 1);
+    HInstruction input = node.inputs[0];
+    if (input.isConstantNull()) {
+      pushStatement(new js.Return(null), node);
+    } else {
+      use(node.inputs[0]);
+      pushStatement(new js.Return(pop()), node);
+    }
+  }
+
+  visitThis(HThis node) {
+    push(new js.This());
+  }
+
+  visitThrow(HThrow node) {
+    if (node.isRethrow) {
+      use(node.inputs[0]);
+      pushStatement(new js.Throw(pop()), node);
+    } else {
+      generateThrowWithHelper(r'$throw', node.inputs[0]);
+    }
+  }
+
+  visitRangeConversion(HRangeConversion node) {
+    // Range conversion instructions are removed by the value range
+    // analyzer.
+    assert(false);
+  }
+
+  visitBoundsCheck(HBoundsCheck node) {
+    // TODO(ngeoffray): Separate the two checks of the bounds check, so,
+    // e.g., the zero checks can be shared if possible.
+
+    // If the checks always succeeds, we would have removed the bounds check
+    // completely.
+    assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE);
+    if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) {
+      js.Expression under;
+      js.Expression over;
+      if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) {
+        use(node.index);
+        under = new js.Binary("<", pop(), new js.LiteralNumber("0"));
+      }
+      if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) {
+        var index = node.index;
+        use(index);
+        js.Expression jsIndex = pop();
+        use(node.length);
+        over = new js.Binary(">=", jsIndex, pop());
+      }
+      assert(over != null || under != null);
+      js.Expression underOver = under == null
+          ? over
+          : over == null
+              ? under
+              : new js.Binary("||", under, over);
+      js.Statement thenBody = new js.Block.empty();
+      js.Block oldContainer = currentContainer;
+      currentContainer = thenBody;
+      generateThrowWithHelper('ioore', node.index);
+      currentContainer = oldContainer;
+      thenBody = unwrapStatement(thenBody);
+      pushStatement(new js.If.noElse(underOver, thenBody), node);
+    } else {
+      generateThrowWithHelper('ioore', node.index);
+    }
+  }
+
+  visitIntegerCheck(HIntegerCheck node) {
+    if (!node.alwaysFalse) {
+      checkInt(node.value, '!==');
+      js.Expression test = pop();
+      js.Statement thenBody = new js.Block.empty();
+      js.Block oldContainer = currentContainer;
+      currentContainer = thenBody;
+      generateThrowWithHelper('iae', node.value);
+      currentContainer = oldContainer;
+      thenBody = unwrapStatement(thenBody);
+      pushStatement(new js.If.noElse(test, thenBody), node);
+    } else {
+      generateThrowWithHelper('iae', node.value);
+    }
+  }
+
+  void generateThrowWithHelper(String helperName, HInstruction argument) {
+    Element helper = compiler.findHelper(new SourceString(helperName));
+    world.registerStaticUse(helper);
+    js.VariableUse jsHelper =
+        new js.VariableUse(backend.namer.isolateAccess(helper));
+    js.Call value = new js.Call(jsHelper, visitArguments([null, argument]));
+    attachLocation(value, argument);
+    // BUG(4906): Using throw here adds to the size of the generated code
+    // but it has the advantage of explicitly telling the JS engine that
+    // this code path will terminate abruptly. Needs more work.
+    pushStatement(new js.Throw(value));
+  }
+
+  void visitSwitch(HSwitch node) {
+    // Switches are handled using [visitSwitchInfo].
+  }
+
+  void visitStatic(HStatic node) {
+    // Check whether this static is used for anything else than as a target in
+    // a static call.
+    node.usedBy.forEach((HInstruction instr) {
+      if (instr is !HInvokeStatic) {
+        backend.registerNonCallStaticUse(node);
+        if (node.element.isFunction()) {
+          world.registerInstantiatedClass(compiler.functionClass);
+        }
+      } else if (instr.target != node) {
+        backend.registerNonCallStaticUse(node);
+      }
+    });
+    Element element = node.element;
+    world.registerStaticUse(element);
+    ClassElement cls = element.getEnclosingClass();
+    if (element.isGenerativeConstructor()
+        || (element.isFactoryConstructor() && cls == compiler.listClass)) {
+      world.registerInstantiatedClass(cls);
+    }
+    push(new js.VariableUse(backend.namer.isolateAccess(node.element)));
+  }
+
+  void visitLazyStatic(HLazyStatic node) {
+    Element element = node.element;
+    world.registerStaticUse(element);
+    String lazyGetter = backend.namer.isolateLazyInitializerAccess(element);
+    js.VariableUse target = new js.VariableUse(lazyGetter);
+    js.Call call = new js.Call(target, <js.Expression>[]);
+    push(call, node);
+  }
+
+  void visitStaticStore(HStaticStore node) {
+    world.registerStaticUse(node.element);
+    js.VariableUse variableUse =
+        new js.VariableUse(backend.namer.isolateAccess(node.element));
+    use(node.inputs[0]);
+    push(new js.Assignment(variableUse, pop()), node);
+  }
+
+  void visitStringConcat(HStringConcat node) {
+    if (isEmptyString(node.left)) {
+      useStringified(node.right);
+   } else if (isEmptyString(node.right)) {
+      useStringified(node.left);
+    } else {
+      useStringified(node.left);
+      js.Expression left = pop();
+      useStringified(node.right);
+      push(new js.Binary("+", left, pop()), node);
+    }
+  }
+
+  bool isEmptyString(HInstruction node) {
+    if (!node.isConstantString()) return false;
+    HConstant constant = node;
+    StringConstant string = constant.constant;
+    return string.value.length == 0;
+  }
+
+  void useStringified(HInstruction node) {
+    if (node.isString(types)) {
+      use(node);
+    } else {
+      Element convertToString = compiler.findHelper(const SourceString("S"));
+      world.registerStaticUse(convertToString);
+      js.VariableUse variableUse =
+          new js.VariableUse(backend.namer.isolateAccess(convertToString));
+      use(node);
+      push(new js.Call(variableUse, <js.Expression>[pop()]), node);
+    }
+  }
+
+  void visitLiteralList(HLiteralList node) {
+    world.registerInstantiatedClass(compiler.listClass);
+    generateArrayLiteral(node);
+  }
+
+  void generateArrayLiteral(HLiteralList node) {
+    int len = node.inputs.length;
+    List<js.ArrayElement> elements = <js.ArrayElement>[];
+    for (int i = 0; i < len; i++) {
+      use(node.inputs[i]);
+      elements.add(new js.ArrayElement(i, pop()));
+    }
+    push(new js.ArrayInitializer(len, elements), node);
+  }
+
+  void visitIndex(HIndex node) {
+    use(node.receiver);
+    js.Expression receiver = pop();
+    use(node.index);
+    push(new js.PropertyAccess(receiver, pop()), node);
+  }
+
+  void visitIndexAssign(HIndexAssign node) {
+    use(node.receiver);
+    js.Expression receiver = pop();
+    use(node.index);
+    js.Expression index = pop();
+    use(node.value);
+    push(new js.Assignment(new js.PropertyAccess(receiver, index), pop()),
+         node);
+  }
+
+  void checkInt(HInstruction input, String cmp) {
+    use(input);
+    js.Expression left = pop();
+    use(input);
+    js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0"));
+    push(new js.Binary(cmp, left, or0));
+  }
+
+  void checkBigInt(HInstruction input, String cmp) {
+    use(input);
+    js.Expression left = pop();
+    use(input);
+    js.Expression right = pop();
+    // TODO(4984): Deal with infinity and -0.0.
+    push(new js.LiteralExpression.withData('Math.floor(#) === #',
+                                           <js.Expression>[left, right]));
+  }
+
+  void checkTypeOf(HInstruction input, String cmp, String typeName) {
+    use(input);
+    js.Expression typeOf = new js.Prefix("typeof", pop());
+    push(new js.Binary(cmp, typeOf, js.string(typeName)));
+  }
+
+  void checkNum(HInstruction input, String cmp)
+      => checkTypeOf(input, cmp, 'number');
+
+  void checkDouble(HInstruction input, String cmp)  => checkNum(input, cmp);
+
+  void checkString(HInstruction input, String cmp)
+      => checkTypeOf(input, cmp, 'string');
+
+  void checkBool(HInstruction input, String cmp)
+      => checkTypeOf(input, cmp, 'boolean');
+
+  void checkObject(HInstruction input, String cmp) {
+    assert(NullConstant.JsNull == 'null');
+    if (cmp == "===") {
+      checkTypeOf(input, '===', 'object');
+      js.Expression left = pop();
+      use(input);
+      js.Expression notNull = new js.Binary("!==", pop(), new js.LiteralNull());
+      push(new js.Binary("&&", left, notNull));
+    } else {
+      assert(cmp == "!==");
+      checkTypeOf(input, '!==', 'object');
+      js.Expression left = pop();
+      use(input);
+      js.Expression eqNull = new js.Binary("===", pop(), new js.LiteralNull());
+      push(new js.Binary("||", left, eqNull));
+    }
+  }
+
+  void checkArray(HInstruction input, String cmp) {
+    use(input);
+    js.PropertyAccess constructor =
+        new js.PropertyAccess.field(pop(), 'constructor');
+    push(new js.Binary(cmp, constructor, new js.VariableUse('Array')));
+  }
+
+  void checkFieldExists(HInstruction input, String fieldName) {
+    use(input);
+    js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName);
+    // Double negate to boolify the result.
+    push(new js.Prefix('!', new js.Prefix('!', field)));
+  }
+
+  void checkImmutableArray(HInstruction input) {
+    checkFieldExists(input, 'immutable\$list');
+  }
+
+  void checkExtendableArray(HInstruction input) {
+    checkFieldExists(input, 'fixed\$length');
+  }
+
+  void checkFixedArray(HInstruction input) {
+    checkFieldExists(input, 'fixed\$length');
+  }
+
+  void checkNull(HInstruction input) {
+    use(input);
+    push(new js.Binary('==', pop(), new js.LiteralNull()));
+  }
+
+  void checkNonNull(HInstruction input) {
+    use(input);
+    push(new js.Binary('!=', pop(), new js.LiteralNull()));
+  }
+
+  void checkFunction(HInstruction input, DartType type) {
+    checkTypeOf(input, '===', 'function');
+    js.Expression functionTest = pop();
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkType(input, type);
+    push(new js.Binary('||',
+                       functionTest,
+                       new js.Binary('&&', objectTest, pop())));
+  }
+
+  void checkType(HInstruction input, DartType type, {bool negative: false}) {
+    assert(invariant(input, !type.isMalformed,
+                     message: 'Attempt to check malformed type $type'));
+    world.registerIsCheck(type);
+    Element element = type.element;
+    use(input);
+    js.PropertyAccess field =
+        new js.PropertyAccess.field(pop(), backend.namer.operatorIs(element));
+    if (backend.emitter.nativeEmitter.requiresNativeIsCheck(element)) {
+      push(new js.Call(field, <js.Expression>[]));
+      if (negative) push(new js.Prefix('!', pop()));
+    } else {
+      // We always negate at least once so that the result is boolified.
+      push(new js.Prefix('!', field));
+      // If the result is not negated, put another '!' in front.
+      if (!negative) push(new js.Prefix('!', pop()));
+    }
+  }
+
+  void handleNumberOrStringSupertypeCheck(HInstruction input, DartType type) {
+    assert(!identical(type.element, compiler.listClass)
+           && !Elements.isListSupertype(type.element, compiler)
+           && !Elements.isStringOnlySupertype(type.element, compiler));
+    checkNum(input, '===');
+    js.Expression numberTest = pop();
+    checkString(input, '===');
+    js.Expression stringTest = pop();
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkType(input, type);
+    push(new js.Binary('||',
+                       new js.Binary('||', numberTest, stringTest),
+                       new js.Binary('&&', objectTest, pop())));
+  }
+
+  void handleStringSupertypeCheck(HInstruction input, DartType type) {
+    assert(!identical(type.element, compiler.listClass)
+           && !Elements.isListSupertype(type.element, compiler)
+           && !Elements.isNumberOrStringSupertype(type.element, compiler));
+    checkString(input, '===');
+    js.Expression stringTest = pop();
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkType(input, type);
+    push(new js.Binary('||',
+                       stringTest,
+                       new js.Binary('&&', objectTest, pop())));
+  }
+
+  void handleListOrSupertypeCheck(HInstruction input, DartType type) {
+    assert(!identical(type.element, compiler.stringClass)
+           && !Elements.isStringOnlySupertype(type.element, compiler)
+           && !Elements.isNumberOrStringSupertype(type.element, compiler));
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkArray(input, '===');
+    js.Expression arrayTest = pop();
+    checkType(input, type);
+    push(new js.Binary('&&',
+                       objectTest,
+                       new js.Binary('||', arrayTest, pop())));
+  }
+
+  void visitIs(HIs node) {
+    DartType type = node.typeExpression;
+    world.registerIsCheck(type);
+    Element element = type.element;
+    if (identical(element.kind, ElementKind.TYPE_VARIABLE)) {
+      compiler.unimplemented("visitIs for type variables",
+                             instruction: node.expression);
+    }
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    ClassElement objectClass = compiler.objectClass;
+    HInstruction input = node.expression;
+
+    if (identical(element, objectClass) ||
+        identical(element, compiler.dynamicClass)) {
+      // The constant folder also does this optimization, but we make
+      // it safe by assuming it may have not run.
+      push(newLiteralBool(true), node);
+    } else if (element == compiler.stringClass) {
+      checkString(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.doubleClass) {
+      checkDouble(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.numClass) {
+      checkNum(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.boolClass) {
+      checkBool(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.functionClass) {
+      checkFunction(input, type);
+      attachLocationToLast(node);
+    } else if (element == compiler.intClass) {
+      // The is check in the code tells us that it might not be an
+      // int. So we do a typeof first to avoid possible
+      // deoptimizations on the JS engine due to the Math.floor check.
+      checkNum(input, '===');
+      js.Expression numTest = pop();
+      checkBigInt(input, '===');
+      push(new js.Binary('&&', numTest, pop()), node);
+    } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
+      handleNumberOrStringSupertypeCheck(input, type);
+      attachLocationToLast(node);
+    } else if (Elements.isStringOnlySupertype(element, compiler)) {
+      handleStringSupertypeCheck(input, type);
+      attachLocationToLast(node);
+    } else if (identical(element, compiler.listClass)
+               || Elements.isListSupertype(element, compiler)) {
+      handleListOrSupertypeCheck(input, type);
+      attachLocationToLast(node);
+    } else if (element.isTypedef()) {
+      checkNonNull(input);
+      js.Expression nullTest = pop();
+      checkType(input, type);
+      push(new js.Binary('&&', nullTest, pop()));
+      attachLocationToLast(node);
+    } else if (types[input].canBePrimitive() || types[input].canBeNull()) {
+      checkObject(input, '===');
+      js.Expression objectTest = pop();
+      checkType(input, type);
+      push(new js.Binary('&&', objectTest, pop()), node);
+    } else {
+      checkType(input, type);
+      attachLocationToLast(node);
+    }
+    if (node.hasArgumentChecks()) {
+      InterfaceType interfaceType = type;
+      ClassElement cls = type.element;
+      Link<DartType> arguments = interfaceType.typeArguments;
+      js.Expression result = pop();
+      for (int i = 0; i < node.checkCount; i++) {
+        use(node.getCheck(i));
+        result = new js.Binary('&&', result, pop());
+      }
+      push(result, node);
+    }
+    if (node.nullOk) {
+      checkNull(input);
+      push(new js.Binary('||', pop(), pop()), node);
+    }
+  }
+
+  // TODO(johnniwinther): Refactor this method.
+  void visitTypeConversion(HTypeConversion node) {
+    Map<String, SourceString> castNames = const <String, SourceString> {
+      "stringTypeCheck":
+          const SourceString("stringTypeCast"),
+      "doubleTypeCheck":
+          const SourceString("doubleTypeCast"),
+      "numTypeCheck":
+          const SourceString("numTypeCast"),
+      "boolTypeCheck":
+          const SourceString("boolTypeCast"),
+      "functionTypeCheck":
+          const SourceString("functionTypeCast"),
+      "intTypeCheck":
+          const SourceString("intTypeCast"),
+      "numberOrStringSuperNativeTypeCheck":
+          const SourceString("numberOrStringSuperNativeTypeCast"),
+      "numberOrStringSuperTypeCheck":
+          const SourceString("numberOrStringSuperTypeCast"),
+      "stringSuperNativeTypeCheck":
+          const SourceString("stringSuperNativeTypeCast"),
+      "stringSuperTypeCheck":
+          const SourceString("stringSuperTypeCast"),
+      "listTypeCheck":
+          const SourceString("listTypeCast"),
+      "listSuperNativeTypeCheck":
+          const SourceString("listSuperNativeTypeCast"),
+      "listSuperTypeCheck":
+          const SourceString("listSuperTypeCast"),
+      "callTypeCheck":
+          const SourceString("callTypeCast"),
+      "propertyTypeCheck":
+          const SourceString("propertyTypeCast"),
+      // TODO(johnniwinther): Add a malformedTypeCast which produces a TypeError
+      // with another message.
+      "malformedTypeCheck":
+          const SourceString("malformedTypeCheck")
+    };
+
+    if (node.isChecked) {
+      DartType type = node.type.computeType(compiler);
+      Element element = type.element;
+      world.registerIsCheck(type);
+
+      if (node.isArgumentTypeCheck) {
+        if (element == backend.jsIntClass) {
+          checkInt(node.checkedInput, '!==');
+        } else {
+          assert(element == backend.jsNumberClass);
+          checkNum(node.checkedInput, '!==');
+        }
+        js.Expression test = pop();
+        js.Block oldContainer = currentContainer;
+        js.Statement body = new js.Block.empty();
+        currentContainer = body;
+        generateThrowWithHelper('iae', node.checkedInput);
+        currentContainer = oldContainer;
+        body = unwrapStatement(body);
+        pushStatement(new js.If.noElse(test, body), node);
+        return;
+      }
+      assert(node.isCheckedModeCheck || node.isCastTypeCheck);
+
+      SourceString helper;
+      if (node.isBooleanConversionCheck) {
+        helper = const SourceString('boolConversionCheck');
+      } else {
+        helper = backend.getCheckedModeHelper(type);
+        if (node.isCastTypeCheck) {
+          helper = castNames[helper.stringValue];
+        }
+      }
+      FunctionElement helperElement = compiler.findHelper(helper);
+      world.registerStaticUse(helperElement);
+      List<js.Expression> arguments = <js.Expression>[];
+      use(node.checkedInput);
+      arguments.add(pop());
+      int parameterCount =
+          helperElement.computeSignature(compiler).parameterCount;
+      if (parameterCount == 2) {
+        // 2 arguments implies that the method is either [propertyTypeCheck]
+        // or [propertyTypeCast].
+        assert(!type.isMalformed);
+        String additionalArgument = backend.namer.operatorIs(element);
+        arguments.add(js.string(additionalArgument));
+      } else if (parameterCount == 3) {
+        // 3 arguments implies that the method is [malformedTypeCheck].
+        assert(type.isMalformed);
+        String reasons = Types.fetchReasonsFromMalformedType(type);
+        arguments.add(js.string('$type'));
+        // TODO(johnniwinther): Handle escaping correctly.
+        arguments.add(js.string(reasons));
+      } else {
+        assert(!type.isMalformed);
+      }
+      String helperName = backend.namer.isolateAccess(helperElement);
+      push(new js.Call(new js.VariableUse(helperName), arguments));
+    } else {
+      use(node.checkedInput);
+    }
+  }
+}
+
+class SsaOptimizedCodeGenerator extends SsaCodeGenerator {
+  SsaOptimizedCodeGenerator(backend, work) : super(backend, work);
+
+  HBasicBlock beginGraph(HGraph graph) {
+    return graph.entry;
+  }
+
+  void endGraph(HGraph graph) {}
+
+  // Called by visitTypeGuard to generate the actual bailout call, something
+  // like "return $.foo$bailout(t0, t1);"
+  js.Statement bailout(HTypeGuard guard, String reason) {
+    HBailoutTarget target = guard.bailoutTarget;
+    List<js.Expression> arguments = <js.Expression>[];
+    arguments.add(new js.LiteralNumber("${guard.state}"));
+
+    for (int i = 0; i < target.inputs.length; i++) {
+      HInstruction parameter = target.inputs[i];
+      for (int pad = target.padding[i]; pad != 0; pad--) {
+        // This argument will not be used by the bailout function, because
+        // of the control flow (controlled by the state argument passed
+        // above).  We need to pass it to get later arguments in the right
+        // position.
+        arguments.add(new js.LiteralNumber('0'));
+      }
+      use(parameter);
+      arguments.add(pop());
+    }
+    // Don't bother emitting the rest of the pending nulls.  Doing so might make
+    // the function invocation a little faster by having the call site and
+    // function defintion have the same number of arguments, but it would be
+    // more verbose and we don't expect the calls to bailout functions to be
+    // hot.
+
+    Element method = work.element;
+    js.Expression bailoutTarget;  // Receiver of the bailout call.
+    Namer namer = backend.namer;
+    if (method.isInstanceMember()) {
+      String bailoutName = namer.getBailoutName(method);
+      bailoutTarget = new js.PropertyAccess.field(new js.This(), bailoutName);
+    } else {
+      assert(!method.isField());
+      bailoutTarget = new js.VariableUse(namer.isolateBailoutAccess(method));
+    }
+    js.Call call = new js.Call(bailoutTarget, arguments);
+    attachLocation(call, guard);
+    return new js.Return(call);
+  }
+
+  // Generate a type guard, something like "if (typeof t0 == 'number')" and the
+  // corresponding bailout call, something like "return $.foo$bailout(t0, t1);"
+  void visitTypeGuard(HTypeGuard node) {
+    HInstruction input = node.guarded;
+    DartType indexingBehavior =
+        backend.jsIndexingBehaviorInterface.computeType(compiler);
+    if (node.isInteger(types)) {
+      // if (input is !int) bailout
+      checkInt(input, '!==');
+      js.Statement then = bailout(node, 'Not an integer');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isNumber(types)) {
+      // if (input is !num) bailout
+      checkNum(input, '!==');
+      js.Statement then = bailout(node, 'Not a number');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isBoolean(types)) {
+      // if (input is !bool) bailout
+      checkBool(input, '!==');
+      js.Statement then = bailout(node, 'Not a boolean');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isString(types)) {
+      // if (input is !string) bailout
+      checkString(input, '!==');
+      js.Statement then = bailout(node, 'Not a string');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isExtendableArray(types)) {
+      // if (input is !Object || input is !Array || input.isFixed) bailout
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkFixedArray(input);
+      js.Binary test = new js.Binary('||', objectTest, arrayTest);
+      test = new js.Binary('||', test, pop());
+      js.Statement then = bailout(node, 'Not an extendable array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else if (node.isMutableArray(types)) {
+      // if (input is !Object
+      //     || ((input is !Array || input.isImmutable)
+      //         && input is !JsIndexingBehavior)) bailout
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkImmutableArray(input);
+      js.Binary notArrayOrImmutable = new js.Binary('||', arrayTest, pop());
+      checkType(input, indexingBehavior, negative: true);
+      js.Binary notIndexing = new js.Binary('&&', notArrayOrImmutable, pop());
+      js.Binary test = new js.Binary('||', objectTest, notIndexing);
+      js.Statement then = bailout(node, 'Not a mutable array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else if (node.isReadableArray(types)) {
+      // if (input is !Object
+      //     || (input is !Array && input is !JsIndexingBehavior)) bailout
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkType(input, indexingBehavior, negative: true);
+      js.Expression notIndexing = new js.Binary('&&', arrayTest, pop());
+      js.Binary test = new js.Binary('||', objectTest, notIndexing);
+      js.Statement then = bailout(node, 'Not an array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else if (node.isIndexablePrimitive(types)) {
+      // if (input is !String
+      //     && (input is !Object
+      //         || (input is !Array && input is !JsIndexingBehavior))) bailout
+      checkString(input, '!==');
+      js.Expression stringTest = pop();
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkType(input, indexingBehavior, negative: true);
+      js.Binary notIndexingTest = new js.Binary('&&', arrayTest, pop());
+      js.Binary notObjectOrIndexingTest =
+          new js.Binary('||', objectTest, notIndexingTest);
+      js.Binary test =
+          new js.Binary('&&', stringTest, notObjectOrIndexingTest);
+      js.Statement then = bailout(node, 'Not a string or array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else {
+      compiler.internalError('Unexpected type guard', instruction: input);
+    }
+  }
+
+  void visitBailoutTarget(HBailoutTarget target) {
+    // Do nothing. Bailout targets are only used in the non-optimized version.
+  }
+
+  void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+  }
+
+  void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+  }
+
+  void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+  }
+}
+
+class SsaUnoptimizedCodeGenerator extends SsaCodeGenerator {
+
+  js.Switch currentBailoutSwitch;
+  final List<js.Switch> oldBailoutSwitches;
+  final List<js.Parameter> newParameters;
+  final List<String> labels;
+  int labelId = 0;
+  /**
+   * Keeps track if a bailout switch already used its [:default::] clause. New
+   * bailout-switches just push [:false:] on the stack and replace it when
+   * they used the [:default::] clause.
+   */
+  final List<bool> defaultClauseUsedInBailoutStack;
+
+  SsaBailoutPropagator propagator;
+  HInstruction savedFirstInstruction;
+
+  SsaUnoptimizedCodeGenerator(backend, work)
+    : super(backend, work),
+      oldBailoutSwitches = <js.Switch>[],
+      newParameters = <js.Parameter>[],
+      labels = <String>[],
+      defaultClauseUsedInBailoutStack = <bool>[];
+
+  String pushLabel() {
+    String label = 'L${labelId++}';
+    labels.addLast(label);
+    return label;
+  }
+
+  String popLabel() {
+    return labels.removeLast();
+  }
+
+  String currentLabel() {
+    return labels.last;
+  }
+
+  js.VariableUse generateStateUse()
+      => new js.VariableUse(variableNames.stateName);
+
+  HBasicBlock beginGraph(HGraph graph) {
+    propagator = new SsaBailoutPropagator(compiler, variableNames);
+    propagator.visitGraph(graph);
+    // TODO(ngeoffray): We could avoid generating the state at the
+    // call site for non-complex bailout methods.
+    newParameters.add(new js.Parameter(variableNames.stateName));
+
+    List<String> names = new List<String>(propagator.bailoutArity);
+    for (String variable in propagator.parameterNames.keys) {
+      int index = propagator.parameterNames[variable];
+      assert(names[index] == null);
+      names[index] = variable;
+    }
+    for (int i = 0; i < names.length; i++) {
+      declaredLocals.add(names[i]);
+      newParameters.add(new js.Parameter(names[i]));
+    }
+
+    if (propagator.hasComplexBailoutTargets) {
+      startBailoutSwitch();
+
+      return graph.entry;
+    } else {
+      // We change the first instruction of the first guard to be the
+      // bailout target. We will change it back in the call to [endGraph].
+      HBasicBlock block = propagator.firstBailoutTarget.block;
+      savedFirstInstruction = block.first;
+      block.first = propagator.firstBailoutTarget;
+      return block;
+    }
+  }
+
+  // If argument is a [HCheck] and it does not have a name, we try to
+  // find the name of its checked input. Note that there must be a
+  // name, otherwise the instruction would not be in the live
+  // environment.
+  HInstruction unwrap(HInstruction argument) {
+    while (argument is HCheck && !variableNames.hasName(argument)) {
+      argument = argument.checkedInput;
+    }
+    assert(variableNames.hasName(argument));
+    return argument;
+  }
+
+  void endGraph(HGraph graph) {
+    if (propagator.hasComplexBailoutTargets) {
+      endBailoutSwitch();
+    } else {
+      // Put back the original first instruction of the block.
+      propagator.firstBailoutTarget.block.first = savedFirstInstruction;
+    }
+  }
+
+  visitParameterValue(HParameterValue node) {
+    // Nothing to do, parameters are dealt with specially in a bailout
+    // method.
+  }
+
+  bool visitAndOrInfo(HAndOrBlockInformation info) => false;
+
+  visitLoopBranch(HLoopBranch node) {
+    if (node.computeLoopHeader().hasBailoutTargets()) {
+      // The graph visitor in [visitLoopInfo] does not handle the
+      // condition. We must instead manually emit it here.
+      handleLoopCondition(node);
+      // We must also visit the body from here.
+      // For a do while loop, the body has already been visited.
+      if (!node.isDoWhile()) {
+        visitBasicBlock(node.block.dominatedBlocks[0]);
+      }
+    } else {
+      super.visitLoopBranch(node);
+    }
+  }
+
+
+  bool visitIfInfo(HIfBlockInformation info) {
+    if (info.thenGraph.start.hasBailoutTargets()) return false;
+    if (info.elseGraph.start.hasBailoutTargets()) return false;
+    return super.visitIfInfo(info);
+  }
+
+  bool visitLoopInfo(HLoopBlockInformation info) {
+    // Always emit with block flow traversal.
+    if (info.loopHeader.hasBailoutTargets()) {
+      // If there are any bailout targets in the loop, we cannot use
+      // the pretty [SsaCodeGenerator.visitLoopInfo] printer.
+      if (info.initializer != null) {
+        generateStatements(info.initializer);
+      }
+      beginLoop(info.loopHeader);
+      if (!info.isDoWhile()) {
+        generateStatements(info.condition);
+      }
+      generateStatements(info.body);
+      if (info.isDoWhile()) {
+        generateStatements(info.condition);
+      }
+      if (info.updates != null) {
+        generateStatements(info.updates);
+      }
+      endLoop(info.end);
+      return true;
+    }
+    return super.visitLoopInfo(info);
+  }
+
+  bool visitTryInfo(HTryBlockInformation info) => false;
+  bool visitSequenceInfo(HStatementSequenceInformation info) => false;
+
+  void visitTypeGuard(HTypeGuard node) {
+    // Do nothing. Type guards are only used in the optimized version.
+  }
+
+  void visitBailoutTarget(HBailoutTarget node) {
+    if (propagator.hasComplexBailoutTargets) {
+      js.Block nextBlock = new js.Block.empty();
+      js.Case clause = new js.Case(new js.LiteralNumber('${node.state}'),
+                                   nextBlock);
+      currentBailoutSwitch.cases.add(clause);
+      currentContainer = nextBlock;
+      pushExpressionAsStatement(new js.Assignment(generateStateUse(),
+                                                  new js.LiteralNumber('0')));
+    }
+    // Here we need to rearrange the inputs of the bailout target, so that they
+    // are output in the correct order, perhaps with interspersed nulls, to
+    // match the order in the bailout function, which is of course common to all
+    // the bailout points.
+    var newInputs = new List<HInstruction>(propagator.bailoutArity);
+    for (HInstruction input in node.inputs) {
+      int index = propagator.parameterNames[variableNames.getName(input)];
+      newInputs[index] = input;
+    }
+    // We record the count of unused arguments instead of just filling in the
+    // inputs list with dummy arguments because it is useful to be able easily
+    // to distinguish between a dummy argument (eg 0 or null) and a real
+    // argument that happens to have the same value.  The dummy arguments are
+    // not going to be accessed by the bailout function due to the control flow
+    // implied by the state argument, so we can put anything there, including
+    // just not emitting enough arguments and letting the JS engine insert
+    // undefined for the trailing arguments.
+    node.padding = new List<int>(node.inputs.length);
+    int j = 0;
+    int pendingUnusedArguments = 0;
+    for (int i = 0; i < newInputs.length; i++) {
+      HInstruction input = newInputs[i];
+      if (input == null) {
+        pendingUnusedArguments++;
+      } else {
+        node.padding[j] = pendingUnusedArguments;
+        pendingUnusedArguments = 0;
+        node.updateInput(j, input);
+        j++;
+      }
+    }
+    assert(j == node.inputs.length);
+  }
+
+  void startBailoutCase(List<HBailoutTarget> bailouts1,
+                        [List<HBailoutTarget> bailouts2 = const []]) {
+    if (!defaultClauseUsedInBailoutStack.last &&
+        bailouts1.length + bailouts2.length >= 2) {
+      currentContainer = new js.Block.empty();
+      currentBailoutSwitch.cases.add(new js.Default(currentContainer));
+      int len = defaultClauseUsedInBailoutStack.length;
+      defaultClauseUsedInBailoutStack[len - 1] = true;
+    } else {
+      _handleBailoutCase(bailouts1);
+      _handleBailoutCase(bailouts2);
+      currentContainer = currentBailoutSwitch.cases.last.body;
+    }
+  }
+
+  void _handleBailoutCase(List<HBailoutTarget> targets) {
+    for (int i = 0, len = targets.length; i < len; i++) {
+      js.LiteralNumber expr = new js.LiteralNumber('${targets[i].state}');
+      currentBailoutSwitch.cases.add(new js.Case(expr, new js.Block.empty()));
+    }
+  }
+
+  void startBailoutSwitch() {
+    defaultClauseUsedInBailoutStack.add(false);
+    oldBailoutSwitches.add(currentBailoutSwitch);
+    List<js.SwitchClause> cases = <js.SwitchClause>[];
+    js.Block firstBlock = new js.Block.empty();
+    cases.add(new js.Case(new js.LiteralNumber("0"), firstBlock));
+    currentBailoutSwitch = new js.Switch(generateStateUse(), cases);
+    pushStatement(currentBailoutSwitch);
+    oldContainerStack.add(currentContainer);
+    currentContainer = firstBlock;
+  }
+
+  js.Switch endBailoutSwitch() {
+    js.Switch result = currentBailoutSwitch;
+    currentBailoutSwitch = oldBailoutSwitches.removeLast();
+    defaultClauseUsedInBailoutStack.removeLast();
+    currentContainer = oldContainerStack.removeLast();
+    return result;
+  }
+
+  void beginLoop(HBasicBlock block) {
+    String loopLabel = pushLabel();
+    if (block.hasBailoutTargets()) {
+      startBailoutCase(block.bailoutTargets);
+    }
+    oldContainerStack.add(currentContainer);
+    currentContainer = new js.Block.empty();
+    if (block.hasBailoutTargets()) {
+      startBailoutSwitch();
+      HLoopInformation loopInformation = block.loopInformation;
+      if (loopInformation.target != null) {
+        breakAction[loopInformation.target] = (TargetElement target) {
+          pushStatement(new js.Break(loopLabel));
+        };
+      }
+    }
+  }
+
+  void endLoop(HBasicBlock block) {
+    String loopLabel = popLabel();
+
+    HBasicBlock header = block.isLoopHeader() ? block : block.parentLoopHeader;
+    HLoopInformation info = header.loopInformation;
+    if (header.hasBailoutTargets()) {
+      endBailoutSwitch();
+      if (info.target != null) breakAction.remove(info.target);
+    }
+
+    js.Statement body = unwrapStatement(currentContainer);
+    currentContainer = oldContainerStack.removeLast();
+
+    js.Statement result = new js.While(newLiteralBool(true), body);
+    attachLocationRange(result,
+                        info.loopBlockInformation.sourcePosition,
+                        info.loopBlockInformation.endSourcePosition);
+    result = new js.LabeledStatement(loopLabel, result);
+    result = wrapIntoLabels(result, info.labels);
+    pushStatement(result);
+  }
+
+  void handleLoopCondition(HLoopBranch node) {
+    use(node.inputs[0]);
+    js.Expression test = new js.Prefix('!', pop());
+    js.Statement then = new js.Break(currentLabel());
+    pushStatement(new js.If.noElse(test, then), node);
+  }
+
+  void generateIf(HIf node, HIfBlockInformation info) {
+    HStatementInformation thenGraph = info.thenGraph;
+    HStatementInformation elseGraph = info.elseGraph;
+    bool thenHasGuards = thenGraph.start.hasBailoutTargets();
+    bool elseHasGuards = elseGraph.start.hasBailoutTargets();
+    bool hasGuards = thenHasGuards || elseHasGuards;
+    if (!hasGuards) {
+      super.generateIf(node, info);
+      return;
+    }
+
+    startBailoutCase(thenGraph.start.bailoutTargets,
+                     elseGraph.start.bailoutTargets);
+
+    use(node.inputs[0]);
+    js.Binary stateEquals0 =
+        new js.Binary('===', generateStateUse(), new js.LiteralNumber('0'));
+    js.Expression condition = new js.Binary('&&', stateEquals0, pop());
+    // TODO(ngeoffray): Put the condition initialization in the
+    // arguments?
+    List<HBailoutTarget> targets = node.thenBlock.bailoutTargets;
+    for (int i = 0, len = targets.length; i < len; i++) {
+      js.VariableUse stateRef = generateStateUse();
+      js.Expression targetState = new js.LiteralNumber('${targets[i].state}');
+      js.Binary stateTest = new js.Binary('===', stateRef, targetState);
+      condition = new js.Binary('||', stateTest, condition);
+    }
+
+    js.Statement thenBody = new js.Block.empty();
+    js.Block oldContainer = currentContainer;
+    currentContainer = thenBody;
+    if (thenHasGuards) startBailoutSwitch();
+    generateStatements(thenGraph);
+    if (thenHasGuards) endBailoutSwitch();
+    thenBody = unwrapStatement(thenBody);
+
+    js.Statement elseBody = null;
+    elseBody = new js.Block.empty();
+    currentContainer = elseBody;
+    if (elseHasGuards) startBailoutSwitch();
+    generateStatements(elseGraph);
+    if (elseHasGuards) endBailoutSwitch();
+    elseBody = unwrapStatement(elseBody);
+
+    currentContainer = oldContainer;
+    pushStatement(new js.If(condition, thenBody, elseBody), node);
+  }
+
+  void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+    if (labeledBlockInfo.body.start.hasBailoutTargets()) {
+      indent--;
+      startBailoutCase(labeledBlockInfo.body.start.bailoutTargets);
+      indent++;
+    }
+  }
+
+  void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+    if (labeledBlockInfo.body.start.hasBailoutTargets()) {
+      startBailoutSwitch();
+    }
+  }
+
+  void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+    if (labeledBlockInfo.body.start.hasBailoutTargets()) {
+      endBailoutSwitch();
+    }
+  }
+}
+
+String singleIdentityComparison(HInstruction left,
+                                HInstruction right,
+                                HTypeMap propagatedTypes) {
+  // Returns the single identity comparison (== or ===) or null if a more
+  // complex expression is required.
+  if ((left.isConstant() && left.isConstantSentinel()) ||
+      (right.isConstant() && right.isConstantSentinel())) return '===';
+  HType leftType = propagatedTypes[left];
+  HType rightType = propagatedTypes[right];
+  if (leftType.canBeNull() && rightType.canBeNull()) {
+    if (left.isConstantNull() || right.isConstantNull() ||
+        (leftType.isPrimitive() && leftType == rightType)) {
+      return '==';
+    }
+    return null;
+  } else {
+    return '===';
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart
new file mode 100644
index 0000000..625eafb
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart
@@ -0,0 +1,380 @@
+// 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.
+
+part of ssa;
+
+/**
+ * Instead of emitting each SSA instruction with a temporary variable
+ * mark instructions that can be emitted at their use-site.
+ * For example, in:
+ *   t0 = 4;
+ *   t1 = 3;
+ *   t2 = add(t0, t1);
+ * t0 and t1 would be marked and the resulting code would then be:
+ *   t2 = add(4, 3);
+ */
+class SsaInstructionMerger extends HBaseVisitor {
+  HTypeMap types;
+  /**
+   * List of [HInstruction] that the instruction merger expects in
+   * order when visiting the inputs of an instruction.
+   */
+  List<HInstruction> expectedInputs;
+  /**
+   * Set of pure [HInstruction] that the instruction merger expects to
+   * find. The order of pure instructions do not matter, as they will
+   * not be affected by side effects.
+   */
+  Set<HInstruction> pureInputs;
+  Set<HInstruction> generateAtUseSite;
+
+  void markAsGenerateAtUseSite(HInstruction instruction) {
+    assert(!instruction.isJsStatement());
+    generateAtUseSite.add(instruction);
+  }
+
+  SsaInstructionMerger(this.types, this.generateAtUseSite);
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+  void analyzeInputs(HInstruction user, int start) {
+    List<HInstruction> inputs = user.inputs;
+    for (int i = start; i < inputs.length; i++) {
+      HInstruction input = inputs[i];
+      if (!generateAtUseSite.contains(input)
+          && !input.isCodeMotionInvariant()
+          && input.usedBy.length == 1
+          && input is !HPhi
+          && input is !HLocalValue
+          && !input.isJsStatement()) {
+        if (input.isPure()) {
+          // Only consider a pure input if it is in the same loop.
+          // Otherwise, we might move GVN'ed instruction back into the
+          // loop.
+          if (user.hasSameLoopHeaderAs(input)) {
+            // Move it closer to [user], so that instructions in
+            // between do not prevent making it generate at use site.
+            input.moveBefore(user);
+            pureInputs.add(input);
+            // Visit the pure input now so that the expected inputs
+            // are after the expected inputs of [user].
+            input.accept(this);
+          }
+        } else {
+          expectedInputs.add(input);
+        }
+      }
+    }
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    // A code motion invariant instruction is dealt before visiting it.
+    assert(!instruction.isCodeMotionInvariant());
+    analyzeInputs(instruction, 0);
+  }
+
+  // The codegen might use the input multiple times, so it must not be
+  // set generate at use site.
+  void visitIs(HIs instruction) {}
+
+  // A bounds check method must not have its first input generated at use site,
+  // because it's using it twice.
+  void visitBoundsCheck(HBoundsCheck instruction) {
+    analyzeInputs(instruction, 1);
+  }
+
+  // An integer check method must not have its input generated at use site,
+  // because it's using it twice.
+  void visitIntegerCheck(HIntegerCheck instruction) {}
+
+  // A type guard should not generate its input at use site, otherwise
+  // they would not be alive.
+  void visitTypeGuard(HTypeGuard instruction) {}
+
+  // An identity operation must only have its inputs generated at use site if
+  // does not require an expression with multiple uses (because of null /
+  // undefined).
+  void visitIdentity(HIdentity instruction) {
+    HInstruction left = instruction.left;
+    HInstruction right = instruction.right;
+    if (singleIdentityComparison(left, right, types) != null) {
+      super.visitIdentity(instruction);
+    }
+    // Do nothing.
+  }
+
+  void visitTypeConversion(HTypeConversion instruction) {
+    if (!instruction.isChecked) {
+      markAsGenerateAtUseSite(instruction);
+    } else if (!instruction.isArgumentTypeCheck) {
+      assert(instruction.isCheckedModeCheck || instruction.isCastTypeCheck);
+      // Checked mode checks and cast checks compile to code that
+      // only use their input once, so we can safely visit them
+      // and try to merge the input.
+      visitInstruction(instruction);
+    }
+  }
+
+  void tryGenerateAtUseSite(HInstruction instruction) {
+    if (instruction.isControlFlow()) return;
+    markAsGenerateAtUseSite(instruction);
+  }
+
+  bool isBlockSinglePredecessor(HBasicBlock block) {
+    return block.successors.length == 1
+        && block.successors[0].predecessors.length == 1;
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    // Compensate from not merging blocks: if the block is the
+    // single predecessor of its single successor, let the successor
+    // visit it.
+    if (isBlockSinglePredecessor(block)) return;
+
+    tryMergingExpressions(block);
+  }
+
+  void tryMergingExpressions(HBasicBlock block) {
+    // Visit each instruction of the basic block in last-to-first order.
+    // Keep a list of expected inputs of the current "expression" being
+    // merged. If instructions occur in the expected order, they are
+    // included in the expression.
+
+    // The expectedInputs list holds non-trivial instructions that may
+    // be generated at their use site, if they occur in the correct order.
+    if (expectedInputs == null) expectedInputs = new List<HInstruction>();
+    if (pureInputs == null) pureInputs = new Set<HInstruction>();
+
+    // Pop instructions from expectedInputs until instruction is found.
+    // Return true if it is found, or false if not.
+    bool findInInputsAndPopNonMatching(HInstruction instruction) {
+      assert(!instruction.isPure());
+      while (!expectedInputs.isEmpty) {
+        HInstruction nextInput = expectedInputs.removeLast();
+        assert(!generateAtUseSite.contains(nextInput));
+        assert(nextInput.usedBy.length == 1);
+        if (identical(nextInput, instruction)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    block.last.accept(this);
+    bool dontVisitPure = false;
+    for (HInstruction instruction = block.last.previous;
+         instruction != null;
+         instruction = instruction.previous) {
+      if (generateAtUseSite.contains(instruction)) {
+        continue;
+      }
+      if (instruction.isCodeMotionInvariant()) {
+        markAsGenerateAtUseSite(instruction);
+        continue;
+      }
+      if (instruction.isJsStatement()) {
+        expectedInputs.clear();
+      }
+      if (instruction.isPure()) {
+        if (pureInputs.contains(instruction)) {
+          tryGenerateAtUseSite(instruction);
+        } else {
+          // If the input is not in the [pureInputs] set, it has not
+          // been visited.
+          instruction.accept(this);
+        }
+      } else {
+        if (findInInputsAndPopNonMatching(instruction)) {
+          // The current instruction is the next non-trivial
+          // expected input.
+          tryGenerateAtUseSite(instruction);
+        } else {
+          assert(expectedInputs.isEmpty);
+        }
+        instruction.accept(this);
+      }
+    }
+
+    if (block.predecessors.length == 1
+        && isBlockSinglePredecessor(block.predecessors[0])) {
+      assert(block.phis.isEmpty);
+      tryMergingExpressions(block.predecessors[0]);
+    } else {
+      expectedInputs = null;
+      pureInputs = null;
+    }
+  }
+}
+
+/**
+ *  Detect control flow arising from short-circuit logical and
+ *  conditional operators, and prepare the program to be generated
+ *  using these operators instead of nested ifs and boolean variables.
+ */
+class SsaConditionMerger extends HGraphVisitor {
+  final HTypeMap types;
+  Set<HInstruction> generateAtUseSite;
+  Set<HInstruction> controlFlowOperators;
+
+  void markAsGenerateAtUseSite(HInstruction instruction) {
+    assert(!instruction.isJsStatement());
+    generateAtUseSite.add(instruction);
+  }
+
+  SsaConditionMerger(this.types,
+                     this.generateAtUseSite,
+                     this.controlFlowOperators);
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+  }
+
+  /**
+   * Check if a block has at least one statement other than
+   * [instruction].
+   */
+  bool hasAnyStatement(HBasicBlock block, HInstruction instruction) {
+    // If [instruction] is not in [block], then if the block is not
+    // empty, we know there will be a statement to emit.
+    if (!identical(instruction.block, block)) return !identical(block.last, block.first);
+
+    // If [instruction] is not the last instruction of the block
+    // before the control flow instruction, or the last instruction,
+    // then we will have to emit a statement for that last instruction.
+    if (instruction != block.last
+        && !identical(instruction, block.last.previous)) return true;
+
+    // If one of the instructions in the block until [instruction] is
+    // not generated at use site, then we will have to emit a
+    // statement for it.
+    // TODO(ngeoffray): we could generate a comma separated
+    // list of expressions.
+    for (HInstruction temp = block.first;
+         !identical(temp, instruction);
+         temp = temp.next) {
+      if (!generateAtUseSite.contains(temp)) return true;
+    }
+
+    return false;
+  }
+
+  bool isSafeToGenerateAtUseSite(HInstruction user, HInstruction input) {
+    // A [HForeign] instruction uses operators and if we generate
+    // [input] at use site, the precedence might be wrong.
+    if (user is HForeign) return false;
+    // A [HCheck] instruction with control flow uses its input
+    // multiple times, so we avoid generating it at use site.
+    if (user is HCheck && user.isControlFlow()) return false;
+    // A [HIs] instruction uses its input multiple times, so we
+    // avoid generating it at use site.
+    if (user is HIs) return false;
+    return true;
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    if (block.last is !HIf) return;
+    HIf startIf = block.last;
+    HBasicBlock end = startIf.joinBlock;
+
+    // We check that the structure is the following:
+    //         If
+    //       /    \
+    //      /      \
+    //   1 expr    goto
+    //    goto     /
+    //      \     /
+    //       \   /
+    // phi(expr, true|false)
+    //
+    // and the same for nested nodes:
+    //
+    //            If
+    //          /    \
+    //         /      \
+    //      1 expr1    \
+    //       If         \
+    //      /  \         \
+    //     /    \         goto
+    //  1 expr2            |
+    //    goto    goto     |
+    //      \     /        |
+    //       \   /         |
+    //   phi1(expr2, true|false)
+    //          \          |
+    //           \         |
+    //             phi(phi1, true|false)
+
+    if (end == null) return;
+    if (end.phis.isEmpty) return;
+    if (!identical(end.phis.first, end.phis.last)) return;
+    HBasicBlock elseBlock = startIf.elseBlock;
+
+    if (!identical(end.predecessors[1], elseBlock)) return;
+    HPhi phi = end.phis.first;
+    HInstruction thenInput = phi.inputs[0];
+    HInstruction elseInput = phi.inputs[1];
+    if (thenInput.isJsStatement() || elseInput.isJsStatement()) return;
+
+    if (hasAnyStatement(elseBlock, elseInput)) return;
+    assert(elseBlock.successors.length == 1);
+    assert(end.predecessors.length == 2);
+
+    HBasicBlock thenBlock = startIf.thenBlock;
+    // Skip trivial goto blocks.
+    while (thenBlock.successors[0] != end && thenBlock.first is HGoto) {
+      thenBlock = thenBlock.successors[0];
+    }
+
+    // If the [thenBlock] is already a control flow operation, and does not
+    // have any statement and its join block is [end], we can emit a
+    // sequence of control flow operation.
+    if (controlFlowOperators.contains(thenBlock.last)) {
+      HIf otherIf = thenBlock.last;
+      if (!identical(otherIf.joinBlock, end)) {
+        // This could be a join block that just feeds into our join block.
+        HBasicBlock otherJoin = otherIf.joinBlock;
+        if (otherJoin.first != otherJoin.last) return;
+        if (otherJoin.successors.length != 1) return;
+        if (otherJoin.successors[0] != end) return;
+        if (otherJoin.phis.isEmpty) return;
+        if (!identical(otherJoin.phis.first, otherJoin.phis.last)) return;
+        HPhi otherPhi = otherJoin.phis.first;
+        if (thenInput != otherPhi) return;
+        if (elseInput != otherPhi.inputs[1]) return;
+      }
+      if (hasAnyStatement(thenBlock, otherIf)) return;
+    } else {
+      if (!identical(end.predecessors[0], thenBlock)) return;
+      if (hasAnyStatement(thenBlock, thenInput)) return;
+      assert(thenBlock.successors.length == 1);
+    }
+
+    // From now on, we have recognized a control flow operation built from
+    // the builder. Mark the if instruction as such.
+    controlFlowOperators.add(startIf);
+
+    // If the operation is only used by the first instruction
+    // of its block and is safe to be generated at use site, mark it
+    // so.
+    if (phi.usedBy.length == 1
+        && identical(phi.usedBy[0], phi.block.first)
+        && isSafeToGenerateAtUseSite(phi.usedBy[0], phi)) {
+      markAsGenerateAtUseSite(phi);
+    }
+
+    if (identical(elseInput.block, elseBlock)) {
+      assert(elseInput.usedBy.length == 1);
+      markAsGenerateAtUseSite(elseInput);
+    }
+
+    // If [thenInput] is defined in the first predecessor, then it is only used
+    // by [phi] and can be generated at use site.
+    if (identical(thenInput.block, end.predecessors[0])) {
+      assert(thenInput.usedBy.length == 1);
+      markAsGenerateAtUseSite(thenInput);
+    }
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart
new file mode 100644
index 0000000..375f617
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart
@@ -0,0 +1,620 @@
+// Copyright (c) 2013, 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.
+
+part of ssa;
+
+/**
+ * [InvokeDynamicSpecializer] and its subclasses are helpers to
+ * optimize intercepted dynamic calls. It knows what input types
+ * would be beneficial for performance, and how to change a invoke
+ * dynamic to a builtin instruction (e.g. HIndex, HBitNot).
+ */
+class InvokeDynamicSpecializer {
+  const InvokeDynamicSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return HType.UNKNOWN;
+  }
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    return null;
+  }
+
+  Operation operation(ConstantSystem constantSystem) => null;
+
+  static InvokeDynamicSpecializer lookupSpecializer(Selector selector) {
+    if (selector.kind == SelectorKind.INDEX) {
+      return selector.name == const SourceString('[]')
+          ? const IndexSpecializer()
+          : const IndexAssignSpecializer();
+    } else if (selector.kind == SelectorKind.OPERATOR) {
+      if (selector.name == const SourceString('unary-')) {
+        return const UnaryNegateSpecializer();
+      } else if (selector.name == const SourceString('~')) {
+        return const BitNotSpecializer();
+      } else if (selector.name == const SourceString('+')) {
+        return const AddSpecializer();
+      } else if (selector.name == const SourceString('-')) {
+        return const SubtractSpecializer();
+      } else if (selector.name == const SourceString('*')) {
+        return const MultiplySpecializer();
+      } else if (selector.name == const SourceString('/')) {
+        return const DivideSpecializer();
+      } else if (selector.name == const SourceString('~/')) {
+        return const TruncatingDivideSpecializer();
+      } else if (selector.name == const SourceString('%')) {
+        return const ModuloSpecializer();
+      } else if (selector.name == const SourceString('>>')) {
+        return const ShiftRightSpecializer();
+      } else if (selector.name == const SourceString('<<')) {
+        return const ShiftLeftSpecializer();
+      } else if (selector.name == const SourceString('&')) {
+        return const BitAndSpecializer();
+      } else if (selector.name == const SourceString('|')) {
+        return const BitOrSpecializer();
+      } else if (selector.name == const SourceString('^')) {
+        return const BitXorSpecializer();
+      } else if (selector.name == const SourceString('==')) {
+        return const EqualsSpecializer();
+      } else if (selector.name == const SourceString('<')) {
+        return const LessSpecializer();
+      } else if (selector.name == const SourceString('<=')) {
+        return const LessEqualSpecializer();
+      } else if (selector.name == const SourceString('>')) {
+        return const GreaterSpecializer();
+      } else if (selector.name == const SourceString('>=')) {
+        return const GreaterEqualSpecializer();
+      }
+    }
+    return const InvokeDynamicSpecializer();
+  }
+}
+
+class IndexAssignSpecializer extends InvokeDynamicSpecializer {
+  const IndexAssignSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HInstruction index = instruction.inputs[2];
+    if (input == instruction.inputs[1] &&
+        (index.isTypeUnknown(types) || index.isNumber(types))) {
+      return HType.MUTABLE_ARRAY;
+    }
+    // The index should be an int when the receiver is a string or array.
+    // However it turns out that inserting an integer check in the optimized
+    // version is cheaper than having another bailout case. This is true,
+    // because the integer check will simply throw if it fails.
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    if (instruction.inputs[1].isMutableArray(types)) {
+      return new HIndexAssign(instruction.inputs[1],
+                              instruction.inputs[2],
+                              instruction.inputs[3]);
+    }
+    return null;
+  }
+}
+
+class IndexSpecializer extends InvokeDynamicSpecializer {
+  const IndexSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HInstruction index = instruction.inputs[2];
+    if (input == instruction.inputs[1] &&
+        (index.isTypeUnknown(types) || index.isNumber(types))) {
+      return HType.INDEXABLE_PRIMITIVE;
+    }
+    // The index should be an int when the receiver is a string or array.
+    // However it turns out that inserting an integer check in the optimized
+    // version is cheaper than having another bailout case. This is true,
+    // because the integer check will simply throw if it fails.
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    if (instruction.inputs[1].isIndexablePrimitive(types)) {
+      return new HIndex(instruction.inputs[1], instruction.inputs[2]);
+    }
+    return null;
+  }
+}
+
+class BitNotSpecializer extends InvokeDynamicSpecializer {
+  const BitNotSpecializer();
+
+  UnaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitNot;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[1]) {
+      HType propagatedType = types[instruction];
+      if (propagatedType.isUnknown() || propagatedType.isNumber()) {
+        return HType.INTEGER;
+      }
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    // All bitwise operations on primitive types either produce an
+    // integer or throw an error.
+    if (instruction.inputs[1].isPrimitive(types)) return HType.INTEGER;
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction input = instruction.inputs[1];
+    if (input.isNumber(types)) return new HBitNot(input);
+    return null;
+  }
+}
+
+class UnaryNegateSpecializer extends InvokeDynamicSpecializer {
+  const UnaryNegateSpecializer();
+
+  UnaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.negate;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[1]) {
+      HType propagatedType = types[instruction];
+      // If the outgoing type should be a number (integer, double or both) we
+      // want the outgoing type to be the input too.
+      // If we don't know the outgoing type we try to make it a number.
+      if (propagatedType.isNumber()) return propagatedType;
+      if (propagatedType.isUnknown()) return HType.NUMBER;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    HType operandType = types[instruction.inputs[1]];
+    if (operandType.isNumber()) return operandType;
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction input = instruction.inputs[1];
+    if (input.isNumber(types)) return new HNegate(input);
+    return null;
+  }
+}
+
+abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer {
+  const BinaryArithmeticSpecializer();
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER;
+    if (left.isNumber(types)) {
+      if (left.isDouble(types) || right.isDouble(types)) return HType.DOUBLE;
+      return HType.NUMBER;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+
+    HType propagatedType = types[instruction];
+    // If the desired output type should be an integer we want to get two
+    // integers as arguments.
+    if (propagatedType.isInteger()) return HType.INTEGER;
+    // If the outgoing type should be a number we can get that if both inputs
+    // are numbers. If we don't know the outgoing type we try to make it a
+    // number.
+    if (propagatedType.isUnknown() || propagatedType.isNumber()) {
+      return HType.NUMBER;
+    }
+    // Even if the desired outgoing type is not a number we still want the
+    // second argument to be a number if the first one is a number. This will
+    // not help for the outgoing type, but at least the binary arithmetic
+    // operation will not have type problems.
+    // TODO(floitsch): normally we shouldn't request a number, but simply
+    // throw an ArgumentError if it isn't. This would be similar
+    // to the array case.
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (input == right && left.isNumber(types)) return HType.NUMBER;
+    return HType.UNKNOWN;
+  }
+
+  bool isBuiltin(HInvokeDynamic instruction, HTypeMap types) {
+    return instruction.inputs[1].isNumber(types)
+        && instruction.inputs[2].isNumber(types);
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    if (isBuiltin(instruction, types)) {
+      HInstruction builtin =
+          newBuiltinVariant(instruction.inputs[1], instruction.inputs[2]);
+      if (builtin != null) return builtin;
+      // Even if there is no builtin equivalent instruction, we know
+      // the instruction does not have any side effect, and that it
+      // can be GVN'ed.
+      instruction.clearAllSideEffects();
+      instruction.clearAllDependencies();
+      instruction.setUseGvn();
+    }
+    return null;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right);
+}
+
+class AddSpecializer extends BinaryArithmeticSpecializer {
+  const AddSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.add;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HAdd(left, right);
+  }
+}
+
+class DivideSpecializer extends BinaryArithmeticSpecializer {
+  const DivideSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.divide;
+  }
+
+  HType computeTypeFromInputTypes(HInstruction instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    HInstruction left = instruction.inputs[1];
+    if (left.isNumber(types)) return HType.DOUBLE;
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInstruction instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+    // A division can never return an integer. So don't ask for integer inputs.
+    if (instruction.isInteger(types)) return HType.UNKNOWN;
+    return super.computeDesiredTypeForInput(
+        instruction, input, types, compiler);
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HDivide(left, right);
+  }
+}
+
+class ModuloSpecializer extends BinaryArithmeticSpecializer {
+  const ModuloSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.modulo;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    // Modulo cannot be mapped to the native operator (different semantics).    
+    return null;
+  }
+}
+
+class MultiplySpecializer extends BinaryArithmeticSpecializer {
+  const MultiplySpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.multiply;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HMultiply(left, right);
+  }
+}
+
+class SubtractSpecializer extends BinaryArithmeticSpecializer {
+  const SubtractSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.subtract;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HSubtract(left, right);
+  }
+}
+
+class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer {
+  const TruncatingDivideSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.truncatingDivide;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    // Truncating divide does not have a JS equivalent.    
+    return null;
+  }
+}
+
+abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer {
+  const BinaryBitOpSpecializer();
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    // All bitwise operations on primitive types either produce an
+    // integer or throw an error.
+    HInstruction left = instruction.inputs[1];
+    if (left.isPrimitive(types)) return HType.INTEGER;
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+    HType propagatedType = types[instruction];
+    // If the outgoing type should be a number we can get that only if both
+    // inputs are integers. If we don't know the outgoing type we try to make
+    // it an integer.
+    if (propagatedType.isUnknown() || propagatedType.isNumber()) {
+      return HType.INTEGER;
+    }
+    return HType.UNKNOWN;
+  }
+}
+
+class ShiftLeftSpecializer extends BinaryBitOpSpecializer {
+  const ShiftLeftSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.shiftLeft;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (!left.isNumber(types) || !right.isConstantInteger()) return null;
+    HConstant rightConstant = right;
+    IntConstant intConstant = rightConstant.constant;
+    int count = intConstant.value;
+    if (count >= 0 && count <= 31) {
+      return newBuiltinVariant(left, right);
+    }
+    return null;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HShiftLeft(left, right);
+  }
+}
+
+class ShiftRightSpecializer extends BinaryBitOpSpecializer {
+  const ShiftRightSpecializer();
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    // Shift right cannot be mapped to the native operator easily.    
+    return null;
+  }
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.shiftRight;
+  }
+}
+
+class BitOrSpecializer extends BinaryBitOpSpecializer {
+  const BitOrSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitOr;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HBitOr(left, right);
+  }
+}
+
+class BitAndSpecializer extends BinaryBitOpSpecializer {
+  const BitAndSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitAnd;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HBitAnd(left, right);
+  }
+}
+
+class BitXorSpecializer extends BinaryBitOpSpecializer {
+  const BitXorSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitXor;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HBitXor(left, right);
+  }
+}
+
+abstract class RelationalSpecializer extends InvokeDynamicSpecializer {
+  const RelationalSpecializer();
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    if (types[instruction.inputs[1]].isPrimitiveOrNull()) return HType.BOOLEAN;
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+    HType propagatedType = types[instruction];
+    // For all relational operations except HIdentity, we expect to get numbers
+    // only. With numbers the outgoing type is a boolean. If something else
+    // is desired, then numbers are incorrect, though.
+    if (propagatedType.isUnknown() || propagatedType.isBoolean()) {
+      HInstruction left = instruction.inputs[1];
+      if (left.isTypeUnknown(types) || left.isNumber(types)) {
+        return HType.NUMBER;
+      }
+    }
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isNumber(types) && right.isNumber(types)) {
+      return newBuiltinVariant(left, right);
+    }
+    return null;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right);
+}
+
+class EqualsSpecializer extends RelationalSpecializer {
+  const EqualsSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    HType propagatedType = types[instruction];
+    if (input == left && types[right].isUseful()) {
+      // All our useful types have 'identical' semantics. But we don't want to
+      // speculatively test for all possible types. Therefore we try to match
+      // the two types. That is, if we see x == 3, then we speculatively test
+      // if x is a number and bailout if it isn't.
+      // If right is a number we don't need more than a number (no need to match
+      // the exact type of right).
+      if (right.isNumber(types)) return HType.NUMBER;
+      return types[right];
+    }
+    // String equality testing is much more common than array equality testing.
+    if (input == left && left.isIndexablePrimitive(types)) {
+      return HType.READABLE_ARRAY;
+    }
+    // String equality testing is much more common than array equality testing.
+    if (input == right && right.isIndexablePrimitive(types)) {
+      return HType.STRING;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (types[left].isPrimitiveOrNull() || right.isConstantNull()) {
+      return newBuiltinVariant(left, right);
+    }
+    return null;
+  }
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.equal;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HIdentity(left, right);
+  }
+}
+
+class LessSpecializer extends RelationalSpecializer {
+  const LessSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.less;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HLess(left, right);
+  }
+}
+
+class GreaterSpecializer extends RelationalSpecializer {
+  const GreaterSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.greater;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HGreater(left, right);
+  }
+}
+
+class GreaterEqualSpecializer extends RelationalSpecializer {
+  const GreaterEqualSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.greaterEqual;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HGreaterEqual(left, right);
+  }
+}
+
+class LessEqualSpecializer extends RelationalSpecializer {
+  const LessEqualSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.lessEqual;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HLessEqual(left, right);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart
new file mode 100644
index 0000000..afbd2e1
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart
@@ -0,0 +1,2711 @@
+// 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.
+
+part of ssa;
+
+abstract class HVisitor<R> {
+  R visitAdd(HAdd node);
+  R visitBailoutTarget(HBailoutTarget node);
+  R visitBitAnd(HBitAnd node);
+  R visitBitNot(HBitNot node);
+  R visitBitOr(HBitOr node);
+  R visitBitXor(HBitXor node);
+  R visitBoolify(HBoolify node);
+  R visitBoundsCheck(HBoundsCheck node);
+  R visitBreak(HBreak node);
+  R visitConstant(HConstant node);
+  R visitContinue(HContinue node);
+  R visitDivide(HDivide node);
+  R visitExit(HExit node);
+  R visitExitTry(HExitTry node);
+  R visitFieldGet(HFieldGet node);
+  R visitFieldSet(HFieldSet node);
+  R visitForeign(HForeign node);
+  R visitForeignNew(HForeignNew node);
+  R visitGoto(HGoto node);
+  R visitGreater(HGreater node);
+  R visitGreaterEqual(HGreaterEqual node);
+  R visitIdentity(HIdentity node);
+  R visitIf(HIf node);
+  R visitIndex(HIndex node);
+  R visitIndexAssign(HIndexAssign node);
+  R visitIntegerCheck(HIntegerCheck node);
+  R visitInterceptor(HInterceptor node);
+  R visitInvokeClosure(HInvokeClosure node);
+  R visitInvokeDynamicGetter(HInvokeDynamicGetter node);
+  R visitInvokeDynamicMethod(HInvokeDynamicMethod node);
+  R visitInvokeDynamicSetter(HInvokeDynamicSetter node);
+  R visitInvokeStatic(HInvokeStatic node);
+  R visitInvokeSuper(HInvokeSuper node);
+  R visitIs(HIs node);
+  R visitLazyStatic(HLazyStatic node);
+  R visitLess(HLess node);
+  R visitLessEqual(HLessEqual node);
+  R visitLiteralList(HLiteralList node);
+  R visitLocalGet(HLocalGet node);
+  R visitLocalSet(HLocalSet node);
+  R visitLocalValue(HLocalValue node);
+  R visitLoopBranch(HLoopBranch node);
+  R visitMultiply(HMultiply node);
+  R visitNegate(HNegate node);
+  R visitNot(HNot node);
+  R visitOneShotInterceptor(HOneShotInterceptor);
+  R visitParameterValue(HParameterValue node);
+  R visitPhi(HPhi node);
+  R visitRangeConversion(HRangeConversion node);
+  R visitReturn(HReturn node);
+  R visitShiftLeft(HShiftLeft node);
+  R visitStatic(HStatic node);
+  R visitStaticStore(HStaticStore node);
+  R visitStringConcat(HStringConcat node);
+  R visitSubtract(HSubtract node);
+  R visitSwitch(HSwitch node);
+  R visitThis(HThis node);
+  R visitThrow(HThrow node);
+  R visitTry(HTry node);
+  R visitTypeGuard(HTypeGuard node);
+  R visitTypeConversion(HTypeConversion node);
+}
+
+abstract class HGraphVisitor {
+  visitDominatorTree(HGraph graph) {
+    void visitBasicBlockAndSuccessors(HBasicBlock block) {
+      visitBasicBlock(block);
+      List dominated = block.dominatedBlocks;
+      for (int i = 0; i < dominated.length; i++) {
+        visitBasicBlockAndSuccessors(dominated[i]);
+      }
+    }
+
+    visitBasicBlockAndSuccessors(graph.entry);
+  }
+
+  visitPostDominatorTree(HGraph graph) {
+    void visitBasicBlockAndSuccessors(HBasicBlock block) {
+      List dominated = block.dominatedBlocks;
+      for (int i = dominated.length - 1; i >= 0; i--) {
+        visitBasicBlockAndSuccessors(dominated[i]);
+      }
+      visitBasicBlock(block);
+    }
+
+    visitBasicBlockAndSuccessors(graph.entry);
+  }
+
+  visitBasicBlock(HBasicBlock block);
+}
+
+abstract class HInstructionVisitor extends HGraphVisitor {
+  HBasicBlock currentBlock;
+
+  visitInstruction(HInstruction node);
+
+  visitBasicBlock(HBasicBlock node) {
+    void visitInstructionList(HInstructionList list) {
+      HInstruction instruction = list.first;
+      while (instruction != null) {
+        visitInstruction(instruction);
+        instruction = instruction.next;
+        assert(instruction != list.first);
+      }
+    }
+
+    currentBlock = node;
+    visitInstructionList(node);
+  }
+}
+
+class HGraph {
+  HBasicBlock entry;
+  HBasicBlock exit;
+  HThis thisInstruction;
+  bool isRecursiveMethod = false;
+  bool calledInLoop = false;
+  final List<HBasicBlock> blocks;
+
+  // We canonicalize all constants used within a graph so we do not
+  // have to worry about them for global value numbering.
+  Map<Constant, HConstant> constants;
+
+  HGraph()
+      : blocks = new List<HBasicBlock>(),
+        constants = new Map<Constant, HConstant>() {
+    entry = addNewBlock();
+    // The exit block will be added later, so it has an id that is
+    // after all others in the system.
+    exit = new HBasicBlock();
+  }
+
+  void addBlock(HBasicBlock block) {
+    int id = blocks.length;
+    block.id = id;
+    blocks.add(block);
+    assert(identical(blocks[id], block));
+  }
+
+  HBasicBlock addNewBlock() {
+    HBasicBlock result = new HBasicBlock();
+    addBlock(result);
+    return result;
+  }
+
+  HBasicBlock addNewLoopHeaderBlock(TargetElement target,
+                                    List<LabelElement> labels) {
+    HBasicBlock result = addNewBlock();
+    result.loopInformation =
+        new HLoopInformation(result, target, labels);
+    return result;
+  }
+
+  static HType mapConstantTypeToSsaType(Constant constant) {
+    if (constant.isNull()) return HType.NULL;
+    if (constant.isBool()) return HType.BOOLEAN;
+    if (constant.isInt()) return HType.INTEGER;
+    if (constant.isDouble()) return HType.DOUBLE;
+    if (constant.isString()) return HType.STRING;
+    if (constant.isList()) return HType.READABLE_ARRAY;
+    if (constant.isFunction()) return HType.UNKNOWN;
+    if (constant.isSentinel()) return HType.UNKNOWN;
+    ObjectConstant objectConstant = constant;
+    return new HBoundedType.exact(objectConstant.type);
+  }
+
+  HConstant addConstant(Constant constant) {
+    HConstant result = constants[constant];
+    if (result == null) {
+      HType type = mapConstantTypeToSsaType(constant);
+      result = new HConstant.internal(constant, type);
+      entry.addAtExit(result);
+      constants[constant] = result;
+    } else if (result.block == null) {
+      // The constant was not used anymore.
+      entry.addAtExit(result);
+    }
+    return result;
+  }
+
+  HConstant addConstantInt(int i, ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createInt(i));
+  }
+
+  HConstant addConstantDouble(double d, ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createDouble(d));
+  }
+
+  HConstant addConstantString(DartString str,
+                              Node diagnosticNode,
+                              ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createString(str, diagnosticNode));
+  }
+
+  HConstant addConstantBool(bool value, ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createBool(value));
+  }
+
+  HConstant addConstantNull(ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createNull());
+  }
+
+  void finalize() {
+    addBlock(exit);
+    exit.open();
+    exit.close(new HExit());
+    assignDominators();
+  }
+
+  void assignDominators() {
+    // Run through the blocks in order of increasing ids so we are
+    // guaranteed that we have computed dominators for all blocks
+    // higher up in the dominator tree.
+    for (int i = 0, length = blocks.length; i < length; i++) {
+      HBasicBlock block = blocks[i];
+      List<HBasicBlock> predecessors = block.predecessors;
+      if (block.isLoopHeader()) {
+        block.assignCommonDominator(predecessors[0]);
+      } else {
+        for (int j = predecessors.length - 1; j >= 0; j--) {
+          block.assignCommonDominator(predecessors[j]);
+        }
+      }
+    }
+  }
+
+  bool isValid() {
+    HValidator validator = new HValidator();
+    validator.visitGraph(this);
+    return validator.isValid;
+  }
+}
+
+class HBaseVisitor extends HGraphVisitor implements HVisitor {
+  HBasicBlock currentBlock;
+
+  visitBasicBlock(HBasicBlock node) {
+    currentBlock = node;
+
+    HInstruction instruction = node.first;
+    while (instruction != null) {
+      instruction.accept(this);
+      instruction = instruction.next;
+    }
+  }
+
+  visitInstruction(HInstruction instruction) {}
+
+  visitBinaryArithmetic(HBinaryArithmetic node) => visitInvokeBinary(node);
+  visitBinaryBitOp(HBinaryBitOp node) => visitBinaryArithmetic(node);
+  visitInvoke(HInvoke node) => visitInstruction(node);
+  visitInvokeBinary(HInvokeBinary node) => visitInstruction(node);
+  visitInvokeDynamic(HInvokeDynamic node) => visitInvoke(node);
+  visitInvokeDynamicField(HInvokeDynamicField node) => visitInvokeDynamic(node);
+  visitInvokeUnary(HInvokeUnary node) => visitInstruction(node);
+  visitConditionalBranch(HConditionalBranch node) => visitControlFlow(node);
+  visitControlFlow(HControlFlow node) => visitInstruction(node);
+  visitFieldAccess(HFieldAccess node) => visitInstruction(node);
+  visitRelational(HRelational node) => visitInvokeBinary(node);
+
+  visitAdd(HAdd node) => visitBinaryArithmetic(node);
+  visitBailoutTarget(HBailoutTarget node) => visitInstruction(node);
+  visitBitAnd(HBitAnd node) => visitBinaryBitOp(node);
+  visitBitNot(HBitNot node) => visitInvokeUnary(node);
+  visitBitOr(HBitOr node) => visitBinaryBitOp(node);
+  visitBitXor(HBitXor node) => visitBinaryBitOp(node);
+  visitBoolify(HBoolify node) => visitInstruction(node);
+  visitBoundsCheck(HBoundsCheck node) => visitCheck(node);
+  visitBreak(HBreak node) => visitJump(node);
+  visitContinue(HContinue node) => visitJump(node);
+  visitCheck(HCheck node) => visitInstruction(node);
+  visitConstant(HConstant node) => visitInstruction(node);
+  visitDivide(HDivide node) => visitBinaryArithmetic(node);
+  visitExit(HExit node) => visitControlFlow(node);
+  visitExitTry(HExitTry node) => visitControlFlow(node);
+  visitFieldGet(HFieldGet node) => visitFieldAccess(node);
+  visitFieldSet(HFieldSet node) => visitFieldAccess(node);
+  visitForeign(HForeign node) => visitInstruction(node);
+  visitForeignNew(HForeignNew node) => visitForeign(node);
+  visitGoto(HGoto node) => visitControlFlow(node);
+  visitGreater(HGreater node) => visitRelational(node);
+  visitGreaterEqual(HGreaterEqual node) => visitRelational(node);
+  visitIdentity(HIdentity node) => visitRelational(node);
+  visitIf(HIf node) => visitConditionalBranch(node);
+  visitIndex(HIndex node) => visitInstruction(node);
+  visitIndexAssign(HIndexAssign node) => visitInstruction(node);
+  visitIntegerCheck(HIntegerCheck node) => visitCheck(node);
+  visitInterceptor(HInterceptor node) => visitInstruction(node);
+  visitInvokeClosure(HInvokeClosure node)
+      => visitInvokeDynamic(node);
+  visitInvokeDynamicMethod(HInvokeDynamicMethod node)
+      => visitInvokeDynamic(node);
+  visitInvokeDynamicGetter(HInvokeDynamicGetter node)
+      => visitInvokeDynamicField(node);
+  visitInvokeDynamicSetter(HInvokeDynamicSetter node)
+      => visitInvokeDynamicField(node);
+  visitInvokeStatic(HInvokeStatic node) => visitInvoke(node);
+  visitInvokeSuper(HInvokeSuper node) => visitInvoke(node);
+  visitJump(HJump node) => visitControlFlow(node);
+  visitLazyStatic(HLazyStatic node) => visitInstruction(node);
+  visitLess(HLess node) => visitRelational(node);
+  visitLessEqual(HLessEqual node) => visitRelational(node);
+  visitLiteralList(HLiteralList node) => visitInstruction(node);
+  visitLocalGet(HLocalGet node) => visitFieldAccess(node);
+  visitLocalSet(HLocalSet node) => visitFieldAccess(node);
+  visitLocalValue(HLocalValue node) => visitInstruction(node);
+  visitLoopBranch(HLoopBranch node) => visitConditionalBranch(node);
+  visitNegate(HNegate node) => visitInvokeUnary(node);
+  visitNot(HNot node) => visitInstruction(node);
+  visitOneShotInterceptor(HOneShotInterceptor node)
+      => visitInvokeDynamic(node);
+  visitPhi(HPhi node) => visitInstruction(node);
+  visitMultiply(HMultiply node) => visitBinaryArithmetic(node);
+  visitParameterValue(HParameterValue node) => visitLocalValue(node);
+  visitRangeConversion(HRangeConversion node) => visitCheck(node);
+  visitReturn(HReturn node) => visitControlFlow(node);
+  visitShiftLeft(HShiftLeft node) => visitBinaryBitOp(node);
+  visitSubtract(HSubtract node) => visitBinaryArithmetic(node);
+  visitSwitch(HSwitch node) => visitControlFlow(node);
+  visitStatic(HStatic node) => visitInstruction(node);
+  visitStaticStore(HStaticStore node) => visitInstruction(node);
+  visitStringConcat(HStringConcat node) => visitInstruction(node);
+  visitThis(HThis node) => visitParameterValue(node);
+  visitThrow(HThrow node) => visitControlFlow(node);
+  visitTry(HTry node) => visitControlFlow(node);
+  visitTypeGuard(HTypeGuard node) => visitCheck(node);
+  visitIs(HIs node) => visitInstruction(node);
+  visitTypeConversion(HTypeConversion node) => visitCheck(node);
+}
+
+class SubGraph {
+  // The first and last block of the sub-graph.
+  final HBasicBlock start;
+  final HBasicBlock end;
+
+  const SubGraph(this.start, this.end);
+
+  bool contains(HBasicBlock block) {
+    assert(start != null);
+    assert(end != null);
+    assert(block != null);
+    return start.id <= block.id && block.id <= end.id;
+  }
+}
+
+class SubExpression extends SubGraph {
+  const SubExpression(HBasicBlock start, HBasicBlock end)
+      : super(start, end);
+
+  /** Find the condition expression if this sub-expression is a condition. */
+  HInstruction get conditionExpression {
+    HInstruction last = end.last;
+    if (last is HConditionalBranch || last is HSwitch) return last.inputs[0];
+    return null;
+  }
+}
+
+class HInstructionList {
+  HInstruction first = null;
+  HInstruction last = null;
+
+  bool get isEmpty {
+    return first == null;
+  }
+
+  void internalAddAfter(HInstruction cursor, HInstruction instruction) {
+    if (cursor == null) {
+      assert(isEmpty);
+      first = last = instruction;
+    } else if (identical(cursor, last)) {
+      last.next = instruction;
+      instruction.previous = last;
+      last = instruction;
+    } else {
+      instruction.previous = cursor;
+      instruction.next = cursor.next;
+      cursor.next.previous = instruction;
+      cursor.next = instruction;
+    }
+  }
+
+  void internalAddBefore(HInstruction cursor, HInstruction instruction) {
+    if (cursor == null) {
+      assert(isEmpty);
+      first = last = instruction;
+    } else if (identical(cursor, first)) {
+      first.previous = instruction;
+      instruction.next = first;
+      first = instruction;
+    } else {
+      instruction.next = cursor;
+      instruction.previous = cursor.previous;
+      cursor.previous.next = instruction;
+      cursor.previous = instruction;
+    }
+  }
+
+  void detach(HInstruction instruction) {
+    assert(contains(instruction));
+    assert(instruction.isInBasicBlock());
+    if (instruction.previous == null) {
+      first = instruction.next;
+    } else {
+      instruction.previous.next = instruction.next;
+    }
+    if (instruction.next == null) {
+      last = instruction.previous;
+    } else {
+      instruction.next.previous = instruction.previous;
+    }
+    instruction.previous = null;
+    instruction.next = null;
+  }
+
+  void remove(HInstruction instruction) {
+    assert(instruction.usedBy.isEmpty);
+    detach(instruction);
+  }
+
+  /** Linear search for [instruction]. */
+  bool contains(HInstruction instruction) {
+    HInstruction cursor = first;
+    while (cursor != null) {
+      if (identical(cursor, instruction)) return true;
+      cursor = cursor.next;
+    }
+    return false;
+  }
+}
+
+class HBasicBlock extends HInstructionList {
+  // The [id] must be such that any successor's id is greater than
+  // this [id]. The exception are back-edges.
+  int id;
+
+  static const int STATUS_NEW = 0;
+  static const int STATUS_OPEN = 1;
+  static const int STATUS_CLOSED = 2;
+  int status = STATUS_NEW;
+
+  HInstructionList phis;
+
+  HLoopInformation loopInformation = null;
+  HBlockFlow blockFlow = null;
+  HBasicBlock parentLoopHeader = null;
+  List<HBailoutTarget> bailoutTargets;
+
+  final List<HBasicBlock> predecessors;
+  List<HBasicBlock> successors;
+
+  HBasicBlock dominator = null;
+  final List<HBasicBlock> dominatedBlocks;
+
+  HBasicBlock() : this.withId(null);
+  HBasicBlock.withId(this.id)
+      : phis = new HInstructionList(),
+        predecessors = <HBasicBlock>[],
+        successors = const <HBasicBlock>[],
+        dominatedBlocks = <HBasicBlock>[],
+        bailoutTargets = <HBailoutTarget>[];
+
+  int get hashCode => id;
+
+  bool isNew() => status == STATUS_NEW;
+  bool isOpen() => status == STATUS_OPEN;
+  bool isClosed() => status == STATUS_CLOSED;
+
+  bool isLoopHeader() {
+    return loopInformation != null;
+  }
+
+  void setBlockFlow(HBlockInformation blockInfo, HBasicBlock continuation) {
+    blockFlow = new HBlockFlow(blockInfo, continuation);
+  }
+
+  bool isLabeledBlock() =>
+    blockFlow != null &&
+    blockFlow.body is HLabeledBlockInformation;
+
+  HBasicBlock get enclosingLoopHeader {
+    if (isLoopHeader()) return this;
+    return parentLoopHeader;
+  }
+
+  bool hasBailoutTargets() => !bailoutTargets.isEmpty;
+
+  void open() {
+    assert(isNew());
+    status = STATUS_OPEN;
+  }
+
+  void close(HControlFlow end) {
+    assert(isOpen());
+    addAfter(last, end);
+    status = STATUS_CLOSED;
+  }
+
+  void addAtEntry(HInstruction instruction) {
+    assert(instruction is !HPhi);
+    internalAddBefore(first, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void addAtExit(HInstruction instruction) {
+    assert(isClosed());
+    assert(last is HControlFlow);
+    assert(instruction is !HPhi);
+    internalAddBefore(last, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void moveAtExit(HInstruction instruction) {
+    assert(instruction is !HPhi);
+    assert(instruction.isInBasicBlock());
+    assert(isClosed());
+    assert(last is HControlFlow);
+    internalAddBefore(last, instruction);
+    instruction.block = this;
+    assert(isValid());
+  }
+
+  void add(HInstruction instruction) {
+    assert(instruction is !HControlFlow);
+    assert(instruction is !HPhi);
+    internalAddAfter(last, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void addPhi(HPhi phi) {
+    phis.internalAddAfter(phis.last, phi);
+    phi.notifyAddedToBlock(this);
+  }
+
+  void removePhi(HPhi phi) {
+    phis.remove(phi);
+    assert(phi.block == this);
+    phi.notifyRemovedFromBlock();
+  }
+
+  void addAfter(HInstruction cursor, HInstruction instruction) {
+    assert(cursor is !HPhi);
+    assert(instruction is !HPhi);
+    assert(isOpen() || isClosed());
+    internalAddAfter(cursor, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void addBefore(HInstruction cursor, HInstruction instruction) {
+    assert(cursor is !HPhi);
+    assert(instruction is !HPhi);
+    assert(isOpen() || isClosed());
+    internalAddBefore(cursor, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void remove(HInstruction instruction) {
+    assert(isOpen() || isClosed());
+    assert(instruction is !HPhi);
+    super.remove(instruction);
+    assert(instruction.block == this);
+    instruction.notifyRemovedFromBlock();
+  }
+
+  void addSuccessor(HBasicBlock block) {
+    if (successors.isEmpty) {
+      successors = [block];
+    } else {
+      successors.add(block);
+    }
+    block.predecessors.add(this);
+  }
+
+  void postProcessLoopHeader() {
+    assert(isLoopHeader());
+    // Only the first entry into the loop is from outside the
+    // loop. All other entries must be back edges.
+    for (int i = 1, length = predecessors.length; i < length; i++) {
+      loopInformation.addBackEdge(predecessors[i]);
+    }
+  }
+
+  /**
+   * Rewrites all uses of the [from] instruction to using the [to]
+   * instruction instead.
+   */
+  void rewrite(HInstruction from, HInstruction to) {
+    for (HInstruction use in from.usedBy) {
+      use.rewriteInput(from, to);
+    }
+    to.usedBy.addAll(from.usedBy);
+    from.usedBy.clear();
+  }
+
+  /**
+   * Rewrites all uses of the [from] instruction to using either the
+   * [to] instruction, or a [HCheck] instruction that has better type
+   * information on [to], and that dominates the user.
+   */
+  void rewriteWithBetterUser(HInstruction from, HInstruction to) {
+    Link<HCheck> better = const Link<HCheck>();
+    for (HInstruction user in to.usedBy) {
+      if (user is HCheck && identical((user as HCheck).checkedInput, to)) {
+        better = better.prepend(user);
+      }
+    }
+
+    if (better.isEmpty) return rewrite(from, to);
+
+    L1: for (HInstruction user in from.usedBy) {
+      for (HCheck check in better) {
+        if (check.dominates(user)) {
+          user.rewriteInput(from, check);
+          check.usedBy.add(user);
+          continue L1;
+        }
+      }
+      user.rewriteInput(from, to);
+      to.usedBy.add(user);
+    }
+    from.usedBy.clear();
+  }
+
+  bool isExitBlock() {
+    return identical(first, last) && first is HExit;
+  }
+
+  void addDominatedBlock(HBasicBlock block) {
+    assert(isClosed());
+    assert(id != null && block.id != null);
+    assert(dominatedBlocks.indexOf(block) < 0);
+    // Keep the list of dominated blocks sorted such that if there are two
+    // succeeding blocks in the list, the predecessor is before the successor.
+    // Assume that we add the dominated blocks in the right order.
+    int index = dominatedBlocks.length;
+    while (index > 0 && dominatedBlocks[index - 1].id > block.id) {
+      index--;
+    }
+    if (index == dominatedBlocks.length) {
+      dominatedBlocks.add(block);
+    } else {
+      dominatedBlocks.insertRange(index, 1, block);
+    }
+    assert(block.dominator == null);
+    block.dominator = this;
+  }
+
+  void removeDominatedBlock(HBasicBlock block) {
+    assert(isClosed());
+    assert(id != null && block.id != null);
+    int index = dominatedBlocks.indexOf(block);
+    assert(index >= 0);
+    if (index == dominatedBlocks.length - 1) {
+      dominatedBlocks.removeLast();
+    } else {
+      dominatedBlocks.removeRange(index, 1);
+    }
+    assert(identical(block.dominator, this));
+    block.dominator = null;
+  }
+
+  void assignCommonDominator(HBasicBlock predecessor) {
+    assert(isClosed());
+    if (dominator == null) {
+      // If this basic block doesn't have a dominator yet we use the
+      // given predecessor as the dominator.
+      predecessor.addDominatedBlock(this);
+    } else if (predecessor.dominator != null) {
+      // If the predecessor has a dominator and this basic block has a
+      // dominator, we find a common parent in the dominator tree and
+      // use that as the dominator.
+      HBasicBlock block0 = dominator;
+      HBasicBlock block1 = predecessor;
+      while (!identical(block0, block1)) {
+        if (block0.id > block1.id) {
+          block0 = block0.dominator;
+        } else {
+          block1 = block1.dominator;
+        }
+        assert(block0 != null && block1 != null);
+      }
+      if (!identical(dominator, block0)) {
+        dominator.removeDominatedBlock(this);
+        block0.addDominatedBlock(this);
+      }
+    }
+  }
+
+  void forEachPhi(void f(HPhi phi)) {
+    HPhi current = phis.first;
+    while (current != null) {
+      HInstruction saved = current.next;
+      f(current);
+      current = saved;
+    }
+  }
+
+  void forEachInstruction(void f(HInstruction instruction)) {
+    HInstruction current = first;
+    while (current != null) {
+      HInstruction saved = current.next;
+      f(current);
+      current = saved;
+    }
+  }
+
+  bool isValid() {
+    assert(isClosed());
+    HValidator validator = new HValidator();
+    validator.visitBasicBlock(this);
+    return validator.isValid;
+  }
+
+  // TODO(ngeoffray): Cache the information if this method ends up
+  // being hot.
+  bool dominates(HBasicBlock other) {
+    do {
+      if (identical(this, other)) return true;
+      other = other.dominator;
+    } while (other != null && other.id >= id);
+    return false;
+  }
+}
+
+
+abstract class HInstruction implements Spannable {
+  Element sourceElement;
+  SourceFileLocation sourcePosition;
+
+  final int id;
+  static int idCounter;
+
+  final List<HInstruction> inputs;
+  final List<HInstruction> usedBy;
+
+  HBasicBlock block;
+  HInstruction previous = null;
+  HInstruction next = null;
+  int flags = 0;
+
+  // Changes flags.
+  static const int FLAG_CHANGES_INDEX = 0;
+  static const int FLAG_CHANGES_INSTANCE_PROPERTY = FLAG_CHANGES_INDEX + 1;
+  static const int FLAG_CHANGES_STATIC_PROPERTY
+      = FLAG_CHANGES_INSTANCE_PROPERTY + 1;
+  static const int FLAG_CHANGES_COUNT = FLAG_CHANGES_STATIC_PROPERTY + 1;
+
+  // Depends flags (one for each changes flag).
+  static const int FLAG_DEPENDS_ON_INDEX_STORE = FLAG_CHANGES_COUNT;
+  static const int FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE =
+      FLAG_DEPENDS_ON_INDEX_STORE + 1;
+  static const int FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE =
+      FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE + 1;
+  static const int FLAG_DEPENDS_ON_COUNT =
+      FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE + 1;
+
+  // Other flags.
+  static const int FLAG_USE_GVN = FLAG_DEPENDS_ON_COUNT;
+
+  // Type codes.
+  static const int UNDEFINED_TYPECODE = -1;
+  static const int BOOLIFY_TYPECODE = 0;
+  static const int TYPE_GUARD_TYPECODE = 1;
+  static const int BOUNDS_CHECK_TYPECODE = 2;
+  static const int INTEGER_CHECK_TYPECODE = 3;
+  static const int INTERCEPTOR_TYPECODE = 4;
+  static const int ADD_TYPECODE = 5;
+  static const int DIVIDE_TYPECODE = 6;
+  static const int MULTIPLY_TYPECODE = 7;
+  static const int SUBTRACT_TYPECODE = 8;
+  static const int SHIFT_LEFT_TYPECODE = 9;
+  static const int BIT_OR_TYPECODE = 10;
+  static const int BIT_AND_TYPECODE = 11;
+  static const int BIT_XOR_TYPECODE = 12;
+  static const int NEGATE_TYPECODE = 13;
+  static const int BIT_NOT_TYPECODE = 14;
+  static const int NOT_TYPECODE = 15;
+  static const int IDENTITY_TYPECODE = 16;
+  static const int GREATER_TYPECODE = 17;
+  static const int GREATER_EQUAL_TYPECODE = 18;
+  static const int LESS_TYPECODE = 19;
+  static const int LESS_EQUAL_TYPECODE = 20;
+  static const int STATIC_TYPECODE = 21;
+  static const int STATIC_STORE_TYPECODE = 22;
+  static const int FIELD_GET_TYPECODE = 23;
+  static const int TYPE_CONVERSION_TYPECODE = 24;
+  static const int BAILOUT_TARGET_TYPECODE = 25;
+  static const int INVOKE_STATIC_TYPECODE = 26;
+  static const int INDEX_TYPECODE = 27;
+  static const int IS_TYPECODE = 28;
+  static const int INVOKE_DYNAMIC_TYPECODE = 29;
+
+  HInstruction(this.inputs) : id = idCounter++, usedBy = <HInstruction>[];
+
+  int get hashCode => id;
+
+  bool getFlag(int position) => (flags & (1 << position)) != 0;
+  void setFlag(int position) { flags |= (1 << position); }
+  void clearFlag(int position) { flags &= ~(1 << position); }
+
+  static int computeDependsOnFlags(int flags) => flags << FLAG_CHANGES_COUNT;
+
+  int getChangesFlags() => flags & ((1 << FLAG_CHANGES_COUNT) - 1);
+  int getDependsOnFlags() {
+    return (flags & ((1 << FLAG_DEPENDS_ON_COUNT) - 1)) >> FLAG_CHANGES_COUNT;
+  }
+
+  bool hasSideEffects() => getChangesFlags() != 0;
+  bool dependsOnSomething() => getDependsOnFlags() != 0;
+
+  void setAllSideEffects() { flags |= ((1 << FLAG_CHANGES_COUNT) - 1); }
+  void clearAllSideEffects() { flags &= ~((1 << FLAG_CHANGES_COUNT) - 1); }
+
+  void setDependsOnSomething() {
+    int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT;
+    flags |= (((1 << count) - 1) << FLAG_CHANGES_COUNT);
+  }
+  void clearAllDependencies() {
+    int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT;
+    flags &= ~(((1 << count) - 1) << FLAG_CHANGES_COUNT);
+  }
+
+  bool dependsOnStaticPropertyStore() {
+    return getFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE);
+  }
+  void setDependsOnStaticPropertyStore() {
+    setFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE);
+  }
+  void setChangesStaticProperty() { setFlag(FLAG_CHANGES_STATIC_PROPERTY); }
+
+  bool dependsOnIndexStore() => getFlag(FLAG_DEPENDS_ON_INDEX_STORE);
+  void setDependsOnIndexStore() { setFlag(FLAG_DEPENDS_ON_INDEX_STORE); }
+  void setChangesIndex() { setFlag(FLAG_CHANGES_INDEX); }
+
+  bool dependsOnInstancePropertyStore() {
+    return getFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE);
+  }
+  void setDependsOnInstancePropertyStore() {
+    setFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE);
+  }
+  void setChangesInstanceProperty() { setFlag(FLAG_CHANGES_INSTANCE_PROPERTY); }
+
+  bool useGvn() => getFlag(FLAG_USE_GVN);
+  void setUseGvn() { setFlag(FLAG_USE_GVN); }
+
+  void updateInput(int i, HInstruction insn) {
+    inputs[i] = insn;
+  }
+
+  /**
+   * A pure instruction is an instruction that does not have any side
+   * effect, nor any dependency. They can be moved anywhere in the
+   * graph.
+   */
+  bool isPure() => !hasSideEffects() && !dependsOnSomething() && !canThrow();
+
+  // Can this node throw an exception?
+  bool canThrow() => false;
+
+  // Does this node potentially affect control flow.
+  bool isControlFlow() => false;
+
+  // All isFunctions work on the propagated types.
+  bool isArray(HTypeMap types) => types[this].isArray();
+  bool isReadableArray(HTypeMap types) => types[this].isReadableArray();
+  bool isMutableArray(HTypeMap types) => types[this].isMutableArray();
+  bool isExtendableArray(HTypeMap types) => types[this].isExtendableArray();
+  bool isFixedArray(HTypeMap types) => types[this].isFixedArray();
+  bool isBoolean(HTypeMap types) => types[this].isBoolean();
+  bool isInteger(HTypeMap types) => types[this].isInteger();
+  bool isDouble(HTypeMap types) => types[this].isDouble();
+  bool isNumber(HTypeMap types) => types[this].isNumber();
+  bool isNumberOrNull(HTypeMap types) => types[this].isNumberOrNull();
+  bool isString(HTypeMap types) => types[this].isString();
+  bool isTypeUnknown(HTypeMap types) => types[this].isUnknown();
+  bool isIndexablePrimitive(HTypeMap types)
+      => types[this].isIndexablePrimitive();
+  bool isPrimitive(HTypeMap types) => types[this].isPrimitive();
+  bool canBePrimitive(HTypeMap types) => types[this].canBePrimitive();
+  bool canBeNull(HTypeMap types) => types[this].canBeNull();
+
+  /**
+   * This is the type the instruction is guaranteed to have. It does not
+   * take any propagation into account.
+   */
+  HType guaranteedType = HType.UNKNOWN;
+  bool hasGuaranteedType() => !guaranteedType.isUnknown();
+
+  /**
+   * Some instructions have a good idea of their return type, but cannot
+   * guarantee the type. The computed does not need to be more specialized
+   * than the provided type for [this].
+   *
+   * Examples: the likely type of [:x == y:] is a boolean. In most cases this
+   * cannot be guaranteed, but when merging types we still want to use this
+   * information.
+   *
+   * Similarily the [HAdd] instruction is likely a number. Note that, even if
+   * the incoming type is already set to integer, the likely type might still
+   * just return the number type.
+   */
+  HType computeLikelyType(HTypeMap types, Compiler compiler) => types[this];
+
+  /**
+   * Compute the type of the instruction by propagating the input types through
+   * the instruction.
+   *
+   * By default just copy the guaranteed type.
+   */
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return guaranteedType;
+  }
+
+  /**
+   * Compute the desired type for the the given [input]. Aside from using
+   * other inputs to compute the desired type one should also use
+   * the given [types] which, during the invocation of this method,
+   * represents the desired type of [this].
+   */
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return HType.UNKNOWN;
+  }
+
+  bool isInBasicBlock() => block != null;
+
+  String inputsToString() {
+    void addAsCommaSeparated(StringBuffer buffer, List<HInstruction> list) {
+      for (int i = 0; i < list.length; i++) {
+        if (i != 0) buffer.add(', ');
+        buffer.add("@${list[i].id}");
+      }
+    }
+
+    StringBuffer buffer = new StringBuffer();
+    buffer.add('(');
+    addAsCommaSeparated(buffer, inputs);
+    buffer.add(') - used at [');
+    addAsCommaSeparated(buffer, usedBy);
+    buffer.add(']');
+    return buffer.toString();
+  }
+
+  bool gvnEquals(HInstruction other) {
+    assert(useGvn() && other.useGvn());
+    // Check that the type and the flags match.
+    bool hasSameType = typeEquals(other);
+    assert(hasSameType == (typeCode() == other.typeCode()));
+    if (!hasSameType) return false;
+    if (flags != other.flags) return false;
+    // Check that the inputs match.
+    final int inputsLength = inputs.length;
+    final List<HInstruction> otherInputs = other.inputs;
+    if (inputsLength != otherInputs.length) return false;
+    for (int i = 0; i < inputsLength; i++) {
+      if (!identical(inputs[i], otherInputs[i])) return false;
+    }
+    // Check that the data in the instruction matches.
+    return dataEquals(other);
+  }
+
+  int gvnHashCode() {
+    int result = typeCode();
+    int length = inputs.length;
+    for (int i = 0; i < length; i++) {
+      result = (result * 19) + (inputs[i].id) + (result >> 7);
+    }
+    return result;
+  }
+
+  // These methods should be overwritten by instructions that
+  // participate in global value numbering.
+  int typeCode() => HInstruction.UNDEFINED_TYPECODE;
+  bool typeEquals(HInstruction other) => false;
+  bool dataEquals(HInstruction other) => false;
+
+  accept(HVisitor visitor);
+
+  void notifyAddedToBlock(HBasicBlock targetBlock) {
+    assert(!isInBasicBlock());
+    assert(block == null);
+    // Add [this] to the inputs' uses.
+    for (int i = 0; i < inputs.length; i++) {
+      assert(inputs[i].isInBasicBlock());
+      inputs[i].usedBy.add(this);
+    }
+    block = targetBlock;
+    assert(isValid());
+  }
+
+  void notifyRemovedFromBlock() {
+    assert(isInBasicBlock());
+    assert(usedBy.isEmpty);
+
+    // Remove [this] from the inputs' uses.
+    for (int i = 0; i < inputs.length; i++) {
+      inputs[i].removeUser(this);
+    }
+    this.block = null;
+    assert(isValid());
+  }
+
+  void rewriteInput(HInstruction from, HInstruction to) {
+    for (int i = 0; i < inputs.length; i++) {
+      if (identical(inputs[i], from)) inputs[i] = to;
+    }
+  }
+
+  /** Removes all occurrences of [user] from [usedBy]. */
+  void removeUser(HInstruction user) {
+    List<HInstruction> users = usedBy;
+    int length = users.length;
+    for (int i = 0; i < length; i++) {
+      if (identical(users[i], user)) {
+        users[i] = users[length - 1];
+        length--;
+      }
+    }
+    users.length = length;
+  }
+
+  // Change all uses of [oldInput] by [this] to [newInput]. Also
+  // updates the [usedBy] of [oldInput] and [newInput].
+  void changeUse(HInstruction oldInput, HInstruction newInput) {
+    for (int i = 0; i < inputs.length; i++) {
+      if (identical(inputs[i], oldInput)) {
+        inputs[i] = newInput;
+        newInput.usedBy.add(this);
+      }
+    }
+    List<HInstruction> oldInputUsers = oldInput.usedBy;
+    int i = 0;
+    while (i < oldInputUsers.length) {
+      if (oldInputUsers[i] == this) {
+        oldInputUsers[i] = oldInputUsers[oldInput.usedBy.length - 1];
+        oldInputUsers.length--;
+      } else {
+        i++;
+      }
+    }
+  }
+
+  // Compute the set of users of this instruction that is dominated by
+  // [other]. If [other] is a user of [this], it is included in the
+  // returned set.
+  Set<HInstruction> dominatedUsers(HInstruction other) {
+    // Keep track of all instructions that we have to deal with later
+    // and count the number of them that are in the current block.
+    Set<HInstruction> users = new Set<HInstruction>();
+    int usersInCurrentBlock = 0;
+
+    // Run through all the users and see if they are dominated or
+    // potentially dominated by [other].
+    HBasicBlock otherBlock = other.block;
+    for (int i = 0, length = usedBy.length; i < length; i++) {
+      HInstruction current = usedBy[i];
+      if (otherBlock.dominates(current.block)) {
+        if (identical(current.block, otherBlock)) usersInCurrentBlock++;
+        users.add(current);
+      }
+    }
+
+    // Run through all the phis in the same block as [other] and remove them
+    // from the users set.
+    if (usersInCurrentBlock > 0) {
+      for (HPhi phi = otherBlock.phis.first; phi != null; phi = phi.next) {
+        if (users.contains(phi)) {
+          users.remove(phi);
+          if (--usersInCurrentBlock == 0) break;
+        }
+      }
+    }
+
+    // Run through all the instructions before [other] and remove them
+    // from the users set.
+    if (usersInCurrentBlock > 0) {
+      HInstruction current = otherBlock.first;
+      while (!identical(current, other)) {
+        if (users.contains(current)) {
+          users.remove(current);
+          if (--usersInCurrentBlock == 0) break;
+        }
+        current = current.next;
+      }
+    }
+
+    return users;
+  }
+
+  void moveBefore(HInstruction other) {
+    assert(this is !HControlFlow);
+    assert(this is !HPhi);
+    assert(other is !HPhi);
+    block.detach(this);
+    other.block.internalAddBefore(other, this);
+    block = other.block;
+  }
+
+  bool isConstant() => false;
+  bool isConstantBoolean() => false;
+  bool isConstantNull() => false;
+  bool isConstantNumber() => false;
+  bool isConstantInteger() => false;
+  bool isConstantString() => false;
+  bool isConstantList() => false;
+  bool isConstantMap() => false;
+  bool isConstantFalse() => false;
+  bool isConstantTrue() => false;
+  bool isConstantSentinel() => false;
+
+  bool isValid() {
+    HValidator validator = new HValidator();
+    validator.currentBlock = block;
+    validator.visitInstruction(this);
+    return validator.isValid;
+  }
+
+  /**
+   * The code for computing a bailout environment, and the code
+   * generation must agree on what does not need to be captured,
+   * so should always be generated at use site.
+   */
+  bool isCodeMotionInvariant() => false;
+
+  bool isJsStatement() => false;
+
+  bool dominates(HInstruction other) {
+    // An instruction does not dominates itself.
+    if (this == other) return false;
+    if (block != other.block) return block.dominates(other.block);
+
+    HInstruction current = this.next;
+    while (current != null) {
+      if (current == other) return true;
+      current = current.next;
+    }
+    return false;
+  }
+
+
+  HInstruction convertType(Compiler compiler, DartType type, int kind) {
+    if (type == null) return this;
+    if (identical(type.element, compiler.dynamicClass)) return this;
+    if (identical(type.element, compiler.objectClass)) return this;
+
+    // If the original can't be null, type conversion also can't produce null.
+    bool canBeNull = this.guaranteedType.canBeNull();
+    HType convertedType =
+        new HType.fromBoundedType(type, compiler, canBeNull);
+
+    // No need to convert if we know the instruction has
+    // [convertedType] as a bound.
+    if (this.guaranteedType == convertedType) {
+      return this;
+    }
+
+    return new HTypeConversion(convertedType, this, kind);
+  }
+
+    /**
+   * Return whether the instructions do not belong to a loop or
+   * belong to the same loop.
+   */
+  bool hasSameLoopHeaderAs(HInstruction other) {
+    return block.enclosingLoopHeader == other.block.enclosingLoopHeader;
+  }
+}
+
+class HBoolify extends HInstruction {
+  HBoolify(HInstruction value) : super(<HInstruction>[value]) {
+    assert(!hasSideEffects());
+    setUseGvn();
+  }
+
+  HType get guaranteedType => HType.BOOLEAN;
+
+  accept(HVisitor visitor) => visitor.visitBoolify(this);
+  int typeCode() => HInstruction.BOOLIFY_TYPECODE;
+  bool typeEquals(other) => other is HBoolify;
+  bool dataEquals(HInstruction other) => true;
+}
+
+/**
+ * A [HCheck] instruction is an instruction that might do a dynamic
+ * check at runtime on another instruction. To have proper instruction
+ * dependencies in the graph, instructions that depend on the check
+ * being done reference the [HCheck] instruction instead of the
+ * instruction itself.
+ */
+abstract class HCheck extends HInstruction {
+  HCheck(inputs) : super(inputs) {
+    assert(!hasSideEffects());
+    setUseGvn();
+  }
+  HInstruction get checkedInput => inputs[0];
+  bool isJsStatement() => true;
+  bool canThrow() => true;
+}
+
+class HBailoutTarget extends HInstruction {
+  final int state;
+  bool isEnabled = true;
+  // For each argument we record how many dummy (unused) arguments should
+  // precede it, to make sure it lands in the correctly named parameter in the
+  // bailout function.
+  List<int> padding;
+  HBailoutTarget(this.state) : super(<HInstruction>[]) {
+    assert(!hasSideEffects());
+    setUseGvn();
+  }
+
+  bool isControlFlow() => isEnabled;
+  bool isJsStatement() => isEnabled;
+
+  accept(HVisitor visitor) => visitor.visitBailoutTarget(this);
+  int typeCode() => HInstruction.BAILOUT_TARGET_TYPECODE;
+  bool typeEquals(other) => other is HBailoutTarget;
+  bool dataEquals(HBailoutTarget other) => other.state == state;
+}
+
+class HTypeGuard extends HCheck {
+  final HType guardedType;
+  bool isEnabled = false;
+
+  HTypeGuard(this.guardedType, HInstruction guarded, HInstruction bailoutTarget)
+      : super(<HInstruction>[guarded, bailoutTarget]);
+
+  HInstruction get guarded => inputs[0];
+  HInstruction get checkedInput => guarded;
+  HBailoutTarget get bailoutTarget => inputs[1];
+  int get state => bailoutTarget.state;
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return isEnabled ? guardedType : types[guarded];
+  }
+
+  HType get guaranteedType => isEnabled ? guardedType : HType.UNKNOWN;
+
+  bool isControlFlow() => true;
+  bool isJsStatement() => isEnabled;
+  bool canThrow() => isEnabled;
+
+  accept(HVisitor visitor) => visitor.visitTypeGuard(this);
+  int typeCode() => HInstruction.TYPE_GUARD_TYPECODE;
+  bool typeEquals(other) => other is HTypeGuard;
+  bool dataEquals(HTypeGuard other) => guardedType == other.guardedType;
+}
+
+class HBoundsCheck extends HCheck {
+  static const int ALWAYS_FALSE = 0;
+  static const int FULL_CHECK = 1;
+  static const int ALWAYS_ABOVE_ZERO = 2;
+  static const int ALWAYS_BELOW_LENGTH = 3;
+  static const int ALWAYS_TRUE = 4;
+  /**
+   * Details which tests have been done statically during compilation.
+   * Default is that all checks must be performed dynamically.
+   */
+  int staticChecks = FULL_CHECK;
+
+  HBoundsCheck(length, index) : super(<HInstruction>[length, index]);
+
+  HInstruction get length => inputs[1];
+  HInstruction get index => inputs[0];
+  bool isControlFlow() => true;
+
+  HType get guaranteedType => HType.INTEGER;
+
+  accept(HVisitor visitor) => visitor.visitBoundsCheck(this);
+  int typeCode() => HInstruction.BOUNDS_CHECK_TYPECODE;
+  bool typeEquals(other) => other is HBoundsCheck;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HIntegerCheck extends HCheck {
+  bool alwaysFalse = false;
+
+  HIntegerCheck(value) : super(<HInstruction>[value]);
+
+  HInstruction get value => inputs[0];
+  bool isControlFlow() => true;
+
+  HType get guaranteedType => HType.INTEGER;
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    // If the desired type of the input is already a number, we want
+    // to specialize it to an integer.
+    return input.isNumber(types)
+      ? HType.INTEGER
+      : super.computeDesiredTypeForInput(input, types, compiler);
+  }
+
+  accept(HVisitor visitor) => visitor.visitIntegerCheck(this);
+  int typeCode() => HInstruction.INTEGER_CHECK_TYPECODE;
+  bool typeEquals(other) => other is HIntegerCheck;
+  bool dataEquals(HInstruction other) => true;
+}
+
+abstract class HConditionalBranch extends HControlFlow {
+  HConditionalBranch(inputs) : super(inputs);
+  HInstruction get condition => inputs[0];
+  HBasicBlock get trueBranch => block.successors[0];
+  HBasicBlock get falseBranch => block.successors[1];
+}
+
+abstract class HControlFlow extends HInstruction {
+  HControlFlow(inputs) : super(inputs);
+  bool isControlFlow() => true;
+  bool isJsStatement() => true;
+}
+
+abstract class HInvoke extends HInstruction {
+  /**
+    * The first argument must be the target: either an [HStatic] node, or
+    * the receiver of a method-call. The remaining inputs are the arguments
+    * to the invocation.
+    */
+  HInvoke(List<HInstruction> inputs) : super(inputs) {
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+  static const int ARGUMENTS_OFFSET = 1;
+  bool canThrow() => true;
+}
+
+abstract class HInvokeDynamic extends HInvoke {
+  final InvokeDynamicSpecializer specializer;
+  final Selector selector;
+  Element element;
+
+  HInvokeDynamic(Selector selector,
+                 this.element,
+                 List<HInstruction> inputs,
+                 [bool isIntercepted = false])
+    : super(inputs),
+      this.selector = selector,
+      specializer = isIntercepted
+          ? InvokeDynamicSpecializer.lookupSpecializer(selector)
+          : const InvokeDynamicSpecializer();
+  toString() => 'invoke dynamic: $selector';
+  HInstruction get receiver => inputs[0];
+
+  bool get isInterceptorCall {
+    // We know it's a selector call if it follows the interceptor
+    // calling convention, which adds the actual receiver as a
+    // parameter to the call.
+    return inputs.length - 2 == selector.argumentCount;
+  }
+
+  int typeCode() => HInstruction.INVOKE_DYNAMIC_TYPECODE;
+  bool typeEquals(other) => other is HInvokeDynamic;
+  bool dataEquals(HInvokeDynamic other) {
+    return selector == other.selector
+        && element == other.element;
+  }
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return specializer.computeDesiredTypeForInput(this, input, types, compiler);
+  }
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return specializer.computeTypeFromInputTypes(this, types, compiler);
+  }
+}
+
+class HInvokeClosure extends HInvokeDynamic {
+  HInvokeClosure(Selector selector, List<HInstruction> inputs)
+    : super(selector, null, inputs) {
+    assert(selector.isClosureCall());
+  }
+  accept(HVisitor visitor) => visitor.visitInvokeClosure(this);
+}
+
+class HInvokeDynamicMethod extends HInvokeDynamic {
+  HInvokeDynamicMethod(Selector selector,
+                       List<HInstruction> inputs,
+                       [bool isIntercepted = false])
+    : super(selector, null, inputs, isIntercepted);
+
+  String toString() => 'invoke dynamic method: $selector';
+  accept(HVisitor visitor) => visitor.visitInvokeDynamicMethod(this);
+
+  bool isIndexOperatorOnIndexablePrimitive(HTypeMap types) {
+    return isInterceptorCall
+        && selector.kind == SelectorKind.INDEX
+        && selector.name == const SourceString('[]')
+        && inputs[1].isIndexablePrimitive(types);
+  }
+}
+
+abstract class HInvokeDynamicField extends HInvokeDynamic {
+  final bool isSideEffectFree;
+  HInvokeDynamicField(
+      Selector selector, Element element, List<HInstruction> inputs,
+      this.isSideEffectFree)
+      : super(selector, element, inputs);
+  toString() => 'invoke dynamic field: $selector';
+}
+
+class HInvokeDynamicGetter extends HInvokeDynamicField {
+  HInvokeDynamicGetter(selector, element, receiver, isSideEffectFree)
+    : super(selector, element, [receiver], isSideEffectFree) {
+    clearAllSideEffects();
+    if (isSideEffectFree) {
+      setUseGvn();
+      setDependsOnInstancePropertyStore();
+    } else {
+      setDependsOnSomething();
+      setAllSideEffects();
+    }
+  }
+  toString() => 'invoke dynamic getter: $selector';
+  accept(HVisitor visitor) => visitor.visitInvokeDynamicGetter(this);
+}
+
+class HInvokeDynamicSetter extends HInvokeDynamicField {
+  HInvokeDynamicSetter(selector, element, receiver, value, isSideEffectFree)
+    : super(selector, element, [receiver, value], isSideEffectFree) {
+    clearAllSideEffects();
+    if (isSideEffectFree) {
+      setChangesInstanceProperty();
+    } else {
+      setAllSideEffects();
+      setDependsOnSomething();
+    }
+  }
+  toString() => 'invoke dynamic setter: $selector';
+  accept(HVisitor visitor) => visitor.visitInvokeDynamicSetter(this);
+}
+
+class HInvokeStatic extends HInvoke {
+  /** The first input must be the target. */
+  HInvokeStatic(inputs, HType type) : super(inputs) {
+    guaranteedType = type;
+  }
+
+  toString() => 'invoke static: ${element.name}';
+  accept(HVisitor visitor) => visitor.visitInvokeStatic(this);
+  int typeCode() => HInstruction.INVOKE_STATIC_TYPECODE;
+  Element get element => target.element;
+  HStatic get target => inputs[0];
+}
+
+class HInvokeSuper extends HInvokeStatic {
+  final bool isSetter;
+  HInvokeSuper(inputs, {this.isSetter: false}) : super(inputs, HType.UNKNOWN);
+  toString() => 'invoke super: ${element.name}';
+  accept(HVisitor visitor) => visitor.visitInvokeSuper(this);
+
+  HInstruction get value {
+    assert(isSetter);
+    // Index 0: the element, index 1: 'this'.
+    return inputs[2];
+  }
+}
+
+abstract class HFieldAccess extends HInstruction {
+  final Element element;
+
+  HFieldAccess(Element element, List<HInstruction> inputs)
+      : this.element = element, super(inputs);
+
+  HInstruction get receiver => inputs[0];
+}
+
+class HFieldGet extends HFieldAccess {
+  final bool isAssignable;
+
+  HFieldGet(Element element, HInstruction receiver, {bool isAssignable})
+      : this.isAssignable = (isAssignable != null)
+            ? isAssignable
+            : element.isAssignable(),
+        super(element, <HInstruction>[receiver]) {
+    clearAllSideEffects();
+    setUseGvn();
+    if (this.isAssignable) {
+      setDependsOnInstancePropertyStore();
+    }
+  }
+
+  // TODO(ngeoffray): Only if input can be null.
+  bool canThrow() => true;
+
+  accept(HVisitor visitor) => visitor.visitFieldGet(this);
+
+  int typeCode() => HInstruction.FIELD_GET_TYPECODE;
+  bool typeEquals(other) => other is HFieldGet;
+  bool dataEquals(HFieldGet other) => element == other.element;
+  String toString() => "FieldGet $element";
+}
+
+class HFieldSet extends HFieldAccess {
+  HFieldSet(Element element,
+            HInstruction receiver,
+            HInstruction value)
+      : super(element, <HInstruction>[receiver, value]) {
+    clearAllSideEffects();
+    setChangesInstanceProperty();
+  }
+
+  // TODO(ngeoffray): Only if input can be null.
+  bool canThrow() => true;
+
+  HInstruction get value => inputs[1];
+  accept(HVisitor visitor) => visitor.visitFieldSet(this);
+
+  bool isJsStatement() => true;
+  String toString() => "FieldSet $element";
+}
+
+class HLocalGet extends HFieldAccess {
+  // No need to use GVN for a [HLocalGet], it is just a local
+  // access.
+  HLocalGet(Element element, HLocalValue local)
+      : super(element, <HInstruction>[local]);
+
+  accept(HVisitor visitor) => visitor.visitLocalGet(this);
+
+  HLocalValue get local => inputs[0];
+}
+
+class HLocalSet extends HFieldAccess {
+  HLocalSet(Element element, HLocalValue local, HInstruction value)
+      : super(element, <HInstruction>[local, value]);
+
+  accept(HVisitor visitor) => visitor.visitLocalSet(this);
+
+  HLocalValue get local => inputs[0];
+  HInstruction get value => inputs[1];
+  bool isJsStatement() => true;
+}
+
+class HForeign extends HInstruction {
+  final DartString code;
+  final HType type;
+  final bool isStatement;
+
+  HForeign(this.code,
+           this.type,
+           List<HInstruction> inputs,
+           {this.isStatement: false})
+      : super(inputs) {
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+
+  HForeign.statement(code, List<HInstruction> inputs)
+      : this(code, HType.UNKNOWN, inputs, isStatement: true);
+
+  accept(HVisitor visitor) => visitor.visitForeign(this);
+
+  HType get guaranteedType => type;
+
+  bool isJsStatement() => isStatement;
+  bool canThrow() => true;
+}
+
+class HForeignNew extends HForeign {
+  ClassElement element;
+  HForeignNew(this.element, HType type, List<HInstruction> inputs)
+      : super(const LiteralDartString("new"), type, inputs);
+  accept(HVisitor visitor) => visitor.visitForeignNew(this);
+}
+
+abstract class HInvokeBinary extends HInstruction {
+  HInvokeBinary(HInstruction left, HInstruction right)
+      : super(<HInstruction>[left, right]) {
+    clearAllSideEffects();
+    setUseGvn();
+  }
+
+  HInstruction get left => inputs[0];
+  HInstruction get right => inputs[1];
+
+  BinaryOperation operation(ConstantSystem constantSystem);
+}
+
+abstract class HBinaryArithmetic extends HInvokeBinary {
+  HBinaryArithmetic(HInstruction left, HInstruction right) : super(left, right);
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER;
+    if (left.isDouble(types)) return HType.DOUBLE;
+    return HType.NUMBER;
+  }
+
+  BinaryOperation operation(ConstantSystem constantSystem);
+}
+
+class HAdd extends HBinaryArithmetic {
+  HAdd(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitAdd(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.add;
+  int typeCode() => HInstruction.ADD_TYPECODE;
+  bool typeEquals(other) => other is HAdd;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HDivide extends HBinaryArithmetic {
+  HDivide(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitDivide(this);
+
+  HType get guaranteedType => HType.DOUBLE;
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.divide;
+  int typeCode() => HInstruction.DIVIDE_TYPECODE;
+  bool typeEquals(other) => other is HDivide;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HMultiply extends HBinaryArithmetic {
+  HMultiply(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitMultiply(this);
+
+  BinaryOperation operation(ConstantSystem operations)
+      => operations.multiply;
+  int typeCode() => HInstruction.MULTIPLY_TYPECODE;
+  bool typeEquals(other) => other is HMultiply;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HSubtract extends HBinaryArithmetic {
+  HSubtract(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitSubtract(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.subtract;
+  int typeCode() => HInstruction.SUBTRACT_TYPECODE;
+  bool typeEquals(other) => other is HSubtract;
+  bool dataEquals(HInstruction other) => true;
+}
+
+/**
+ * An [HSwitch] instruction has one input for the incoming
+ * value, and one input per constant that it can switch on.
+ * Its block has one successor per constant, and one for the default.
+ */
+class HSwitch extends HControlFlow {
+  HSwitch(List<HInstruction> inputs) : super(inputs);
+
+  HConstant constant(int index) => inputs[index + 1];
+  HInstruction get expression => inputs[0];
+
+  /**
+   * Provides the target to jump to if none of the constants match
+   * the expression. If the switch had no default case, this is the
+   * following join-block.
+   */
+  HBasicBlock get defaultTarget => block.successors.last;
+
+  accept(HVisitor visitor) => visitor.visitSwitch(this);
+
+  String toString() => "HSwitch cases = $inputs";
+}
+
+// TODO(floitsch): Should HBinaryArithmetic really be the super class of
+// HBinaryBitOp?
+abstract class HBinaryBitOp extends HBinaryArithmetic {
+  HBinaryBitOp(HInstruction left, HInstruction right) : super(left, right);
+  HType get guaranteedType => HType.INTEGER;
+}
+
+class HShiftLeft extends HBinaryBitOp {
+  HShiftLeft(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitShiftLeft(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.shiftLeft;
+  int typeCode() => HInstruction.SHIFT_LEFT_TYPECODE;
+  bool typeEquals(other) => other is HShiftLeft;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitOr extends HBinaryBitOp {
+  HBitOr(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitBitOr(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitOr;
+  int typeCode() => HInstruction.BIT_OR_TYPECODE;
+  bool typeEquals(other) => other is HBitOr;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitAnd extends HBinaryBitOp {
+  HBitAnd(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitBitAnd(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitAnd;
+  int typeCode() => HInstruction.BIT_AND_TYPECODE;
+  bool typeEquals(other) => other is HBitAnd;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitXor extends HBinaryBitOp {
+  HBitXor(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitBitXor(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitXor;
+  int typeCode() => HInstruction.BIT_XOR_TYPECODE;
+  bool typeEquals(other) => other is HBitXor;
+  bool dataEquals(HInstruction other) => true;
+}
+
+abstract class HInvokeUnary extends HInstruction {
+  HInvokeUnary(HInstruction input) : super(<HInstruction>[input]) {
+    clearAllSideEffects();
+    setUseGvn();
+  }
+
+  HInstruction get operand => inputs[0];
+
+  UnaryOperation operation(ConstantSystem constantSystem);
+}
+
+class HNegate extends HInvokeUnary {
+  HNegate(HInstruction input) : super(input);
+  accept(HVisitor visitor) => visitor.visitNegate(this);
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return types[operand];
+  }
+
+  UnaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.negate;
+  int typeCode() => HInstruction.NEGATE_TYPECODE;
+  bool typeEquals(other) => other is HNegate;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitNot extends HInvokeUnary {
+  HBitNot(HInstruction input) : super(input);
+  accept(HVisitor visitor) => visitor.visitBitNot(this);
+  
+  HType get guaranteedType => HType.INTEGER;
+  UnaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitNot;
+  int typeCode() => HInstruction.BIT_NOT_TYPECODE;
+  bool typeEquals(other) => other is HBitNot;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HExit extends HControlFlow {
+  HExit() : super(const <HInstruction>[]);
+  toString() => 'exit';
+  accept(HVisitor visitor) => visitor.visitExit(this);
+}
+
+class HGoto extends HControlFlow {
+  HGoto() : super(const <HInstruction>[]);
+  toString() => 'goto';
+  accept(HVisitor visitor) => visitor.visitGoto(this);
+}
+
+abstract class HJump extends HControlFlow {
+  final TargetElement target;
+  final LabelElement label;
+  HJump(this.target) : label = null, super(const <HInstruction>[]);
+  HJump.toLabel(LabelElement label)
+      : label = label, target = label.target, super(const <HInstruction>[]);
+}
+
+class HBreak extends HJump {
+  HBreak(TargetElement target) : super(target);
+  HBreak.toLabel(LabelElement label) : super.toLabel(label);
+  toString() => (label != null) ? 'break ${label.labelName}' : 'break';
+  accept(HVisitor visitor) => visitor.visitBreak(this);
+}
+
+class HContinue extends HJump {
+  HContinue(TargetElement target) : super(target);
+  HContinue.toLabel(LabelElement label) : super.toLabel(label);
+  toString() => (label != null) ? 'continue ${label.labelName}' : 'continue';
+  accept(HVisitor visitor) => visitor.visitContinue(this);
+}
+
+class HTry extends HControlFlow {
+  HLocalValue exception;
+  HBasicBlock catchBlock;
+  HBasicBlock finallyBlock;
+  HTry() : super(const <HInstruction>[]);
+  toString() => 'try';
+  accept(HVisitor visitor) => visitor.visitTry(this);
+  HBasicBlock get joinBlock => this.block.successors.last;
+}
+
+// An [HExitTry] control flow node is used when the body of a try or
+// the body of a catch contains a return, break or continue. To build
+// the control flow graph, we explicitly mark the body that
+// leads to one of this instruction a predecessor of catch and
+// finally.
+class HExitTry extends HControlFlow {
+  HExitTry() : super(const <HInstruction>[]);
+  toString() => 'exit try';
+  accept(HVisitor visitor) => visitor.visitExitTry(this);
+  HBasicBlock get bodyTrySuccessor => block.successors[0];
+}
+
+class HIf extends HConditionalBranch {
+  HBlockFlow blockInformation = null;
+  HIf(HInstruction condition) : super(<HInstruction>[condition]);
+  toString() => 'if';
+  accept(HVisitor visitor) => visitor.visitIf(this);
+
+  HBasicBlock get thenBlock {
+    assert(identical(block.dominatedBlocks[0], block.successors[0]));
+    return block.successors[0];
+  }
+
+  HBasicBlock get elseBlock {
+    assert(identical(block.dominatedBlocks[1], block.successors[1]));
+    return block.successors[1];
+  }
+
+  HBasicBlock get joinBlock => blockInformation.continuation;
+}
+
+class HLoopBranch extends HConditionalBranch {
+  static const int CONDITION_FIRST_LOOP = 0;
+  static const int DO_WHILE_LOOP = 1;
+
+  final int kind;
+  HLoopBranch(HInstruction condition, [this.kind = CONDITION_FIRST_LOOP])
+      : super(<HInstruction>[condition]);
+  toString() => 'loop-branch';
+  accept(HVisitor visitor) => visitor.visitLoopBranch(this);
+
+  bool isDoWhile() {
+    return identical(kind, DO_WHILE_LOOP);
+  }
+
+  HBasicBlock computeLoopHeader() {
+    HBasicBlock result;
+    if (isDoWhile()) {
+      // In case of a do/while, the successor is a block that avoids
+      // a critical edge and branchs to the loop header.
+      result = block.successors[0].successors[0];
+    } else {
+      // For other loops, the loop header might be up the dominator
+      // tree if the loop condition has control flow.
+      result = block;
+      while (!result.isLoopHeader()) result = result.dominator;
+    }
+
+    assert(result.isLoopHeader());
+    return result;
+  }
+}
+
+class HConstant extends HInstruction {
+  final Constant constant;
+  final HType constantType;
+  HConstant.internal(this.constant, HType this.constantType)
+      : super(<HInstruction>[]);
+
+  toString() => 'literal: $constant';
+  accept(HVisitor visitor) => visitor.visitConstant(this);
+
+  HType get guaranteedType => constantType;
+
+  bool isConstant() => true;
+  bool isConstantBoolean() => constant.isBool();
+  bool isConstantNull() => constant.isNull();
+  bool isConstantNumber() => constant.isNum();
+  bool isConstantInteger() => constant.isInt();
+  bool isConstantString() => constant.isString();
+  bool isConstantList() => constant.isList();
+  bool isConstantMap() => constant.isMap();
+  bool isConstantFalse() => constant.isFalse();
+  bool isConstantTrue() => constant.isTrue();
+  bool isConstantSentinel() => constant.isSentinel();
+
+  // Maybe avoid this if the literal is big?
+  bool isCodeMotionInvariant() => true;
+}
+
+class HNot extends HInstruction {
+  HNot(HInstruction value) : super(<HInstruction>[value]) {
+    setUseGvn();
+  }
+
+  HType get guaranteedType => HType.BOOLEAN;
+
+  // 'Not' only works on booleans. That's what we want as input.
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return HType.BOOLEAN;
+  }
+
+  accept(HVisitor visitor) => visitor.visitNot(this);
+  int typeCode() => HInstruction.NOT_TYPECODE;
+  bool typeEquals(other) => other is HNot;
+  bool dataEquals(HInstruction other) => true;
+}
+
+/**
+  * An [HLocalValue] represents a local. Unlike [HParameterValue]s its
+  * first use must be in an HLocalSet. That is, [HParameterValue]s have a
+  * value from the start, whereas [HLocalValue]s need to be initialized first.
+  */
+class HLocalValue extends HInstruction {
+  HLocalValue(Element element) : super(<HInstruction>[]) {
+    sourceElement = element;
+  }
+
+  toString() => 'local ${sourceElement.name}';
+  accept(HVisitor visitor) => visitor.visitLocalValue(this);
+}
+
+class HParameterValue extends HLocalValue {
+  HParameterValue(Element element) : super(element);
+
+  toString() => 'parameter ${sourceElement.name.slowToString()}';
+  accept(HVisitor visitor) => visitor.visitParameterValue(this);
+}
+
+class HThis extends HParameterValue {
+  HThis(Element element, [HType type = HType.UNKNOWN]) : super(element) {
+    guaranteedType = type;
+  }
+  toString() => 'this';
+  accept(HVisitor visitor) => visitor.visitThis(this);
+  bool isCodeMotionInvariant() => true;
+}
+
+class HPhi extends HInstruction {
+  static const IS_NOT_LOGICAL_OPERATOR = 0;
+  static const IS_AND = 1;
+  static const IS_OR = 2;
+
+  int logicalOperatorType = IS_NOT_LOGICAL_OPERATOR;
+
+  // The order of the [inputs] must correspond to the order of the
+  // predecessor-edges. That is if an input comes from the first predecessor
+  // of the surrounding block, then the input must be the first in the [HPhi].
+  HPhi(Element element, List<HInstruction> inputs) : super(inputs) {
+    sourceElement = element;
+  }
+  HPhi.noInputs(Element element) : this(element, <HInstruction>[]);
+  HPhi.singleInput(Element element, HInstruction input)
+      : this(element, <HInstruction>[input]);
+  HPhi.manyInputs(Element element, List<HInstruction> inputs)
+      : this(element, inputs);
+
+  void addInput(HInstruction input) {
+    assert(isInBasicBlock());
+    inputs.add(input);
+    input.usedBy.add(this);
+  }
+
+  // Compute the (shared) type of the inputs if any. If all inputs
+  // have the same known type return it. If any two inputs have
+  // different known types, we'll return a conflict -- otherwise we'll
+  // simply return an unknown type.
+  HType computeInputsType(bool ignoreUnknowns,
+                          HTypeMap types,
+                          Compiler compiler) {
+    HType candidateType = HType.CONFLICTING;
+    for (int i = 0, length = inputs.length; i < length; i++) {
+      HType inputType = types[inputs[i]];
+      if (ignoreUnknowns && inputType.isUnknown()) continue;
+      // Phis need to combine the incoming types using the union operation.
+      // For example, if one incoming edge has type integer and the other has
+      // type double, then the phi is either an integer or double and thus has
+      // type number.
+      candidateType = candidateType.union(inputType, compiler);
+      if (candidateType.isUnknown()) return HType.UNKNOWN;
+    }
+    return candidateType;
+  }
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    HType inputsType = computeInputsType(false, types, compiler);
+    if (inputsType.isConflicting()) return HType.UNKNOWN;
+    return inputsType;
+  }
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HType propagatedType = types[this];
+    // Best case scenario for a phi is, when all inputs have the same type. If
+    // there is no desired outgoing type we therefore try to unify the input
+    // types (which is basically the [likelyType]).
+    if (propagatedType.isUnknown()) return computeLikelyType(types, compiler);
+    // When the desired outgoing type is conflicting we don't need to give any
+    // requirements on the inputs.
+    if (propagatedType.isConflicting()) return HType.UNKNOWN;
+    // Otherwise the input type must match the desired outgoing type.
+    return propagatedType;
+  }
+
+  HType computeLikelyType(HTypeMap types, Compiler compiler) {
+    HType agreedType = computeInputsType(true, types, compiler);
+    if (agreedType.isConflicting()) return HType.UNKNOWN;
+    // Don't be too restrictive. If the agreed type is integer or double just
+    // say that the likely type is number. If more is expected the type will be
+    // propagated back.
+    if (agreedType.isNumber()) return HType.NUMBER;
+    return agreedType;
+  }
+
+  bool isLogicalOperator() => logicalOperatorType != IS_NOT_LOGICAL_OPERATOR;
+
+  String logicalOperator() {
+    assert(isLogicalOperator());
+    if (logicalOperatorType == IS_AND) return "&&";
+    assert(logicalOperatorType == IS_OR);
+    return "||";
+  }
+
+  toString() => 'phi';
+  accept(HVisitor visitor) => visitor.visitPhi(this);
+}
+
+abstract class HRelational extends HInvokeBinary {
+  bool usesBoolifiedInterceptor = false;
+  HRelational(HInstruction left, HInstruction right) : super(left, right);
+  HType get guaranteedType => HType.BOOLEAN;
+}
+
+class HIdentity extends HRelational {
+  HIdentity(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitIdentity(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.identity;
+  int typeCode() => HInstruction.IDENTITY_TYPECODE;
+  bool typeEquals(other) => other is HIdentity;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HGreater extends HRelational {
+  HGreater(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitGreater(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.greater;
+  int typeCode() => HInstruction.GREATER_TYPECODE;
+  bool typeEquals(other) => other is HGreater;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HGreaterEqual extends HRelational {
+  HGreaterEqual(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitGreaterEqual(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.greaterEqual;
+  int typeCode() => HInstruction.GREATER_EQUAL_TYPECODE;
+  bool typeEquals(other) => other is HGreaterEqual;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HLess extends HRelational {
+  HLess(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitLess(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.less;
+  int typeCode() => HInstruction.LESS_TYPECODE;
+  bool typeEquals(other) => other is HLess;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HLessEqual extends HRelational {
+  HLessEqual(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitLessEqual(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.lessEqual;
+  int typeCode() => HInstruction.LESS_EQUAL_TYPECODE;
+  bool typeEquals(other) => other is HLessEqual;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HReturn extends HControlFlow {
+  HReturn(value) : super(<HInstruction>[value]);
+  toString() => 'return';
+  accept(HVisitor visitor) => visitor.visitReturn(this);
+}
+
+class HThrow extends HControlFlow {
+  final bool isRethrow;
+  HThrow(value, {this.isRethrow: false}) : super(<HInstruction>[value]);
+  toString() => 'throw';
+  accept(HVisitor visitor) => visitor.visitThrow(this);
+}
+
+class HStatic extends HInstruction {
+  final Element element;
+  HStatic(this.element) : super(<HInstruction>[]) {
+    assert(element != null);
+    assert(invariant(this, element.isDeclaration));
+    clearAllSideEffects();
+    if (element.isAssignable()) {
+      setDependsOnStaticPropertyStore();
+    }
+    setUseGvn();
+  }
+  toString() => 'static ${element.name}';
+  accept(HVisitor visitor) => visitor.visitStatic(this);
+
+  int gvnHashCode() => super.gvnHashCode() ^ element.hashCode;
+  int typeCode() => HInstruction.STATIC_TYPECODE;
+  bool typeEquals(other) => other is HStatic;
+  bool dataEquals(HStatic other) => element == other.element;
+  bool isCodeMotionInvariant() => !element.isAssignable();
+}
+
+class HInterceptor extends HInstruction {
+  Set<ClassElement> interceptedClasses;
+  HInterceptor(this.interceptedClasses, HInstruction receiver)
+      : super(<HInstruction>[receiver]) {
+    clearAllSideEffects();
+    setUseGvn();
+  }
+  String toString() => 'interceptor on $interceptedClasses';
+  accept(HVisitor visitor) => visitor.visitInterceptor(this);
+  HInstruction get receiver => inputs[0];
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (interceptedClasses.length != 1) return HType.UNKNOWN;
+    // If the only class being intercepted is of type number, we
+    // make this interceptor call say it wants that class as input.
+    Element interceptor = interceptedClasses.toList()[0];
+    JavaScriptBackend backend = compiler.backend;
+    if (interceptor == backend.jsNumberClass) {
+      return HType.NUMBER;
+    } else if (interceptor == backend.jsIntClass) {
+      return HType.INTEGER;
+    } else if (interceptor == backend.jsDoubleClass) {
+      return HType.DOUBLE;
+    }
+    return HType.UNKNOWN;
+  }
+
+  int typeCode() => HInstruction.INTERCEPTOR_TYPECODE;
+  bool typeEquals(other) => other is HInterceptor;
+  bool dataEquals(HInterceptor other) {
+    return interceptedClasses == other.interceptedClasses
+        || (interceptedClasses.length == other.interceptedClasses.length
+            && interceptedClasses.containsAll(other.interceptedClasses));
+  }
+}
+
+/**
+ * A "one-shot" interceptor is a call to a synthetized method that
+ * will fetch the interceptor of its first parameter, and make a call
+ * on a given selector with the remaining parameters.
+ *
+ * In order to share the same optimizations with regular interceptor
+ * calls, this class extends [HInvokeDynamic] and also has the null
+ * constant as the first input.
+ */
+class HOneShotInterceptor extends HInvokeDynamic {
+  Set<ClassElement> interceptedClasses;
+  HOneShotInterceptor(Selector selector,
+                      List<HInstruction> inputs,
+                      this.interceptedClasses)
+      : super(selector, null, inputs, true) {
+    assert(inputs[0] is HConstant);
+    assert(inputs[0].guaranteedType == HType.NULL);
+  }
+
+  String toString() => 'one shot interceptor on $selector';
+  accept(HVisitor visitor) => visitor.visitOneShotInterceptor(this);
+}
+
+/** An [HLazyStatic] is a static that is initialized lazily at first read. */
+class HLazyStatic extends HInstruction {
+  final Element element;
+  HLazyStatic(this.element) : super(<HInstruction>[]) {
+    // TODO(4931): The first access has side-effects, but we afterwards we
+    // should be able to GVN.
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+
+  toString() => 'lazy static ${element.name}';
+  accept(HVisitor visitor) => visitor.visitLazyStatic(this);
+
+  int typeCode() => 30;
+  // TODO(4931): can we do better here?
+  bool isCodeMotionInvariant() => false;
+  bool canThrow() => true;
+}
+
+class HStaticStore extends HInstruction {
+  Element element;
+  HStaticStore(this.element, HInstruction value)
+      : super(<HInstruction>[value]) {
+    clearAllSideEffects();
+    setChangesStaticProperty();
+  }
+  toString() => 'static store ${element.name}';
+  accept(HVisitor visitor) => visitor.visitStaticStore(this);
+
+  int typeCode() => HInstruction.STATIC_STORE_TYPECODE;
+  bool typeEquals(other) => other is HStaticStore;
+  bool dataEquals(HStaticStore other) => element == other.element;
+  bool isJsStatement() => true;
+}
+
+class HLiteralList extends HInstruction {
+  HLiteralList(inputs) : super(inputs);
+  toString() => 'literal list';
+  accept(HVisitor visitor) => visitor.visitLiteralList(this);
+
+  HType get guaranteedType => HType.EXTENDABLE_ARRAY;
+}
+
+/**
+ * The primitive array indexing operation. Note that this instruction
+ * does not throw because we generate the checks explicitly.
+ */
+class HIndex extends HInstruction {
+  HIndex(HInstruction receiver, HInstruction index)
+      : super(<HInstruction>[receiver, index]) {
+    clearAllSideEffects();
+    setDependsOnIndexStore();
+    setUseGvn();
+  }
+
+  String toString() => 'index operator';
+  accept(HVisitor visitor) => visitor.visitIndex(this);
+
+  HInstruction get receiver => inputs[0];
+  HInstruction get index => inputs[1];
+
+  int typeCode() => HInstruction.INDEX_TYPECODE;
+  bool typeEquals(HInstruction other) => other is HIndex;
+  bool dataEquals(HIndex other) => true;
+}
+
+/**
+ * The primitive array assignment operation. Note that this instruction
+ * does not throw because we generate the checks explicitly.
+ */
+class HIndexAssign extends HInstruction {
+  HIndexAssign(HInstruction receiver,
+               HInstruction index,
+               HInstruction value)
+      : super(<HInstruction>[receiver, index, value]) {
+    clearAllSideEffects();
+    setChangesIndex();
+  }
+  String toString() => 'index assign operator';
+  accept(HVisitor visitor) => visitor.visitIndexAssign(this);
+
+  HInstruction get receiver => inputs[0];
+  HInstruction get index => inputs[1];
+  HInstruction get value => inputs[2];
+}
+
+class HIs extends HInstruction {
+  final DartType typeExpression;
+  final bool nullOk;
+
+  HIs(this.typeExpression, List<HInstruction> inputs, {this.nullOk: false})
+     : super(inputs) {
+    setUseGvn();
+  }
+
+  HInstruction get expression => inputs[0];
+  HInstruction getCheck(int index) => inputs[index + 1];
+  int get checkCount => inputs.length - 1;
+
+  bool hasArgumentChecks() => inputs.length > 1;
+
+  HType get guaranteedType => HType.BOOLEAN;
+
+  accept(HVisitor visitor) => visitor.visitIs(this);
+
+  toString() => "$expression is $typeExpression";
+
+  int typeCode() => HInstruction.IS_TYPECODE;
+  bool typeEquals(HInstruction other) => other is HIs;
+  bool dataEquals(HIs other) {
+    return typeExpression == other.typeExpression
+        && nullOk == other.nullOk;
+  }
+}
+
+class HTypeConversion extends HCheck {
+  HType type;
+  final int kind;
+
+  static const int NO_CHECK = 0;
+  static const int CHECKED_MODE_CHECK = 1;
+  static const int ARGUMENT_TYPE_CHECK = 2;
+  static const int CAST_TYPE_CHECK = 3;
+  static const int BOOLEAN_CONVERSION_CHECK = 4;
+
+  HTypeConversion(this.type, HInstruction input, [this.kind = NO_CHECK])
+      : super(<HInstruction>[input]) {
+    sourceElement = input.sourceElement;
+  }
+  HTypeConversion.checkedModeCheck(HType type, HInstruction input)
+      : this(type, input, CHECKED_MODE_CHECK);
+  HTypeConversion.argumentTypeCheck(HType type, HInstruction input)
+      : this(type, input, ARGUMENT_TYPE_CHECK);
+  HTypeConversion.castCheck(HType type, HInstruction input)
+      : this(type, input, CAST_TYPE_CHECK);
+
+
+  bool get isChecked => kind != NO_CHECK;
+  bool get isCheckedModeCheck {
+    return kind == CHECKED_MODE_CHECK || kind == BOOLEAN_CONVERSION_CHECK;
+  }
+  bool get isArgumentTypeCheck => kind == ARGUMENT_TYPE_CHECK;
+  bool get isCastTypeCheck => kind == CAST_TYPE_CHECK;
+  bool get isBooleanConversionCheck => kind == BOOLEAN_CONVERSION_CHECK;
+
+  HType get guaranteedType => type;
+
+  accept(HVisitor visitor) => visitor.visitTypeConversion(this);
+
+  bool isJsStatement() => kind == ARGUMENT_TYPE_CHECK;
+  bool isControlFlow() => kind == ARGUMENT_TYPE_CHECK;
+  bool canThrow() => isChecked;
+
+  int typeCode() => HInstruction.TYPE_CONVERSION_TYPECODE;
+  bool typeEquals(HInstruction other) => other is HTypeConversion;
+  bool dataEquals(HTypeConversion other) {
+    return type == other.type && kind == other.kind;
+  }
+}
+
+class HRangeConversion extends HCheck {
+  HRangeConversion(HInstruction input) : super(<HInstruction>[input]) {
+    sourceElement = input.sourceElement;
+  }
+  accept(HVisitor visitor) => visitor.visitRangeConversion(this);
+
+  // We currently only do range analysis for integers.
+  HType get guaranteedType => HType.INTEGER;
+}
+
+class HStringConcat extends HInstruction {
+  final Node node;
+  HStringConcat(HInstruction left, HInstruction right, this.node)
+      : super(<HInstruction>[left, right]) {
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+  HType get guaranteedType => HType.STRING;
+
+  HInstruction get left => inputs[0];
+  HInstruction get right => inputs[1];
+
+  accept(HVisitor visitor) => visitor.visitStringConcat(this);
+  toString() => "string concat";
+}
+
+/** Non-block-based (aka. traditional) loop information. */
+class HLoopInformation {
+  final HBasicBlock header;
+  final List<HBasicBlock> blocks;
+  final List<HBasicBlock> backEdges;
+  final List<LabelElement> labels;
+  final TargetElement target;
+
+  /** Corresponding block information for the loop. */
+  HLoopBlockInformation loopBlockInformation;
+
+  HLoopInformation(this.header, this.target, this.labels)
+      : blocks = new List<HBasicBlock>(),
+        backEdges = new List<HBasicBlock>();
+
+  void addBackEdge(HBasicBlock predecessor) {
+    backEdges.add(predecessor);
+    addBlock(predecessor);
+  }
+
+  // Adds a block and transitively all its predecessors in the loop as
+  // loop blocks.
+  void addBlock(HBasicBlock block) {
+    if (identical(block, header)) return;
+    HBasicBlock parentHeader = block.parentLoopHeader;
+    if (identical(parentHeader, header)) {
+      // Nothing to do in this case.
+    } else if (parentHeader != null) {
+      addBlock(parentHeader);
+    } else {
+      block.parentLoopHeader = header;
+      blocks.add(block);
+      for (int i = 0, length = block.predecessors.length; i < length; i++) {
+        addBlock(block.predecessors[i]);
+      }
+    }
+  }
+
+  HBasicBlock getLastBackEdge() {
+    int maxId = -1;
+    HBasicBlock result = null;
+    for (int i = 0, length = backEdges.length; i < length; i++) {
+      HBasicBlock current = backEdges[i];
+      if (current.id > maxId) {
+        maxId = current.id;
+        result = current;
+      }
+    }
+    return result;
+  }
+}
+
+
+/**
+ * Embedding of a [HBlockInformation] for block-structure based traversal
+ * in a dominator based flow traversal by attaching it to a basic block.
+ * To go back to dominator-based traversal, a [HSubGraphBlockInformation]
+ * structure can be added in the block structure.
+ */
+class HBlockFlow {
+  final HBlockInformation body;
+  final HBasicBlock continuation;
+  HBlockFlow(this.body, this.continuation);
+}
+
+
+/**
+ * Information about a syntactic-like structure.
+ */
+abstract class HBlockInformation {
+  HBasicBlock get start;
+  HBasicBlock get end;
+  bool accept(HBlockInformationVisitor visitor);
+}
+
+
+/**
+ * Information about a statement-like structure.
+ */
+abstract class HStatementInformation extends HBlockInformation {
+  bool accept(HStatementInformationVisitor visitor);
+}
+
+
+/**
+ * Information about an expression-like structure.
+ */
+abstract class HExpressionInformation extends HBlockInformation {
+  bool accept(HExpressionInformationVisitor visitor);
+  HInstruction get conditionExpression;
+}
+
+
+abstract class HStatementInformationVisitor {
+  bool visitLabeledBlockInfo(HLabeledBlockInformation info);
+  bool visitLoopInfo(HLoopBlockInformation info);
+  bool visitIfInfo(HIfBlockInformation info);
+  bool visitTryInfo(HTryBlockInformation info);
+  bool visitSwitchInfo(HSwitchBlockInformation info);
+  bool visitSequenceInfo(HStatementSequenceInformation info);
+  // Pseudo-structure embedding a dominator-based traversal into
+  // the block-structure traversal. This will eventually go away.
+  bool visitSubGraphInfo(HSubGraphBlockInformation info);
+}
+
+
+abstract class HExpressionInformationVisitor {
+  bool visitAndOrInfo(HAndOrBlockInformation info);
+  bool visitSubExpressionInfo(HSubExpressionBlockInformation info);
+}
+
+
+abstract class HBlockInformationVisitor
+    implements HStatementInformationVisitor, HExpressionInformationVisitor {
+}
+
+
+/**
+ * Generic class wrapping a [SubGraph] as a block-information until
+ * all structures are handled properly.
+ */
+class HSubGraphBlockInformation implements HStatementInformation {
+  final SubGraph subGraph;
+  HSubGraphBlockInformation(this.subGraph);
+
+  HBasicBlock get start => subGraph.start;
+  HBasicBlock get end => subGraph.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitSubGraphInfo(this);
+}
+
+
+/**
+ * Generic class wrapping a [SubExpression] as a block-information until
+ * expressions structures are handled properly.
+ */
+class HSubExpressionBlockInformation implements HExpressionInformation {
+  final SubExpression subExpression;
+  HSubExpressionBlockInformation(this.subExpression);
+
+  HBasicBlock get start => subExpression.start;
+  HBasicBlock get end => subExpression.end;
+
+  HInstruction get conditionExpression => subExpression.conditionExpression;
+
+  bool accept(HExpressionInformationVisitor visitor) =>
+    visitor.visitSubExpressionInfo(this);
+}
+
+
+/** A sequence of separate statements. */
+class HStatementSequenceInformation implements HStatementInformation {
+  final List<HStatementInformation> statements;
+  HStatementSequenceInformation(this.statements);
+
+  HBasicBlock get start => statements[0].start;
+  HBasicBlock get end => statements.last.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitSequenceInfo(this);
+}
+
+
+class HLabeledBlockInformation implements HStatementInformation {
+  final HStatementInformation body;
+  final List<LabelElement> labels;
+  final TargetElement target;
+  final bool isContinue;
+
+  HLabeledBlockInformation(this.body,
+                           List<LabelElement> labels,
+                           {this.isContinue: false}) :
+      this.labels = labels, this.target = labels[0].target;
+
+  HLabeledBlockInformation.implicit(this.body,
+                                    this.target,
+                                    {this.isContinue: false})
+      : this.labels = const<LabelElement>[];
+
+  HBasicBlock get start => body.start;
+  HBasicBlock get end => body.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitLabeledBlockInfo(this);
+}
+
+class LoopTypeVisitor extends Visitor {
+  const LoopTypeVisitor();
+  int visitNode(Node node) => HLoopBlockInformation.NOT_A_LOOP;
+  int visitWhile(While node) => HLoopBlockInformation.WHILE_LOOP;
+  int visitFor(For node) => HLoopBlockInformation.FOR_LOOP;
+  int visitDoWhile(DoWhile node) => HLoopBlockInformation.DO_WHILE_LOOP;
+  int visitForIn(ForIn node) => HLoopBlockInformation.FOR_IN_LOOP;
+}
+
+class HLoopBlockInformation implements HStatementInformation {
+  static const int WHILE_LOOP = 0;
+  static const int FOR_LOOP = 1;
+  static const int DO_WHILE_LOOP = 2;
+  static const int FOR_IN_LOOP = 3;
+  static const int NOT_A_LOOP = -1;
+
+  final int kind;
+  final HExpressionInformation initializer;
+  final HExpressionInformation condition;
+  final HStatementInformation body;
+  final HExpressionInformation updates;
+  final TargetElement target;
+  final List<LabelElement> labels;
+  final SourceFileLocation sourcePosition;
+  final SourceFileLocation endSourcePosition;
+
+  HLoopBlockInformation(this.kind,
+                        this.initializer,
+                        this.condition,
+                        this.body,
+                        this.updates,
+                        this.target,
+                        this.labels,
+                        this.sourcePosition,
+                        this.endSourcePosition) {
+    assert(
+        (kind == DO_WHILE_LOOP ? body.start : condition.start).isLoopHeader());
+  }
+
+  HBasicBlock get start {
+    if (initializer != null) return initializer.start;
+    if (kind == DO_WHILE_LOOP) {
+      return body.start;
+    }
+    return condition.start;
+  }
+
+  HBasicBlock get loopHeader {
+    return kind == DO_WHILE_LOOP ? body.start : condition.start;
+  }
+
+  HBasicBlock get end {
+    if (updates != null) return updates.end;
+    if (kind == DO_WHILE_LOOP && condition != null) {
+      return condition.end;
+    }
+    return body.end;
+  }
+
+  static int loopType(Node node) {
+    return node.accept(const LoopTypeVisitor());
+  }
+
+  bool isDoWhile() => kind == DO_WHILE_LOOP;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitLoopInfo(this);
+}
+
+class HIfBlockInformation implements HStatementInformation {
+  final HExpressionInformation condition;
+  final HStatementInformation thenGraph;
+  final HStatementInformation elseGraph;
+  HIfBlockInformation(this.condition,
+                      this.thenGraph,
+                      this.elseGraph);
+
+  HBasicBlock get start => condition.start;
+  HBasicBlock get end => elseGraph == null ? thenGraph.end : elseGraph.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitIfInfo(this);
+}
+
+class HAndOrBlockInformation implements HExpressionInformation {
+  final bool isAnd;
+  final HExpressionInformation left;
+  final HExpressionInformation right;
+  HAndOrBlockInformation(this.isAnd,
+                         this.left,
+                         this.right);
+
+  HBasicBlock get start => left.start;
+  HBasicBlock get end => right.end;
+
+  // We don't currently use HAndOrBlockInformation.
+  HInstruction get conditionExpression {
+    return null;
+  }
+  bool accept(HExpressionInformationVisitor visitor) =>
+    visitor.visitAndOrInfo(this);
+}
+
+class HTryBlockInformation implements HStatementInformation {
+  final HStatementInformation body;
+  final HLocalValue catchVariable;
+  final HStatementInformation catchBlock;
+  final HStatementInformation finallyBlock;
+  HTryBlockInformation(this.body,
+                       this.catchVariable,
+                       this.catchBlock,
+                       this.finallyBlock);
+
+  HBasicBlock get start => body.start;
+  HBasicBlock get end =>
+      finallyBlock == null ? catchBlock.end : finallyBlock.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitTryInfo(this);
+}
+
+
+
+class HSwitchBlockInformation implements HStatementInformation {
+  final HExpressionInformation expression;
+  final List<List<Constant>> matchExpressions;
+  final List<HStatementInformation> statements;
+  // If the switch has a default, it's the last statement block, which
+  // may or may not have other expresions.
+  final bool hasDefault;
+  final TargetElement target;
+  final List<LabelElement> labels;
+
+  HSwitchBlockInformation(this.expression,
+                          this.matchExpressions,
+                          this.statements,
+                          this.hasDefault,
+                          this.target,
+                          this.labels);
+
+  HBasicBlock get start => expression.start;
+  HBasicBlock get end {
+    // We don't create a switch block if there are no cases.
+    assert(!statements.isEmpty);
+    return statements.last.end;
+  }
+
+  bool accept(HStatementInformationVisitor visitor) =>
+      visitor.visitSwitchInfo(this);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart
new file mode 100644
index 0000000..35ba2cb
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart
@@ -0,0 +1,1545 @@
+// 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.
+
+part of ssa;
+
+abstract class OptimizationPhase {
+  String get name;
+  void visitGraph(HGraph graph);
+}
+
+class SsaOptimizerTask extends CompilerTask {
+  final JavaScriptBackend backend;
+  SsaOptimizerTask(JavaScriptBackend backend)
+    : this.backend = backend,
+      super(backend.compiler);
+  String get name => 'SSA optimizer';
+  Compiler get compiler => backend.compiler;
+
+  void runPhases(HGraph graph, List<OptimizationPhase> phases) {
+    for (OptimizationPhase phase in phases) {
+      runPhase(graph, phase);
+    }
+  }
+
+  void runPhase(HGraph graph, OptimizationPhase phase) {
+    phase.visitGraph(graph);
+    compiler.tracer.traceGraph(phase.name, graph);
+    assert(graph.isValid());
+  }
+
+  void optimize(CodegenWorkItem work, HGraph graph, bool speculative) {
+    ConstantSystem constantSystem = compiler.backend.constantSystem;
+    JavaScriptItemCompilationContext context = work.compilationContext;
+    HTypeMap types = context.types;
+    measure(() {
+      List<OptimizationPhase> phases = <OptimizationPhase>[
+          // Run trivial constant folding first to optimize
+          // some patterns useful for type conversion.
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          new SsaTypeConversionInserter(compiler),
+          new SsaTypePropagator(compiler, types),
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          // The constant folder affects the types of instructions, so
+          // we run the type propagator again. Note that this would
+          // not be necessary if types were directly stored on
+          // instructions.
+          new SsaTypePropagator(compiler, types),
+          new SsaCheckInserter(backend, work, types, context.boundsChecked),
+          new SsaRedundantPhiEliminator(),
+          new SsaDeadPhiEliminator(),
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          new SsaTypePropagator(compiler, types),
+          new SsaReceiverSpecialization(compiler),
+          new SsaGlobalValueNumberer(compiler, types),
+          new SsaCodeMotion(),
+          new SsaValueRangeAnalyzer(constantSystem, types, work),
+          // Previous optimizations may have generated new
+          // opportunities for constant folding.
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          new SsaSimplifyInterceptors(constantSystem),
+          new SsaDeadCodeEliminator(types)];
+      runPhases(graph, phases);
+      if (!speculative) {
+        runPhase(graph, new SsaConstructionFieldTypes(backend, work, types));
+      }
+    });
+  }
+
+  bool trySpeculativeOptimizations(CodegenWorkItem work, HGraph graph) {
+    if (work.element.isField()) {
+      // Lazy initializers may not have bailout methods.
+      return false;
+    }
+    JavaScriptItemCompilationContext context = work.compilationContext;
+    HTypeMap types = context.types;
+    return measure(() {
+      // Run the phases that will generate type guards.
+      List<OptimizationPhase> phases = <OptimizationPhase>[
+          new SsaSpeculativeTypePropagator(compiler, types),
+          new SsaTypeGuardInserter(compiler, work, types),
+          new SsaEnvironmentBuilder(compiler),
+          // Change the propagated types back to what they were before we
+          // speculatively propagated, so that we can generate the bailout
+          // version.
+          // Note that we do this even if there were no guards inserted. If a
+          // guard is not beneficial enough we don't emit one, but there might
+          // still be speculative types on the instructions.
+          new SsaTypePropagator(compiler, types),
+          // Then run the [SsaCheckInserter] because the type propagator also
+          // propagated types non-speculatively. For example, it might have
+          // propagated the type array for a call to the List constructor.
+          new SsaCheckInserter(backend, work, types, context.boundsChecked)];
+      runPhases(graph, phases);
+      return !work.guards.isEmpty;
+    });
+  }
+
+  void prepareForSpeculativeOptimizations(CodegenWorkItem work, HGraph graph) {
+    JavaScriptItemCompilationContext context = work.compilationContext;
+    HTypeMap types = context.types;
+    measure(() {
+      // In order to generate correct code for the bailout version, we did not
+      // propagate types from the instruction to the type guard. We do it
+      // now to be able to optimize further.
+      work.guards.forEach((HTypeGuard guard) {
+        guard.bailoutTarget.isEnabled = false;
+        guard.isEnabled = true;
+      });
+      // We also need to insert range and integer checks for the type
+      // guards. Now that they claim to have a certain type, some
+      // depending instructions might become builtin (like native array
+      // accesses) and need to be checked.
+      // Also run the type propagator, to please the codegen in case
+      // no other optimization is run.
+      runPhases(graph, <OptimizationPhase>[
+          new SsaCheckInserter(backend, work, types, context.boundsChecked),
+          new SsaTypePropagator(compiler, types)]);
+    });
+  }
+}
+
+/**
+ * If both inputs to known operations are available execute the operation at
+ * compile-time.
+ */
+class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
+  final String name = "SsaConstantFolder";
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final ConstantSystem constantSystem;
+  final HTypeMap types;
+  HGraph graph;
+  Compiler get compiler => backend.compiler;
+
+  SsaConstantFolder(this.constantSystem, this.backend, this.work, this.types);
+
+  void visitGraph(HGraph visitee) {
+    graph = visitee;
+    visitDominatorTree(visitee);
+  }
+
+  visitBasicBlock(HBasicBlock block) {
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      HInstruction replacement = instruction.accept(this);
+      if (replacement != instruction) {
+        block.rewrite(instruction, replacement);
+
+        // If we can replace [instruction] with [replacement], then
+        // [replacement]'s type can be narrowed.
+        types[replacement] =
+            types[replacement].intersection(types[instruction], compiler);
+
+        // If the replacement instruction does not know its
+        // source element, use the source element of the
+        // instruction.
+        if (replacement.sourceElement == null) {
+          replacement.sourceElement = instruction.sourceElement;
+        }
+        if (replacement.sourcePosition == null) {
+          replacement.sourcePosition = instruction.sourcePosition;
+        }
+        if (!replacement.isInBasicBlock()) {
+          // The constant folding can return an instruction that is already
+          // part of the graph (like an input), so we only add the replacement
+          // if necessary.
+          block.addAfter(instruction, replacement);
+          // Visit the replacement as the next instruction in case it
+          // can also be constant folded away.
+          next = replacement;
+        }
+        block.remove(instruction);
+      }
+      instruction = next;
+    }
+  }
+
+  HInstruction visitInstruction(HInstruction node) {
+    return node;
+  }
+
+  HInstruction visitBoolify(HBoolify node) {
+    List<HInstruction> inputs = node.inputs;
+    assert(inputs.length == 1);
+    HInstruction input = inputs[0];
+    HType type = types[input];
+    if (type.isBoolean()) return input;
+    // All values !== true are boolified to false.
+    if (!type.isBooleanOrNull() && !type.isUnknown()) {
+      return graph.addConstantBool(false, constantSystem);
+    }
+    return node;
+  }
+
+  HInstruction visitNot(HNot node) {
+    List<HInstruction> inputs = node.inputs;
+    assert(inputs.length == 1);
+    HInstruction input = inputs[0];
+    if (input is HConstant) {
+      HConstant constant = input;
+      bool isTrue = constant.constant.isTrue();
+      return graph.addConstantBool(!isTrue, constantSystem);
+    } else if (input is HNot) {
+      return input.inputs[0];
+    }
+    return node;
+  }
+
+  HInstruction visitInvokeUnary(HInvokeUnary node) {
+    HInstruction folded =
+        foldUnary(node.operation(constantSystem), node.operand);
+    return folded != null ? folded : node;
+  }
+
+  HInstruction foldUnary(UnaryOperation operation, HInstruction operand) {
+    if (operand is HConstant) {
+      HConstant receiver = operand;
+      Constant folded = operation.fold(receiver.constant);
+      if (folded != null) return graph.addConstant(folded);
+    }
+    return null;
+  }
+
+  HInstruction optimizeLengthInterceptedGetter(HInvokeDynamic node) {
+    HInstruction actualReceiver = node.inputs[1];
+    if (actualReceiver.isIndexablePrimitive(types)) {
+      if (actualReceiver.isConstantString()) {
+        HConstant constantInput = actualReceiver;
+        StringConstant constant = constantInput.constant;
+        return graph.addConstantInt(constant.length, constantSystem);
+      } else if (actualReceiver.isConstantList()) {
+        HConstant constantInput = actualReceiver;
+        ListConstant constant = constantInput.constant;
+        return graph.addConstantInt(constant.length, constantSystem);
+      }
+      Element element;
+      bool isAssignable;
+      if (actualReceiver.isString(types)) {
+        element = backend.jsStringLength;
+        isAssignable = false;
+      } else {
+        element = backend.jsArrayLength;
+        isAssignable = !actualReceiver.isFixedArray(types);
+      }
+      HFieldGet result = new HFieldGet(
+          element, actualReceiver, isAssignable: isAssignable);
+      result.guaranteedType = HType.INTEGER;
+      types[result] = HType.INTEGER;
+      return result;
+    } else if (actualReceiver.isConstantMap()) {
+      HConstant constantInput = actualReceiver;
+      MapConstant constant = constantInput.constant;
+      return graph.addConstantInt(constant.length, constantSystem);
+    }
+    return node;
+  }
+
+  HInstruction handleInterceptorCall(HInvokeDynamic node) {
+    // We only optimize for intercepted method calls in this method.
+    Selector selector = node.selector;
+
+    // Try constant folding the instruction.
+    Operation operation = node.specializer.operation(constantSystem);
+    if (operation != null) {
+      HInstruction instruction = node.inputs.length == 2
+          ? foldUnary(operation, node.inputs[1])
+          : foldBinary(operation, node.inputs[1], node.inputs[2]);
+      if (instruction != null) return instruction;
+    }
+
+    // Try converting the instruction to a builtin instruction.
+    HInstruction instruction =
+        node.specializer.tryConvertToBuiltin(node, types);
+    if (instruction != null) return instruction;
+
+    // Check if this call does not need to be intercepted.
+    HInstruction input = node.inputs[1];
+    HType type = types[input];
+    var interceptor = node.inputs[0];
+
+    if (interceptor.isConstant() && selector.isCall()) {
+      DartType type = types[interceptor].computeType(compiler);
+      ClassElement cls = type.element;
+      node.element = cls.lookupSelector(selector);
+    }
+
+    if (interceptor is !HThis && !type.canBePrimitive()) {
+      // If the type can be null, and the intercepted method can be in
+      // the object class, keep the interceptor.
+      if (type.canBeNull()) {
+        Set<ClassElement> interceptedClasses;
+        if (interceptor is HInterceptor) {
+          interceptedClasses = interceptor.interceptedClasses;
+        } else if (node is HOneShotInterceptor) {
+          var oneShotInterceptor = node;
+          interceptedClasses = oneShotInterceptor.interceptedClasses;
+        }
+        if (interceptedClasses.contains(compiler.objectClass)) return node;
+      }
+      if (selector.isGetter()) {
+        // Change the call to a regular invoke dynamic call.
+        return new HInvokeDynamicGetter(selector, null, input, false);
+      } else if (selector.isSetter()) {
+        return new HInvokeDynamicSetter(
+            selector, null, input, node.inputs[2], false);
+      } else {
+        // Change the call to a regular invoke dynamic call.
+        return new HInvokeDynamicMethod(
+            selector, node.inputs.getRange(1, node.inputs.length - 1));
+      }
+    }
+
+    if (selector.isCall()) {
+      Element target;
+      if (input.isExtendableArray(types)) {
+        if (selector.applies(backend.jsArrayRemoveLast, compiler)) {
+          target = backend.jsArrayRemoveLast;
+        } else if (selector.applies(backend.jsArrayAdd, compiler)) {
+          // The codegen special cases array calls, but does not
+          // inline argument type checks.
+          if (!compiler.enableTypeAssertions) {
+            target = backend.jsArrayAdd;
+          }
+        }
+      } else if (input.isString(types)) {
+        if (selector.applies(backend.jsStringSplit, compiler)) {
+          if (node.inputs[2].isString(types)) {
+            target = backend.jsStringSplit;
+          }
+        } else if (selector.applies(backend.jsStringConcat, compiler)) {
+          if (node.inputs[2].isString(types)) {
+            target = backend.jsStringConcat;
+          }
+        } else if (selector.applies(backend.jsStringToString, compiler)) {
+          return input;
+        }
+      }
+      if (target != null) {
+        // TODO(ngeoffray): There is a strong dependency between codegen
+        // and this optimization that the dynamic invoke does not need an
+        // interceptor. We currently need to keep a
+        // HInvokeDynamicMethod and not create a HForeign because
+        // HForeign is too opaque for the SssaCheckInserter (that adds a
+        // bounds check on removeLast). Once we start inlining, the
+        // bounds check will become explicit, so we won't need this
+        // optimization.
+        HInvokeDynamicMethod result = new HInvokeDynamicMethod(
+            node.selector, node.inputs.getRange(1, node.inputs.length - 1));
+        result.element = target;
+        return result;
+      }
+    } else if (selector.isGetter()) {
+      if (selector.applies(backend.jsArrayLength, compiler)) {
+        return optimizeLengthInterceptedGetter(node);
+      }
+    }
+    return node;
+  }
+
+  bool isFixedSizeListConstructor(HInvokeStatic node) {
+    Element element = node.target.element;
+    if (backend.fixedLengthListConstructor == null) {
+      backend.fixedLengthListConstructor =
+        compiler.listClass.lookupConstructor(
+            new Selector.callConstructor(const SourceString("fixedLength"),
+                                         compiler.listClass.getLibrary()));
+    }
+    // TODO(ngeoffray): checking if the second input is an integer
+    // should not be necessary but it currently makes it easier for
+    // other optimizations to reason on a fixed length constructor
+    // that we know takes an int.
+    return element == backend.fixedLengthListConstructor
+        && node.inputs[1].isInteger(types);
+  }
+
+  HInstruction visitInvokeStatic(HInvokeStatic node) {
+    if (isFixedSizeListConstructor(node)) {
+      node.guaranteedType = HType.FIXED_ARRAY;
+    }
+    return node;
+  }
+
+  HInstruction visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
+    if (node.isInterceptorCall) return handleInterceptorCall(node);
+    HType receiverType = types[node.receiver];
+    if (receiverType.isExact()) {
+      HBoundedType type = receiverType;
+      Element element = type.lookupMember(node.selector.name);
+      // TODO(ngeoffray): Also fold if it's a getter or variable.
+      if (element != null && element.isFunction()) {
+        if (node.selector.applies(element, compiler)) {
+          FunctionElement method = element;
+          FunctionSignature parameters = method.computeSignature(compiler);
+          if (parameters.optionalParameterCount == 0) {
+            node.element = element;
+          }
+          // TODO(ngeoffray): If the method has optional parameters,
+          // we should pass the default values here.
+        }
+      }
+    }
+    return node;
+  }
+
+  HInstruction visitIntegerCheck(HIntegerCheck node) {
+    HInstruction value = node.value;
+    if (value.isInteger(types)) return value;
+    if (value.isConstant()) {
+      HConstant constantInstruction = value;
+      assert(!constantInstruction.constant.isInt());
+      if (!constantSystem.isInt(constantInstruction.constant)) {
+        // -0.0 is a double but will pass the runtime integer check.
+        node.alwaysFalse = true;
+      }
+    }
+    return node;
+  }
+
+  HInstruction foldBinary(BinaryOperation operation,
+                          HInstruction left,
+                          HInstruction right) {
+    if (left is HConstant && right is HConstant) {
+      HConstant op1 = left;
+      HConstant op2 = right;
+      Constant folded = operation.fold(op1.constant, op2.constant);
+      if (folded != null) return graph.addConstant(folded);
+    }
+    return null;
+  }
+
+  HInstruction visitInvokeBinary(HInvokeBinary node) {
+    HInstruction left = node.left;
+    HInstruction right = node.right;
+    BinaryOperation operation = node.operation(constantSystem);
+    HConstant folded = foldBinary(operation, left, right);
+    if (folded != null) return folded;
+    return node;
+  }
+
+  bool allUsersAreBoolifies(HInstruction instruction) {
+    List<HInstruction> users = instruction.usedBy;
+    int length = users.length;
+    for (int i = 0; i < length; i++) {
+      if (users[i] is! HBoolify) return false;
+    }
+    return true;
+  }
+
+  HInstruction visitRelational(HRelational node) {
+    if (allUsersAreBoolifies(node)) {
+      // TODO(ngeoffray): Call a boolified selector.
+      // This node stays the same, but the Boolify node will go away.
+    }
+    // Note that we still have to call [super] to make sure that we end up
+    // in the remaining optimizations.
+    return super.visitRelational(node);
+  }
+
+  HInstruction handleIdentityCheck(HRelational node) {
+    HInstruction left = node.left;
+    HInstruction right = node.right;
+    HType leftType = types[left];
+    HType rightType = types[right];
+
+    // We don't optimize on numbers to preserve the runtime semantics.
+    if (!(left.isNumberOrNull(types) && right.isNumberOrNull(types)) &&
+        leftType.intersection(rightType, compiler).isConflicting()) {
+      return graph.addConstantBool(false, constantSystem);
+    }
+
+    if (left.isConstantBoolean() && right.isBoolean(types)) {
+      HConstant constant = left;
+      if (constant.constant.isTrue()) {
+        return right;
+      } else {
+        return new HNot(right);
+      }
+    }
+
+    if (right.isConstantBoolean() && left.isBoolean(types)) {
+      HConstant constant = right;
+      if (constant.constant.isTrue()) {
+        return left;
+      } else {
+        return new HNot(left);
+      }
+    }
+
+    return null;
+  }
+
+  HInstruction visitIdentity(HIdentity node) {
+    HInstruction newInstruction = handleIdentityCheck(node);
+    return newInstruction == null ? super.visitIdentity(node) : newInstruction;
+  }
+
+  HInstruction visitTypeGuard(HTypeGuard node) {
+    HInstruction value = node.guarded;
+    // If the intersection of the types is still the incoming type then
+    // the incoming type was a subtype of the guarded type, and no check
+    // is required.
+    HType combinedType = types[value].intersection(node.guardedType, compiler);
+    return (combinedType == types[value]) ? value : node;
+  }
+
+  HInstruction visitIs(HIs node) {
+    DartType type = node.typeExpression;
+    Element element = type.element;
+    if (element.isTypeVariable()) {
+      compiler.unimplemented("visitIs for type variables");
+    } if (element.isTypedef()) {
+      return node;
+    }
+
+    HType expressionType = types[node.expression];
+    if (identical(element, compiler.objectClass)
+        || identical(element, compiler.dynamicClass)) {
+      return graph.addConstantBool(true, constantSystem);
+    } else if (expressionType.isInteger()) {
+      if (identical(element, compiler.intClass)
+          || identical(element, compiler.numClass)
+          || Elements.isNumberOrStringSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else if (identical(element, compiler.doubleClass)) {
+        // We let the JS semantics decide for that check. Currently
+        // the code we emit will always return true.
+        return node;
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    } else if (expressionType.isDouble()) {
+      if (identical(element, compiler.doubleClass)
+          || identical(element, compiler.numClass)
+          || Elements.isNumberOrStringSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else if (identical(element, compiler.intClass)) {
+        // We let the JS semantics decide for that check. Currently
+        // the code we emit will return true for a double that can be
+        // represented as a 31-bit integer and for -0.0.
+        return node;
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    } else if (expressionType.isNumber()) {
+      if (identical(element, compiler.numClass)) {
+        return graph.addConstantBool(true, constantSystem);
+      }
+      // We cannot just return false, because the expression may be of
+      // type int or double.
+    } else if (expressionType.isString()) {
+      if (identical(element, compiler.stringClass)
+               || Elements.isStringOnlySupertype(element, compiler)
+               || Elements.isNumberOrStringSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    } else if (expressionType.isArray()) {
+      if (identical(element, compiler.listClass)
+          || Elements.isListSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    // TODO(karlklose): remove the hasTypeArguments check.
+    } else if (expressionType.isUseful()
+               && !expressionType.canBeNull()
+               && !RuntimeTypeInformation.hasTypeArguments(type)) {
+      DartType receiverType = expressionType.computeType(compiler);
+      if (receiverType != null) {
+        if (!receiverType.isMalformed &&
+            !type.isMalformed &&
+            compiler.types.isSubtype(receiverType, type)) {
+          return graph.addConstantBool(true, constantSystem);
+        } else if (expressionType.isExact()) {
+          return graph.addConstantBool(false, constantSystem);
+        }
+      }
+    }
+    return node;
+  }
+
+  HInstruction visitTypeConversion(HTypeConversion node) {
+    HInstruction value = node.inputs[0];
+    DartType type = types[node].computeType(compiler);
+    if (identical(type.element, compiler.dynamicClass)
+        || identical(type.element, compiler.objectClass)) {
+      return value;
+    }
+    if (types[value].canBeNull() && node.isBooleanConversionCheck) {
+      return node;
+    }
+    HType combinedType = types[value].intersection(types[node], compiler);
+    return (combinedType == types[value]) ? value : node;
+  }
+
+  Element findConcreteFieldForDynamicAccess(HInstruction receiver,
+                                            Selector selector) {
+    HType receiverType = types[receiver];
+    if (!receiverType.isUseful()) return null;
+    if (receiverType.canBeNull()) return null;
+    DartType type = receiverType.computeType(compiler);
+    if (type == null) return null;
+    return compiler.world.locateSingleField(type, selector);
+  }
+
+  HInstruction visitFieldGet(HFieldGet node) {
+    if (node.element == backend.jsArrayLength) {
+      if (node.receiver is HInvokeStatic) {
+        // Try to recognize the length getter with input
+        // [:new List.fixedLength(int):].
+        HInvokeStatic call = node.receiver;
+        if (isFixedSizeListConstructor(call)) {
+          return call.inputs[1];
+        }
+      }
+    }
+    return node;
+  }
+
+  HInstruction visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
+    if (node.isInterceptorCall) return handleInterceptorCall(node);
+
+    Element field =
+        findConcreteFieldForDynamicAccess(node.receiver, node.selector);
+    if (field == null) return node;
+
+    Modifiers modifiers = field.modifiers;
+    bool isFinalOrConst = modifiers.isFinal() || modifiers.isConst();
+    if (!compiler.resolverWorld.hasInvokedSetter(field, compiler)) {
+      // If no setter is ever used for this field it is only initialized in the
+      // initializer list.
+      isFinalOrConst = true;
+    }
+    HFieldGet result = new HFieldGet(
+        field, node.inputs[0], isAssignable: !isFinalOrConst);
+    HType type = backend.optimisticFieldType(field);
+    if (type != null) {
+      result.guaranteedType = type;
+      backend.registerFieldTypesOptimization(
+          work.element, field, result.guaranteedType);
+    }
+    return result;
+  }
+
+  HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
+    if (node.isInterceptorCall) return handleInterceptorCall(node);
+
+    Element field =
+        findConcreteFieldForDynamicAccess(node.receiver, node.selector);
+    if (field == null || !field.isAssignable()) return node;
+    HInstruction value = node.inputs[1];
+    if (compiler.enableTypeAssertions) {
+      HInstruction other = value.convertType(
+          compiler,
+          field.computeType(compiler),
+          HTypeConversion.CHECKED_MODE_CHECK);
+      if (other != value) {
+        node.block.addBefore(node, other);
+        value = other;
+      }
+    }
+    return new HFieldSet(field, node.inputs[0], value);
+  }
+
+  HInstruction visitStringConcat(HStringConcat node) {
+    DartString folded = const LiteralDartString("");
+    for (int i = 0; i < node.inputs.length; i++) {
+      HInstruction part = node.inputs[i];
+      if (!part.isConstant()) return node;
+      HConstant constant = part;
+      if (!constant.constant.isPrimitive()) return node;
+      PrimitiveConstant primitive = constant.constant;
+      folded = new DartString.concat(folded, primitive.toDartString());
+    }
+    return graph.addConstant(constantSystem.createString(folded, node.node));
+  }
+
+  HInstruction visitInterceptor(HInterceptor node) {
+    if (node.isConstant()) return node;
+    HInstruction constant = tryComputeConstantInterceptor(
+        node.inputs[0], node.interceptedClasses);
+    if (constant == null) return node;
+    return constant;
+  }
+
+  HInstruction tryComputeConstantInterceptor(HInstruction input,
+                                             Set<ClassElement> intercepted) {
+    HType type = types[input];
+    ClassElement constantInterceptor;
+    if (type.isInteger()) {
+      constantInterceptor = backend.jsIntClass;
+    } else if (type.isDouble()) {
+      constantInterceptor = backend.jsDoubleClass;
+    } else if (type.isBoolean()) {
+      constantInterceptor = backend.jsBoolClass;
+    } else if (type.isString()) {
+      constantInterceptor = backend.jsStringClass;
+    } else if (type.isArray()) {
+      constantInterceptor = backend.jsArrayClass;
+    } else if (type.isNull()) {
+      constantInterceptor = backend.jsNullClass;
+    } else if (type.isNumber()) {
+      // If the method being intercepted is not defined in [int] or
+      // [double] we can safely use the number interceptor.
+      if (!intercepted.contains(compiler.intClass)
+          && !intercepted.contains(compiler.doubleClass)) {
+        constantInterceptor = backend.jsNumberClass;
+      }
+    }
+
+    if (constantInterceptor == null) return null;
+    if (constantInterceptor == work.element.getEnclosingClass()) {
+      return graph.thisInstruction;
+    }
+
+    Constant constant = new ConstructedConstant(
+        constantInterceptor.computeType(compiler), <Constant>[]);
+    return graph.addConstant(constant);
+  }
+
+  HInstruction visitOneShotInterceptor(HOneShotInterceptor node) {
+    HInstruction newInstruction = handleInterceptorCall(node);
+    if (newInstruction != node) return newInstruction;
+
+    HInstruction constant = tryComputeConstantInterceptor(
+        node.inputs[1], node.interceptedClasses);
+
+    if (constant == null) return node;
+
+    Selector selector = node.selector;
+    // TODO(ngeoffray): make one shot interceptors know whether
+    // they have side effects.
+    if (selector.isGetter()) {
+      HInstruction res = new HInvokeDynamicGetter(
+          selector, node.element, constant, false);
+      res.inputs.add(node.inputs[1]);
+      return res;
+    } else if (node.selector.isSetter()) {
+      HInstruction res = new HInvokeDynamicSetter(
+          selector, node.element, constant, node.inputs[1], false);
+      res.inputs.add(node.inputs[2]);
+      return res;
+    } else {
+      List<HInstruction> inputs = new List<HInstruction>.from(node.inputs);
+      inputs[0] = constant;
+      return new HInvokeDynamicMethod(selector, inputs, true);
+    }
+  }
+}
+
+class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
+  final HTypeMap types;
+  final Set<HInstruction> boundsChecked;
+  final CodegenWorkItem work;
+  final JavaScriptBackend backend;
+  final String name = "SsaCheckInserter";
+  HGraph graph;
+
+  SsaCheckInserter(this.backend,
+                   this.work,
+                   this.types,
+                   this.boundsChecked);
+
+  void visitGraph(HGraph graph) {
+    this.graph = graph;
+    visitDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      instruction = instruction.accept(this);
+      instruction = next;
+    }
+  }
+
+  HBoundsCheck insertBoundsCheck(HInstruction node,
+                                 HInstruction receiver,
+                                 HInstruction index) {
+    bool isAssignable = !receiver.isFixedArray(types);
+    HFieldGet length = new HFieldGet(
+        backend.jsArrayLength, receiver, isAssignable: isAssignable);
+    length.guaranteedType = HType.INTEGER;
+    types[length] = HType.INTEGER;
+    node.block.addBefore(node, length);
+
+    HBoundsCheck check = new HBoundsCheck(index, length);
+    node.block.addBefore(node, check);
+    boundsChecked.add(node);
+    return check;
+  }
+
+  HIntegerCheck insertIntegerCheck(HInstruction node, HInstruction value) {
+    HIntegerCheck check = new HIntegerCheck(value);
+    node.block.addBefore(node, check);
+    Set<HInstruction> dominatedUsers = value.dominatedUsers(node);
+    for (HInstruction user in dominatedUsers) {
+      user.changeUse(value, check);
+    }
+    return check;
+  }
+
+  void visitIndex(HIndex node) {
+    if (boundsChecked.contains(node)) return;
+    HInstruction index = node.index;
+    if (!node.index.isInteger(types)) {
+      index = insertIntegerCheck(node, index);
+    }
+    index = insertBoundsCheck(node, node.receiver, index);
+    node.changeUse(node.index, index);
+  }
+
+  void visitIndexAssign(HIndexAssign node) {
+    if (!node.receiver.isMutableArray(types)) return;
+    if (boundsChecked.contains(node)) return;
+    HInstruction index = node.index;
+    if (!node.index.isInteger(types)) {
+      index = insertIntegerCheck(node, index);
+    }
+    index = insertBoundsCheck(node, node.receiver, index);
+    node.changeUse(node.index, index);
+  }
+
+  void visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
+    Element element = node.element;
+    if (node.isInterceptorCall) return;
+    if (element != backend.jsArrayRemoveLast) return;
+    if (boundsChecked.contains(node)) return;
+    insertBoundsCheck(
+        node, node.receiver, graph.addConstantInt(0, backend.constantSystem));
+  }
+}
+
+class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
+  final HTypeMap types;
+  final String name = "SsaDeadCodeEliminator";
+
+  SsaDeadCodeEliminator(this.types);
+
+  bool isDeadCode(HInstruction instruction) {
+    return !instruction.hasSideEffects()
+           && !instruction.canThrow()
+           && instruction.usedBy.isEmpty
+           && instruction is !HTypeGuard
+           && instruction is !HParameterValue
+           && instruction is !HLocalSet
+           && !instruction.isControlFlow();
+  }
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    HInstruction instruction = block.last;
+    while (instruction != null) {
+      var previous = instruction.previous;
+      if (isDeadCode(instruction)) block.remove(instruction);
+      instruction = previous;
+    }
+  }
+}
+
+class SsaDeadPhiEliminator implements OptimizationPhase {
+  final String name = "SsaDeadPhiEliminator";
+
+  void visitGraph(HGraph graph) {
+    final List<HPhi> worklist = <HPhi>[];
+    // A set to keep track of the live phis that we found.
+    final Set<HPhi> livePhis = new Set<HPhi>();
+
+    // Add to the worklist all live phis: phis referenced by non-phi
+    // instructions.
+    for (final block in graph.blocks) {
+      block.forEachPhi((HPhi phi) {
+        for (final user in phi.usedBy) {
+          if (user is !HPhi) {
+            worklist.add(phi);
+            livePhis.add(phi);
+            break;
+          }
+        }
+      });
+    }
+
+    // Process the worklist by propagating liveness to phi inputs.
+    while (!worklist.isEmpty) {
+      HPhi phi = worklist.removeLast();
+      for (final input in phi.inputs) {
+        if (input is HPhi && !livePhis.contains(input)) {
+          worklist.add(input);
+          livePhis.add(input);
+        }
+      }
+    }
+
+    // Remove phis that are not live.
+    // Traverse in reverse order to remove phis with no uses before the
+    // phis that they might use.
+    // NOTICE: Doesn't handle circular references, but we don't currently
+    // create any.
+    List<HBasicBlock> blocks = graph.blocks;
+    for (int i = blocks.length - 1; i >= 0; i--) {
+      HBasicBlock block = blocks[i];
+      HPhi current = block.phis.first;
+      HPhi next = null;
+      while (current != null) {
+        next = current.next;
+        if (!livePhis.contains(current)
+            // TODO(ahe): Not sure the following is correct.
+            && current.usedBy.isEmpty) {
+          block.removePhi(current);
+        }
+        current = next;
+      }
+    }
+  }
+}
+
+class SsaRedundantPhiEliminator implements OptimizationPhase {
+  final String name = "SsaRedundantPhiEliminator";
+
+  void visitGraph(HGraph graph) {
+    final List<HPhi> worklist = <HPhi>[];
+
+    // Add all phis in the worklist.
+    for (final block in graph.blocks) {
+      block.forEachPhi((HPhi phi) => worklist.add(phi));
+    }
+
+    while (!worklist.isEmpty) {
+      HPhi phi = worklist.removeLast();
+
+      // If the phi has already been processed, continue.
+      if (!phi.isInBasicBlock()) continue;
+
+      // Find if the inputs of the phi are the same instruction.
+      // The builder ensures that phi.inputs[0] cannot be the phi
+      // itself.
+      assert(!identical(phi.inputs[0], phi));
+      HInstruction candidate = phi.inputs[0];
+      for (int i = 1; i < phi.inputs.length; i++) {
+        HInstruction input = phi.inputs[i];
+        // If the input is the phi, the phi is still candidate for
+        // elimination.
+        if (!identical(input, candidate) && !identical(input, phi)) {
+          candidate = null;
+          break;
+        }
+      }
+
+      // If the inputs are not the same, continue.
+      if (candidate == null) continue;
+
+      // Because we're updating the users of this phi, we may have new
+      // phis candidate for elimination. Add phis that used this phi
+      // to the worklist.
+      for (final user in phi.usedBy) {
+        if (user is HPhi) worklist.add(user);
+      }
+      phi.block.rewrite(phi, candidate);
+      phi.block.removePhi(phi);
+    }
+  }
+}
+
+class SsaGlobalValueNumberer implements OptimizationPhase {
+  final String name = "SsaGlobalValueNumberer";
+  final Compiler compiler;
+  final HTypeMap types;
+  final Set<int> visited;
+
+  List<int> blockChangesFlags;
+  List<int> loopChangesFlags;
+
+  SsaGlobalValueNumberer(this.compiler, this.types) : visited = new Set<int>();
+
+  void visitGraph(HGraph graph) {
+    computeChangesFlags(graph);
+    moveLoopInvariantCode(graph);
+    visitBasicBlock(graph.entry, new ValueSet());
+  }
+
+  void moveLoopInvariantCode(HGraph graph) {
+    for (int i = graph.blocks.length - 1; i >= 0; i--) {
+      HBasicBlock block = graph.blocks[i];
+      if (block.isLoopHeader()) {
+        int changesFlags = loopChangesFlags[block.id];
+        HLoopInformation info = block.loopInformation;
+        // Iterate over all blocks of this loop. Note that blocks in
+        // inner loops are not visited here, but we know they
+        // were visited before because we are iterating in post-order.
+        // So instructions that are GVN'ed in an inner loop are in their
+        // loop entry, and [info.blocks] contains this loop entry.
+        for (HBasicBlock other in info.blocks) {
+          moveLoopInvariantCodeFromBlock(other, block, changesFlags);
+        }
+      }
+    }
+  }
+
+  void moveLoopInvariantCodeFromBlock(HBasicBlock block,
+                                      HBasicBlock loopHeader,
+                                      int changesFlags) {
+    assert(block.parentLoopHeader == loopHeader);
+    HBasicBlock preheader = loopHeader.predecessors[0];
+    int dependsFlags = HInstruction.computeDependsOnFlags(changesFlags);
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      if (instruction.useGvn()
+          && (instruction is !HCheck)
+          && (instruction.flags & dependsFlags) == 0) {
+        bool loopInvariantInputs = true;
+        List<HInstruction> inputs = instruction.inputs;
+        for (int i = 0, length = inputs.length; i < length; i++) {
+          if (isInputDefinedAfterDominator(inputs[i], preheader)) {
+            loopInvariantInputs = false;
+            break;
+          }
+        }
+
+        // If the inputs are loop invariant, we can move the
+        // instruction from the current block to the pre-header block.
+        if (loopInvariantInputs) {
+          block.detach(instruction);
+          preheader.moveAtExit(instruction);
+        }
+      }
+      int oldChangesFlags = changesFlags;
+      changesFlags |= instruction.getChangesFlags();
+      if (oldChangesFlags != changesFlags) {
+        dependsFlags = HInstruction.computeDependsOnFlags(changesFlags);
+      }
+      instruction = next;
+    }
+  }
+
+  bool isInputDefinedAfterDominator(HInstruction input,
+                                    HBasicBlock dominator) {
+    return input.block.id > dominator.id;
+  }
+
+  void visitBasicBlock(HBasicBlock block, ValueSet values) {
+    HInstruction instruction = block.first;
+    if (block.isLoopHeader()) {
+      int flags = loopChangesFlags[block.id];
+      values.kill(flags);
+    }
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      int flags = instruction.getChangesFlags();
+      assert(flags == 0 || !instruction.useGvn());
+      values.kill(flags);
+      if (instruction.useGvn()) {
+        HInstruction other = values.lookup(instruction);
+        if (other != null) {
+          assert(other.gvnEquals(instruction) && instruction.gvnEquals(other));
+          block.rewriteWithBetterUser(instruction, other);
+          block.remove(instruction);
+        } else {
+          values.add(instruction);
+        }
+      }
+      instruction = next;
+    }
+
+    List<HBasicBlock> dominatedBlocks = block.dominatedBlocks;
+    for (int i = 0, length = dominatedBlocks.length; i < length; i++) {
+      HBasicBlock dominated = dominatedBlocks[i];
+      // No need to copy the value set for the last child.
+      ValueSet successorValues = (i == length - 1) ? values : values.copy();
+      // If we have no values in our set, we do not have to kill
+      // anything. Also, if the range of block ids from the current
+      // block to the dominated block is empty, there is no blocks on
+      // any path from the current block to the dominated block so we
+      // don't have to do anything either.
+      assert(block.id < dominated.id);
+      if (!successorValues.isEmpty && block.id + 1 < dominated.id) {
+        visited.clear();
+        int changesFlags = getChangesFlagsForDominatedBlock(block, dominated);
+        successorValues.kill(changesFlags);
+      }
+      visitBasicBlock(dominated, successorValues);
+    }
+  }
+
+  void computeChangesFlags(HGraph graph) {
+    // Create the changes flags lists. Make sure to initialize the
+    // loop changes flags list to zero so we can use bitwise or when
+    // propagating loop changes upwards.
+    final int length = graph.blocks.length;
+    blockChangesFlags = new List<int>.fixedLength(length);
+    loopChangesFlags = new List<int>.fixedLength(length);
+    for (int i = 0; i < length; i++) loopChangesFlags[i] = 0;
+
+    // Run through all the basic blocks in the graph and fill in the
+    // changes flags lists.
+    for (int i = length - 1; i >= 0; i--) {
+      final HBasicBlock block = graph.blocks[i];
+      final int id = block.id;
+
+      // Compute block changes flags for the block.
+      int changesFlags = 0;
+      HInstruction instruction = block.first;
+      while (instruction != null) {
+        changesFlags |= instruction.getChangesFlags();
+        instruction = instruction.next;
+      }
+      assert(blockChangesFlags[id] == null);
+      blockChangesFlags[id] = changesFlags;
+
+      // Loop headers are part of their loop, so update the loop
+      // changes flags accordingly.
+      if (block.isLoopHeader()) {
+        loopChangesFlags[id] |= changesFlags;
+      }
+
+      // Propagate loop changes flags upwards.
+      HBasicBlock parentLoopHeader = block.parentLoopHeader;
+      if (parentLoopHeader != null) {
+        loopChangesFlags[parentLoopHeader.id] |= (block.isLoopHeader())
+            ? loopChangesFlags[id]
+            : changesFlags;
+      }
+    }
+  }
+
+  int getChangesFlagsForDominatedBlock(HBasicBlock dominator,
+                                       HBasicBlock dominated) {
+    int changesFlags = 0;
+    List<HBasicBlock> predecessors = dominated.predecessors;
+    for (int i = 0, length = predecessors.length; i < length; i++) {
+      HBasicBlock block = predecessors[i];
+      int id = block.id;
+      // If the current predecessor block is on the path from the
+      // dominator to the dominated, it must have an id that is in the
+      // range from the dominator to the dominated.
+      if (dominator.id < id && id < dominated.id && !visited.contains(id)) {
+        visited.add(id);
+        changesFlags |= blockChangesFlags[id];
+        // Loop bodies might not be on the path from dominator to dominated,
+        // but they can invalidate values.
+        changesFlags |= loopChangesFlags[id];
+        changesFlags |= getChangesFlagsForDominatedBlock(dominator, block);
+      }
+    }
+    return changesFlags;
+  }
+}
+
+// This phase merges equivalent instructions on different paths into
+// one instruction in a dominator block. It runs through the graph
+// post dominator order and computes a ValueSet for each block of
+// instructions that can be moved to a dominator block. These
+// instructions are the ones that:
+// 1) can be used for GVN, and
+// 2) do not use definitions of their own block.
+//
+// A basic block looks at its sucessors and finds the intersection of
+// these computed ValueSet. It moves all instructions of the
+// intersection into its own list of instructions.
+class SsaCodeMotion extends HBaseVisitor implements OptimizationPhase {
+  final String name = "SsaCodeMotion";
+
+  List<ValueSet> values;
+
+  void visitGraph(HGraph graph) {
+    values = new List<ValueSet>.fixedLength(graph.blocks.length);
+    for (int i = 0; i < graph.blocks.length; i++) {
+      values[graph.blocks[i].id] = new ValueSet();
+    }
+    visitPostDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    List<HBasicBlock> successors = block.successors;
+
+    // Phase 1: get the ValueSet of all successors (if there are more than one),
+    // compute the intersection and move the instructions of the intersection
+    // into this block.
+    if (successors.length > 1) {
+      ValueSet instructions = values[successors[0].id];
+      for (int i = 1; i < successors.length; i++) {
+        ValueSet other = values[successors[i].id];
+        instructions = instructions.intersection(other);
+      }
+
+      if (!instructions.isEmpty) {
+        List<HInstruction> list = instructions.toList();
+        for (HInstruction instruction in list) {
+          // Move the instruction to the current block.
+          instruction.block.detach(instruction);
+          block.moveAtExit(instruction);
+          // Go through all successors and rewrite their instruction
+          // to the shared one.
+          for (final successor in successors) {
+            HInstruction toRewrite = values[successor.id].lookup(instruction);
+            if (toRewrite != instruction) {
+              successor.rewriteWithBetterUser(toRewrite, instruction);
+              successor.remove(toRewrite);
+            }
+          }
+        }
+      }
+    }
+
+    // Don't try to merge instructions to a dominator if we have
+    // multiple predecessors.
+    if (block.predecessors.length != 1) return;
+
+    // Phase 2: Go through all instructions of this block and find
+    // which instructions can be moved to a dominator block.
+    ValueSet set_ = values[block.id];
+    HInstruction instruction = block.first;
+    int flags = 0;
+    while (instruction != null) {
+      int dependsFlags = HInstruction.computeDependsOnFlags(flags);
+      flags |= instruction.getChangesFlags();
+
+      HInstruction current = instruction;
+      instruction = instruction.next;
+
+      // TODO(ngeoffray): this check is needed because we currently do
+      // not have flags to express 'Gvn'able', but not movable.
+      if (current is HCheck) continue;
+      if (!current.useGvn()) continue;
+      if ((current.flags & dependsFlags) != 0) continue;
+
+      bool canBeMoved = true;
+      for (final HInstruction input in current.inputs) {
+        if (input.block == block) {
+          canBeMoved = false;
+          break;
+        }
+      }
+      if (!canBeMoved) continue;
+
+      // This is safe because we are running after GVN.
+      // TODO(ngeoffray): ensure GVN has been run.
+      set_.add(current);
+    }
+  }
+}
+
+class SsaTypeConversionInserter extends HBaseVisitor
+    implements OptimizationPhase {
+  final String name = "SsaTypeconversionInserter";
+  final Compiler compiler;
+
+  SsaTypeConversionInserter(this.compiler);
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+
+  // Update users of [input] that are dominated by [:dominator.first:]
+  // to use [newInput] instead.
+  void changeUsesDominatedBy(HBasicBlock dominator,
+                             HInstruction input,
+                             HType convertedType) {
+    Set<HInstruction> dominatedUsers = input.dominatedUsers(dominator.first);
+    if (dominatedUsers.isEmpty) return;
+
+    HTypeConversion newInput = new HTypeConversion(convertedType, input);
+    dominator.addBefore(dominator.first, newInput);
+    dominatedUsers.forEach((HInstruction user) {
+      user.changeUse(input, newInput);
+    });
+  }
+
+  void visitIs(HIs instruction) {
+    HInstruction input = instruction.expression;
+    HType convertedType =
+        new HType.fromBoundedType(instruction.typeExpression, compiler);
+
+    List<HInstruction> ifUsers = <HInstruction>[];
+    List<HInstruction> notIfUsers = <HInstruction>[];
+
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HIf) {
+        ifUsers.add(user);
+      } else if (user is HNot) {
+        for (HInstruction notUser in user.usedBy) {
+          if (notUser is HIf) notIfUsers.add(notUser);
+        }
+      }
+    }
+
+    if (ifUsers.isEmpty && notIfUsers.isEmpty) return;
+
+    for (HIf ifUser in ifUsers) {
+      changeUsesDominatedBy(ifUser.thenBlock, input, convertedType);
+      // TODO(ngeoffray): Also change uses for the else block on a HType
+      // that knows it is not of a specific Type.
+    }
+
+    for (HIf ifUser in notIfUsers) {
+      changeUsesDominatedBy(ifUser.elseBlock, input, convertedType);
+      // TODO(ngeoffray): Also change uses for the then block on a HType
+      // that knows it is not of a specific Type.
+    }
+  }
+}
+
+
+// Analyze the constructors to see if some fields will always have a specific
+// type after construction. If this is the case we can ignore the type given
+// by the field initializer. This is especially useful when the field
+// initializer is initializing the field to null.
+class SsaConstructionFieldTypes
+    extends HBaseVisitor implements OptimizationPhase {
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final HTypeMap types;
+  final String name = "SsaConstructionFieldTypes";
+  final Set<HInstruction> thisUsers;
+  final Set<Element> allSetters;
+  final Map<HBasicBlock, Map<Element, HType>> blockFieldSetters;
+  bool thisExposed = false;
+  HGraph currentGraph;
+  Map<Element, HType> currentFieldSetters;
+
+  SsaConstructionFieldTypes(JavaScriptBackend this.backend,
+         CodegenWorkItem this.work,
+         HTypeMap this.types)
+      : thisUsers = new Set<HInstruction>(),
+        allSetters = new Set<Element>(),
+        blockFieldSetters = new Map<HBasicBlock, Map<Element, HType>>();
+
+  void visitGraph(HGraph graph) {
+    currentGraph = graph;
+    if (!work.element.isGenerativeConstructorBody() &&
+        !work.element.isGenerativeConstructor()) return;
+    visitDominatorTree(graph);
+    if (work.element.isGenerativeConstructor()) {
+      backend.registerConstructor(work.element);
+    }
+  }
+
+  visitBasicBlock(HBasicBlock block) {
+    if (block.predecessors.length == 0) {
+      // Create a new empty map for the first block.
+      currentFieldSetters = new Map<Element, HType>();
+    } else {
+      // Build a map which intersects the fields from all predecessors. For
+      // each field in this intersection it unions the types.
+      currentFieldSetters =
+          new Map.from(blockFieldSetters[block.predecessors[0]]);
+      // Loop headers are the only nodes with back edges.
+      if (!block.isLoopHeader()) {
+        for (int i = 1; i < block.predecessors.length; i++) {
+          Map<Element, HType> predecessorsFieldSetters =
+              blockFieldSetters[block.predecessors[i]];
+          Map<Element, HType> newFieldSetters = new Map<Element, HType>();
+          predecessorsFieldSetters.forEach((Element element, HType type) {
+            HType currentType = currentFieldSetters[element];
+            if (currentType != null) {
+              newFieldSetters[element] =
+                  currentType.union(type, backend.compiler);
+            }
+          });
+          currentFieldSetters = newFieldSetters;
+        }
+      } else {
+        assert(block.predecessors.length <= 2);
+      }
+    }
+    block.forEachPhi((HPhi phi) => phi.accept(this));
+    block.forEachInstruction(
+        (HInstruction instruction) => instruction.accept(this));
+    assert(currentFieldSetters != null);
+    blockFieldSetters[block] = currentFieldSetters;
+  }
+
+  visitInstruction(HInstruction instruction) {
+    // All instructions not explicitly handled below will flag the this
+    // exposure if using this.
+    thisExposed = thisExposed || thisUsers.contains(instruction);
+  }
+
+  visitPhi(HPhi phi) {
+    if (thisUsers.contains(phi)) {
+      thisUsers.addAll(phi.usedBy);
+    }
+  }
+
+  visitThis(HThis instruction) {
+    // Collect all users of this in a set to make the this exposed check simple
+    // and cheap.
+    thisUsers.addAll(instruction.usedBy);
+  }
+
+  visitFieldGet(HInstruction _) {
+    // The field get instruction is allowed to use this.
+  }
+
+  visitForeignNew(HForeignNew node) {
+    // The HForeignNew instruction is used in the generative constructor to
+    // initialize all fields in newly created objects. The fields are
+    // initialized to the value present in the initializer list or set to null
+    // if not otherwise initialized.
+    // Here we handle members in superclasses as well, as the handling of
+    // the generative constructor bodies will ensure, that the initializer
+    // type will not be used if the field is in any of these.
+    int j = 0;
+    node.element.forEachInstanceField(
+        (ClassElement enclosingClass, Element element) {
+          backend.registerFieldInitializer(element, types[node.inputs[j]]);
+          j++;
+        },
+        includeBackendMembers: false,
+        includeSuperMembers: true);
+  }
+
+  visitFieldSet(HFieldSet node) {
+    Element field = node.element;
+    HInstruction value = node.value;
+    HType type = types[value];
+    // [HFieldSet] is also used for variables in try/catch.
+    if (field.isField()) allSetters.add(field);
+    // Don't handle fields defined in superclasses. Given that the field is
+    // always added to the [allSetters] set, setting a field defined in a
+    // superclass will get an inferred type of UNKNOWN.
+    if (identical(work.element.getEnclosingClass(), field.getEnclosingClass()) &&
+        value.hasGuaranteedType()) {
+      currentFieldSetters[field] = type;
+    }
+  }
+
+  visitExit(HExit node) {
+    // If this has been exposed then we cannot say anything about types after
+    // construction.
+    if (!thisExposed) {
+      // Register the known field types.
+      currentFieldSetters.forEach((Element element, HType type) {
+        backend.registerFieldConstructor(element, type);
+        allSetters.remove(element);
+      });
+    }
+
+    // For other fields having setters in the generative constructor body, set
+    // the type to UNKNOWN to avoid relying on the type set in the initializer
+    // list.
+    allSetters.forEach((Element element) {
+      backend.registerFieldConstructor(element, HType.UNKNOWN);
+    });
+  }
+}
+
+/**
+ * This phase specializes dominated uses of a call, where the call
+ * can give us some type information of what the receiver might be.
+ * For example, after a call to [:a.foo():], if [:foo:] is only
+ * in class [:A:], a can be of type [:A:].
+ */
+class SsaReceiverSpecialization extends HBaseVisitor
+    implements OptimizationPhase {
+  final String name = "SsaReceiverSpecialization";
+  final Compiler compiler;
+
+  SsaReceiverSpecialization(this.compiler);
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+  void visitInterceptor(HInterceptor interceptor) {
+    HInstruction receiver = interceptor.receiver;
+    JavaScriptBackend backend = compiler.backend;
+    for (var user in receiver.usedBy) {
+      if (user is HInterceptor && interceptor.dominates(user)) {
+        Set<ClassElement> otherIntercepted = user.interceptedClasses;
+        // If the dominated interceptor intercepts the int class or
+        // the double class, we make sure these classes are also being
+        // intercepted by the dominating interceptor. Otherwise, the
+        // dominating interceptor could just intercept the number
+        // class and therefore not implement the methods in the int or
+        // double class.
+        if (otherIntercepted.contains(backend.jsIntClass)
+            || otherIntercepted.contains(backend.jsDoubleClass)) {
+          interceptor.interceptedClasses.addAll(user.interceptedClasses);
+        }
+        user.interceptedClasses = interceptor.interceptedClasses;
+      }
+    }
+  }
+
+  // TODO(ngeoffray): Also implement it for non-intercepted calls.
+}
+
+/**
+ * This phase replaces all interceptors that are used only once with
+ * one-shot interceptors. It saves code size and makes the receiver of
+ * an intercepted call a candidate for being generated at use site.
+ */
+class SsaSimplifyInterceptors extends HBaseVisitor
+    implements OptimizationPhase {
+  final String name = "SsaSimplifyInterceptors";
+  final ConstantSystem constantSystem;
+  HGraph graph;
+
+  SsaSimplifyInterceptors(this.constantSystem);
+
+  void visitGraph(HGraph graph) {
+    this.graph = graph;
+    visitDominatorTree(graph);
+  }
+
+  void visitInterceptor(HInterceptor node) {
+    if (node.usedBy.length != 1) return;
+    // [HBailoutTarget] instructions might have the interceptor as
+    // input. In such situation we let the dead code analyzer find out
+    // the interceptor is not needed.
+    if (node.usedBy[0] is !HInvokeDynamic) return;
+
+    HInvokeDynamic user = node.usedBy[0];
+
+    // If [node] was loop hoisted, we keep the interceptor.
+    if (!user.hasSameLoopHeaderAs(node)) return;
+
+    // Replace the user with a [HOneShotInterceptor].
+    HConstant nullConstant = graph.addConstantNull(constantSystem);
+    List<HInstruction> inputs = new List<HInstruction>.from(user.inputs);
+    inputs[0] = nullConstant;
+    HOneShotInterceptor interceptor = new HOneShotInterceptor(
+        user.selector, inputs, node.interceptedClasses);
+    interceptor.sourcePosition = user.sourcePosition;
+    interceptor.sourceElement = user.sourceElement;
+
+    HBasicBlock block = user.block;
+    block.addAfter(user, interceptor);
+    block.rewrite(user, interceptor);
+    block.remove(user);
+
+    // The interceptor will be removed in the dead code elimination
+    // phase. Note that removing it here would not work because of how
+    // the [visitBasicBlock] is implemented.
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart
new file mode 100644
index 0000000..13de8b6
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart
@@ -0,0 +1,43 @@
+// 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.
+
+library ssa;
+
+import 'dart:collection';
+
+import '../closure.dart';
+import '../js/js.dart' as js;
+import '../dart2jslib.dart' hide Selector;
+import '../dart_types.dart';
+import '../source_file.dart';
+import '../source_map_builder.dart';
+import '../elements/elements.dart';
+import '../js_backend/js_backend.dart';
+import '../native_handler.dart' as native;
+import '../tree/tree.dart';
+import '../types/types.dart';
+import '../universe/universe.dart';
+import '../util/util.dart';
+import '../util/characters.dart';
+
+import '../scanner/scannerlib.dart'
+    show PartialFunctionElement, Token, PLUS_TOKEN;
+
+import '../elements/modelx.dart'
+    show ElementX,
+         ConstructorBodyElementX;
+
+part 'bailout.dart';
+part 'builder.dart';
+part 'codegen.dart';
+part 'codegen_helpers.dart';
+part 'invoke_dynamic_specializers.dart';
+part 'nodes.dart';
+part 'optimize.dart';
+part 'types.dart';
+part 'types_propagation.dart';
+part 'validate.dart';
+part 'variable_allocator.dart';
+part 'value_range_analyzer.dart';
+part 'value_set.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart
new file mode 100644
index 0000000..7a8f6bc
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart
@@ -0,0 +1,563 @@
+// 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.
+
+library tracer;
+
+import 'dart:io';
+import 'ssa.dart';
+import '../js_backend/js_backend.dart';
+import '../dart2jslib.dart';
+
+const bool GENERATE_SSA_TRACE = false;
+const String SSA_TRACE_FILTER = null;
+
+class HTracer extends HGraphVisitor implements Tracer {
+  JavaScriptItemCompilationContext context;
+  int indent = 0;
+  final RandomAccessFile output;
+  final bool enabled = GENERATE_SSA_TRACE;
+  bool traceActive = false;
+
+  HTracer([String path = "dart.cfg"])
+      : output = GENERATE_SSA_TRACE ? new File(path).openSync(FileMode.WRITE)
+                                    : null;
+
+  void close() {
+    if (enabled) output.closeSync();
+  }
+
+  void traceCompilation(String methodName,
+                        JavaScriptItemCompilationContext compilationContext) {
+    if (!enabled) return;
+    this.context = compilationContext;
+    traceActive =
+        SSA_TRACE_FILTER == null || methodName.contains(SSA_TRACE_FILTER);
+    if (!traceActive) return;
+    tag("compilation", () {
+      printProperty("name", methodName);
+      printProperty("method", methodName);
+      printProperty("date", new DateTime.now().millisecondsSinceEpoch);
+    });
+  }
+
+  void traceGraph(String name, HGraph graph) {
+    if (!traceActive) return;
+    tag("cfg", () {
+      printProperty("name", name);
+      visitDominatorTree(graph);
+    });
+  }
+
+  void addPredecessors(HBasicBlock block) {
+    if (block.predecessors.isEmpty) {
+      printEmptyProperty("predecessors");
+    } else {
+      addIndent();
+      add("predecessors");
+      for (HBasicBlock predecessor in block.predecessors) {
+        add(' "B${predecessor.id}"');
+      }
+      add("\n");
+    }
+  }
+
+  void addSuccessors(HBasicBlock block) {
+    if (block.successors.isEmpty) {
+      printEmptyProperty("successors");
+    } else {
+      addIndent();
+      add("successors");
+      for (HBasicBlock successor in block.successors) {
+        add(' "B${successor.id}"');
+      }
+      add("\n");
+    }
+  }
+
+  void addInstructions(HInstructionStringifier stringifier,
+                       HInstructionList list) {
+    HTypeMap types = context.types;
+    for (HInstruction instruction = list.first;
+         instruction != null;
+         instruction = instruction.next) {
+      int bci = 0;
+      int uses = instruction.usedBy.length;
+      String changes = instruction.hasSideEffects() ? '!' : ' ';
+      String depends = instruction.dependsOnSomething() ? '?' : '';
+      addIndent();
+      String temporaryId = stringifier.temporaryId(instruction);
+      String instructionString = stringifier.visit(instruction);
+      add("$bci $uses $temporaryId $instructionString $changes $depends <|@\n");
+    }
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    HInstructionStringifier stringifier =
+        new HInstructionStringifier(context, block);
+    assert(block.id != null);
+    tag("block", () {
+      printProperty("name", "B${block.id}");
+      printProperty("from_bci", -1);
+      printProperty("to_bci", -1);
+      addPredecessors(block);
+      addSuccessors(block);
+      printEmptyProperty("xhandlers");
+      printEmptyProperty("flags");
+      if (block.dominator != null) {
+        printProperty("dominator", "B${block.dominator.id}");
+      }
+      tag("states", () {
+        tag("locals", () {
+          printProperty("size", 0);
+          printProperty("method", "None");
+          block.forEachPhi((phi) {
+            String phiId = stringifier.temporaryId(phi);
+            StringBuffer inputIds = new StringBuffer();
+            for (int i = 0; i < phi.inputs.length; i++) {
+              inputIds.add(stringifier.temporaryId(phi.inputs[i]));
+              inputIds.add(" ");
+            }
+            println("${phi.id} $phiId [ $inputIds]");
+          });
+        });
+      });
+      tag("HIR", () {
+        addInstructions(stringifier, block.phis);
+        addInstructions(stringifier, block);
+      });
+    });
+  }
+
+  void tag(String tagName, Function f) {
+    println("begin_$tagName");
+    indent++;
+    f();
+    indent--;
+    println("end_$tagName");
+  }
+
+  void println(String string) {
+    addIndent();
+    add(string);
+    add("\n");
+  }
+
+  void printEmptyProperty(String propertyName) {
+    println(propertyName);
+  }
+
+  void printProperty(String propertyName, var value) {
+    if (value is num) {
+      println("$propertyName $value");
+    } else {
+      println('$propertyName "$value"');
+    }
+  }
+
+  void add(String string) {
+    output.writeStringSync(string);
+  }
+
+  void addIndent() {
+    for (int i = 0; i < indent; i++) {
+      add("  ");
+    }
+  }
+}
+
+class HInstructionStringifier implements HVisitor<String> {
+  JavaScriptItemCompilationContext context;
+  HBasicBlock currentBlock;
+
+  HInstructionStringifier(this.context, this.currentBlock);
+
+  visit(HInstruction node) => node.accept(this);
+
+  String temporaryId(HInstruction instruction) {
+    String prefix;
+    HType type = context.types[instruction];
+    if (!type.isPrimitive()) {
+      prefix = 'U';
+    } else {
+      if (type == HType.MUTABLE_ARRAY) {
+        prefix = 'm';
+      } else if (type == HType.READABLE_ARRAY) {
+        prefix = 'a';
+      } else if (type == HType.EXTENDABLE_ARRAY) {
+        prefix = 'e';
+      } else if (type == HType.BOOLEAN) {
+        prefix = 'b';
+      } else if (type == HType.INTEGER) {
+        prefix = 'i';
+      } else if (type == HType.DOUBLE) {
+        prefix = 'd';
+      } else if (type == HType.NUMBER) {
+        prefix = 'n';
+      } else if (type == HType.STRING) {
+        prefix = 's';
+      } else if (type == HType.UNKNOWN) {
+        prefix = 'v';
+      } else if (type == HType.CONFLICTING) {
+        prefix = 'c';
+      } else if (type == HType.INDEXABLE_PRIMITIVE) {
+        prefix = 'r';
+      } else if (type == HType.NULL) {
+        prefix = 'u';
+      } else {
+        prefix = 'x';
+      }
+    }
+    return "$prefix${instruction.id}";
+  }
+
+  String visitBailoutTarget(HBailoutTarget node) {
+    StringBuffer envBuffer = new StringBuffer();
+    List<HInstruction> inputs = node.inputs;
+    for (int i = 0; i < inputs.length; i++) {
+      envBuffer.add(" ${temporaryId(inputs[i])}");
+    }
+    String on = node.isEnabled ? "enabled" : "disabled";
+    return "BailoutTarget($on): id: ${node.state} env: $envBuffer";
+  }
+
+  String visitBoolify(HBoolify node) {
+    return "Boolify: ${temporaryId(node.inputs[0])}";
+  }
+
+  String handleInvokeBinary(HInvokeBinary node, String op) {
+    String left = temporaryId(node.left);
+    String right= temporaryId(node.right);
+    return '$left $op $right';
+  }
+
+  String visitAdd(HAdd node) => handleInvokeBinary(node, '+');
+
+  String visitBitAnd(HBitAnd node) => handleInvokeBinary(node, '&');
+
+  String visitBitNot(HBitNot node) {
+    String operand = temporaryId(node.operand);
+    return "~$operand";
+  }
+
+  String visitBitOr(HBitOr node) => handleInvokeBinary(node, '|');
+
+  String visitBitXor(HBitXor node) => handleInvokeBinary(node, '^');
+
+  String visitBoundsCheck(HBoundsCheck node) {
+    String lengthId = temporaryId(node.length);
+    String indexId = temporaryId(node.index);
+    return "Bounds check: length = $lengthId, index = $indexId";
+  }
+
+  String visitBreak(HBreak node) {
+    HBasicBlock target = currentBlock.successors[0];
+    if (node.label != null) {
+      return "Break ${node.label.labelName}: (B${target.id})";
+    }
+    return "Break: (B${target.id})";
+  }
+
+  String visitConstant(HConstant constant) => "Constant ${constant.constant}";
+
+  String visitContinue(HContinue node) {
+    HBasicBlock target = currentBlock.successors[0];
+    if (node.label != null) {
+      return "Continue ${node.label.labelName}: (B${target.id})";
+    }
+    return "Continue: (B${target.id})";
+  }
+
+  String visitDivide(HDivide node) => handleInvokeBinary(node, '/');
+
+  String visitExit(HExit node) => "exit";
+
+  String visitFieldGet(HFieldGet node) {
+    String fieldName = node.element.name.slowToString();
+    return 'field get ${temporaryId(node.receiver)}.$fieldName';
+  }
+
+  String visitFieldSet(HFieldSet node) {
+    String valueId = temporaryId(node.value);
+    String fieldName = node.element.name.slowToString();
+    return 'field set ${temporaryId(node.receiver)}.$fieldName to $valueId';
+  }
+
+  String visitLocalGet(HLocalGet node) {
+    String localName = node.element.name.slowToString();
+    return 'local get ${temporaryId(node.local)}.$localName';
+  }
+
+  String visitLocalSet(HLocalSet node) {
+    String valueId = temporaryId(node.value);
+    String localName = node.element.name.slowToString();
+    return 'local set ${temporaryId(node.local)}.$localName to $valueId';
+  }
+
+  String visitGoto(HGoto node) {
+    HBasicBlock target = currentBlock.successors[0];
+    return "Goto: (B${target.id})";
+  }
+
+  String visitGreater(HGreater node) => handleInvokeBinary(node, '>');
+  String visitGreaterEqual(HGreaterEqual node) {
+    handleInvokeBinary(node, '>=');
+  }
+  String visitIdentity(HIdentity node) => handleInvokeBinary(node, '===');
+
+  String visitIf(HIf node) {
+    HBasicBlock thenBlock = currentBlock.successors[0];
+    HBasicBlock elseBlock = currentBlock.successors[1];
+    String conditionId = temporaryId(node.inputs[0]);
+    return "If ($conditionId): (B${thenBlock.id}) else (B${elseBlock.id})";
+  }
+
+  String visitGenericInvoke(String invokeType, String functionName,
+                            List<HInstruction> arguments) {
+    StringBuffer argumentsString = new StringBuffer();
+    for (int i = 0; i < arguments.length; i++) {
+      if (i != 0) argumentsString.add(", ");
+      argumentsString.add(temporaryId(arguments[i]));
+    }
+    return "$invokeType: $functionName($argumentsString)";
+  }
+
+  String visitIndex(HIndex node) {
+    String receiver = temporaryId(node.receiver);
+    String index = temporaryId(node.index);
+    return "Index: $receiver[$index]";
+  }
+
+  String visitIndexAssign(HIndexAssign node) {
+    String receiver = temporaryId(node.receiver);
+    String index = temporaryId(node.index);
+    String value = temporaryId(node.value);
+    return "IndexAssign: $receiver[$index] = $value";
+  }
+
+  String visitIntegerCheck(HIntegerCheck node) {
+    String value = temporaryId(node.value);
+    return "Integer check: $value";
+  }
+
+  String visitInterceptor(HInterceptor node) {
+    String value = temporaryId(node.inputs[0]);
+    return "Intercept: $value";
+  }
+
+  String visitInvokeClosure(HInvokeClosure node)
+      => visitInvokeDynamic(node, "closure");
+
+  String visitInvokeDynamic(HInvokeDynamic invoke, String kind) {
+    String receiver = temporaryId(invoke.receiver);
+    String name = invoke.selector.name.slowToString();
+    String target = "($kind) $receiver.$name";
+    int offset = HInvoke.ARGUMENTS_OFFSET;
+    List arguments =
+        invoke.inputs.getRange(offset, invoke.inputs.length - offset);
+    return visitGenericInvoke("Invoke", target, arguments);
+  }
+
+  String visitInvokeDynamicMethod(HInvokeDynamicMethod node)
+      => visitInvokeDynamic(node, "method");
+  String visitInvokeDynamicGetter(HInvokeDynamicGetter node)
+      => visitInvokeDynamic(node, "get");
+  String visitInvokeDynamicSetter(HInvokeDynamicSetter node)
+      => visitInvokeDynamic(node, "set");
+
+  String visitInvokeStatic(HInvokeStatic invoke) {
+    String target = temporaryId(invoke.target);
+    int offset = HInvoke.ARGUMENTS_OFFSET;
+    List arguments =
+        invoke.inputs.getRange(offset, invoke.inputs.length - offset);
+    return visitGenericInvoke("Invoke", target, arguments);
+  }
+
+  String visitInvokeSuper(HInvokeSuper invoke) {
+    String target = temporaryId(invoke.target);
+    int offset = HInvoke.ARGUMENTS_OFFSET + 1;
+    List arguments =
+        invoke.inputs.getRange(offset, invoke.inputs.length - offset);
+    return visitGenericInvoke("Invoke super", target, arguments);
+  }
+
+  String visitForeign(HForeign foreign) {
+    return visitGenericInvoke("Foreign", "${foreign.code}", foreign.inputs);
+  }
+
+  String visitForeignNew(HForeignNew node) {
+    return visitGenericInvoke("New",
+                              "${node.element.name.slowToString()}",
+                              node.inputs);
+  }
+
+  String visitLess(HLess node) => handleInvokeBinary(node, '<');
+  String visitLessEqual(HLessEqual node) => handleInvokeBinary(node, '<=');
+
+  String visitLiteralList(HLiteralList node) {
+    StringBuffer elementsString = new StringBuffer();
+    for (int i = 0; i < node.inputs.length; i++) {
+      if (i != 0) elementsString.add(", ");
+      elementsString.add(temporaryId(node.inputs[i]));
+    }
+    return "Literal list: [$elementsString]";
+  }
+
+  String visitLoopBranch(HLoopBranch branch) {
+    HBasicBlock bodyBlock = currentBlock.successors[0];
+    HBasicBlock exitBlock = currentBlock.successors[1];
+    String conditionId = temporaryId(branch.inputs[0]);
+    return "While ($conditionId): (B${bodyBlock.id}) then (B${exitBlock.id})";
+  }
+
+  String visitMultiply(HMultiply node) => handleInvokeBinary(node, '*');
+
+  String visitNegate(HNegate node) {
+    String operand = temporaryId(node.operand);
+    return "-$operand";
+  }
+
+  String visitNot(HNot node) => "Not: ${temporaryId(node.inputs[0])}";
+
+  String visitParameterValue(HParameterValue node) {
+    return "p${node.sourceElement.name.slowToString()}";
+  }
+
+  String visitLocalValue(HLocalValue node) {
+    return "l${node.sourceElement.name.slowToString()}";
+  }
+
+  String visitPhi(HPhi phi) {
+    StringBuffer buffer = new StringBuffer();
+    buffer.add("Phi(");
+    for (int i = 0; i < phi.inputs.length; i++) {
+      if (i > 0) buffer.add(", ");
+      buffer.add(temporaryId(phi.inputs[i]));
+    }
+    buffer.add(")");
+    return buffer.toString();
+  }
+
+  String visitReturn(HReturn node) => "Return ${temporaryId(node.inputs[0])}";
+
+  String visitShiftLeft(HShiftLeft node) => handleInvokeBinary(node, '<<');
+
+  String visitStatic(HStatic node)
+      => "Static ${node.element.name.slowToString()}";
+
+  String visitLazyStatic(HLazyStatic node)
+      => "LazyStatic ${node.element.name.slowToString()}";
+
+  String visitOneShotInterceptor(HOneShotInterceptor node)
+      => visitInvokeDynamic(node, "one shot interceptor");
+
+  String visitStaticStore(HStaticStore node) {
+    String lhs = node.element.name.slowToString();
+    return "Static $lhs = ${temporaryId(node.inputs[0])}";
+  }
+
+  String visitStringConcat(HStringConcat node) {
+    var leftId = temporaryId(node.left);
+    var rightId = temporaryId(node.right);
+    return "StringConcat: $leftId + $rightId";
+  }
+
+  String visitSubtract(HSubtract node) => handleInvokeBinary(node, '-');
+
+  String visitSwitch(HSwitch node) {
+    StringBuffer buf = new StringBuffer();
+    buf.add("Switch: (");
+    buf.add(temporaryId(node.inputs[0]));
+    buf.add(") ");
+    for (int i = 1; i < node.inputs.length; i++) {
+      buf.add(temporaryId(node.inputs[i]));
+      buf.add(": B");
+      buf.add(node.block.successors[i - 1].id);
+      buf.add(", ");
+    }
+    buf.add("default: B");
+    buf.add(node.block.successors.last.id);
+    return buf.toString();
+  }
+
+  String visitThis(HThis node) => "this";
+
+  String visitThrow(HThrow node) => "Throw ${temporaryId(node.inputs[0])}";
+
+  String visitExitTry(HExitTry node) {
+    return "Exit try";
+  }
+
+  String visitTry(HTry node) {
+    List<HBasicBlock> successors = currentBlock.successors;
+    String tryBlock = 'B${successors[0].id}';
+    String catchBlock = 'none';
+    if (node.catchBlock != null) {
+      catchBlock = 'B${successors[1].id}';
+    }
+
+    String finallyBlock = 'none';
+    if (node.finallyBlock != null) {
+      finallyBlock = 'B${node.finallyBlock.id}';
+    }
+
+    return "Try: $tryBlock, Catch: $catchBlock, Finally: $finallyBlock, "
+        "Join: B${successors.last.id}";
+  }
+
+  String visitTypeGuard(HTypeGuard node) {
+    String type;
+    HType guardedType = node.guardedType;
+    if (guardedType == HType.MUTABLE_ARRAY) {
+      type = "mutable_array";
+    } else if (guardedType == HType.READABLE_ARRAY) {
+      type = "readable_array";
+    } else if (guardedType == HType.EXTENDABLE_ARRAY) {
+      type = "extendable_array";
+    } else if (guardedType == HType.BOOLEAN) {
+      type = "bool";
+    } else if (guardedType == HType.INTEGER) {
+      type = "integer";
+    } else if (guardedType == HType.DOUBLE) {
+      type = "double";
+    } else if (guardedType == HType.NUMBER) {
+      type = "number";
+    } else if (guardedType == HType.STRING) {
+      type = "string";
+    } else if (guardedType == HType.INDEXABLE_PRIMITIVE) {
+      type = "string_or_array";
+    } else if (guardedType == HType.UNKNOWN) {
+      type = 'unknown';
+    } else {
+      throw new CompilerCancelledException('Unexpected type guard: $type');
+    }
+    HInstruction guarded = node.guarded;
+    HInstruction bailoutTarget = node.bailoutTarget;
+    StringBuffer envBuffer = new StringBuffer();
+    List<HInstruction> inputs = node.inputs;
+    assert(inputs.length >= 2);
+    assert(inputs[0] == guarded);
+    assert(inputs[1] == bailoutTarget);
+    for (int i = 2; i < inputs.length; i++) {
+      envBuffer.add(" ${temporaryId(inputs[i])}");
+    }
+    String on = node.isEnabled ? "enabled" : "disabled";
+    String guardedId = temporaryId(node.guarded);
+    String bailoutId = temporaryId(node.bailoutTarget);
+    return "TypeGuard($on): $guardedId is $type bailout: $bailoutId "
+           "env: $envBuffer";
+  }
+
+  String visitIs(HIs node) {
+    String type = node.typeExpression.toString();
+    return "TypeTest: ${temporaryId(node.expression)} is $type";
+  }
+
+  String visitTypeConversion(HTypeConversion node) {
+    return "TypeConversion: ${temporaryId(node.checkedInput)} to ${node.type}";
+  }
+
+  String visitRangeConversion(HRangeConversion node) {
+    return "RangeConversion: ${node.checkedInput}";
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart
new file mode 100644
index 0000000..b6d4746
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart
@@ -0,0 +1,997 @@
+// 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.
+
+part of ssa;
+
+abstract class HType {
+  const HType();
+
+  /**
+   * Returns an [HType] that represents [type] and all types that have
+   * [type] as supertype.
+   */
+  factory HType.fromBoundedType(DartType type,
+                                Compiler compiler,
+                                [bool canBeNull = false]) {
+    Element element = type.element;
+    if (element.kind == ElementKind.TYPE_VARIABLE) {
+      // TODO(ngeoffray): Replace object type with [type].
+      return new HBoundedPotentialPrimitiveType(
+          compiler.objectClass.computeType(compiler), canBeNull, true);
+    }
+
+    if (element == compiler.intClass) {
+      return canBeNull ? HType.INTEGER_OR_NULL : HType.INTEGER;
+    } else if (element == compiler.numClass) {
+      return canBeNull ? HType.NUMBER_OR_NULL : HType.NUMBER;
+    } else if (element == compiler.doubleClass) {
+      return canBeNull ? HType.DOUBLE_OR_NULL : HType.DOUBLE;
+    } else if (element == compiler.stringClass) {
+      return canBeNull ? HType.STRING_OR_NULL : HType.STRING;
+    } else if (element == compiler.boolClass) {
+      return canBeNull ? HType.BOOLEAN_OR_NULL : HType.BOOLEAN;
+    } else if (element == compiler.nullClass) {
+      return HType.NULL;
+    } else if (element == compiler.listClass
+        || Elements.isListSupertype(element, compiler)) {
+      return new HBoundedPotentialPrimitiveArray(type, canBeNull);
+    } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
+      return new HBoundedPotentialPrimitiveNumberOrString(type, canBeNull);
+    } else if (Elements.isStringOnlySupertype(element, compiler)) {
+      return new HBoundedPotentialPrimitiveString(type, canBeNull);
+    } else if (element == compiler.objectClass) {
+      return new HBoundedPotentialPrimitiveType(
+          compiler.objectClass.computeType(compiler), canBeNull, true);
+    } else {
+      return canBeNull ? new HBoundedType.withNull(type)
+                       : new HBoundedType.nonNull(type);
+    }
+  }
+
+  static const HType CONFLICTING = const HConflictingType();
+  static const HType UNKNOWN = const HUnknownType();
+  static const HType BOOLEAN = const HBooleanType();
+  static const HType NUMBER = const HNumberType();
+  static const HType INTEGER = const HIntegerType();
+  static const HType DOUBLE = const HDoubleType();
+  static const HType INDEXABLE_PRIMITIVE = const HIndexablePrimitiveType();
+  static const HType STRING = const HStringType();
+  static const HType READABLE_ARRAY = const HReadableArrayType();
+  static const HType MUTABLE_ARRAY = const HMutableArrayType();
+  static const HType FIXED_ARRAY = const HFixedArrayType();
+  static const HType EXTENDABLE_ARRAY = const HExtendableArrayType();
+  static const HType NULL = const HNullType();
+
+  static const HType BOOLEAN_OR_NULL = const HBooleanOrNullType();
+  static const HType NUMBER_OR_NULL = const HNumberOrNullType();
+  static const HType INTEGER_OR_NULL = const HIntegerOrNullType();
+  static const HType DOUBLE_OR_NULL = const HDoubleOrNullType();
+  static const HType STRING_OR_NULL = const HStringOrNullType();
+
+  bool isConflicting() => identical(this, CONFLICTING);
+  bool isUnknown() => identical(this, UNKNOWN);
+  bool isNull() => false;
+  bool isBoolean() => false;
+  bool isNumber() => false;
+  bool isInteger() => false;
+  bool isDouble() => false;
+  bool isString() => false;
+  bool isBooleanOrNull() => false;
+  bool isNumberOrNull() => false;
+  bool isIntegerOrNull() => false;
+  bool isDoubleOrNull() => false;
+  bool isStringOrNull() => false;
+  bool isIndexablePrimitive() => false;
+  bool isFixedArray() => false;
+  bool isReadableArray() => false;
+  bool isMutableArray() => false;
+  bool isExtendableArray() => false;
+  bool isPrimitive() => false;
+  bool isExact() => false;
+  bool isPrimitiveOrNull() => false;
+  bool isTop() => false;
+
+  bool canBePrimitive() => false;
+  bool canBeNull() => false;
+
+  /** A type is useful it is not unknown, not conflicting, and not null. */
+  bool isUseful() => !isUnknown() && !isConflicting() && !isNull();
+  /** Alias for isReadableArray. */
+  bool isArray() => isReadableArray();
+
+  DartType computeType(Compiler compiler);
+
+  /**
+   * The intersection of two types is the intersection of its values. For
+   * example:
+   *   * INTEGER.intersect(NUMBER) => INTEGER.
+   *   * DOUBLE.intersect(INTEGER) => CONFLICTING.
+   *   * MUTABLE_ARRAY.intersect(READABLE_ARRAY) => MUTABLE_ARRAY.
+   *
+   * When there is no predefined type to represent the intersection returns
+   * [CONFLICTING].
+   *
+   * An intersection with [UNKNOWN] returns the non-UNKNOWN type. An
+   * intersection with [CONFLICTING] returns [CONFLICTING].
+   */
+  HType intersection(HType other, Compiler compiler);
+
+  /**
+   * The union of two types is the union of its values. For example:
+   *   * INTEGER.union(NUMBER) => NUMBER.
+   *   * DOUBLE.union(INTEGER) => NUMBER.
+   *   * MUTABLE_ARRAY.union(READABLE_ARRAY) => READABLE_ARRAY.
+   *
+   * When there is no predefined type to represent the union returns
+   * [UNKNOWN].
+   *
+   * A union with [UNKNOWN] returns [UNKNOWN].
+   * A union of [CONFLICTING] with any other types returns the other type.
+   */
+  HType union(HType other, Compiler compiler);
+}
+
+/** Used to represent [HType.UNKNOWN] and [HType.CONFLICTING]. */
+abstract class HAnalysisType extends HType {
+  final String name;
+  const HAnalysisType(this.name);
+  String toString() => name;
+
+  DartType computeType(Compiler compiler) => null;
+}
+
+class HUnknownType extends HAnalysisType {
+  const HUnknownType() : super("unknown");
+  bool canBePrimitive() => true;
+  bool canBeNull() => true;
+
+  HType union(HType other, Compiler compiler) => this;
+  HType intersection(HType other, Compiler compiler) => other;
+}
+
+class HConflictingType extends HAnalysisType {
+  const HConflictingType() : super("conflicting");
+  bool canBePrimitive() => true;
+  bool canBeNull() => true;
+
+  HType union(HType other, Compiler compiler) => other;
+  HType intersection(HType other, Compiler compiler) => this;
+}
+
+abstract class HPrimitiveType extends HType {
+  const HPrimitiveType();
+  bool isPrimitive() => true;
+  bool canBePrimitive() => true;
+  bool isPrimitiveOrNull() => true;
+}
+
+class HNullType extends HPrimitiveType {
+  const HNullType();
+  bool canBeNull() => true;
+  bool isNull() => true;
+  String toString() => 'null';
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsNullClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isString()) return HType.STRING_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER_OR_NULL;
+    if (other.isDouble()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isBoolean()) return HType.BOOLEAN_OR_NULL;
+    // TODO(ngeoffray): Deal with the type of null more generally.
+    if (other.isReadableArray()) return other.union(this, compiler);
+    if (!other.canBeNull()) return HType.UNKNOWN;
+    return other;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isUnknown()) return HType.NULL;
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (!other.canBeNull()) return HType.CONFLICTING;
+    return HType.NULL;
+  }
+}
+
+abstract class HPrimitiveOrNullType extends HType {
+  const HPrimitiveOrNullType();
+  bool canBePrimitive() => true;
+  bool canBeNull() => true;
+  bool isPrimitiveOrNull() => true;
+}
+
+class HBooleanOrNullType extends HPrimitiveOrNullType {
+  const HBooleanOrNullType();
+  String toString() => "boolean or null";
+  bool isBooleanOrNull() => true;
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsBoolClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.BOOLEAN_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL;
+    if (other.isBoolean()) return HType.BOOLEAN_OR_NULL;
+    if (other.isNull()) return HType.BOOLEAN_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.BOOLEAN_OR_NULL;
+    if (other.isBoolean()) return HType.BOOLEAN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.BOOLEAN;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HBooleanType extends HPrimitiveType {
+  const HBooleanType();
+  bool isBoolean() => true;
+  bool isBooleanOrNull() => true;
+  String toString() => "boolean";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsBoolClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.BOOLEAN;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isBoolean()) return HType.BOOLEAN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL;
+    if (other.isNull()) return HType.BOOLEAN_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.BOOLEAN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN;
+    if (other.isBoolean()) return HType.BOOLEAN;
+    return HType.CONFLICTING;
+  }
+}
+
+class HNumberOrNullType extends HPrimitiveOrNullType {
+  const HNumberOrNullType();
+  bool isNumberOrNull() => true;
+  String toString() => "number or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsNumberClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.NUMBER_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.NUMBER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.NUMBER_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.NUMBER;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HNumberType extends HPrimitiveType {
+  const HNumberType();
+  bool isNumber() => true;
+  bool isNumberOrNull() => true;
+  String toString() => "number";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsNumberClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.NUMBER;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.NUMBER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.NUMBER;
+    if (other.isNumber()) return other;
+    if (other.isIntegerOrNull()) return HType.INTEGER;
+    if (other.isDoubleOrNull()) return HType.DOUBLE;
+    if (other.isNumberOrNull()) return HType.NUMBER;
+    return HType.CONFLICTING;
+  }
+}
+
+class HIntegerOrNullType extends HNumberOrNullType {
+  const HIntegerOrNullType();
+  bool isIntegerOrNull() => true;
+  String toString() => "integer or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsIntClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.INTEGER_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.INTEGER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.INTEGER_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isDouble()) return HType.CONFLICTING;
+    if (other.isDoubleOrNull()) return HType.NULL;
+    if (other.isNumber()) return HType.INTEGER;
+    if (other.isNumberOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.INTEGER;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HIntegerType extends HNumberType {
+  const HIntegerType();
+  bool isInteger() => true;
+  bool isIntegerOrNull() => true;
+  String toString() => "integer";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsIntClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.INTEGER;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.INTEGER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.INTEGER;
+    if (other.isIntegerOrNull()) return HType.INTEGER;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isDouble()) return HType.CONFLICTING;
+    if (other.isDoubleOrNull()) return HType.CONFLICTING;
+    if (other.isNumber()) return HType.INTEGER;
+    if (other.isNumberOrNull()) return HType.INTEGER;
+    return HType.CONFLICTING;
+  }
+}
+
+class HDoubleOrNullType extends HNumberOrNullType {
+  const HDoubleOrNullType();
+  bool isDoubleOrNull() => true;
+  String toString() => "double or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsDoubleClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.DOUBLE_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isDouble()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.DOUBLE_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.DOUBLE_OR_NULL;
+    if (other.isInteger()) return HType.CONFLICTING;
+    if (other.isIntegerOrNull()) return HType.NULL;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.DOUBLE;
+    if (other.isNumberOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.DOUBLE;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HDoubleType extends HNumberType {
+  const HDoubleType();
+  bool isDouble() => true;
+  bool isDoubleOrNull() => true;
+  String toString() => "double";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsDoubleClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.DOUBLE;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.DOUBLE_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.DOUBLE;
+    if (other.isIntegerOrNull()) return HType.CONFLICTING;
+    if (other.isInteger()) return HType.CONFLICTING;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isDoubleOrNull()) return HType.DOUBLE;
+    if (other.isNumber()) return HType.DOUBLE;
+    if (other.isNumberOrNull()) return HType.DOUBLE;
+    return HType.CONFLICTING;
+  }
+}
+
+class HIndexablePrimitiveType extends HPrimitiveType {
+  const HIndexablePrimitiveType();
+  bool isIndexablePrimitive() => true;
+  String toString() => "indexable";
+
+  DartType computeType(Compiler compiler) {
+    // TODO(ngeoffray): Represent union types.
+    return null;
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.INDEXABLE_PRIMITIVE;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveString) {
+      // TODO(ngeoffray): Represent union types.
+      return HType.UNKNOWN;
+    }
+    if (other is HBoundedPotentialPrimitiveArray) {
+      // TODO(ngeoffray): Represent union types.
+      return HType.UNKNOWN;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.INDEXABLE_PRIMITIVE;
+    if (other.isIndexablePrimitive()) return other;
+    if (other is HBoundedPotentialPrimitiveString) return HType.STRING;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HStringOrNullType extends HPrimitiveOrNullType {
+  const HStringOrNullType();
+  bool isStringOrNull() => true;
+  String toString() => "String or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsStringClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.STRING_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isString()) return HType.STRING_OR_NULL;
+    if (other.isStringOrNull()) return HType.STRING_OR_NULL;
+    if (other.isIndexablePrimitive()) {
+      // We don't have a type that represents the nullable indexable
+      // primitive.
+      return HType.UNKNOWN;
+    }
+    if (other is HBoundedPotentialPrimitiveString) {
+      if (other.canBeNull()) {
+        return other;
+      } else {
+        HBoundedType boundedType = other;
+        return new HBoundedPotentialPrimitiveString(boundedType.type, true);
+      }
+    }
+    if (other.isNull()) return HType.STRING_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.STRING_OR_NULL;
+    if (other.isString()) return HType.STRING;
+    if (other.isStringOrNull()) return HType.STRING_OR_NULL;
+    if (other.isArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.STRING;
+    if (other is HBoundedPotentialPrimitiveString) {
+      return other.canBeNull() ? HType.STRING_OR_NULL : HType.STRING;
+    }
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.STRING;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HStringType extends HIndexablePrimitiveType {
+  const HStringType();
+  bool isString() => true;
+  bool isStringOrNull() => true;
+  String toString() => "String";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsStringClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.STRING;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isString()) return HType.STRING;
+    if (other.isStringOrNull()) return HType.STRING_OR_NULL;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveString) return other;
+    if (other.isNull()) return HType.STRING_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.STRING;
+    if (other.isString()) return HType.STRING;
+    if (other.isArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.STRING;
+    if (other.isStringOrNull()) return HType.STRING;
+    if (other is HBoundedPotentialPrimitiveString) return HType.STRING;
+    return HType.CONFLICTING;
+  }
+}
+
+class HReadableArrayType extends HIndexablePrimitiveType {
+  const HReadableArrayType();
+  bool isReadableArray() => true;
+  String toString() => "readable array";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsArrayClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.READABLE_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be readable array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.READABLE_ARRAY;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isReadableArray()) return other;
+    if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HMutableArrayType extends HReadableArrayType {
+  const HMutableArrayType();
+  bool isMutableArray() => true;
+  String toString() => "mutable array";
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.MUTABLE_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isMutableArray()) return HType.MUTABLE_ARRAY;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be mutable array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.MUTABLE_ARRAY;
+    if (other.isMutableArray()) return other;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.MUTABLE_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.MUTABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HFixedArrayType extends HMutableArrayType {
+  const HFixedArrayType();
+  bool isFixedArray() => true;
+  String toString() => "fixed array";
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.FIXED_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isFixedArray()) return HType.FIXED_ARRAY;
+    if (other.isMutableArray()) return HType.MUTABLE_ARRAY;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be fixed array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.FIXED_ARRAY;
+    if (other.isFixedArray()) return HType.FIXED_ARRAY;
+    if (other.isExtendableArray()) return HType.CONFLICTING;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.FIXED_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.FIXED_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HExtendableArrayType extends HMutableArrayType {
+  const HExtendableArrayType();
+  bool isExtendableArray() => true;
+  String toString() => "extendable array";
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.EXTENDABLE_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY;
+    if (other.isMutableArray()) return HType.MUTABLE_ARRAY;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be extendable array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.EXTENDABLE_ARRAY;
+    if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isFixedArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.EXTENDABLE_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.EXTENDABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HBoundedType extends HType {
+  final DartType type;
+  final bool _canBeNull;
+  final bool _isExact;
+
+  String toString() {
+    return 'BoundedType($type, canBeNull: $_canBeNull, isExact: $_isExact)';
+  }
+
+  bool canBeNull() => _canBeNull;
+
+  bool isExact() => _isExact;
+
+  const HBoundedType(DartType this.type,
+                     [bool canBeNull = false, isExact = false])
+      : _canBeNull = canBeNull, _isExact = isExact;
+  const HBoundedType.exact(DartType type) : this(type, false, true);
+  const HBoundedType.withNull(DartType type) : this(type, true, false);
+  const HBoundedType.nonNull(DartType type) : this(type);
+
+  DartType computeType(Compiler compiler) => type;
+
+  Element lookupMember(SourceString name) {
+    if (!isExact()) return null;
+    ClassElement classElement = type.element;
+    return classElement.lookupMember(name);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    assert(!(isExact() && canBeNull()));
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isNull()) return canBeNull() ? HType.NULL : HType.CONFLICTING;
+
+    if (other is HBoundedType) {
+      HBoundedType temp = other;
+      if (identical(this.type, temp.type)) {
+        // If the types are the same, we return the [HBoundedType]
+        // that has the most restrictive representation: if it's exact
+        // (eg cannot be a subtype), and if it cannot be null.
+        if (isExact()) {
+          return this;
+        } else if (other.isExact()) {
+          return other;
+        } else if (canBeNull()) {
+          return other;
+        } else {
+          return this;
+        }
+      // If one type is a subtype of the other, we return the former,
+      // which is the narrower type.
+      } else if (!type.isMalformed && !other.type.isMalformed) {
+        if (compiler.types.isSubtype(type, other.type)) {
+          return this;
+        } else if (compiler.types.isSubtype(other.type, type)) {
+          return other;
+        }
+      }
+    }
+    if (other.isUnknown()) return this;
+    if (other.canBeNull() && canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+
+  bool operator ==(HType other) {
+    if (other is !HBoundedType) return false;
+    HBoundedType bounded = other;
+    return (identical(type, bounded.type)
+            && identical(canBeNull(), bounded.canBeNull())
+            && identical(isExact(), other.isExact()));
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedType.withNull(type);
+      }
+    }
+    if (other is HBoundedType) {
+      HBoundedType temp = other;
+      if (!identical(type, temp.type)) return HType.UNKNOWN;
+      if (isExact()) return other;
+      if (other.isExact()) return this;
+      return canBeNull() ? this : other;
+    }
+    if (other.isConflicting()) return this;
+    return HType.UNKNOWN;
+  }
+}
+
+class HBoundedPotentialPrimitiveType extends HBoundedType {
+  final bool _isObject;
+  const HBoundedPotentialPrimitiveType(DartType type,
+                                       bool canBeNull,
+                                       this._isObject)
+      : super(type, canBeNull, false);
+
+  String toString() {
+    return 'BoundedPotentialPrimitiveType($type, canBeNull: $_canBeNull)';
+  }
+
+  bool canBePrimitive() => true;
+  bool isTop() => _isObject;
+
+  HType union(HType other, Compiler compiler) {
+    if (isTop()) {
+      // The union of the top type and another type is the top type.
+      if (!canBeNull() && other.canBeNull()) {
+        return new HBoundedPotentialPrimitiveType(type, true, true);
+      } else {
+        return this;
+      }
+    } else {
+      return super.union(other, compiler);
+    }
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (isTop()) {
+      // The intersection of the top type and any other type is the other type.
+      // TODO(ngeoffray): Also update the canBeNull information.
+      return other;
+    } else {
+      return super.intersection(other, compiler);
+    }
+  }
+}
+
+class HBoundedPotentialPrimitiveNumberOrString
+    extends HBoundedPotentialPrimitiveType {
+  const HBoundedPotentialPrimitiveNumberOrString(DartType type, bool canBeNull)
+      : super(type, canBeNull, false);
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isNumber()) return this;
+    if (other.isNumberOrNull()) {
+      if (canBeNull()) return this;
+      return new HBoundedPotentialPrimitiveNumberOrString(type, true);
+    }
+
+    if (other.isString()) return this;
+    if (other.isStringOrNull()) {
+      if (canBeNull()) return this;
+      return new HBoundedPotentialPrimitiveNumberOrString(type, true);
+    }
+
+    if (other.isNull()) {
+      if (canBeNull()) return this;
+      return new HBoundedPotentialPrimitiveNumberOrString(type, true);
+    }
+
+    return super.union(other, compiler);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isNumber()) return other;
+    if (other.isNumberOrNull()) {
+      if (!canBeNull()) return HType.NUMBER;
+      return other;
+    }
+    if (other.isString()) return other;
+    if (other.isStringOrNull()) {
+      if (!canBeNull()) return HType.STRING;
+      return other;
+    }
+    return super.intersection(other, compiler);
+  }
+}
+
+class HBoundedPotentialPrimitiveArray extends HBoundedPotentialPrimitiveType {
+  const HBoundedPotentialPrimitiveArray(DartType type, bool canBeNull)
+      : super(type, canBeNull, false);
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isString()) return HType.UNKNOWN;
+    if (other.isReadableArray()) return this;
+    // TODO(ngeoffray): implement union types.
+    if (other.isIndexablePrimitive()) return HType.UNKNOWN;
+    if (other.isNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedPotentialPrimitiveArray(type, true);
+      }
+    }
+    return super.union(other, compiler);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isReadableArray()) return other;
+    if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY;
+    return super.intersection(other, compiler);
+  }
+}
+
+class HBoundedPotentialPrimitiveString extends HBoundedPotentialPrimitiveType {
+  const HBoundedPotentialPrimitiveString(DartType type, bool canBeNull)
+      : super(type, canBeNull, false);
+
+  bool isPrimitiveOrNull() => true;
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isString()) return this;
+    if (other.isStringOrNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedPotentialPrimitiveString(type, true);
+      }
+    }
+    if (other.isNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedPotentialPrimitiveString(type, true);
+      }
+    }
+    // TODO(ngeoffray): implement union types.
+    if (other.isIndexablePrimitive()) return HType.UNKNOWN;
+    return super.union(other, compiler);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isString()) return HType.STRING;
+    if (other.isStringOrNull()) {
+      return canBeNull() ? HType.STRING_OR_NULL : HType.STRING;
+    }
+    if (other.isReadableArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.STRING;
+    return super.intersection(other, compiler);
+  }
+}
+
+class HTypeMap {
+  // Approximately 85% of methods in the sample "swarm" have less than
+  // 32 instructions.
+  static const int INITIAL_SIZE = 32;
+
+  List<HType> _list = new List<HType>()..length = INITIAL_SIZE;
+
+  operator [](HInstruction instruction) {
+    HType result;
+    if (instruction.id < _list.length) result = _list[instruction.id];
+    if (result == null) return instruction.guaranteedType;
+    return result;
+  }
+
+  operator []=(HInstruction instruction, HType value) {
+    int length = _list.length;
+    int id = instruction.id;
+    if (length <= id) {
+      if (id + 1 < length * 2) {
+        _list.length = length * 2;
+      } else {
+        _list.length = id + 1;
+      }
+    }
+    _list[id] = value;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart
new file mode 100644
index 0000000..ee0f1a1
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart
@@ -0,0 +1,210 @@
+// 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.
+
+part of ssa;
+
+class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase {
+
+  final Map<int, HInstruction> workmap;
+  final List<int> worklist;
+  final Map<HInstruction, Function> pendingOptimizations;
+  final HTypeMap types;
+
+  final Compiler compiler;
+  String get name => 'type propagator';
+
+  SsaTypePropagator(this.compiler, this.types)
+      : workmap = new Map<int, HInstruction>(),
+        worklist = new List<int>(),
+        pendingOptimizations = new Map<HInstruction, Function>();
+
+  HType computeType(HInstruction instruction) {
+    if (instruction.hasGuaranteedType()) return instruction.guaranteedType;
+    return instruction.computeTypeFromInputTypes(types, compiler);
+  }
+
+  // Re-compute and update the type of the instruction. Returns
+  // whether or not the type was changed.
+  bool updateType(HInstruction instruction) {
+    // The [updateType] method is invoked when one of the inputs of
+    // the instruction changes its type. That gives us a new
+    // opportunity to consider this instruction for optimizations.
+    considerForArgumentTypeOptimization(instruction);
+    // Compute old and new types.
+    HType oldType = types[instruction];
+    HType newType = computeType(instruction);
+    // We unconditionally replace the propagated type with the new type. The
+    // computeType must make sure that we eventually reach a stable state.
+    types[instruction] = newType;
+    return oldType != newType;
+  }
+
+  void considerForArgumentTypeOptimization(HInstruction instruction) {
+    // Update the pending optimizations map based on the potentially
+    // new types of the operands. If the operand types no longer allow
+    // us to optimize, we remove the pending optimization.
+    if (instruction is !HInvokeDynamicMethod) return;
+    HInvokeDynamicMethod invoke = instruction;
+    if (instruction.specializer is !BinaryArithmeticSpecializer) return;
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isNumber(types) && !right.isNumber(types)) {
+      pendingOptimizations[instruction] = () {
+        // This callback function is invoked after we're done
+        // propagating types. The types shouldn't have changed.
+        assert(left.isNumber(types) && !right.isNumber(types));
+        convertInput(instruction, right, HType.NUMBER);
+      };
+    } else {
+      pendingOptimizations.remove(instruction);
+    }
+  }
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+    processWorklist();
+  }
+
+  visitBasicBlock(HBasicBlock block) {
+    if (block.isLoopHeader()) {
+      block.forEachPhi((HPhi phi) {
+        // Set the initial type for the phi. We're not using the type
+        // the phi thinks it has because new optimizations may imply
+        // changing it.
+        // In theory we would need to mark
+        // the type of all other incoming edges as "unitialized" and take this
+        // into account when doing the propagation inside the phis. Just
+        // setting the propagated type is however easier.
+        types[phi] = types[phi.inputs[0]];
+        addToWorkList(phi);
+      });
+    } else {
+      block.forEachPhi((HPhi phi) {
+        if (updateType(phi)) {
+          addDependentInstructionsToWorkList(phi);
+        }
+      });
+    }
+
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      if (updateType(instruction)) {
+        addDependentInstructionsToWorkList(instruction);
+      }
+      instruction = instruction.next;
+    }
+  }
+
+  void processWorklist() {
+    do {
+      while (!worklist.isEmpty) {
+        int id = worklist.removeLast();
+        HInstruction instruction = workmap[id];
+        assert(instruction != null);
+        workmap.remove(id);
+        if (updateType(instruction)) {
+          addDependentInstructionsToWorkList(instruction);
+        }
+      }
+      // While processing the optimizable arithmetic instructions, we
+      // may discover better type information for dominated users of
+      // replaced operands, so we may need to take another stab at
+      // emptying the worklist afterwards.
+      processPendingOptimizations();
+    } while (!worklist.isEmpty);
+  }
+
+  void addDependentInstructionsToWorkList(HInstruction instruction) {
+    for (int i = 0, length = instruction.usedBy.length; i < length; i++) {
+      // The non-speculative type propagator only propagates types forward. We
+      // thus only need to add the users of the [instruction] to the list.
+      addToWorkList(instruction.usedBy[i]);
+    }
+  }
+
+  void addToWorkList(HInstruction instruction) {
+    final int id = instruction.id;
+    if (!workmap.containsKey(id)) {
+      worklist.add(id);
+      workmap[id] = instruction;
+    }
+  }
+
+  void processPendingOptimizations() {
+    pendingOptimizations.forEach((instruction, action) => action());
+    pendingOptimizations.clear();
+  }
+
+  void convertInput(HInstruction instruction, HInstruction input, HType type) {
+    HTypeConversion converted =
+        new HTypeConversion.argumentTypeCheck(type, input);
+    instruction.block.addBefore(instruction, converted);
+    Set<HInstruction> dominatedUsers = input.dominatedUsers(instruction);
+    for (HInstruction user in dominatedUsers) {
+      user.changeUse(input, converted);
+      addToWorkList(user);
+    }
+  }
+}
+
+class SsaSpeculativeTypePropagator extends SsaTypePropagator {
+  final String name = 'speculative type propagator';
+  SsaSpeculativeTypePropagator(Compiler compiler, HTypeMap types)
+      : super(compiler, types);
+
+  void addDependentInstructionsToWorkList(HInstruction instruction) {
+    // The speculative type propagator propagates types forward and backward.
+    // Not only do we need to add the users of the [instruction] to the list.
+    // We also need to add the inputs fo the [instruction], since they might
+    // want to propagate the desired outgoing type.
+    for (int i = 0, length = instruction.usedBy.length; i < length; i++) {
+      addToWorkList(instruction.usedBy[i]);
+    }
+    for (int i = 0, length = instruction.inputs.length; i < length; i++) {
+      addToWorkList(instruction.inputs[i]);
+    }
+  }
+
+  HType computeDesiredType(HInstruction instruction) {
+    HType desiredType = HType.UNKNOWN;
+    for (final user in instruction.usedBy) {
+      HType userType =
+          user.computeDesiredTypeForInput(instruction, types, compiler);
+      // Mainly due to the "if (true)" added by hackAroundPossiblyAbortingBody
+      // in builder.dart uninitialized variables will propagate a type of null
+      // which will result in a conflicting type when combined with a primitive
+      // type. Avoid this to improve generated code.
+      // TODO(sgjesse): Reconcider this when hackAroundPossiblyAbortingBody
+      // has been removed.
+      if (desiredType.isPrimitive() && userType == HType.NULL) continue;
+      desiredType = desiredType.intersection(userType, compiler);
+      // No need to continue if two users disagree on the type.
+      if (desiredType.isConflicting()) break;
+    }
+    return desiredType;
+  }
+
+  HType computeType(HInstruction instruction) {
+    // Once we are in a conflicting state don't update the type anymore.
+    HType oldType = types[instruction];
+    if (oldType.isConflicting()) return oldType;
+
+    HType newType = super.computeType(instruction);
+    // [computeDesiredType] goes to all usedBys and lets them compute their
+    // desired type. By setting the [newType] here we give them more context to
+    // work with.
+    types[instruction] = newType;
+    HType desiredType = computeDesiredType(instruction);
+    // If the desired type is conflicting just return the computed type.
+    if (desiredType.isConflicting()) return newType;
+    // TODO(ngeoffray): Allow speculative optimizations on
+    // non-primitive types?
+    if (!desiredType.isPrimitive()) return newType;
+    return newType.intersection(desiredType, compiler);
+  }
+
+  // Do not use speculative argument type optimization for now.
+  void considerForArgumentTypeOptimization(HInstruction instruction) { }
+
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart
new file mode 100644
index 0000000..5e9598e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart
@@ -0,0 +1,181 @@
+// Copyright (c) 2011, 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.
+
+part of ssa;
+
+class HValidator extends HInstructionVisitor {
+  bool isValid = true;
+  HGraph graph;
+
+  void visitGraph(HGraph visitee) {
+    graph = visitee;
+    visitDominatorTree(visitee);
+  }
+
+  void markInvalid(String reason) {
+    print(reason);
+    isValid = false;
+  }
+
+  // Note that during construction of the Ssa graph the basic blocks are
+  // not required to be valid yet.
+  void visitBasicBlock(HBasicBlock block) {
+    currentBlock = block;
+    if (!isValid) return;  // Don't need to continue if we are already invalid.
+
+    // Test that the last instruction is a branching instruction and that the
+    // basic block contains the branch-target.
+    if (block.first == null || block.last == null) {
+      markInvalid("empty block");
+    }
+    if (block.last is !HControlFlow) {
+      markInvalid("block ends with non-tail node.");
+    }
+    if (block.last is HIf && block.successors.length != 2) {
+      markInvalid("If node without two successors");
+    }
+    if (block.last is HConditionalBranch && block.successors.length != 2) {
+      markInvalid("Conditional node without two successors");
+    }
+    if (block.last is HGoto && block.successors.length != 1) {
+      markInvalid("Goto node with not exactly one successor");
+    }
+    if (block.last is HJump && block.successors.length != 1) {
+      markInvalid("Break or continue node without one successor");
+    }
+    if (block.last is HReturn &&
+        (block.successors.length != 1 || !block.successors[0].isExitBlock())) {
+      markInvalid("Return node with > 1 succesor or not going to exit-block");
+    }
+    if (block.last is HExit && !block.successors.isEmpty) {
+      markInvalid("Exit block with successor");
+    }
+    if (block.last is HThrow && !block.successors.isEmpty) {
+      markInvalid("Throw block with successor");
+    }
+
+    if (block.successors.isEmpty &&
+        block.last is !HThrow &&
+        !block.isExitBlock()) {
+      markInvalid("Non-exit or throw block without successor");
+    }
+
+    // Check that successors ids are always higher than the current one.
+    // TODO(floitsch): this is, of course, not true for back-branches.
+    if (block.id == null) markInvalid("block without id");
+    for (HBasicBlock successor in block.successors) {
+      if (!isValid) break;
+      if (successor.id == null) markInvalid("successor without id");
+      if (successor.id <= block.id && !successor.isLoopHeader()) {
+        markInvalid("successor with lower id, but not a loop-header");
+      }
+    }
+
+    // Check that the entries in the dominated-list are sorted.
+    int lastId = 0;
+    for (HBasicBlock dominated in block.dominatedBlocks) {
+      if (!isValid) break;
+      if (!identical(dominated.dominator, block)) {
+        markInvalid("dominated block not pointing back");
+      }
+      if (dominated.id == null || dominated.id <= lastId) {
+        markInvalid("dominated.id == null or dominated has <= id");
+      }
+      lastId = dominated.id;
+    }
+
+    if (!isValid) return;
+    block.forEachPhi(visitInstruction);
+
+    // Check that the blocks of the parameters of a phi are dominating the
+    // corresponding predecessor block. Note that a block dominates
+    // itself.
+    block.forEachPhi((HPhi phi) {
+      for (int i = 0; i < phi.inputs.length; i++) {
+        HInstruction input = phi.inputs[i];
+        if (!input.block.dominates(block.predecessors[i])) {
+          markInvalid("Definition does not dominate use");
+        }
+      }
+    });
+
+    // Check that the blocks of the inputs of an instruction dominate the
+    // instruction's block.
+    block.forEachInstruction((HInstruction instruction) {
+      for (HInstruction input in instruction.inputs) {
+        if (!input.block.dominates(block)) {
+          markInvalid("Definition does not dominate use");
+        }
+      }
+    });
+
+    super.visitBasicBlock(block);
+  }
+
+  /** Returns how often [instruction] is contained in [instructions]. */
+  static int countInstruction(List<HInstruction> instructions,
+                              HInstruction instruction) {
+    int result = 0;
+    for (int i = 0; i < instructions.length; i++) {
+      if (identical(instructions[i], instruction)) result++;
+    }
+    return result;
+  }
+
+  /**
+   * Returns true if the predicate returns true for every instruction in the
+   * list. The argument to [f] is an instruction with the count of how often
+   * it appeared in the list [instructions].
+   */
+  static bool everyInstruction(List<HInstruction> instructions, Function f) {
+    var copy = new List<HInstruction>.from(instructions);
+    // TODO(floitsch): there is currently no way to sort HInstructions before
+    // we have assigned an ID. The loop is therefore O(n^2) for now.
+    for (int i = 0; i < copy.length; i++) {
+      var current = copy[i];
+      if (current == null) continue;
+      int count = 1;
+      for (int j = i + 1; j < copy.length; j++) {
+        if (identical(copy[j], current)) {
+          copy[j] = null;
+          count++;
+        }
+      }
+      if (!f(current, count)) return false;
+    }
+    return true;
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    // Verifies that we are in the use list of our inputs.
+    bool hasCorrectInputs() {
+      bool inBasicBlock = instruction.isInBasicBlock();
+      return everyInstruction(instruction.inputs, (input, count) {
+        if (inBasicBlock) {
+          return countInstruction(input.usedBy, instruction) == count;
+        } else {
+          return countInstruction(input.usedBy, instruction) == 0;
+        }
+      });
+    }
+
+    // Verifies that all our uses have us in their inputs.
+    bool hasCorrectUses() {
+      if (!instruction.isInBasicBlock()) return true;
+      return everyInstruction(instruction.usedBy, (use, count) {
+        return countInstruction(use.inputs, instruction) == count;
+      });
+    }
+
+    if (!identical(instruction.block, currentBlock)) {
+      markInvalid("Instruction in wrong block");
+    }
+    if (!hasCorrectInputs()) {
+      markInvalid("Incorrect inputs");
+    }
+    if (!hasCorrectUses()) {
+      markInvalid("Incorrect uses");
+    }
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart
new file mode 100644
index 0000000..c28d5c4
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart
@@ -0,0 +1,996 @@
+// 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.
+
+part of ssa;
+
+
+class ValueRangeInfo {
+  final ConstantSystem constantSystem;
+
+  IntValue intZero;
+  IntValue intOne;
+
+  ValueRangeInfo(this.constantSystem) {
+    intZero = newIntValue(0);
+    intOne = newIntValue(1);
+  }
+
+  Value newIntValue(int value) {
+    return new IntValue(value, this);
+  }
+
+  Value newInstructionValue(HInstruction instruction) {
+    return new InstructionValue(instruction, this);
+  }
+
+  Value newLengthValue(HInstruction instruction) {
+    return new LengthValue(instruction, this);
+  }
+
+  Value newAddValue(Value left, Value right) {
+    return new AddValue(left, right, this);
+  }
+
+  Value newSubtractValue(Value left, Value right) {
+    return new SubtractValue(left, right, this);
+  }
+
+  Value newNegateValue(Value value) {
+    return new NegateValue(value, this);
+  }
+
+  Range newRange(Value low, Value up) {
+    return new Range(low, up, this);
+  }
+
+  Range newUnboundRange() {
+    return new Range.unbound(this);
+  }
+
+  Range newNormalizedRange(Value low, Value up) {
+    return new Range.normalize(low, up, this);
+  }
+}
+
+/**
+ * A [Value] represents both symbolic values like the value of a
+ * parameter, or the length of an array, and concrete values, like
+ * constants.
+ */
+abstract class Value {
+  final ValueRangeInfo info;
+  const Value([this.info = null]);
+
+  Value operator +(Value other) => const UnknownValue();
+  Value operator -(Value other) => const UnknownValue();
+  Value operator -()  => const UnknownValue();
+  Value operator &(Value other) => const UnknownValue();
+
+  Value min(Value other) {
+    if (this == other) return this;
+    if (other == const MinIntValue()) return other;
+    if (other == const MaxIntValue()) return this;
+    Value value = this - other;
+    if (value.isPositive) return other;
+    if (value.isNegative) return this;
+    return const UnknownValue();
+  }
+
+  Value max(Value other) {
+    if (this == other) return this;
+    if (other == const MinIntValue()) return this;
+    if (other == const MaxIntValue()) return other;
+    Value value = this - other;
+    if (value.isPositive) return this;
+    if (value.isNegative) return other;
+    return const UnknownValue();
+  }
+
+  bool get isNegative => false;
+  bool get isPositive => false;
+  bool get isZero => false;
+}
+
+/**
+ * An [IntValue] contains a constant integer value.
+ */
+class IntValue extends Value {
+  final int value;
+
+  const IntValue(this.value, info) : super(info);
+
+  Value operator +(other) {
+    if (other.isZero) return this;
+    if (other is !IntValue) return other + this;
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.add.fold(
+        constantSystem.createInt(value), constantSystem.createInt(other.value));
+    if (!constant.isInt()) return const UnknownValue();
+    return info.newIntValue(constant.value);
+  }
+
+  Value operator -(other) {
+    if (other.isZero) return this;
+    if (other is !IntValue) return -other + this;
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.subtract.fold(
+        constantSystem.createInt(value), constantSystem.createInt(other.value));
+    if (!constant.isInt()) return const UnknownValue();
+    return info.newIntValue(constant.value);
+  }
+
+  Value operator -() {
+    if (isZero) return this;
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.negate.fold(
+        constantSystem.createInt(value));
+    if (!constant.isInt()) return const UnknownValue();
+    return info.newIntValue(constant.value);
+  }
+
+  Value operator &(other) {
+    if (other is !IntValue) return const UnknownValue();
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.bitAnd.fold(
+        constantSystem.createInt(value), constantSystem.createInt(other.value));
+    return info.newIntValue(constant.value);
+  }
+
+  Value min(other) {
+    if (other is !IntValue) return other.min(this);
+    return this.value < other.value ? this : other;
+  }
+
+  Value max(other) {
+    if (other is !IntValue) return other.max(this);
+    return this.value < other.value ? other : this;
+  }
+
+  bool operator ==(other) {
+    if (other is !IntValue) return false;
+    return this.value == other.value;
+  }
+
+  String toString() => 'IntValue $value';
+  bool get isNegative => value < 0;
+  bool get isPositive => value >= 0;
+  bool get isZero => value == 0;
+}
+
+/**
+ * The [MaxIntValue] represents the maximum value an integer can have,
+ * which is currently +infinity.
+ */
+class MaxIntValue extends Value {
+  const MaxIntValue() : super(null);
+  Value operator +(Value other) => this;
+  Value operator -(Value other) => this;
+  Value operator -() => const MinIntValue();
+  Value min(Value other) => other;
+  Value max(Value other) => this;
+  String toString() => 'Max';
+  bool get isNegative => false;
+  bool get isPositive => true;
+}
+
+/**
+ * The [MinIntValue] represents the minimum value an integer can have,
+ * which is currently -infinity.
+ */
+class MinIntValue extends Value {
+  const MinIntValue() : super(null);
+  Value operator +(Value other) => this;
+  Value operator -(Value other) => this;
+  Value operator -() => const MaxIntValue();
+  Value min(Value other) => this;
+  Value max(Value other) => other;
+  String toString() => 'Min';
+  bool get isNegative => true;
+  bool get isPositive => false;
+}
+
+/**
+ * The [UnknownValue] is the sentinel in our analysis to mark an
+ * operation that could not be done because of too much complexity.
+ */
+class UnknownValue extends Value {
+  const UnknownValue() : super(null);
+  Value operator +(Value other) => const UnknownValue();
+  Value operator -(Value other) => const UnknownValue();
+  Value operator -() => const UnknownValue();
+  Value min(Value other) => const UnknownValue();
+  Value max(Value other) => const UnknownValue();
+  bool get isNegative => false;
+  bool get isPositive => false;
+  String toString() => 'Unknown';
+}
+
+/**
+ * A symbolic value representing an [HInstruction].
+ */
+class InstructionValue extends Value {
+  final HInstruction instruction;
+  InstructionValue(this.instruction, info) : super(info);
+
+  bool operator ==(other) {
+    if (other is !InstructionValue) return false;
+    return this.instruction == other.instruction;
+  }
+
+  Value operator +(Value other) {
+    if (other.isZero) return this;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newSubtractValue(this, -other);
+      }
+      return info.newAddValue(this, other);
+    }
+    if (other is InstructionValue) {
+      return info.newAddValue(this, other);
+    }
+    return other + this;
+  }
+
+  Value operator -(Value other) {
+    if (other.isZero) return this;
+    if (this == other) return info.intZero;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newAddValue(this, -other);
+      }
+      return info.newSubtractValue(this, other);
+    }
+    if (other is InstructionValue) {
+      return info.newSubtractValue(this, other);
+    }
+    return -other + this;
+  }
+
+  Value operator -() {
+    return info.newNegateValue(this);
+  }
+
+  bool get isNegative => false;
+  bool get isPositive => false;
+
+  String toString() => 'Instruction: $instruction';
+}
+
+/**
+ * Special value for instructions that represent the length of an
+ * array. The difference with an [InstructionValue] is that we know
+ * the value is positive.
+ */
+class LengthValue extends InstructionValue {
+  LengthValue(HInstruction instruction, info) : super(instruction, info);
+  bool get isPositive => true;
+  String toString() => 'Length: $instruction';
+}
+
+/**
+ * Represents a binary operation on two [Value], where the operation
+ * did not yield a canonical value.
+ */
+class BinaryOperationValue extends Value {
+  final Value left;
+  final Value right;
+  BinaryOperationValue(this.left, this.right, info) : super(info);
+}
+
+class AddValue extends BinaryOperationValue {
+  AddValue(left, right, info) : super(left, right, info);
+
+  bool operator ==(other) {
+    if (other is !AddValue) return false;
+    return (left == other.left && right == other.right)
+      || (left == other.right && right == other.left);
+  }
+
+  Value operator -() => -left - right;
+
+  Value operator +(Value other) {
+    if (other.isZero) return this;
+    Value value = left + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value + right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = right + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left + value;
+    }
+    return const UnknownValue();
+  }
+
+  Value operator -(Value other) {
+    if (other.isZero) return this;
+    Value value = left - other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value + right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = right - other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left + value;
+    }
+    return const UnknownValue();
+  }
+
+  bool get isNegative => left.isNegative && right.isNegative;
+  bool get isPositive => left.isPositive && right.isPositive;
+  String toString() => '$left + $right';
+}
+
+class SubtractValue extends BinaryOperationValue {
+  SubtractValue(left, right, info) : super(left, right, info);
+
+  bool operator ==(other) {
+    if (other is !SubtractValue) return false;
+    return left == other.left && right == other.right;
+  }
+
+  Value operator -() => right - left;
+
+  Value operator +(Value other) {
+    if (other.isZero) return this;
+    Value value = left + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value - right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = other - right;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left + value;
+    }
+    return const UnknownValue();
+  }
+
+  Value operator -(Value other) {
+    if (other.isZero) return this;
+    Value value = left - other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value - right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = right + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left - value;
+    }
+    return const UnknownValue();
+  }
+
+  bool get isNegative => left.isNegative && right.isPositive;
+  bool get isPositive => left.isPositive && right.isNegative;
+  String toString() => '$left - $right';
+}
+
+class NegateValue extends Value {
+  final Value value;
+  NegateValue(this.value, info) : super(info);
+
+  bool operator ==(other) {
+    if (other is !NegateValue) return false;
+    return value == other.value;
+  }
+
+  Value operator +(other) {
+    if (other.isZero) return this;
+    if (other == value) return info.intZero;
+    if (other is NegateValue) return this - other.value;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newSubtractValue(this, -other);
+      }
+      return info.newSubtractValue(other, value);
+    }
+    if (other is InstructionValue) {
+      return info.newSubtractValue(other, value);
+    }
+    return other - value;
+  }
+
+  Value operator &(Value other) => const UnknownValue();
+
+  Value operator -(other) {
+    if (other.isZero) return this;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newSubtractValue(-other, value);
+      }
+      return info.newSubtractValue(this, other);
+    }
+    if (other is InstructionValue) {
+      return info.newSubtractValue(this, other);
+    }
+    if (other is NegateValue) return this + other.value;
+    return -other - value;
+  }
+
+  Value operator -() => value;
+
+  bool get isNegative => value.isPositive;
+  bool get isPositive => value.isNegative;
+  String toString() => '-$value';
+}
+
+/**
+ * A [Range] represents the possible integer values an instruction
+ * can have, from its [lower] bound to its [upper] bound, both
+ * included.
+ */
+class Range {
+  final Value lower;
+  final Value upper;
+  final ValueRangeInfo info;
+  Range(this.lower, this.upper, this.info);
+
+  Range.unbound(info) : this(const MinIntValue(), const MaxIntValue(), info);
+
+  /**
+   * Checks if the given values are unknown, and creates a
+   * range that does not have any unknown values.
+   */
+  Range.normalize(Value low, Value up, info) : this(
+      low == const UnknownValue() ? const MinIntValue() : low,
+      up == const UnknownValue() ? const MaxIntValue() : up,
+      info);
+
+  Range union(Range other) {
+    return info.newNormalizedRange(
+        lower.min(other.lower), upper.max(other.upper));
+  }
+
+  intersection(Range other) {
+    Value low = lower.max(other.lower);
+    Value up = upper.min(other.upper);
+    // If we could not compute max or min, pick a value in the two
+    // ranges, with priority to [IntValue]s because they are simpler.
+    if (low == const UnknownValue()) {
+      if (lower is IntValue) low = lower;
+      else if (other.lower is IntValue) low = other.lower;
+      else low = lower;
+    }
+    if (up == const UnknownValue()) {
+      if (upper is IntValue) up = upper;
+      else if (other.upper is IntValue) up = other.upper;
+      else up = upper;
+    }
+    return info.newRange(low, up);
+  }
+
+  Range operator +(Range other) {
+    return info.newNormalizedRange(lower + other.lower, upper + other.upper);
+  }
+
+  Range operator -(Range other) {
+    return info.newNormalizedRange(lower - other.upper, upper - other.lower);
+  }
+
+  Range operator -() {
+    return info.newNormalizedRange(-upper, -lower);
+  }
+
+  Range operator &(Range other) {
+    if (isSingleValue
+        && other.isSingleValue
+        && lower is IntValue
+        && other.lower is IntValue) {
+      return info.newRange(lower & other.lower, upper & other.upper);
+    }
+    if (isPositive && other.isPositive) {
+      Value up = upper.min(other.upper);
+      if (up == const UnknownValue()) {
+        // If we could not find a trivial bound, just try to use the
+        // one that is an int.
+        up = upper is IntValue ? upper : other.upper;
+        // Make sure we get the same upper bound, whether it's a & b
+        // or b & a.
+        if (up is! IntValue && upper != other.upper) up = const MaxIntValue();
+      }
+      return info.newRange(info.intZero, up);
+    } else if (isPositive) {
+      return info.newRange(info.intZero, upper);
+    } else if (other.isPositive) {
+      return info.newRange(info.intZero, other.upper);
+    } else {
+      return info.newUnboundRange();
+    }
+  }
+
+  bool operator ==(other) {
+    if (other is! Range) return false;
+    return other.lower == lower && other.upper == upper;
+  }
+
+  bool operator <(Range other) {
+    return upper != other.lower && upper.min(other.lower) == upper;
+  }
+
+  bool operator >(Range other) {
+    return lower != other.upper && lower.max(other.upper) == lower;
+  }
+
+  bool operator <=(Range other) {
+    return upper.min(other.lower) == upper;
+  }
+
+  bool operator >=(Range other) {
+    return lower.max(other.upper) == lower;
+  }
+
+  bool get isNegative => upper.isNegative;
+  bool get isPositive => lower.isPositive;
+  bool get isSingleValue => lower == upper;
+
+  String toString() => '[$lower, $upper]';
+}
+
+/**
+ * Visits the graph in dominator order, and computes value ranges for
+ * integer instructions. While visiting the graph, this phase also
+ * removes unnecessary bounds checks, and comparisons that are proven
+ * to be true or false.
+ */
+class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
+  String get name => 'SSA value range builder';
+
+  /**
+   * List of [HRangeConversion] instructions created by the phase. We
+   * save them here in order to remove them once the phase is done.
+   */
+  final List<HRangeConversion> conversions = <HRangeConversion>[];
+
+  /**
+   * Value ranges for integer instructions. This map gets populated by
+   * the dominator tree visit.
+   */
+  final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>();
+
+  final ConstantSystem constantSystem;
+  final HTypeMap types;
+  final ValueRangeInfo info;
+
+  CodegenWorkItem work;
+  HGraph graph;
+
+  SsaValueRangeAnalyzer(constantSystem, this.types, this.work)
+      : info = new ValueRangeInfo(constantSystem),
+        this.constantSystem = constantSystem;
+
+  void visitGraph(HGraph graph) {
+    this.graph = graph;
+    visitDominatorTree(graph);
+    // We remove the range conversions after visiting the graph so
+    // that the graph does not get polluted with these instructions
+    // only necessary for this phase.
+    removeRangeConversion();
+  }
+
+  void removeRangeConversion() {
+    conversions.forEach((HRangeConversion instruction) {
+      instruction.block.rewrite(instruction, instruction.inputs[0]);;
+      instruction.block.remove(instruction);
+    });
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+
+    void visit(HInstruction instruction) {
+      Range range = instruction.accept(this);
+      if (instruction.isInteger(types)) {
+        assert(range != null);
+        ranges[instruction] = range;
+      }
+    }
+
+    block.forEachPhi(visit);
+    block.forEachInstruction(visit);
+  }
+
+  Range visitInstruction(HInstruction instruction) {
+    return info.newUnboundRange();
+  }
+
+  Range visitParameterValue(HParameterValue parameter) {
+    if (!parameter.isInteger(types)) return info.newUnboundRange();
+    Value value = info.newInstructionValue(parameter);
+    return info.newRange(value, value);
+  }
+
+  Range visitPhi(HPhi phi) {
+    if (!phi.isInteger(types)) return info.newUnboundRange();
+    if (phi.block.isLoopHeader()) {
+      Range range = tryInferLoopPhiRange(phi);
+      if (range == null) return info.newUnboundRange();
+      return range;
+    }
+
+    Range range = ranges[phi.inputs[0]];
+    for (int i = 1; i < phi.inputs.length; i++) {
+      range = range.union(ranges[phi.inputs[i]]);
+    }
+    return range;
+  }
+
+  Range tryInferLoopPhiRange(HPhi phi) {
+    HInstruction update = phi.inputs[1];
+    return update.accept(new LoopUpdateRecognizer(phi, ranges, types, info));
+  }
+
+  Range visitConstant(HConstant constant) {
+    if (!constant.isInteger(types)) return info.newUnboundRange();
+    IntConstant constantInt = constant.constant;
+    Value value = info.newIntValue(constantInt.value);
+    return info.newRange(value, value);
+  }
+
+  Range visitFieldGet(HFieldGet fieldGet) {
+    if (!fieldGet.isInteger(types)) return info.newUnboundRange();
+    if (!fieldGet.receiver.isIndexablePrimitive(types)) {
+      return visitInstruction(fieldGet);
+    }
+    LengthValue value = info.newLengthValue(fieldGet);
+    // We know this range is above zero. To simplify the analysis, we
+    // put the zero value as the lower bound of this range. This
+    // allows to easily remove the second bound check in the following
+    // expression: a[1] + a[0].
+    return info.newRange(info.intZero, value);
+  }
+
+  Range visitBoundsCheck(HBoundsCheck check) {
+    // Save the next instruction, in case the check gets removed.
+    HInstruction next = check.next;
+    Range indexRange = ranges[check.index];
+    Range lengthRange = ranges[check.length];
+
+    // Check if the index is strictly below the upper bound of the length
+    // range.
+    Value maxIndex = lengthRange.upper - info.intOne;
+    bool belowLength = maxIndex != const MaxIntValue()
+        && indexRange.upper.min(maxIndex) == indexRange.upper;
+
+    // Check if the index is strictly below the lower bound of the length
+    // range.
+    belowLength = belowLength
+        || (indexRange.upper != lengthRange.lower
+            && indexRange.upper.min(lengthRange.lower) == indexRange.upper);
+    if (indexRange.isPositive && belowLength) {
+      check.block.rewrite(check, check.index);
+      check.block.remove(check);
+    } else if (indexRange.isNegative || lengthRange < indexRange) {
+      check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
+      // The check is always false, and whatever instruction it
+      // dominates is dead code.
+      return indexRange;
+    } else if (indexRange.isPositive) {
+      check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
+    } else if (belowLength) {
+      check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
+    }
+
+    if (indexRange.isPositive) {
+      // If the test passes, we know the lower bound of the length is
+      // greater or equal than the lower bound of the index.
+      Value low = lengthRange.lower.max(indexRange.lower);
+      if (low != const UnknownValue()) {
+        HInstruction instruction =
+            createRangeConversion(next, check.length);
+        ranges[instruction] = info.newRange(low, lengthRange.upper);
+      }
+    }
+
+    if (!belowLength) {
+      // Update the range of the index if using the maximum index
+      // narrows it.
+      Range newIndexRange = indexRange.intersection(
+          info.newRange(info.intZero, maxIndex));
+      if (indexRange == newIndexRange) return indexRange;
+      HInstruction instruction = createRangeConversion(next, check.index);
+      ranges[instruction] = newIndexRange;
+      return newIndexRange;
+    }
+
+    return indexRange;
+  }
+
+  Range visitRelational(HRelational relational) {
+    HInstruction right = relational.right;
+    HInstruction left = relational.left;
+    if (!left.isInteger(types)) return info.newUnboundRange();
+    if (!right.isInteger(types)) return info.newUnboundRange();
+    BinaryOperation operation = relational.operation(constantSystem);
+    Range rightRange = ranges[relational.right];
+    Range leftRange = ranges[relational.left];
+
+    if (relational is HIdentity) {
+      handleEqualityCheck(relational);
+    } else if (operation.apply(leftRange, rightRange)) {
+      relational.block.rewrite(
+          relational, graph.addConstantBool(true, constantSystem));
+      relational.block.remove(relational);
+    } else if (reverseOperation(operation).apply(leftRange, rightRange)) {
+      relational.block.rewrite(
+          relational, graph.addConstantBool(false, constantSystem));
+      relational.block.remove(relational);
+    }
+    return info.newUnboundRange();
+  }
+
+  void handleEqualityCheck(HRelational node) {
+    Range right = ranges[node.right];
+    Range left = ranges[node.left];
+    if (left.isSingleValue && right.isSingleValue && left == right) {
+      node.block.rewrite(
+          node, graph.addConstantBool(true, constantSystem));
+      node.block.remove(node);
+    }
+  }
+
+  Range handleBinaryOperation(HBinaryArithmetic instruction) {
+    if (!instruction.isInteger(types)) return info.newUnboundRange();
+    return instruction.operation(constantSystem).apply(
+        ranges[instruction.left], ranges[instruction.right]);
+  }
+
+  Range visitAdd(HAdd add) {
+    return handleBinaryOperation(add);
+  }
+
+  Range visitSubtract(HSubtract sub) {
+    return handleBinaryOperation(sub);
+  }
+
+  Range visitBitAnd(HBitAnd node) {
+    if (!node.isInteger(types)) return info.newUnboundRange();
+    HInstruction right = node.right;
+    HInstruction left = node.left;
+    if (left.isInteger(types) && right.isInteger(types)) {
+      return ranges[left] & ranges[right];
+    }
+
+    Range tryComputeRange(HInstruction instruction) {
+      Range range = ranges[instruction];
+      if (range.isPositive) {
+        return info.newRange(info.intZero, range.upper);
+      } else if (range.isNegative) {
+        return info.newRange(range.lower, info.intZero);
+      }
+      return info.newUnboundRange();
+    }
+
+    if (left.isInteger(types)) {
+      return tryComputeRange(left);
+    } else if (right.isInteger(types)) {
+      return tryComputeRange(right);
+    }
+    return info.newUnboundRange();
+  }
+
+  Range visitCheck(HCheck instruction) {
+    if (ranges[instruction.checkedInput] == null) {
+      return info.newUnboundRange();
+    }
+    return ranges[instruction.checkedInput];
+  }
+
+  HInstruction createRangeConversion(HInstruction cursor,
+                                     HInstruction instruction) {
+    HRangeConversion newInstruction = new HRangeConversion(instruction);
+    conversions.add(newInstruction);
+    cursor.block.addBefore(cursor, newInstruction);
+    // Update the users of the instruction dominated by [cursor] to
+    // use the new instruction, that has an narrower range.
+    Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor);
+    for (HInstruction user in dominatedUsers) {
+      user.changeUse(instruction, newInstruction);
+    }
+    return newInstruction;
+  }
+
+  static BinaryOperation reverseOperation(BinaryOperation operation) {
+    if (operation == const LessOperation()) {
+      return const GreaterEqualOperation();
+    } else if (operation == const LessEqualOperation()) {
+      return const GreaterOperation();
+    } else if (operation == const GreaterOperation()) {
+      return const LessEqualOperation();
+    } else if (operation == const GreaterEqualOperation()) {
+      return const LessOperation();
+    } else {
+      return null;
+    }
+  }
+
+  Range computeConstrainedRange(BinaryOperation operation,
+                                Range leftRange,
+                                Range rightRange) {
+    Range range;
+    if (operation == const LessOperation()) {
+      range = info.newRange(
+          const MinIntValue(), rightRange.upper - info.intOne);
+    } else if (operation == const LessEqualOperation()) {
+      range = info.newRange(const MinIntValue(), rightRange.upper);
+    } else if (operation == const GreaterOperation()) {
+      range = info.newRange(
+          rightRange.lower + info.intOne, const MaxIntValue());
+    } else if (operation == const GreaterEqualOperation()) {
+      range = info.newRange(rightRange.lower, const MaxIntValue());
+    } else {
+      range = info.newUnboundRange();
+    }
+    return range.intersection(leftRange);
+  }
+
+  Range visitConditionalBranch(HConditionalBranch branch) {
+    var condition = branch.condition;
+    // TODO(ngeoffray): Handle complex conditions.
+    if (condition is !HRelational) return info.newUnboundRange();
+    if (condition is HIdentity) return info.newUnboundRange();
+    HInstruction right = condition.right;
+    HInstruction left = condition.left;
+    if (!left.isInteger(types)) return info.newUnboundRange();
+    if (!right.isInteger(types)) return info.newUnboundRange();
+
+    Range rightRange = ranges[right];
+    Range leftRange = ranges[left];
+    Operation operation = condition.operation(constantSystem);
+    Operation reverse = reverseOperation(operation);
+    // Only update the true branch if this block is the only
+    // predecessor.
+    if (branch.trueBranch.predecessors.length == 1) {
+      assert(branch.trueBranch.predecessors[0] == branch.block);
+      // Update the true branch to use narrower ranges for [left] and
+      // [right].
+      Range range = computeConstrainedRange(operation, leftRange, rightRange);
+      if (leftRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.trueBranch.first, left);
+        ranges[instruction] = range;
+      }
+
+      range = computeConstrainedRange(reverse, rightRange, leftRange);
+      if (rightRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.trueBranch.first, right);
+        ranges[instruction] = range;
+      }
+    }
+
+    // Only update the false branch if this block is the only
+    // predecessor.
+    if (branch.falseBranch.predecessors.length == 1) {
+      assert(branch.falseBranch.predecessors[0] == branch.block);
+      // Update the false branch to use narrower ranges for [left] and
+      // [right].
+      Range range = computeConstrainedRange(reverse, leftRange, rightRange);
+      if (leftRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.falseBranch.first, left);
+        ranges[instruction] = range;
+      }
+
+      range = computeConstrainedRange(operation, rightRange, leftRange);
+      if (rightRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.falseBranch.first, right);
+        ranges[instruction] = range;
+      }
+    }
+
+    return info.newUnboundRange();
+  }
+
+  Range visitRangeConversion(HRangeConversion conversion) {
+    return ranges[conversion];
+  }
+}
+
+/**
+ * Recognizes a number of patterns in a loop update instruction and
+ * tries to infer a range for the loop phi.
+ */
+class LoopUpdateRecognizer extends HBaseVisitor {
+  final HPhi loopPhi;
+  final Map<HInstruction, Range> ranges;
+  final HTypeMap types;
+  final ValueRangeInfo info;
+  LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types, this.info);
+
+  Range visitAdd(HAdd operation) {
+    Range range = getRangeForRecognizableOperation(operation);
+    if (range == null) return info.newUnboundRange();
+    Range initial = ranges[loopPhi.inputs[0]];
+    if (range.isPositive) {
+      return info.newRange(initial.lower, const MaxIntValue());
+    } else if (range.isNegative) {
+      return info.newRange(const MinIntValue(), initial.upper);
+    }
+    return info.newUnboundRange();
+  }
+
+  Range visitSubtract(HSubtract operation) {
+    Range range = getRangeForRecognizableOperation(operation);
+    if (range == null) return info.newUnboundRange();
+    Range initial = ranges[loopPhi.inputs[0]];
+    if (range.isPositive) {
+      return info.newRange(const MinIntValue(), initial.upper);
+    } else if (range.isNegative) {
+      return info.newRange(initial.lower, const MaxIntValue());
+    }
+    return info.newUnboundRange();
+  }
+
+  Range visitPhi(HPhi phi) {
+    Range phiRange;
+    for (HInstruction input in phi.inputs) {
+      HInstruction instruction = unwrap(input);
+      // If one of the inputs is the loop phi, then we're only
+      // interested in the other inputs: a loop phi feeding itself means
+      // it is not being updated.
+      if (instruction == loopPhi) continue;
+
+      // If another loop phi is involved, it's too complex to analyze.
+      if (instruction is HPhi && instruction.block.isLoopHeader()) return null;
+
+      Range inputRange = instruction.accept(this);
+      if (inputRange == null) return null;
+      if (phiRange == null) {
+        phiRange = inputRange;
+      } else {
+        phiRange = phiRange.union(inputRange);
+      }
+    }
+    return phiRange;
+  }
+
+  /**
+   * If [operation] is recognizable, returns the inferred range.
+   * Otherwise returns [null].
+   */
+  Range getRangeForRecognizableOperation(HBinaryArithmetic operation) {
+    if (!operation.left.isInteger(types)) return null;
+    if (!operation.right.isInteger(types)) return null;
+    HInstruction left = unwrap(operation.left);
+    HInstruction right = unwrap(operation.right);
+    // We only recognize operations that operate on the loop phi.
+    bool isLeftLoopPhi = (left == loopPhi);
+    bool isRightLoopPhi = (right == loopPhi);
+    if (!isLeftLoopPhi && !isRightLoopPhi) return null;
+
+    var other = isLeftLoopPhi ? right : left;
+    // If the analysis already computed range for the update, use it.
+    if (ranges[other] != null) return ranges[other];
+
+    // We currently only handle constants in updates if the
+    // update does not have a range.
+    if (other.isConstant()) {
+      Value value = info.newIntValue(other.constant.value);
+      return info.newRange(value, value);
+    }
+    return null;
+  }
+
+  /**
+   * [HCheck] instructions may check the loop phi. Since we only
+   * recognize updates on the loop phi, we must [unwrap] the [HCheck]
+   * instruction to check if it references the loop phi.
+   */
+  HInstruction unwrap(instruction) {
+    if (instruction is HCheck) return unwrap(instruction.checkedInput);
+    // [HPhi] might have two different [HCheck] instructions as
+    // inputs, checking the same instruction.
+    if (instruction is HPhi && !instruction.block.isLoopHeader()) {
+      HInstruction result = unwrap(instruction.inputs[0]);
+      for (int i = 1; i < instruction.inputs.length; i++) {
+        if (result != unwrap(instruction.inputs[i])) return instruction;
+      }
+      return result;
+    }
+    return instruction;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart
new file mode 100644
index 0000000..d565de7
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart
@@ -0,0 +1,157 @@
+// 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.
+
+part of ssa;
+
+class ValueSet {
+  int size = 0;
+  List<HInstruction> table;
+  ValueSetNode collisions;
+  ValueSet() : table = new List<HInstruction>.fixedLength(8);
+
+  bool get isEmpty => size == 0;
+  int get length => size;
+
+  void add(HInstruction instruction) {
+    assert(lookup(instruction) == null);
+    int hashCode = instruction.gvnHashCode();
+    int capacity = table.length;
+    // Resize when half of the hash table is in use.
+    if (size >= capacity >> 1) {
+      capacity = capacity << 1;
+      resize(capacity);
+    }
+    // Try to insert in the hash table first.
+    int index = hashCode % capacity;
+    if (table[index] == null) {
+      table[index] = instruction;
+    } else {
+      collisions = new ValueSetNode(instruction, hashCode, collisions);
+    }
+    size++;
+  }
+
+  HInstruction lookup(HInstruction instruction) {
+    int hashCode = instruction.gvnHashCode();
+    int index = hashCode % table.length;
+    // Look in the hash table.
+    HInstruction probe = table[index];
+    if (probe != null && probe.gvnEquals(instruction)) return probe;
+    // Look in the collisions list.
+    for (ValueSetNode node = collisions; node != null; node = node.next) {
+      if (node.hashCode == hashCode) {
+        HInstruction cached = node.value;
+        if (cached.gvnEquals(instruction)) return cached;
+      }
+    }
+    return null;
+  }
+
+  void kill(int flags) {
+    if (flags == 0) return;
+    int depends = HInstruction.computeDependsOnFlags(flags);
+    // Kill in the hash table.
+    for (int index = 0, length = table.length; index < length; index++) {
+      HInstruction instruction = table[index];
+      if (instruction != null && (instruction.flags & depends) != 0) {
+        table[index] = null;
+        size--;
+      }
+    }
+    // Kill in the collisions list.
+    ValueSetNode previous = null;
+    ValueSetNode current = collisions;
+    while (current != null) {
+      ValueSetNode next = current.next;
+      HInstruction cached = current.value;
+      if ((cached.flags & depends) != 0) {
+        if (previous == null) {
+          collisions = next;
+        } else {
+          previous.next = next;
+        }
+        size--;
+      } else {
+        previous = current;
+      }
+      current = next;
+    }
+  }
+
+  ValueSet copy() {
+    return copyTo(new ValueSet(), table, collisions);
+  }
+
+  List<HInstruction> toList() {
+    return copyTo(<HInstruction>[], table, collisions);
+  }
+
+  // Copy the instructions in value set defined by [table] and
+  // [collisions] into [other] and returns [other]. The copy is done
+  // by iterating through the hash table and the collisions list and
+  // calling [:other.add:].
+  static copyTo(var other, List<HInstruction> table, ValueSetNode collisions) {
+    // Copy elements from the hash table.
+    for (int index = 0, length = table.length; index < length; index++) {
+      HInstruction instruction = table[index];
+      if (instruction != null) other.add(instruction);
+    }
+    // Copy elements from the collision list.
+    ValueSetNode current = collisions;
+    while (current != null) {
+      // TODO(kasperl): Maybe find a way of reusing the hash code
+      // rather than recomputing it every time.
+      other.add(current.value);
+      current = current.next;
+    }
+    return other;
+  }
+
+  ValueSet intersection(ValueSet other) {
+    if (size > other.size) return other.intersection(this);
+    ValueSet result = new ValueSet();
+    // Look in the hash table.
+    for (int index = 0, length = table.length; index < length; index++) {
+      HInstruction instruction = table[index];
+      if (instruction != null && other.lookup(instruction) != null) {
+        result.add(instruction);
+      }
+    }
+    // Look in the collision list.
+    ValueSetNode current = collisions;
+    while (current != null) {
+      HInstruction value = current.value;
+      if (other.lookup(value) != null) {
+        result.add(value);
+      }
+      current = current.next;
+    }
+    return result;
+  }
+
+  void resize(int capacity) {
+    var oldSize = size;
+    var oldTable = table;
+    var oldCollisions = collisions;
+    // Reset the table with a bigger capacity.
+    assert(capacity > table.length);
+    size = 0;
+    table = new List<HInstruction>.fixedLength(capacity);
+    collisions = null;
+    // Add the old instructions to the new table.
+    copyTo(this, oldTable, oldCollisions);
+    // Make sure we preserved all elements and that no resizing
+    // happened as part of this resizing.
+    assert(size == oldSize);
+    assert(table.length == capacity);
+  }
+}
+
+class ValueSetNode {
+  final HInstruction value;
+  final int hash;
+  int get hashCode => hash;
+  ValueSetNode next;
+  ValueSetNode(this.value, this.hash, this.next);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart
new file mode 100644
index 0000000..34f960d
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart
@@ -0,0 +1,669 @@
+// 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.
+
+part of ssa;
+
+/**
+ * The [LiveRange] class covers a range where an instruction is live.
+ */
+class LiveRange {
+  final int start;
+  // [end] is not final because it can be updated due to loops.
+  int end;
+  LiveRange(this.start, this.end) {
+    assert(start <= end);
+  }
+
+  String toString() => '[$start $end[';
+}
+
+/**
+ * The [LiveInterval] class contains the list of ranges where an
+ * instruction is live.
+ */
+class LiveInterval {
+  /**
+   * The id where the instruction is defined.
+   */
+  int start;
+  final List<LiveRange> ranges;
+  LiveInterval() : ranges = <LiveRange>[];
+
+  /**
+   * Update all ranges that are contained in [from, to[ to
+   * die at [to].
+   */
+  void loopUpdate(int from, int to) {
+    for (LiveRange range in ranges) {
+      if (from <= range.start && range.end < to) {
+        range.end = to;
+      }
+    }
+  }
+
+  /**
+   * Add a new range to this interval.
+   */
+  void add(LiveRange interval) {
+    ranges.add(interval);
+  }
+
+  /**
+   * Returns true if one of the ranges of this interval dies at [at].
+   */
+  bool diesAt(int at) {
+    for (LiveRange range in ranges) {
+      if (range.end == at) return true;
+    }
+    return false;
+  }
+
+  String toString() {
+    List<String> res = new List<String>();
+    for (final interval in ranges) res.add(interval.toString());
+    return '(${Strings.join(res, ', ')})';
+  }
+}
+
+/**
+ * The [LiveEnvironment] class contains the liveIn set of a basic
+ * block. A liveIn set of a block contains the instructions that are
+ * live when entering that block.
+ */
+class LiveEnvironment {
+  /**
+   * The instruction id where the basic block starts. See
+   * [SsaLiveIntervalBuilder.instructionId].
+   */
+  int startId;
+
+  /**
+   * The instruction id where the basic block ends.
+   */
+  final int endId;
+
+  /**
+   * Loop markers that will be updated once the loop header is
+   * visited. The liveIn set of the loop header will be merged into this
+   * environment. [loopMarkers] is a mapping from block header to the
+   * end instruction id of the loop exit block.
+   */
+  final Map<HBasicBlock, int> loopMarkers;
+
+  /**
+   * The instructions that are live in this basic block. The values of
+   * the map contain the instruction ids where the instructions die.
+   * It will be used when adding a range to the live interval of an
+   * instruction.
+   */
+  final Map<HInstruction, int> liveInstructions;
+
+  /**
+   * Map containing the live intervals of instructions.
+   */
+  final Map<HInstruction, LiveInterval> liveIntervals;
+
+  LiveEnvironment(this.liveIntervals, this.endId)
+    : liveInstructions = new Map<HInstruction, int>(),
+      loopMarkers = new Map<HBasicBlock, int>();
+
+  /**
+   * Remove an instruction from the liveIn set. This method also
+   * updates the live interval of [instruction] to contain the new
+   * range: [id, / id contained in [liveInstructions] /].
+   */
+  void remove(HInstruction instruction, int id) {
+    // Special case the HCheck instruction to have the same live
+    // interval as the instruction it is checking.
+    if (instruction is HCheck) {
+      var input = instruction.checkedInput;
+      while (input is HCheck) input = input.checkedInput;
+      liveIntervals.putIfAbsent(input, () => new LiveInterval());
+      // Unconditionally force the live interval of the HCheck to
+      // be the live interval of the instruction it is checking.
+      liveIntervals[instruction] = liveIntervals[input];
+    } else {
+      LiveInterval range = liveIntervals.putIfAbsent(
+          instruction, () => new LiveInterval());
+      int lastId = liveInstructions[instruction];
+      // If [lastId] is null, then this instruction is not being used.
+      range.add(new LiveRange(id, lastId == null ? id : lastId));
+      // The instruction is defined at [id].
+      range.start = id;
+    }
+    liveInstructions.remove(instruction);
+  }
+
+  /**
+   * Add [instruction] to the liveIn set. If the instruction is not
+   * already in the set, we save the id where it dies.
+   */
+  void add(HInstruction instruction, int userId) {
+    // Note that we are visiting the graph in post-dominator order, so
+    // the first time we see a variable is when it dies.
+    liveInstructions.putIfAbsent(instruction, () => userId);
+    if (instruction is HCheck) {
+      // Special case the HCheck instruction to mark the actual
+      // checked instruction live.
+      var input = instruction.checkedInput;
+      while (input is HCheck) input = input.checkedInput;
+      liveInstructions.putIfAbsent(input, () => userId);
+    }
+  }
+
+  /**
+   * Merge this environment with [other]. Update the end id of
+   * instructions in case they are different between this and [other].
+   */
+  void mergeWith(LiveEnvironment other) {
+    other.liveInstructions.forEach((HInstruction instruction, int existingId) {
+      // If both environments have the same instruction id of where
+      // [instruction] dies, there is no need to update the live
+      // interval of [instruction]. For example the if block and the
+      // else block have the same end id for an instruction that is
+      // being used in the join block and defined before the if/else.
+      if (existingId == endId) return;
+      LiveInterval range = liveIntervals.putIfAbsent(
+          instruction, () => new LiveInterval());
+      range.add(new LiveRange(other.startId, existingId));
+      liveInstructions[instruction] = endId;
+    });
+    other.loopMarkers.forEach((k, v) { loopMarkers[k] = v; });
+  }
+
+  void addLoopMarker(HBasicBlock header, int id) {
+    assert(!loopMarkers.containsKey(header));
+    loopMarkers[header] = id;
+  }
+
+  void removeLoopMarker(HBasicBlock header) {
+    assert(loopMarkers.containsKey(header));
+    loopMarkers.remove(header);
+  }
+
+  bool get isEmpty => liveInstructions.isEmpty && loopMarkers.isEmpty;
+  bool contains(HInstruction instruction) =>
+      liveInstructions.containsKey(instruction);
+  String toString() => liveInstructions.toString();
+}
+
+/**
+ * Builds the live intervals of each instruction. The algorithm visits
+ * the graph post-dominator tree to find the last uses of an
+ * instruction, and computes the liveIns of each basic block.
+ */
+class SsaLiveIntervalBuilder extends HBaseVisitor {
+  final Compiler compiler;
+  final Set<HInstruction> generateAtUseSite;
+
+  /**
+   * A counter to assign start and end ids to live ranges. The initial
+   * value is not relevant. Note that instructionId goes downward to ease
+   * reasoning about live ranges (the first instruction of a graph has
+   * the lowest id).
+   */
+  int instructionId = 0;
+
+  /**
+   * The liveIns of basic blocks.
+   */
+  final Map<HBasicBlock, LiveEnvironment> liveInstructions;
+
+  /**
+   * The live intervals of instructions.
+   */
+  final Map<HInstruction, LiveInterval> liveIntervals;
+
+  SsaLiveIntervalBuilder(this.compiler, this.generateAtUseSite)
+    : liveInstructions = new Map<HBasicBlock, LiveEnvironment>(),
+      liveIntervals = new Map<HInstruction, LiveInterval>();
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+    if (!liveInstructions[graph.entry].isEmpty) {
+      compiler.internalError('LiveIntervalBuilder',
+          node: compiler.currentElement.parseNode(compiler));
+    }
+  }
+
+  void markInputsAsLiveInEnvironment(HInstruction instruction,
+                                     LiveEnvironment environment) {
+    for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+      markAsLiveInEnvironment(instruction.inputs[i], environment);
+    }
+  }
+
+  void markAsLiveInEnvironment(HInstruction instruction,
+                               LiveEnvironment environment) {
+    if (environment.contains(instruction)) return;
+    environment.add(instruction, instructionId);
+    // HPhis are treated specially.
+    if (generateAtUseSite.contains(instruction) && instruction is !HPhi) {
+      markInputsAsLiveInEnvironment(instruction, environment);
+    }
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    LiveEnvironment environment =
+        new LiveEnvironment(liveIntervals, instructionId);
+
+    // Add to the environment the liveIn of its successor, as well as
+    // the inputs of the phis of the successor that flow from this block.
+    for (int i = 0; i < block.successors.length; i++) {
+      HBasicBlock successor = block.successors[i];
+      LiveEnvironment successorEnv = liveInstructions[successor];
+      if (successorEnv != null) {
+        environment.mergeWith(successorEnv);
+      } else {
+        environment.addLoopMarker(successor, instructionId);
+      }
+
+      int index = successor.predecessors.indexOf(block);
+      for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) {
+        markAsLiveInEnvironment(phi.inputs[index], environment);
+      }
+    }
+
+    // Iterate over all instructions to remove an instruction from the
+    // environment and add its inputs.
+    HInstruction instruction = block.last;
+    while (instruction != null) {
+      environment.remove(instruction, instructionId);
+      markInputsAsLiveInEnvironment(instruction, environment);
+      instruction = instruction.previous;
+      instructionId--;
+    }
+
+    // We just remove the phis from the environment. The inputs of the
+    // phis will be put in the environment of the predecessors.
+    for (HPhi phi = block.phis.first; phi != null; phi = phi.next) {
+      environment.remove(phi, instructionId);
+    }
+
+    // Save the liveInstructions of that block.
+    environment.startId = instructionId + 1;
+    liveInstructions[block] = environment;
+
+    // If the block is a loop header, we can remove the loop marker,
+    // because it will just recompute the loop phis.
+    // We also check if this loop header has any back edges. If not,
+    // we know there is no loop marker for it.
+    if (block.isLoopHeader() && block.predecessors.length > 1) {
+      updateLoopMarker(block);
+    }
+  }
+
+  void updateLoopMarker(HBasicBlock header) {
+    LiveEnvironment env = liveInstructions[header];
+    int lastId = env.loopMarkers[header];
+    // Update all instructions that are liveIns in [header] to have a
+    // range that covers the loop.
+    env.liveInstructions.forEach((HInstruction instruction, int id) {
+      LiveInterval range = env.liveIntervals.putIfAbsent(
+          instruction, () => new LiveInterval());
+      range.loopUpdate(env.startId, lastId);
+      env.liveInstructions[instruction] = lastId;
+    });
+
+    env.removeLoopMarker(header);
+
+    // Update all liveIns set to contain the liveIns of [header].
+    liveInstructions.forEach((HBasicBlock block, LiveEnvironment other) {
+      if (other.loopMarkers.containsKey(header)) {
+        env.liveInstructions.forEach((HInstruction instruction, int id) {
+          other.liveInstructions[instruction] = id;
+        });
+        other.removeLoopMarker(header);
+        env.loopMarkers.forEach((k, v) { other.loopMarkers[k] = v; });
+      }
+    });
+  }
+}
+
+/**
+ * Represents a copy from one instruction to another. The codegen
+ * also uses this class to represent a copy from one variable to
+ * another.
+ */
+class Copy {
+  final source;
+  final destination;
+  Copy(this.source, this.destination);
+  String toString() => '$destination <- $source';
+}
+
+/**
+ * A copy handler contains the copies that a basic block needs to do
+ * after executing all its instructions.
+ */
+class CopyHandler {
+  /**
+   * The copies from an instruction to a phi of the successor.
+   */
+  final List<Copy> copies;
+
+  /**
+   * Assignments from an instruction that does not need a name (e.g. a
+   * constant) to the phi of a successor.
+   */
+  final List<Copy> assignments;
+
+  CopyHandler()
+    : copies = new List<Copy>(),
+      assignments = new List<Copy>();
+
+  void addCopy(HInstruction source, HInstruction destination) {
+    copies.add(new Copy(source, destination));
+  }
+
+  void addAssignment(HInstruction source, HInstruction destination) {
+    assignments.add(new Copy(source, destination));
+  }
+
+  String toString() => 'Copies: $copies, assignments: $assignments';
+  bool get isEmpty => copies.isEmpty && assignments.isEmpty;
+}
+
+/**
+ * Contains the mapping between instructions and their names for code
+ * generation, as well as the [CopyHandler] for each basic block.
+ */
+class VariableNames {
+  final Map<HInstruction, String> ownName;
+  final Map<HBasicBlock, CopyHandler> copyHandlers;
+
+  // Used to control heuristic that determines how local variables are declared.
+  final Set<String> allUsedNames;
+  /**
+   * Name that is used as a temporary to break cycles in
+   * parallel copies. We make sure this name is not being used
+   * anywhere by reserving it when we allocate names for instructions.
+   */
+  final String swapTemp;
+  /**
+   * Name that is used in bailout code. We make sure this name is not being used
+   * anywhere by reserving it when we allocate names for instructions.
+   */
+  final String stateName;
+
+  String getSwapTemp() {
+    allUsedNames.add(swapTemp);
+    return swapTemp;
+  }
+
+  VariableNames()
+    : ownName = new Map<HInstruction, String>(),
+      copyHandlers = new Map<HBasicBlock, CopyHandler>(),
+      allUsedNames = new Set<String>(),
+      swapTemp = computeFreshWithPrefix("t"),
+      stateName = computeFreshWithPrefix("state");
+
+  int get numberOfVariables => allUsedNames.length;
+
+  /** Returns a fresh variable with the given prefix. */
+  static String computeFreshWithPrefix(String prefix) {
+    String name = '${prefix}0';
+    int i = 1;
+    return name;
+  }
+
+  String getName(HInstruction instruction) {
+    return ownName[instruction];
+  }
+
+  CopyHandler getCopyHandler(HBasicBlock block) {
+    return copyHandlers[block];
+  }
+
+  void addNameUsed(String name) => allUsedNames.add(name);
+
+  bool hasName(HInstruction instruction) => ownName.containsKey(instruction);
+
+  void addCopy(HBasicBlock block, HInstruction source, HPhi destination) {
+    CopyHandler handler =
+        copyHandlers.putIfAbsent(block, () => new CopyHandler());
+    handler.addCopy(source, destination);
+  }
+
+  void addAssignment(HBasicBlock block, HInstruction source, HPhi destination) {
+    CopyHandler handler =
+        copyHandlers.putIfAbsent(block, () => new CopyHandler());
+    handler.addAssignment(source, destination);
+  }
+}
+
+/**
+ * Allocates variable names for instructions, making sure they don't collide.
+ */
+class VariableNamer {
+  final VariableNames names;
+  final Compiler compiler;
+  final Set<String> usedNames;
+  final List<String> freeTemporaryNames;
+  int temporaryIndex = 0;
+  static final RegExp regexp = new RegExp('t[0-9]+');
+
+  VariableNamer(LiveEnvironment environment,
+                this.names,
+                this.compiler)
+    : usedNames = new Set<String>(),
+      freeTemporaryNames = new List<String>() {
+    // [VariableNames.swapTemp] is used when there is a cycle in a copy handler.
+    // Therefore we make sure no one uses it.
+    usedNames.add(names.swapTemp);
+    // [VariableNames.stateName] is being used throughout a bailout function.
+    // Whenever a bailout-target is reached we set the state-variable to 0. We
+    // must therefore not have any local variable that could clash with the
+    // state variable.
+    // Therefore we make sure no one uses it at any time.
+    usedNames.add(names.stateName);
+
+    // All liveIns instructions must have a name at this point, so we
+    // add them to the list of used names.
+    environment.liveInstructions.forEach((HInstruction instruction, int index) {
+      String name = names.getName(instruction);
+      if (name != null) {
+        usedNames.add(name);
+        names.addNameUsed(name);
+      }
+    });
+  }
+
+  String allocateWithHint(String originalName) {
+    int i = 0;
+    JavaScriptBackend backend = compiler.backend;
+    String name = backend.namer.safeVariableName(originalName);
+    while (usedNames.contains(name)) {
+      name = backend.namer.safeVariableName('$originalName${i++}');
+    }
+    return name;
+  }
+
+  String allocateTemporary() {
+    while (!freeTemporaryNames.isEmpty) {
+      String name = freeTemporaryNames.removeLast();
+      if (!usedNames.contains(name)) return name;
+    }
+    String name = 't${temporaryIndex++}';
+    while (usedNames.contains(name)) name = 't${temporaryIndex++}';
+    return name;
+  }
+
+  HPhi firstPhiUserWithElement(HInstruction instruction) {
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HPhi && user.sourceElement != null) {
+        return user;
+      }
+    }
+    return null;
+  }
+
+  String allocateName(HInstruction instruction) {
+    String name;
+    if (instruction is HCheck) {
+      // Special case this instruction to use the name of its
+      // input if it has one.
+      var temp = instruction;
+      do {
+        temp = temp.checkedInput;
+        name = names.ownName[temp];
+      } while (name == null && temp is HCheck);
+      if (name != null) return addAllocatedName(instruction, name);
+    }
+
+    if (instruction.sourceElement != null) {
+      name = allocateWithHint(instruction.sourceElement.name.slowToString());
+    } else {
+      // We could not find an element for the instruction. If the
+      // instruction is used by a phi, try to use the name of the phi.
+      // Otherwise, just allocate a temporary name.
+      HPhi phi = firstPhiUserWithElement(instruction);
+      if (phi != null) {
+        name = allocateWithHint(phi.sourceElement.name.slowToString());
+      } else {
+        name = allocateTemporary();
+      }
+    }
+    return addAllocatedName(instruction, name);
+  }
+
+  String addAllocatedName(HInstruction instruction, String name) {
+    usedNames.add(name);
+    names.addNameUsed(name);
+    names.ownName[instruction] = name;
+    return name;
+  }
+
+  /**
+   * Frees [instruction]'s name so it can be used for other instructions.
+   */
+  void freeName(HInstruction instruction) {
+    String ownName = names.ownName[instruction];
+    if (ownName != null) {
+      // We check if we have already looked for temporary names
+      // because if we haven't, chances are the temporary we allocate
+      // in this block can match a phi with the same name in the
+      // successor block.
+      if (temporaryIndex != 0 && regexp.hasMatch(ownName)) {
+        freeTemporaryNames.addLast(ownName);
+      }
+      usedNames.remove(ownName);
+    }
+  }
+}
+
+/**
+ * Visits all blocks in the graph, sets names to instructions, and
+ * creates the [CopyHandler] for each block. This class needs to have
+ * the liveIns set as well as all the live intervals of instructions.
+ * It visits the graph in dominator order, so that at each entry of a
+ * block, the instructions in its liveIns set have names.
+ *
+ * When visiting a block, it goes through all instructions. For each
+ * instruction, it frees the names of the inputs that die at that
+ * instruction, and allocates a name to the instruction. For each phi,
+ * it adds a copy to the CopyHandler of the corresponding predecessor.
+ */
+class SsaVariableAllocator extends HBaseVisitor {
+
+  final Compiler compiler;
+  final Map<HBasicBlock, LiveEnvironment> liveInstructions;
+  final Map<HInstruction, LiveInterval> liveIntervals;
+  final Set<HInstruction> generateAtUseSite;
+
+  final VariableNames names;
+
+  SsaVariableAllocator(this.compiler,
+                       this.liveInstructions,
+                       this.liveIntervals,
+                       this.generateAtUseSite)
+    : this.names = new VariableNames();
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    VariableNamer namer = new VariableNamer(
+        liveInstructions[block], names, compiler);
+
+    block.forEachPhi((HPhi phi) {
+      handlePhi(phi, namer);
+    });
+
+    block.forEachInstruction((HInstruction instruction) {
+      handleInstruction(instruction, namer);
+    });
+  }
+
+  /**
+   * Returns whether [instruction] needs a name. Instructions that
+   * have no users or that are generated at use site does not need a name.
+   */
+  bool needsName(HInstruction instruction) {
+    if (instruction is HThis) return false;
+    if (instruction is HParameterValue) return true;
+    if (instruction.usedBy.isEmpty) return false;
+    if (generateAtUseSite.contains(instruction)) return false;
+    // A [HCheck] instruction that has control flow needs a name only if its
+    // checked input needs a name (e.g. a check [HConstant] does not
+    // need a name).
+    if (instruction is HCheck && instruction.isControlFlow()) {
+      HCheck check = instruction;
+      return needsName(instruction.checkedInput);
+    }
+    return true;
+  }
+
+  /**
+   * Returns whether [instruction] dies at the instruction [at].
+   */
+  bool diesAt(HInstruction instruction, HInstruction at) {
+    LiveInterval atInterval = liveIntervals[at];
+    LiveInterval instructionInterval = liveIntervals[instruction];
+    int start = atInterval.start;
+    return instructionInterval.diesAt(start);
+  }
+
+  void handleInstruction(HInstruction instruction, VariableNamer namer) {
+    // TODO(ager): We cannot perform this check to free names for
+    // HCheck instructions because they are special cased to have the
+    // same live intervals as the instruction they are checking. This
+    // includes sharing the start id with the checked
+    // input. Therefore, for HCheck(checkedInput, otherInput) we would
+    // end up checking that otherInput dies not here, but at the
+    // location of checkedInput. We should preserve the start id for
+    // the check instruction.
+    if (instruction is! HCheck) {
+      for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+        HInstruction input = instruction.inputs[i];
+        // If [input] has a name, and its use here is the last use, free
+        // its name.
+        if (needsName(input) && diesAt(input, instruction)) {
+          namer.freeName(input);
+        }
+      }
+    }
+
+    if (needsName(instruction)) {
+      namer.allocateName(instruction);
+    }
+  }
+
+  void handlePhi(HPhi phi, VariableNamer namer) {
+    if (!needsName(phi)) return;
+
+    for (int i = 0; i < phi.inputs.length; i++) {
+      HInstruction input = phi.inputs[i];
+      HBasicBlock predecessor = phi.block.predecessors[i];
+      if (!needsName(input)) {
+        names.addAssignment(predecessor, input, phi);
+      } else {
+        names.addCopy(predecessor, input, phi);
+      }
+    }
+
+    namer.allocateName(phi);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/string_validator.dart b/pkgs/markdown/lib/src/compiler/implementation/string_validator.dart
new file mode 100644
index 0000000..ef294bd
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/string_validator.dart
@@ -0,0 +1,214 @@
+// 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.
+
+// Check the validity of string literals.
+
+library stringvalidator;
+
+import "dart:collection";
+
+import "dart2jslib.dart";
+import "tree/tree.dart";
+import "elements/elements.dart";
+import "util/characters.dart";
+import "scanner/scannerlib.dart" show Token;
+
+class StringValidator {
+  final DiagnosticListener listener;
+
+  StringValidator(this.listener);
+
+  DartString validateQuotedString(Token token) {
+    SourceString source = token.value;
+    StringQuoting quoting = quotingFromString(source);
+    int leftQuote = quoting.leftQuoteLength;
+    int rightQuote = quoting.rightQuoteLength;
+    SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote);
+    return validateString(token,
+                          token.charOffset + leftQuote,
+                          content,
+                          quoting);
+  }
+
+  DartString validateInterpolationPart(Token token, StringQuoting quoting,
+                                       {bool isFirst: false,
+                                        bool isLast: false}) {
+    SourceString source = token.value;
+    int leftQuote = 0;
+    int rightQuote = 0;
+    if (isFirst) leftQuote = quoting.leftQuoteLength;
+    if (isLast) rightQuote = quoting.rightQuoteLength;
+    SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote);
+    return validateString(token,
+                          token.charOffset + leftQuote,
+                          content,
+                          quoting);
+  }
+
+  static StringQuoting quotingFromString(SourceString sourceString) {
+    Iterator<int> source = sourceString.iterator;
+    bool raw = false;
+    int quoteLength = 1;
+    source.moveNext();
+    int quoteChar = source.current;
+    if (quoteChar == $r) {
+      raw = true;
+      source.moveNext();
+      quoteChar = source.current;
+    }
+    assert(quoteChar == $SQ || quoteChar == $DQ);
+    // String has at least one quote. Check it if has three.
+    // If it only have two, the string must be an empty string literal,
+    // and end after the second quote.
+    bool multiline = false;
+    if (source.moveNext() && source.current == quoteChar && source.moveNext()) {
+      int code = source.current;
+      assert(code == quoteChar);  // If not, there is a bug in the parser.
+      quoteLength = 3;
+      // Check if a multiline string starts with a newline (CR, LF or CR+LF).
+      if (source.moveNext()) {
+        code = source.current;
+        if (code == $CR) {
+          quoteLength += 1;
+          if (source.moveNext() && source.current == $LF) {
+            quoteLength += 1;
+          }
+        } else if (code == $LF) {
+          quoteLength += 1;
+        }
+      }
+    }
+    return StringQuoting.getQuoting(quoteChar, raw, quoteLength);
+  }
+
+  void stringParseError(String message, Token token, int offset) {
+    listener.cancel("$message @ $offset", token : token);
+  }
+
+  /**
+   * Validates the escape sequences and special characters of a string literal.
+   * Returns a DartString if valid, and null if not.
+   */
+  DartString validateString(Token token,
+                            int startOffset,
+                            SourceString string,
+                            StringQuoting quoting) {
+    // We need to check for invalid x and u escapes, for line
+    // terminators in non-multiline strings, and for invalid Unicode
+    // scalar values (either directly or as u-escape values).  We also check
+    // for unpaired UTF-16 surrogates.
+    int length = 0;
+    int index = startOffset;
+    bool containsEscape = false;
+    bool previousWasLeadSurrogate = false;
+    bool invalidUtf16 = false;
+    for(HasNextIterator<int> iter = new HasNextIterator(string.iterator);
+        iter.hasNext;
+        length++) {
+      index++;
+      int code = iter.next();
+      if (code == $BACKSLASH) {
+        if (quoting.raw) continue;
+        containsEscape = true;
+        if (!iter.hasNext) {
+          stringParseError("Incomplete escape sequence",token, index);
+          return null;
+        }
+        index++;
+        code = iter.next();
+        if (code == $x) {
+          for (int i = 0; i < 2; i++) {
+            if (!iter.hasNext) {
+              stringParseError("Incomplete escape sequence", token, index);
+              return null;
+            }
+            index++;
+            code = iter.next();
+            if (!isHexDigit(code)) {
+              stringParseError("Invalid character in escape sequence",
+                               token, index);
+              return null;
+            }
+          }
+          // A two-byte hex escape can't generate an invalid value.
+          continue;
+        } else if (code == $u) {
+          int escapeStart = index - 1;
+          index++;
+          code = iter.hasNext ? iter.next() : 0;
+          int value = 0;
+          if (code == $OPEN_CURLY_BRACKET) {
+            // expect 1-6 hex digits.
+            int count = 0;
+            while (iter.hasNext) {
+              code = iter.next();
+              index++;
+              if (code == $CLOSE_CURLY_BRACKET) {
+                break;
+              }
+              if (!isHexDigit(code)) {
+                stringParseError("Invalid character in escape sequence",
+                                 token, index);
+                return null;
+              }
+              count++;
+              value = value * 16 + hexDigitValue(code);
+            }
+            if (code != $CLOSE_CURLY_BRACKET || count == 0 || count > 6) {
+              int errorPosition = index - count;
+              if (count > 6) errorPosition += 6;
+              stringParseError("Invalid character in escape sequence",
+                               token, errorPosition);
+              return null;
+            }
+          } else {
+            // Expect four hex digits, including the one just read.
+            for (int i = 0; i < 4; i++) {
+              if (i > 0) {
+                if (iter.hasNext) {
+                  index++;
+                  code = iter.next();
+                } else {
+                  code = 0;
+                }
+              }
+              if (!isHexDigit(code)) {
+                stringParseError("Invalid character in escape sequence",
+                                 token, index);
+                return null;
+              }
+              value = value * 16 + hexDigitValue(code);
+            }
+          }
+          code = value;
+        }
+      }
+      if (code >= 0x10000) length++;
+      // This handles both unescaped characters and the value of unicode
+      // escapes.
+      if (previousWasLeadSurrogate) {
+        if (!isUtf16TrailSurrogate(code)) {
+          invalidUtf16 = true;
+          break;
+        }
+        previousWasLeadSurrogate = false;
+      } else if (isUtf16LeadSurrogate(code)) {
+        previousWasLeadSurrogate = true;
+      } else if (!isUnicodeScalarValue(code)) {
+        invalidUtf16 = true;
+        break;
+      }
+    }
+    if (previousWasLeadSurrogate || invalidUtf16) {
+      stringParseError("Invalid Utf16 surrogate", token, index);
+      return null;
+    }
+    // String literal successfully validated.
+    if (quoting.raw || !containsEscape) {
+      // A string without escapes could just as well have been raw.
+      return new DartString.rawString(string, length);
+    }
+    return new DartString.escapedString(string, length);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart
new file mode 100644
index 0000000..ea56771
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart
@@ -0,0 +1,321 @@
+// 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.
+
+library parser;
+
+import 'dart:io';
+import 'dart:scalarlist';
+
+import 'dart:utf';
+
+import '../elements/elements.dart';
+import '../scanner/scanner_implementation.dart';
+import '../scanner/scannerlib.dart';
+import '../tree/tree.dart';
+import '../util/characters.dart';
+import '../source_file.dart';
+import '../ssa/ssa.dart';
+
+import '../../compiler.dart' as api;
+
+part '../diagnostic_listener.dart';
+part '../scanner/byte_array_scanner.dart';
+part '../scanner/byte_strings.dart';
+
+int charCount = 0;
+Stopwatch stopwatch;
+
+void main() {
+  toolMain(new Options().arguments);
+}
+
+void toolMain(List<String> arguments) {
+  filesWithCrashes = [];
+  stopwatch = new Stopwatch();
+  MyOptions options = new MyOptions();
+
+  void printStats() {
+    int kb = (charCount / 1024).round().toInt();
+    String stats =
+        '$classCount classes (${kb}Kb) in ${stopwatch.elapsedMilliseconds}ms';
+    if (errorCount != 0) {
+      stats = '$stats with $errorCount errors';
+    }
+    if (options.diet) {
+      print('Diet parsed $stats.');
+    } else {
+      print('Parsed $stats.');
+    }
+    if (filesWithCrashes.length != 0) {
+      print('The following ${filesWithCrashes.length} files caused a crash:');
+      for (String file in filesWithCrashes) {
+        print(file);
+      }
+    }
+  }
+
+  for (String argument in arguments) {
+    if (argument == "--diet") {
+      options.diet = true;
+      continue;
+    }
+    if (argument == "--throw") {
+      options.throwOnError = true;
+      continue;
+    }
+    if (argument == "--scan-only") {
+      options.scanOnly = true;
+      continue;
+    }
+    if (argument == "--read-only") {
+      options.readOnly = true;
+      continue;
+    }
+    if (argument == "--ast") {
+      options.buildAst = true;
+      continue;
+    }
+    if (argument == "-") {
+      parseFilesFrom(stdin, options, printStats);
+      return;
+    }
+    stopwatch.start();
+    parseFile(argument, options);
+    stopwatch.stop();
+  }
+
+  printStats();
+}
+
+void parseFile(String filename, MyOptions options) {
+  List<int> bytes = read(filename);
+  charCount += bytes.length;
+  if (options.readOnly) return;
+  MySourceFile file = new MySourceFile(filename, bytes);
+  final Listener listener = options.buildAst
+      ? new MyNodeListener(file, options)
+      : new MyListener(file);
+  final Parser parser = options.diet
+      ? new PartialParser(listener)
+      : new Parser(listener);
+  try {
+    Token token = scan(file);
+    if (!options.scanOnly) parser.parseUnit(token);
+  } on ParserError catch (ex) {
+    if (options.throwOnError) {
+      throw;
+    } else {
+      print(ex);
+    }
+  } catch (ex) {
+    print('Error in file: $filename');
+    throw;
+  }
+  if (options.buildAst) {
+    MyNodeListener l = listener;
+    if (!l.nodes.isEmpty) {
+      String message = 'Stack not empty after parsing';
+      print(formatError(message, l.nodes.head.getBeginToken(),
+                        l.nodes.head.getEndToken(), file));
+      throw message;
+    }
+  }
+}
+
+Token scan(MySourceFile source) {
+  Scanner scanner = new ByteArrayScanner(source.rawText);
+  return scanner.tokenize();
+}
+
+var filesWithCrashes;
+
+void parseFilesFrom(InputStream input, MyOptions options, Function whenDone) {
+  void readLine(String line) {
+    stopwatch.start();
+    try {
+      parseFile(line, options);
+    } catch (ex, trace) {
+      filesWithCrashes.add(line);
+      print(ex);
+      print(trace);
+    }
+    stopwatch.stop();
+  }
+  forEachLine(input, readLine, whenDone);
+}
+
+void forEachLine(InputStream input,
+                 void lineHandler(String line),
+                 void closeHandler()) {
+  StringInputStream stringStream = new StringInputStream(input);
+  stringStream.onLine = () {
+    String line;
+    while ((line = stringStream.readLine()) != null) {
+      lineHandler(line);
+    }
+  };
+  stringStream.onClosed = closeHandler;
+}
+
+List<int> read(String filename) {
+  RandomAccessFile file = new File(filename).openSync();
+  bool threw = true;
+  try {
+    int size = file.lengthSync();
+    List<int> bytes = new Uint8List(size + 1);
+    file.readListSync(bytes, 0, size);
+    bytes[size] = $EOF;
+    threw = false;
+    return bytes;
+  } finally {
+    try {
+      file.closeSync();
+    } catch (ex) {
+      if (!threw) throw;
+    }
+  }
+}
+
+int classCount = 0;
+int errorCount = 0;
+
+class MyListener extends Listener {
+  final SourceFile file;
+
+  MyListener(this.file);
+
+  void beginClassDeclaration(Token token) {
+    classCount++;
+  }
+
+  void beginInterface(Token token) {
+    classCount++;
+  }
+
+  void error(String message, Token token) {
+    throw new ParserError(formatError(message, token, token, file));
+  }
+}
+
+String formatError(String message, Token beginToken, Token endToken,
+                   SourceFile file) {
+  ++errorCount;
+  if (beginToken == null) return '${file.filename}: $message';
+  String tokenString = endToken.toString();
+  int begin = beginToken.charOffset;
+  int end = endToken.charOffset + tokenString.length;
+  return file.getLocationMessage(message, begin, end, true, (x) => x);
+}
+
+class MyNodeListener extends NodeListener {
+  MyNodeListener(SourceFile file, MyOptions options)
+    : super(new MyCanceller(file, options), null);
+
+  void beginClassDeclaration(Token token) {
+    classCount++;
+  }
+
+  void beginInterface(Token token) {
+    classCount++;
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+    super.endClassDeclaration(interfacesCount, beginToken,
+                              extendsKeyword, implementsKeyword,
+                              endToken);
+    ClassNode node = popNode(); // Discard ClassNode and assert the type.
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+    super.endInterface(supertypeCount, interfaceKeyword, extendsKeyword,
+                       endToken);
+    ClassNode node = popNode(); // Discard ClassNode and assert the type.
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+    super.endTopLevelFields(count, beginToken, endToken);
+    VariableDefinitions node = popNode(); // Discard node and assert the type.
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+    super.endFunctionTypeAlias(typedefKeyword, endToken);
+    Typedef node = popNode(); // Discard Typedef and assert type type.
+  }
+
+  void endLibraryTag(bool hasPrefix, Token beginToken, Token endToken) {
+    super.endLibraryTag(hasPrefix, beginToken, endToken);
+    ScriptTag node = popNode(); // Discard ScriptTag and assert type type.
+  }
+
+  void log(message) {
+    print(message);
+  }
+}
+
+class MyCanceller implements DiagnosticListener {
+  final SourceFile file;
+  final MyOptions options;
+
+  MyCanceller(this.file, this.options);
+
+  void log(String message) {}
+
+  void cancel(String reason, {node, token, instruction, element}) {
+    Token beginToken;
+    Token endToken;
+    if (token != null) {
+      beginToken = token;
+      endToken = token;
+    } else if (node != null) {
+      beginToken = node.getBeginToken();
+      endToken = node.getEndToken();
+    }
+    String message = formatError(reason, beginToken, endToken, file);
+    if (options.throwOnError) throw new ParserError(message);
+    print(message);
+  }
+}
+
+class MyOptions {
+  bool diet = false;
+  bool throwOnError = false;
+  bool scanOnly = false;
+  bool readOnly = false;
+  bool buildAst = false;
+}
+
+class MySourceFile extends SourceFile {
+  final rawText;
+  var stringText;
+
+  MySourceFile(filename, this.rawText) : super(filename, null);
+
+  String get text {
+    if (rawText is String) {
+      return rawText;
+    } else {
+      if (stringText == null) {
+        stringText = new String.fromCharCodes(rawText);
+        if (stringText.endsWith('\u0000')) {
+          // Strip trailing NUL used by ByteArrayScanner to signal EOF.
+          stringText = stringText.substring(0, stringText.length - 1);
+        }
+      }
+      return stringText;
+    }
+  }
+
+  set text(String newText) {
+    throw "not supported";
+  }
+}
+
+class Mock {
+  const Mock();
+  bool get useColors => true;
+  internalError(message) { throw message.toString(); }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart
new file mode 100644
index 0000000..5a3494d
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart
@@ -0,0 +1,235 @@
+// 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.
+
+part of tree;
+
+/**
+ * The [DartString] type represents a Dart string value as a sequence of Unicode
+ * Scalar Values.
+ * After parsing, any valid [LiteralString] will contain a [DartString]
+ * representing its content after removing quotes and resolving escapes in
+ * its source.
+ */
+abstract class DartString extends Iterable<int> {
+  factory DartString.empty() => const LiteralDartString("");
+  // This is a convenience constructor. If you need a const literal DartString,
+  // use [const LiteralDartString(string)] directly.
+  factory DartString.literal(String string) => new LiteralDartString(string);
+  factory DartString.rawString(SourceString source, int length) =>
+      new RawSourceDartString(source, length);
+  factory DartString.escapedString(SourceString source, int length) =>
+      new EscapedSourceDartString(source, length);
+  factory DartString.concat(DartString first, DartString second) {
+    if (first.isEmpty) return second;
+    if (second.isEmpty) return first;
+    return new ConsDartString(first, second);
+  }
+  const DartString();
+  int get length;
+  bool get isEmpty => length == 0;
+  Iterator<int> get iterator;
+  String slowToString();
+
+  bool operator ==(var other) {
+    if (other is !DartString) return false;
+    DartString otherString = other;
+    if (length != otherString.length) return false;
+    Iterator it1 = iterator;
+    Iterator it2 = otherString.iterator;
+    while (it1.moveNext()) {
+      if (!it2.moveNext()) return false;
+      if (it1.current != it2.current) return false;
+    }
+    return true;
+  }
+  String toString() => "DartString#${length}:${slowToString()}";
+  SourceString get source;
+}
+
+
+/**
+ * A [DartString] where the content is represented by an actual [String].
+ */
+class LiteralDartString extends DartString {
+  final String string;
+  const LiteralDartString(this.string);
+  int get length => string.length;
+  Iterator<int> get iterator => new StringCodeIterator(string);
+  String slowToString() => string;
+  SourceString get source => new StringWrapper(string);
+}
+
+/**
+ * A [DartString] where the content comes from a slice of the program source.
+ */
+abstract class SourceBasedDartString extends DartString {
+  String toStringCache = null;
+  final SourceString source;
+  final int length;
+  SourceBasedDartString(this.source, this.length);
+  Iterator<int> get iterator;
+}
+
+/**
+ * Special case of a [SourceBasedDartString] where we know the source doesn't
+ * contain any escapes.
+ */
+class RawSourceDartString extends SourceBasedDartString {
+  RawSourceDartString(source, length) : super(source, length);
+  Iterator<int> get iterator => source.iterator;
+  String slowToString() {
+    if (toStringCache != null) return toStringCache;
+    toStringCache  = source.slowToString();
+    return toStringCache;
+  }
+}
+
+/**
+ * General case of a [SourceBasedDartString] where the source might contain
+ * escapes.
+ */
+class EscapedSourceDartString extends SourceBasedDartString {
+  EscapedSourceDartString(source, length) : super(source, length);
+  Iterator<int> get iterator {
+    if (toStringCache != null) return new StringCodeIterator(toStringCache);
+    return new StringEscapeIterator(source);
+  }
+  String slowToString() {
+    if (toStringCache != null) return toStringCache;
+    StringBuffer buffer = new StringBuffer();
+    StringEscapeIterator it = new StringEscapeIterator(source);
+    while (it.moveNext()) {
+      buffer.addCharCode(it.current);
+    }
+    toStringCache = buffer.toString();
+    return toStringCache;
+  }
+}
+
+/**
+ * The concatenation of two [DartString]s.
+ */
+class ConsDartString extends DartString {
+  final DartString left;
+  final DartString right;
+  final int length;
+  String toStringCache;
+  ConsDartString(DartString left, DartString right)
+      : this.left = left,
+        this.right = right,
+        length = left.length + right.length;
+
+  Iterator<int> get iterator => new ConsDartStringIterator(this);
+
+  String slowToString() {
+    if (toStringCache != null) return toStringCache;
+    toStringCache = left.slowToString().concat(right.slowToString());
+    return toStringCache;
+  }
+  SourceString get source => new StringWrapper(slowToString());
+}
+
+class ConsDartStringIterator implements Iterator<int> {
+  HasNextIterator<int> currentIterator;
+  DartString right;
+  bool hasNextLookAhead;
+  int _current = null;
+
+  ConsDartStringIterator(ConsDartString cons)
+      : currentIterator = new HasNextIterator<int>(cons.left.iterator),
+        right = cons.right {
+    hasNextLookAhead = currentIterator.hasNext;
+    if (!hasNextLookAhead) {
+      nextPart();
+    }
+  }
+
+  int get current => _current;
+
+  bool moveNext() {
+    if (!hasNextLookAhead) {
+      _current = null;
+      return false;
+    }
+    _current = currentIterator.next();
+    hasNextLookAhead = currentIterator.hasNext;
+    if (!hasNextLookAhead) {
+      nextPart();
+    }
+    return true;
+  }
+  void nextPart() {
+    if (right != null) {
+      currentIterator = new HasNextIterator<int>(right.iterator);
+      right = null;
+      hasNextLookAhead = currentIterator.hasNext;
+    }
+  }
+}
+
+/**
+ *Iterator that returns the actual string contents of a string with escapes.
+ */
+class StringEscapeIterator implements Iterator<int>{
+  final Iterator<int> source;
+  int _current = null;
+
+  StringEscapeIterator(SourceString source) : this.source = source.iterator;
+
+  int get current => _current;
+
+  bool moveNext() {
+    if (!source.moveNext()) {
+      _current = null;
+      return false;
+    }
+    int code = source.current;
+    if (code != $BACKSLASH) {
+      _current = code;
+      return true;
+    }
+    source.moveNext();
+    code = source.current;
+    switch (code) {
+      case $n: _current = $LF; break;
+      case $r: _current = $CR; break;
+      case $t: _current = $TAB; break;
+      case $b: _current = $BS; break;
+      case $f: _current = $FF; break;
+      case $v: _current = $VTAB; break;
+      case $x:
+        source.moveNext();
+        int value = hexDigitValue(source.current);
+        source.moveNext();
+        value = value * 16 + hexDigitValue(source.current);
+        _current = value;
+        break;
+      case $u:
+        int value = 0;
+        source.moveNext();
+        code = source.current;
+        if (code == $OPEN_CURLY_BRACKET) {
+          source.moveNext();
+          while (source.current != $CLOSE_CURLY_BRACKET) {
+            value = value * 16 + hexDigitValue(source.current);
+            source.moveNext();
+          }
+          _current = value;
+          break;
+        }
+        // Four digit hex value.
+        value = hexDigitValue(code);
+        for (int i = 0; i < 3; i++) {
+          source.moveNext();
+          value = value * 16 + hexDigitValue(source.current);
+        }
+        _current = value;
+        break;
+      default:
+        _current = code;
+    }
+    return true;
+  }
+}
+
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart
new file mode 100644
index 0000000..1b3b455
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart
@@ -0,0 +1,2086 @@
+// 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.
+
+part of tree;
+
+abstract class Visitor<R> {
+  const Visitor();
+
+  R visitNode(Node node);
+
+  R visitBlock(Block node) => visitStatement(node);
+  R visitBreakStatement(BreakStatement node) => visitGotoStatement(node);
+  R visitCascade(Cascade node) => visitExpression(node);
+  R visitCascadeReceiver(CascadeReceiver node) => visitExpression(node);
+  R visitCaseMatch(CaseMatch node) => visitNode(node);
+  R visitCatchBlock(CatchBlock node) => visitNode(node);
+  R visitClassNode(ClassNode node) => visitNode(node);
+  R visitCombinator(Combinator node) => visitNode(node);
+  R visitConditional(Conditional node) => visitExpression(node);
+  R visitContinueStatement(ContinueStatement node) => visitGotoStatement(node);
+  R visitDoWhile(DoWhile node) => visitLoop(node);
+  R visitEmptyStatement(EmptyStatement node) => visitStatement(node);
+  R visitExport(Export node) => visitLibraryDependency(node);
+  R visitExpression(Expression node) => visitNode(node);
+  R visitExpressionStatement(ExpressionStatement node) => visitStatement(node);
+  R visitFor(For node) => visitLoop(node);
+  R visitForIn(ForIn node) => visitLoop(node);
+  R visitFunctionDeclaration(FunctionDeclaration node) => visitStatement(node);
+  R visitFunctionExpression(FunctionExpression node) => visitExpression(node);
+  R visitGotoStatement(GotoStatement node) => visitStatement(node);
+  R visitIdentifier(Identifier node) => visitExpression(node);
+  R visitIf(If node) => visitStatement(node);
+  R visitImport(Import node) => visitLibraryDependency(node);
+  R visitLabel(Label node) => visitNode(node);
+  R visitLabeledStatement(LabeledStatement node) => visitStatement(node);
+  R visitLibraryDependency(LibraryDependency node) => visitLibraryTag(node);
+  R visitLibraryName(LibraryName node) => visitLibraryTag(node);
+  R visitLibraryTag(LibraryTag node) => visitNode(node);
+  R visitLiteral(Literal node) => visitExpression(node);
+  R visitLiteralBool(LiteralBool node) => visitLiteral(node);
+  R visitLiteralDouble(LiteralDouble node) => visitLiteral(node);
+  R visitLiteralInt(LiteralInt node) => visitLiteral(node);
+  R visitLiteralList(LiteralList node) => visitExpression(node);
+  R visitLiteralMap(LiteralMap node) => visitExpression(node);
+  R visitLiteralMapEntry(LiteralMapEntry node) => visitNode(node);
+  R visitLiteralNull(LiteralNull node) => visitLiteral(node);
+  R visitLiteralString(LiteralString node) => visitStringNode(node);
+  R visitStringJuxtaposition(StringJuxtaposition node) => visitStringNode(node);
+  R visitLoop(Loop node) => visitStatement(node);
+  R visitMixinApplication(MixinApplication node) => visitNode(node);
+  R visitModifiers(Modifiers node) => visitNode(node);
+  R visitNamedArgument(NamedArgument node) => visitExpression(node);
+  R visitNamedMixinApplication(NamedMixinApplication node) {
+    return visitMixinApplication(node);
+  }
+  R visitNewExpression(NewExpression node) => visitExpression(node);
+  R visitNodeList(NodeList node) => visitNode(node);
+  R visitOperator(Operator node) => visitIdentifier(node);
+  R visitParenthesizedExpression(ParenthesizedExpression node) {
+    return visitExpression(node);
+  }
+  R visitPart(Part node) => visitLibraryTag(node);
+  R visitPartOf(PartOf node) => visitNode(node);
+  R visitPostfix(Postfix node) => visitNodeList(node);
+  R visitPrefix(Prefix node) => visitNodeList(node);
+  R visitReturn(Return node) => visitStatement(node);
+  R visitScriptTag(ScriptTag node) => visitNode(node);
+  R visitSend(Send node) => visitExpression(node);
+  R visitSendSet(SendSet node) => visitSend(node);
+  R visitStatement(Statement node) => visitNode(node);
+  R visitStringNode(StringNode node) => visitExpression(node);
+  R visitStringInterpolation(StringInterpolation node) => visitStringNode(node);
+  R visitStringInterpolationPart(StringInterpolationPart node) {
+    return visitNode(node);
+  }
+  R visitSwitchCase(SwitchCase node) => visitNode(node);
+  R visitSwitchStatement(SwitchStatement node) => visitStatement(node);
+  R visitThrow(Throw node) => visitStatement(node);
+  R visitTryStatement(TryStatement node) => visitStatement(node);
+  R visitTypeAnnotation(TypeAnnotation node) => visitNode(node);
+  R visitTypedef(Typedef node) => visitNode(node);
+  R visitTypeVariable(TypeVariable node) => visitNode(node);
+  R visitVariableDefinitions(VariableDefinitions node) => visitStatement(node);
+  R visitWhile(While node) => visitLoop(node);
+}
+
+Token firstBeginToken(Node first, Node second) {
+  Token token = null;
+  if (first != null) {
+    token = first.getBeginToken();
+  }
+  if (token == null && second != null) {
+    // [token] might be null even when [first] is not, e.g. for empty Modifiers.
+    token = second.getBeginToken();
+  }
+  return token;
+}
+
+/**
+ * A node in a syntax tree.
+ *
+ * The abstract part of "abstract syntax tree" is invalidated when
+ * supporting tools such as code formatting. These tools need concrete
+ * syntax such as parentheses and no constant folding.
+ *
+ * We support these tools by storing additional references back to the
+ * token stream. These references are stored in fields ending with
+ * "Token".
+ */
+abstract class Node extends TreeElementMixin implements Spannable {
+  final int hashCode;
+  static int _HASH_COUNTER = 0;
+
+  Node() : hashCode = ++_HASH_COUNTER;
+
+  accept(Visitor visitor);
+
+  visitChildren(Visitor visitor);
+
+  /**
+   * Returns this node unparsed to Dart source string.
+   */
+  toString() => unparse(this);
+
+  /**
+   * Returns Xml-like tree representation of this node.
+   */
+  toDebugString() {
+    return PrettyPrinter.prettyPrint(this);
+  }
+
+  String getObjectDescription() => super.toString();
+
+  Token getBeginToken();
+
+  Token getEndToken();
+
+  Block asBlock() => null;
+  BreakStatement asBreakStatement() => null;
+  Cascade asCascade() => null;
+  CascadeReceiver asCascadeReceiver() => null;
+  CaseMatch asCaseMatch() => null;
+  CatchBlock asCatchBlock() => null;
+  ClassNode asClassNode() => null;
+  Combinator asCombinator() => null;
+  Conditional asConditional() => null;
+  ContinueStatement asContinueStatement() => null;
+  DoWhile asDoWhile() => null;
+  EmptyStatement asEmptyStatement() => null;
+  Export asExport() => null;
+  Expression asExpression() => null;
+  ExpressionStatement asExpressionStatement() => null;
+  For asFor() => null;
+  ForIn asForIn() => null;
+  FunctionDeclaration asFunctionDeclaration() => null;
+  FunctionExpression asFunctionExpression() => null;
+  Identifier asIdentifier() => null;
+  If asIf() => null;
+  Import asImport() => null;
+  Label asLabel() => null;
+  LabeledStatement asLabeledStatement() => null;
+  LibraryName asLibraryName() => null;
+  LiteralBool asLiteralBool() => null;
+  LiteralDouble asLiteralDouble() => null;
+  LiteralInt asLiteralInt() => null;
+  LiteralList asLiteralList() => null;
+  LiteralMap asLiteralMap() => null;
+  LiteralMapEntry asLiteralMapEntry() => null;
+  LiteralNull asLiteralNull() => null;
+  LiteralString asLiteralString() => null;
+  MixinApplication asMixinApplication() => null;
+  Modifiers asModifiers() => null;
+  NamedArgument asNamedArgument() => null;
+  NamedMixinApplication asNamedMixinApplication() => null;
+  NodeList asNodeList() => null;
+  Operator asOperator() => null;
+  ParenthesizedExpression asParenthesizedExpression() => null;
+  Part asPart() => null;
+  PartOf asPartOf() => null;
+  Return asReturn() => null;
+  ScriptTag asScriptTag() => null;
+  Send asSend() => null;
+  SendSet asSendSet() => null;
+  Statement asStatement() => null;
+  StringInterpolation asStringInterpolation() => null;
+  StringInterpolationPart asStringInterpolationPart() => null;
+  StringJuxtaposition asStringJuxtaposition() => null;
+  StringNode asStringNode() => null;
+  SwitchCase asSwitchCase() => null;
+  SwitchStatement asSwitchStatement() => null;
+  Throw asThrow() => null;
+  TryStatement asTryStatement() => null;
+  TypeAnnotation asTypeAnnotation() => null;
+  TypeVariable asTypeVariable() => null;
+  Typedef asTypedef() => null;
+  VariableDefinitions asVariableDefinitions() => null;
+  While asWhile() => null;
+
+  bool isValidBreakTarget() => false;
+  bool isValidContinueTarget() => false;
+}
+
+class ClassNode extends Node {
+  final Modifiers modifiers;
+  final Identifier name;
+  final Node superclass;
+  final NodeList interfaces;
+  final NodeList typeParameters;
+  final NodeList body;
+
+  // TODO(ahe, karlklose): the default keyword is not recorded.
+  final TypeAnnotation defaultClause;
+
+  final Token beginToken;
+  final Token extendsKeyword;
+  final Token endToken;
+
+  ClassNode(this.modifiers, this.name, this.typeParameters, this.superclass,
+            this.interfaces, this.defaultClause, this.beginToken,
+            this.extendsKeyword, this.body, this.endToken);
+
+  ClassNode asClassNode() => this;
+
+  accept(Visitor visitor) => visitor.visitClassNode(this);
+
+  visitChildren(Visitor visitor) {
+    if (name != null) name.accept(visitor);
+    if (typeParameters != null) typeParameters.accept(visitor);
+    if (superclass != null) superclass.accept(visitor);
+    if (interfaces != null) interfaces.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  bool get isInterface => identical(beginToken.stringValue, 'interface');
+
+  bool get isClass => !isInterface;
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => endToken;
+}
+
+class MixinApplication extends Node {
+  final TypeAnnotation superclass;
+  final NodeList mixins;
+
+  MixinApplication(this.superclass, this.mixins);
+
+  MixinApplication asMixinApplication() => this;
+
+  accept(Visitor visitor) => visitor.visitMixinApplication(this);
+
+  visitChildren(Visitor visitor) {
+    if (superclass != null) superclass.accept(visitor);
+    if (mixins != null) mixins.accept(visitor);
+  }
+
+  Token getBeginToken() => superclass.getBeginToken();
+  Token getEndToken() => mixins.getEndToken();
+}
+
+// TODO(kasperl): Let this share some structure with the typedef for function
+// type aliases?
+class NamedMixinApplication extends Node implements MixinApplication {
+  final Identifier name;
+  final NodeList typeParameters;
+
+  final Modifiers modifiers;
+  final MixinApplication mixinApplication;
+  final NodeList interfaces;
+
+  final Token typedefKeyword;
+  final Token endToken;
+
+  NamedMixinApplication(this.name, this.typeParameters,
+                        this.modifiers, this.mixinApplication, this.interfaces,
+                        this.typedefKeyword, this.endToken);
+
+  TypeAnnotation get superclass => mixinApplication.superclass;
+  NodeList get mixins => mixinApplication.mixins;
+
+  MixinApplication asMixinApplication() => this;
+  NamedMixinApplication asNamedMixinApplication() => this;
+
+  accept(Visitor visitor) => visitor.visitNamedMixinApplication(this);
+
+  visitChildren(Visitor visitor) {
+    name.accept(visitor);
+    if (typeParameters != null) typeParameters.accept(visitor);
+    if (modifiers != null) modifiers.accept(visitor);
+    if (interfaces != null) interfaces.accept(visitor);
+    mixinApplication.accept(visitor);
+  }
+
+  Token getBeginToken() => typedefKeyword;
+  Token getEndToken() => endToken;
+}
+
+abstract class Expression extends Node {
+  Expression();
+
+  Expression asExpression() => this;
+
+  // TODO(ahe): make class abstract instead of adding an abstract method.
+  accept(Visitor visitor);
+}
+
+abstract class Statement extends Node {
+  Statement();
+
+  Statement asStatement() => this;
+
+  // TODO(ahe): make class abstract instead of adding an abstract method.
+  accept(Visitor visitor);
+
+  bool isValidBreakTarget() => true;
+}
+
+/**
+ * A message send aka method invocation. In Dart, most operations can
+ * (and should) be considered as message sends. Getters and setters
+ * are just methods with a special syntax. Consequently, we model
+ * property access, assignment, operators, and method calls with this
+ * one node.
+ */
+class Send extends Expression {
+  final Node receiver;
+  final Node selector;
+  final NodeList argumentsNode;
+  Link<Node> get arguments => argumentsNode.nodes;
+
+  Send([this.receiver, this.selector, this.argumentsNode]);
+  Send.postfix(this.receiver, this.selector, [Node argument = null])
+      : argumentsNode = (argument == null)
+        ? new Postfix()
+        : new Postfix.singleton(argument);
+  Send.prefix(this.receiver, this.selector, [Node argument = null])
+      : argumentsNode = (argument == null)
+        ? new Prefix()
+        : new Prefix.singleton(argument);
+
+  Send asSend() => this;
+
+  accept(Visitor visitor) => visitor.visitSend(this);
+
+  visitChildren(Visitor visitor) {
+    if (receiver != null) receiver.accept(visitor);
+    if (selector != null) selector.accept(visitor);
+    if (argumentsNode != null) argumentsNode.accept(visitor);
+  }
+
+  int argumentCount() {
+    return (argumentsNode == null) ? -1 : argumentsNode.slowLength();
+  }
+
+  bool get isSuperCall {
+    return receiver != null &&
+           receiver.asIdentifier() != null &&
+           receiver.asIdentifier().isSuper();
+  }
+  bool get isOperator => selector is Operator;
+  bool get isPropertyAccess => argumentsNode == null;
+  bool get isFunctionObjectInvocation => selector == null;
+  bool get isPrefix => argumentsNode is Prefix;
+  bool get isPostfix => argumentsNode is Postfix;
+  bool get isCall => !isOperator && !isPropertyAccess;
+  bool get isIndex =>
+      isOperator && identical(selector.asOperator().source.stringValue, '[]');
+  bool get isLogicalAnd =>
+      isOperator && identical(selector.asOperator().source.stringValue, '&&');
+  bool get isLogicalOr =>
+      isOperator && identical(selector.asOperator().source.stringValue, '||');
+  bool get isParameterCheck =>
+      isOperator && identical(selector.asOperator().source.stringValue, '?');
+
+  Token getBeginToken() {
+    if (isPrefix && !isIndex) return selector.getBeginToken();
+    return firstBeginToken(receiver, selector);
+  }
+
+  Token getEndToken() {
+    if (isPrefix) {
+      if (receiver != null) return receiver.getEndToken();
+      if (selector != null) return selector.getEndToken();
+      return null;
+    }
+    if (!isPostfix && argumentsNode != null) {
+      return argumentsNode.getEndToken();
+    }
+    if (selector != null) return selector.getEndToken();
+    return receiver.getBeginToken();
+  }
+
+  Send copyWithReceiver(Node newReceiver) {
+    assert(receiver == null);
+    return new Send(newReceiver, selector, argumentsNode);
+  }
+}
+
+class Postfix extends NodeList {
+  Postfix() : super(null, const Link<Node>());
+  Postfix.singleton(Node argument) : super.singleton(argument);
+}
+
+class Prefix extends NodeList {
+  Prefix() : super(null, const Link<Node>());
+  Prefix.singleton(Node argument) : super.singleton(argument);
+}
+
+class SendSet extends Send {
+  final Operator assignmentOperator;
+  SendSet(receiver, selector, this.assignmentOperator, argumentsNode)
+    : super(receiver, selector, argumentsNode);
+  SendSet.postfix(receiver,
+                  selector,
+                  this.assignmentOperator,
+                  [Node argument = null])
+      : super.postfix(receiver, selector, argument);
+  SendSet.prefix(receiver,
+                 selector,
+                 this.assignmentOperator,
+                 [Node argument = null])
+      : super.prefix(receiver, selector, argument);
+
+  SendSet asSendSet() => this;
+
+  accept(Visitor visitor) => visitor.visitSendSet(this);
+
+  visitChildren(Visitor visitor) {
+    super.visitChildren(visitor);
+    if (assignmentOperator != null) assignmentOperator.accept(visitor);
+  }
+
+  Send copyWithReceiver(Node newReceiver) {
+    assert(receiver == null);
+    return new SendSet(newReceiver, selector, assignmentOperator,
+                       argumentsNode);
+  }
+
+  Token getBeginToken() {
+    if (isPrefix) return assignmentOperator.getBeginToken();
+    return super.getBeginToken();
+  }
+
+  Token getEndToken() {
+    if (isPostfix) return assignmentOperator.getEndToken();
+    return super.getEndToken();
+  }
+}
+
+class NewExpression extends Expression {
+  /** The token NEW or CONST */
+  final Token newToken;
+
+  // Note: we expect that send.receiver is null.
+  final Send send;
+
+  NewExpression([this.newToken, this.send]);
+
+  accept(Visitor visitor) => visitor.visitNewExpression(this);
+
+  visitChildren(Visitor visitor) {
+    if (send != null) send.accept(visitor);
+  }
+
+  bool isConst() {
+    return identical(newToken.stringValue, 'const')
+        || identical(newToken.stringValue, '@');
+  }
+
+  Token getBeginToken() => newToken;
+
+  Token getEndToken() => send.getEndToken();
+}
+
+class NodeList extends Node {
+  final Link<Node> nodes;
+  final Token beginToken;
+  final Token endToken;
+  final SourceString delimiter;
+  bool get isEmpty => nodes.isEmpty;
+
+  NodeList([this.beginToken, this.nodes, this.endToken, this.delimiter]);
+
+  Iterator<Node> get iterator => nodes.iterator;
+
+  NodeList.singleton(Node node) : this(null, const Link<Node>().prepend(node));
+  NodeList.empty() : this(null, const Link<Node>());
+
+  NodeList asNodeList() => this;
+
+  int slowLength() {
+    int result = 0;
+    for (Link<Node> cursor = nodes; !cursor.isEmpty; cursor = cursor.tail) {
+      result++;
+    }
+    return result;
+  }
+
+  accept(Visitor visitor) => visitor.visitNodeList(this);
+
+  visitChildren(Visitor visitor) {
+    if (nodes == null) return;
+    for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) {
+      if (link.head != null) link.head.accept(visitor);
+    }
+  }
+
+  Token getBeginToken() {
+    if (beginToken != null) return beginToken;
+     if (nodes != null) {
+       for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) {
+         if (link.head.getBeginToken() != null) {
+           return link.head.getBeginToken();
+         }
+         if (link.head.getEndToken() != null) {
+           return link.head.getEndToken();
+         }
+       }
+     }
+    return endToken;
+  }
+
+  Token getEndToken() {
+    if (endToken != null) return endToken;
+    if (nodes != null) {
+      Link<Node> link = nodes;
+      if (link.isEmpty) return beginToken;
+      while (!link.tail.isEmpty) link = link.tail;
+      if (link.head.getEndToken() != null) return link.head.getEndToken();
+      if (link.head.getBeginToken() != null) return link.head.getBeginToken();
+    }
+    return beginToken;
+  }
+}
+
+class Block extends Statement {
+  final NodeList statements;
+
+  Block(this.statements);
+
+  Block asBlock() => this;
+
+  accept(Visitor visitor) => visitor.visitBlock(this);
+
+  visitChildren(Visitor visitor) {
+    if (statements != null) statements.accept(visitor);
+  }
+
+  Token getBeginToken() => statements.getBeginToken();
+
+  Token getEndToken() => statements.getEndToken();
+}
+
+class If extends Statement {
+  final ParenthesizedExpression condition;
+  final Statement thenPart;
+  final Statement elsePart;
+
+  final Token ifToken;
+  final Token elseToken;
+
+  If(this.condition, this.thenPart, this.elsePart,
+     this.ifToken, this.elseToken);
+
+  If asIf() => this;
+
+  bool get hasElsePart => elsePart != null;
+
+  void validate() {
+    // TODO(ahe): Check that condition has size one.
+  }
+
+  accept(Visitor visitor) => visitor.visitIf(this);
+
+  visitChildren(Visitor visitor) {
+    if (condition != null) condition.accept(visitor);
+    if (thenPart != null) thenPart.accept(visitor);
+    if (elsePart != null) elsePart.accept(visitor);
+  }
+
+  Token getBeginToken() => ifToken;
+
+  Token getEndToken() {
+    if (elsePart == null) return thenPart.getEndToken();
+    return elsePart.getEndToken();
+  }
+}
+
+class Conditional extends Expression {
+  final Expression condition;
+  final Expression thenExpression;
+  final Expression elseExpression;
+
+  final Token questionToken;
+  final Token colonToken;
+
+  Conditional(this.condition, this.thenExpression,
+              this.elseExpression, this.questionToken, this.colonToken);
+
+  Conditional asConditional() => this;
+
+  accept(Visitor visitor) => visitor.visitConditional(this);
+
+  visitChildren(Visitor visitor) {
+    condition.accept(visitor);
+    thenExpression.accept(visitor);
+    elseExpression.accept(visitor);
+  }
+
+  Token getBeginToken() => condition.getBeginToken();
+
+  Token getEndToken() => elseExpression.getEndToken();
+}
+
+class For extends Loop {
+  /** Either a variable declaration or an expression. */
+  final Node initializer;
+  /** Either an expression statement or an empty statement. */
+  final Statement conditionStatement;
+  final NodeList update;
+
+  final Token forToken;
+
+  For(this.initializer, this.conditionStatement, this.update, body,
+      this.forToken) : super(body);
+
+  For asFor() => this;
+
+  Expression get condition {
+    if (conditionStatement is ExpressionStatement) {
+      return conditionStatement.asExpressionStatement().expression;
+    } else {
+      return null;
+    }
+  }
+
+  accept(Visitor visitor) => visitor.visitFor(this);
+
+  visitChildren(Visitor visitor) {
+    if (initializer != null) initializer.accept(visitor);
+    if (conditionStatement != null) conditionStatement.accept(visitor);
+    if (update != null) update.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  Token getBeginToken() => forToken;
+
+  Token getEndToken() {
+    return body.getEndToken();
+  }
+}
+
+class FunctionDeclaration extends Statement {
+  final FunctionExpression function;
+
+  FunctionDeclaration(this.function);
+
+  FunctionDeclaration asFunctionDeclaration() => this;
+
+  accept(Visitor visitor) => visitor.visitFunctionDeclaration(this);
+
+  visitChildren(Visitor visitor) => function.accept(visitor);
+
+  Token getBeginToken() => function.getBeginToken();
+  Token getEndToken() => function.getEndToken();
+}
+
+class FunctionExpression extends Expression {
+  final Node name;
+
+  /**
+   * List of VariableDefinitions or NodeList.
+   *
+   * A NodeList can only occur at the end and holds named parameters.
+   */
+  final NodeList parameters;
+
+  final Statement body;
+  final TypeAnnotation returnType;
+  final Modifiers modifiers;
+  final NodeList initializers;
+
+  final Token getOrSet;
+
+  FunctionExpression(this.name, this.parameters, this.body, this.returnType,
+                     this.modifiers, this.initializers, this.getOrSet) {
+    assert(modifiers != null);
+  }
+
+  FunctionExpression asFunctionExpression() => this;
+
+  accept(Visitor visitor) => visitor.visitFunctionExpression(this);
+
+  visitChildren(Visitor visitor) {
+    if (modifiers != null) modifiers.accept(visitor);
+    if (returnType != null) returnType.accept(visitor);
+    if (name != null) name.accept(visitor);
+    if (parameters != null) parameters.accept(visitor);
+    if (initializers != null) initializers.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  bool hasBody() => body.asEmptyStatement() == null;
+
+  bool hasEmptyBody() {
+    Block block = body.asBlock();
+    if (block == null) return false;
+    return block.statements.isEmpty;
+  }
+
+  Token getBeginToken() {
+    Token token = firstBeginToken(modifiers, returnType);
+    if (token != null) return token;
+    if (getOrSet != null) return getOrSet;
+    return firstBeginToken(name, parameters);
+  }
+
+  Token getEndToken() {
+    Token token = (body == null) ? null : body.getEndToken();
+    token = (token == null) ? parameters.getEndToken() : token;
+    return (token == null) ? name.getEndToken() : token;
+  }
+}
+
+typedef void DecodeErrorHandler(Token token, var error);
+
+abstract class Literal<T> extends Expression {
+  final Token token;
+  final DecodeErrorHandler handler;
+
+  Literal(Token this.token, DecodeErrorHandler this.handler);
+
+  T get value;
+
+  visitChildren(Visitor visitor) {}
+
+  Token getBeginToken() => token;
+
+  Token getEndToken() => token;
+}
+
+class LiteralInt extends Literal<int> {
+  LiteralInt(Token token, DecodeErrorHandler handler) : super(token, handler);
+
+  LiteralInt asLiteralInt() => this;
+
+  int get value {
+    try {
+      Token valueToken = token;
+      if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next;
+      return int.parse(valueToken.value.slowToString());
+    } on FormatException catch (ex) {
+      (this.handler)(token, ex);
+    }
+  }
+
+  accept(Visitor visitor) => visitor.visitLiteralInt(this);
+}
+
+class LiteralDouble extends Literal<double> {
+  LiteralDouble(Token token, DecodeErrorHandler handler)
+    : super(token, handler);
+
+  LiteralDouble asLiteralDouble() => this;
+
+  double get value {
+    try {
+      Token valueToken = token;
+      if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next;
+      return double.parse(valueToken.value.slowToString());
+    } on FormatException catch (ex) {
+      (this.handler)(token, ex);
+    }
+  }
+
+  accept(Visitor visitor) => visitor.visitLiteralDouble(this);
+}
+
+class LiteralBool extends Literal<bool> {
+  LiteralBool(Token token, DecodeErrorHandler handler) : super(token, handler);
+
+  LiteralBool asLiteralBool() => this;
+
+  bool get value {
+    if (identical(token.stringValue, 'true')) return true;
+    if (identical(token.stringValue, 'false')) return false;
+    (this.handler)(token, "not a bool ${token.value}");
+  }
+
+  accept(Visitor visitor) => visitor.visitLiteralBool(this);
+}
+
+
+class StringQuoting {
+  static const StringQuoting SINGLELINE_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 1);
+  static const StringQuoting RAW_SINGLELINE_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 1);
+  static const StringQuoting MULTILINE_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 3);
+  static const StringQuoting RAW_MULTILINE_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 3);
+  static const StringQuoting MULTILINE_NL_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 4);
+  static const StringQuoting RAW_MULTILINE_NL_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 4);
+  static const StringQuoting MULTILINE_NL2_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 5);
+  static const StringQuoting RAW_MULTILINE_NL2_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 5);
+  static const StringQuoting SINGLELINE_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 1);
+  static const StringQuoting RAW_SINGLELINE_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 1);
+  static const StringQuoting MULTILINE_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 3);
+  static const StringQuoting RAW_MULTILINE_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 3);
+  static const StringQuoting MULTILINE_NL_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 4);
+  static const StringQuoting RAW_MULTILINE_NL_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 4);
+  static const StringQuoting MULTILINE_NL2_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 5);
+  static const StringQuoting RAW_MULTILINE_NL2_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 5);
+
+
+  static const List<StringQuoting> mapping = const <StringQuoting>[
+    SINGLELINE_DQ,
+    RAW_SINGLELINE_DQ,
+    MULTILINE_DQ,
+    RAW_MULTILINE_DQ,
+    MULTILINE_NL_DQ,
+    RAW_MULTILINE_NL_DQ,
+    MULTILINE_NL2_DQ,
+    RAW_MULTILINE_NL2_DQ,
+    SINGLELINE_SQ,
+    RAW_SINGLELINE_SQ,
+    MULTILINE_SQ,
+    RAW_MULTILINE_SQ,
+    MULTILINE_NL_SQ,
+    RAW_MULTILINE_NL_SQ,
+    MULTILINE_NL2_SQ,
+    RAW_MULTILINE_NL2_SQ
+  ];
+  final bool raw;
+  final int leftQuoteCharCount;
+  final int quote;
+  const StringQuoting(this.quote, {bool raw, int leftQuoteLength})
+      : this.raw = raw, this.leftQuoteCharCount = leftQuoteLength;
+  String get quoteChar => identical(quote, $DQ) ? '"' : "'";
+
+  int get leftQuoteLength => (raw ? 1 : 0) + leftQuoteCharCount;
+  int get rightQuoteLength => (leftQuoteCharCount > 2) ? 3 : 1;
+  static StringQuoting getQuoting(int quote, bool raw, int quoteLength) {
+    int index = quoteLength - 1;
+    if (quoteLength > 2) index -= 1;
+    return mapping[(raw ? 1 : 0) + index * 2 + (identical(quote, $SQ) ? 8 : 0)];
+  }
+}
+
+/**
+  * Superclass for classes representing string literals.
+  */
+abstract class StringNode extends Expression {
+  DartString get dartString;
+  bool get isInterpolation;
+
+  StringNode asStringNode() => this;
+}
+
+class LiteralString extends StringNode {
+  final Token token;
+  /** Non-null on validated string literals. */
+  final DartString dartString;
+
+  LiteralString(this.token, this.dartString);
+
+  LiteralString asLiteralString() => this;
+
+  void visitChildren(Visitor visitor) {}
+
+  bool get isInterpolation => false;
+  bool isValidated() => dartString != null;
+
+  Token getBeginToken() => token;
+  Token getEndToken() => token;
+
+  accept(Visitor visitor) => visitor.visitLiteralString(this);
+}
+
+class LiteralNull extends Literal<SourceString> {
+  LiteralNull(Token token) : super(token, null);
+
+  LiteralNull asLiteralNull() => this;
+
+  SourceString get value => null;
+
+  accept(Visitor visitor) => visitor.visitLiteralNull(this);
+}
+
+class LiteralList extends Expression {
+  final NodeList typeArguments;
+  final NodeList elements;
+
+  final Token constKeyword;
+
+  LiteralList(this.typeArguments, this.elements, this.constKeyword);
+
+  bool isConst() => constKeyword != null;
+
+  LiteralList asLiteralList() => this;
+  accept(Visitor visitor) => visitor.visitLiteralList(this);
+
+  visitChildren(Visitor visitor) {
+    if (typeArguments != null) typeArguments.accept(visitor);
+    elements.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    if (constKeyword != null) return constKeyword;
+    return firstBeginToken(typeArguments, elements);
+  }
+
+  Token getEndToken() => elements.getEndToken();
+}
+
+class Identifier extends Expression {
+  final Token token;
+
+  SourceString get source => token.value;
+
+  Identifier(Token this.token);
+
+  bool isThis() => identical(source.stringValue, 'this');
+
+  bool isSuper() => identical(source.stringValue, 'super');
+
+  Identifier asIdentifier() => this;
+
+  accept(Visitor visitor) => visitor.visitIdentifier(this);
+
+  visitChildren(Visitor visitor) {}
+
+  Token getBeginToken() => token;
+
+  Token getEndToken() => token;
+}
+
+class Operator extends Identifier {
+  Operator(Token token) : super(token);
+
+  Operator asOperator() => this;
+
+  accept(Visitor visitor) => visitor.visitOperator(this);
+}
+
+class Return extends Statement {
+  final Node expression;
+  final Token beginToken;
+  final Token endToken;
+
+  Return(this.beginToken, this.endToken, this.expression);
+
+  Return asReturn() => this;
+
+  bool get hasExpression => expression != null;
+
+  bool get isRedirectingFactoryBody => beginToken.stringValue == '=';
+
+  accept(Visitor visitor) => visitor.visitReturn(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() {
+    if (endToken == null) return expression.getEndToken();
+    return endToken;
+  }
+}
+
+class ExpressionStatement extends Statement {
+  final Expression expression;
+  final Token endToken;
+
+  ExpressionStatement(this.expression, this.endToken);
+
+  ExpressionStatement asExpressionStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitExpressionStatement(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => endToken;
+}
+
+class Throw extends Statement {
+  final Expression expression;
+
+  final Token throwToken;
+  final Token endToken;
+
+  Throw(this.expression, this.throwToken, this.endToken);
+
+  Throw asThrow() => this;
+
+  accept(Visitor visitor) => visitor.visitThrow(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => throwToken;
+
+  Token getEndToken() => endToken;
+}
+
+class TypeAnnotation extends Node {
+  final Expression typeName;
+  final NodeList typeArguments;
+
+  TypeAnnotation(Expression this.typeName, NodeList this.typeArguments);
+
+  TypeAnnotation asTypeAnnotation() => this;
+
+  accept(Visitor visitor) => visitor.visitTypeAnnotation(this);
+
+  visitChildren(Visitor visitor) {
+    typeName.accept(visitor);
+    if (typeArguments != null) typeArguments.accept(visitor);
+  }
+
+  Token getBeginToken() => typeName.getBeginToken();
+
+  Token getEndToken() => typeName.getEndToken();
+}
+
+class TypeVariable extends Node {
+  final Identifier name;
+  final TypeAnnotation bound;
+  TypeVariable(Identifier this.name, TypeAnnotation this.bound);
+
+  accept(Visitor visitor) => visitor.visitTypeVariable(this);
+
+  visitChildren(Visitor visitor) {
+    name.accept(visitor);
+    if (bound != null) {
+      bound.accept(visitor);
+    }
+  }
+
+  TypeVariable asTypeVariable() => this;
+
+  Token getBeginToken() => name.getBeginToken();
+
+  Token getEndToken() {
+    return (bound != null) ? bound.getEndToken() : name.getEndToken();
+  }
+}
+
+class VariableDefinitions extends Statement {
+  final TypeAnnotation type;
+  final Modifiers modifiers;
+  final NodeList definitions;
+  VariableDefinitions(this.type, this.modifiers, this.definitions) {
+    assert(modifiers != null);
+  }
+
+  VariableDefinitions asVariableDefinitions() => this;
+
+  accept(Visitor visitor) => visitor.visitVariableDefinitions(this);
+
+  visitChildren(Visitor visitor) {
+    if (type != null) type.accept(visitor);
+    if (definitions != null) definitions.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    var token = firstBeginToken(modifiers, type);
+    if (token == null) {
+      token = definitions.getBeginToken();
+    }
+    return token;
+  }
+
+  Token getEndToken() => definitions.getEndToken();
+}
+
+abstract class Loop extends Statement {
+  Expression get condition;
+  final Statement body;
+
+  Loop(this.body);
+
+  bool isValidContinueTarget() => true;
+}
+
+class DoWhile extends Loop {
+  final Token doKeyword;
+  final Token whileKeyword;
+  final Token endToken;
+
+  final Expression condition;
+
+  DoWhile(Statement body, Expression this.condition,
+          Token this.doKeyword, Token this.whileKeyword, Token this.endToken)
+    : super(body);
+
+  DoWhile asDoWhile() => this;
+
+  accept(Visitor visitor) => visitor.visitDoWhile(this);
+
+  visitChildren(Visitor visitor) {
+    if (condition != null) condition.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  Token getBeginToken() => doKeyword;
+
+  Token getEndToken() => endToken;
+}
+
+class While extends Loop {
+  final Token whileKeyword;
+  final Expression condition;
+
+  While(Expression this.condition, Statement body,
+        Token this.whileKeyword) : super(body);
+
+  While asWhile() => this;
+
+  accept(Visitor visitor) => visitor.visitWhile(this);
+
+  visitChildren(Visitor visitor) {
+    if (condition != null) condition.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  Token getBeginToken() => whileKeyword;
+
+  Token getEndToken() => body.getEndToken();
+}
+
+class ParenthesizedExpression extends Expression {
+  final Expression expression;
+  final BeginGroupToken beginToken;
+
+  ParenthesizedExpression(Expression this.expression,
+                          BeginGroupToken this.beginToken);
+
+  ParenthesizedExpression asParenthesizedExpression() => this;
+
+  accept(Visitor visitor) => visitor.visitParenthesizedExpression(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => beginToken.endGroup;
+}
+
+/** Representation of modifiers such as static, abstract, final, etc. */
+class Modifiers extends Node {
+  /**
+   * Pseudo-constant for empty modifiers.
+   */
+  static final Modifiers EMPTY = new Modifiers(new NodeList.empty());
+
+  /* TODO(ahe): The following should be validated relating to modifiers:
+   * 1. The nodes must come in a certain order.
+   * 2. The keywords "var" and "final" may not be used at the same time.
+   * 3. The keywords "abstract" and "external" may not be used at the same time.
+   * 4. The type of an element must be null if isVar() is true.
+   */
+
+  final NodeList nodes;
+  /** Bit pattern to easy check what modifiers are present. */
+  final int flags;
+
+  static const int FLAG_STATIC = 1;
+  static const int FLAG_ABSTRACT = FLAG_STATIC << 1;
+  static const int FLAG_FINAL = FLAG_ABSTRACT << 1;
+  static const int FLAG_VAR = FLAG_FINAL << 1;
+  static const int FLAG_CONST = FLAG_VAR << 1;
+  static const int FLAG_FACTORY = FLAG_CONST << 1;
+  static const int FLAG_EXTERNAL = FLAG_FACTORY << 1;
+
+  Modifiers(NodeList nodes) : this.withFlags(nodes, computeFlags(nodes.nodes));
+
+  Modifiers.withFlags(this.nodes, this.flags);
+
+  static int computeFlags(Link<Node> nodes) {
+    int flags = 0;
+    for (; !nodes.isEmpty; nodes = nodes.tail) {
+      String value = nodes.head.asIdentifier().source.stringValue;
+      if (identical(value, 'static')) flags |= FLAG_STATIC;
+      else if (identical(value, 'abstract')) flags |= FLAG_ABSTRACT;
+      else if (identical(value, 'final')) flags |= FLAG_FINAL;
+      else if (identical(value, 'var')) flags |= FLAG_VAR;
+      else if (identical(value, 'const')) flags |= FLAG_CONST;
+      else if (identical(value, 'factory')) flags |= FLAG_FACTORY;
+      else if (identical(value, 'external')) flags |= FLAG_EXTERNAL;
+      else throw 'internal error: ${nodes.head}';
+    }
+    return flags;
+  }
+
+  Node findModifier(String modifier) {
+    Link<Node> nodeList = nodes.nodes;
+    for (; !nodeList.isEmpty; nodeList = nodeList.tail) {
+      String value = nodeList.head.asIdentifier().source.stringValue;
+      if(identical(value, modifier)) {
+        return nodeList.head;
+      }
+    }
+    return null;
+  }
+
+  Modifiers asModifiers() => this;
+  Token getBeginToken() => nodes.getBeginToken();
+  Token getEndToken() => nodes.getEndToken();
+  accept(Visitor visitor) => visitor.visitModifiers(this);
+  visitChildren(Visitor visitor) => nodes.accept(visitor);
+
+  bool isStatic() => (flags & FLAG_STATIC) != 0;
+  bool isAbstract() => (flags & FLAG_ABSTRACT) != 0;
+  bool isFinal() => (flags & FLAG_FINAL) != 0;
+  bool isVar() => (flags & FLAG_VAR) != 0;
+  bool isConst() => (flags & FLAG_CONST) != 0;
+  bool isFactory() => (flags & FLAG_FACTORY) != 0;
+  bool isExternal() => (flags & FLAG_EXTERNAL) != 0;
+
+  Node getStatic() => findModifier('static');
+
+  /**
+   * Use this to check if the declaration is either explicitly or implicitly
+   * final.
+   */
+  bool isFinalOrConst() => isFinal() || isConst();
+
+  String toString() {
+    LinkBuilder<String> builder = new LinkBuilder<String>();
+    if (isStatic()) builder.addLast('static');
+    if (isAbstract()) builder.addLast('abstract');
+    if (isFinal()) builder.addLast('final');
+    if (isVar()) builder.addLast('var');
+    if (isConst()) builder.addLast('const');
+    if (isFactory()) builder.addLast('factory');
+    if (isExternal()) builder.addLast('external');
+    StringBuffer buffer = new StringBuffer();
+    builder.toLink().printOn(buffer, ', ');
+    return buffer.toString();
+  }
+}
+
+class StringInterpolation extends StringNode {
+  final LiteralString string;
+  final NodeList parts;
+
+  StringInterpolation(this.string, this.parts);
+
+  StringInterpolation asStringInterpolation() => this;
+
+  DartString get dartString => null;
+  bool get isInterpolation => true;
+
+  accept(Visitor visitor) => visitor.visitStringInterpolation(this);
+
+  visitChildren(Visitor visitor) {
+    string.accept(visitor);
+    parts.accept(visitor);
+  }
+
+  Token getBeginToken() => string.getBeginToken();
+  Token getEndToken() => parts.getEndToken();
+}
+
+class StringInterpolationPart extends Node {
+  final Expression expression;
+  final LiteralString string;
+
+  StringInterpolationPart(this.expression, this.string);
+
+  StringInterpolationPart asStringInterpolationPart() => this;
+
+  accept(Visitor visitor) => visitor.visitStringInterpolationPart(this);
+
+  visitChildren(Visitor visitor) {
+    expression.accept(visitor);
+    string.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => string.getEndToken();
+}
+
+/**
+ * A class representing juxtaposed string literals.
+ * The string literals can be both plain literals and string interpolations.
+ */
+class StringJuxtaposition extends StringNode {
+  final Expression first;
+  final Expression second;
+
+  /**
+   * Caches the check for whether this juxtaposition contains a string
+   * interpolation
+   */
+  bool isInterpolationCache = null;
+
+  /**
+   * Caches a Dart string representation of the entire juxtaposition's
+   * content. Only juxtapositions that don't (transitively) contains
+   * interpolations have a static representation.
+   */
+  DartString dartStringCache = null;
+
+  StringJuxtaposition(this.first, this.second);
+
+  StringJuxtaposition asStringJuxtaposition() => this;
+
+  bool get isInterpolation {
+    if (isInterpolationCache == null) {
+      isInterpolationCache = (first.accept(const IsInterpolationVisitor()) ||
+                          second.accept(const IsInterpolationVisitor()));
+    }
+    return isInterpolationCache;
+  }
+
+  /**
+   * Retrieve a single DartString that represents this entire juxtaposition
+   * of string literals.
+   * Should only be called if [isInterpolation] returns false.
+   */
+  DartString get dartString {
+    if (isInterpolation) {
+      throw new SpannableAssertionFailure(
+          this, "Getting dartString on interpolation;");
+    }
+    if (dartStringCache == null) {
+      DartString firstString = first.accept(const GetDartStringVisitor());
+      DartString secondString = second.accept(const GetDartStringVisitor());
+      if (firstString == null || secondString == null) {
+        return null;
+      }
+      dartStringCache = new DartString.concat(firstString, secondString);
+    }
+    return dartStringCache;
+  }
+
+  accept(Visitor visitor) => visitor.visitStringJuxtaposition(this);
+
+  void visitChildren(Visitor visitor) {
+    first.accept(visitor);
+    second.accept(visitor);
+  }
+
+  Token getBeginToken() => first.getBeginToken();
+
+  Token getEndToken() => second.getEndToken();
+}
+
+class EmptyStatement extends Statement {
+  final Token semicolonToken;
+
+  EmptyStatement(this.semicolonToken);
+
+  EmptyStatement asEmptyStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitEmptyStatement(this);
+
+  visitChildren(Visitor visitor) {}
+
+  Token getBeginToken() => semicolonToken;
+
+  Token getEndToken() => semicolonToken;
+}
+
+class LiteralMap extends Expression {
+  final NodeList typeArguments;
+  final NodeList entries;
+
+  final Token constKeyword;
+
+  LiteralMap(this.typeArguments, this.entries, this.constKeyword);
+
+  bool isConst() => constKeyword != null;
+
+  LiteralMap asLiteralMap() => this;
+
+  accept(Visitor visitor) => visitor.visitLiteralMap(this);
+
+  visitChildren(Visitor visitor) {
+    if (typeArguments != null) typeArguments.accept(visitor);
+    entries.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    if (constKeyword != null) return constKeyword;
+    return firstBeginToken(typeArguments, entries);
+  }
+
+  Token getEndToken() => entries.getEndToken();
+}
+
+class LiteralMapEntry extends Node {
+  final Expression key;
+  final Expression value;
+
+  final Token colonToken;
+
+  LiteralMapEntry(this.key, this.colonToken, this.value);
+
+  LiteralMapEntry asLiteralMapEntry() => this;
+
+  accept(Visitor visitor) => visitor.visitLiteralMapEntry(this);
+
+  visitChildren(Visitor visitor) {
+    key.accept(visitor);
+    value.accept(visitor);
+  }
+
+  Token getBeginToken() => key.getBeginToken();
+
+  Token getEndToken() => value.getEndToken();
+}
+
+class NamedArgument extends Expression {
+  final Identifier name;
+  final Expression expression;
+
+  final Token colonToken;
+
+  NamedArgument(this.name, this.colonToken, this.expression);
+
+  NamedArgument asNamedArgument() => this;
+
+  accept(Visitor visitor) => visitor.visitNamedArgument(this);
+
+  visitChildren(Visitor visitor) {
+    name.accept(visitor);
+    expression.accept(visitor);
+  }
+
+  Token getBeginToken() => name.getBeginToken();
+
+  Token getEndToken() => expression.getEndToken();
+}
+
+class SwitchStatement extends Statement {
+  final ParenthesizedExpression parenthesizedExpression;
+  final NodeList cases;
+
+  final Token switchKeyword;
+
+  SwitchStatement(this.parenthesizedExpression, this.cases,
+                  this.switchKeyword);
+
+  SwitchStatement asSwitchStatement() => this;
+
+  Expression get expression => parenthesizedExpression.expression;
+
+  accept(Visitor visitor) => visitor.visitSwitchStatement(this);
+
+  visitChildren(Visitor visitor) {
+    parenthesizedExpression.accept(visitor);
+    cases.accept(visitor);
+  }
+
+  Token getBeginToken() => switchKeyword;
+
+  Token getEndToken() => cases.getEndToken();
+}
+
+class CaseMatch extends Node {
+  final Token caseKeyword;
+  final Expression expression;
+  final Token colonToken;
+  CaseMatch(this.caseKeyword, this.expression, this.colonToken);
+
+  CaseMatch asCaseMatch() => this;
+  Token getBeginToken() => caseKeyword;
+  Token getEndToken() => colonToken;
+  accept(Visitor visitor) => visitor.visitCaseMatch(this);
+  visitChildren(Visitor visitor) => expression.accept(visitor);
+}
+
+class SwitchCase extends Node {
+  // The labels and case patterns are collected in [labelsAndCases].
+  // The default keyword, if present, is collected in [defaultKeyword].
+  // Any actual switch case must have at least one 'case' or 'default'
+  // clause.
+  // Notice: The labels and cases can occur interleaved in the source.
+  // They are separated here, since the order is irrelevant to the meaning
+  // of the switch.
+
+  /** List of [Label] and [CaseMatch] nodes. */
+  final NodeList labelsAndCases;
+  /** A "default" keyword token, if applicable. */
+  final Token defaultKeyword;
+  /** List of statements, the body of the case. */
+  final NodeList statements;
+
+  final Token startToken;
+
+  SwitchCase(this.labelsAndCases, this.defaultKeyword,
+             this.statements, this.startToken);
+
+  SwitchCase asSwitchCase() => this;
+
+  bool get isDefaultCase => defaultKeyword != null;
+
+  bool isValidContinueTarget() => true;
+
+  accept(Visitor visitor) => visitor.visitSwitchCase(this);
+
+  visitChildren(Visitor visitor) {
+    labelsAndCases.accept(visitor);
+    statements.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    return startToken;
+  }
+
+  Token getEndToken() {
+    if (statements.nodes.isEmpty) {
+      // All cases must have at least one expression or be the default.
+      if (defaultKeyword != null) {
+        // The colon after 'default'.
+        return defaultKeyword.next;
+      }
+      // The colon after the last expression.
+      return labelsAndCases.getEndToken();
+    } else {
+      return statements.getEndToken();
+    }
+  }
+}
+
+abstract class GotoStatement extends Statement {
+  final Identifier target;
+  final Token keywordToken;
+  final Token semicolonToken;
+
+  GotoStatement(this.target, this.keywordToken, this.semicolonToken);
+
+  visitChildren(Visitor visitor) {
+    if (target != null) target.accept(visitor);
+  }
+
+  Token getBeginToken() => keywordToken;
+
+  Token getEndToken() => semicolonToken;
+
+  // TODO(ahe): make class abstract instead of adding an abstract method.
+  accept(Visitor visitor);
+}
+
+class BreakStatement extends GotoStatement {
+  BreakStatement(Identifier target, Token keywordToken, Token semicolonToken)
+    : super(target, keywordToken, semicolonToken);
+
+  BreakStatement asBreakStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitBreakStatement(this);
+}
+
+class ContinueStatement extends GotoStatement {
+  ContinueStatement(Identifier target, Token keywordToken, Token semicolonToken)
+    : super(target, keywordToken, semicolonToken);
+
+  ContinueStatement asContinueStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitContinueStatement(this);
+}
+
+class ForIn extends Loop {
+  final Node declaredIdentifier;
+  final Expression expression;
+
+  final Token forToken;
+  final Token inToken;
+
+  ForIn(this.declaredIdentifier, this.expression,
+        Statement body, this.forToken, this.inToken) : super(body);
+
+  Expression get condition => null;
+
+  ForIn asForIn() => this;
+
+  accept(Visitor visitor) => visitor.visitForIn(this);
+
+  visitChildren(Visitor visitor) {
+    declaredIdentifier.accept(visitor);
+    expression.accept(visitor);
+    body.accept(visitor);
+  }
+
+  Token getBeginToken() => forToken;
+
+  Token getEndToken() => body.getEndToken();
+}
+
+class Label extends Node {
+  final Identifier identifier;
+  final Token colonToken;
+
+  Label(this.identifier, this.colonToken);
+
+  String slowToString() => identifier.source.slowToString();
+
+  Label asLabel() => this;
+
+  accept(Visitor visitor) => visitor.visitLabel(this);
+
+  void visitChildren(Visitor visitor) {
+    identifier.accept(visitor);
+  }
+
+  Token getBeginToken() => identifier.token;
+  Token getEndToken() => colonToken;
+}
+
+class LabeledStatement extends Statement {
+  final NodeList labels;
+  final Statement statement;
+
+  LabeledStatement(this.labels, this.statement);
+
+  LabeledStatement asLabeledStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitLabeledStatement(this);
+
+  visitChildren(Visitor visitor) {
+    labels.accept(visitor);
+    statement.accept(visitor);
+  }
+
+  Token getBeginToken() => labels.getBeginToken();
+
+  Token getEndToken() => statement.getEndToken();
+
+  bool isValidContinueTarget() => statement.isValidContinueTarget();
+
+  Node getBody() => statement;
+}
+
+class ScriptTag extends Node {
+  final Identifier tag;
+  final StringNode argument;
+  final Identifier prefixIdentifier;
+  final StringNode prefix;
+
+  final Token beginToken;
+  final Token endToken;
+
+  ScriptTag(this.tag, this.argument, this.prefixIdentifier, this.prefix,
+            this.beginToken, this.endToken);
+
+  bool isImport() => tag.source == const SourceString("import");
+  bool isSource() => tag.source == const SourceString("source");
+  bool isLibrary() => tag.source == const SourceString("library");
+
+  ScriptTag asScriptTag() => this;
+
+  accept(Visitor visitor) => visitor.visitScriptTag(this);
+
+  visitChildren(Visitor visitor) {
+    tag.accept(visitor);
+    argument.accept(visitor);
+    if (prefixIdentifier != null) prefixIdentifier.accept(visitor);
+    if (prefix != null) prefix.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => endToken;
+
+  LibraryTag toLibraryTag() {
+    if (isImport()) {
+      Identifier prefixNode;
+      if (prefix != null) {
+        SourceString source = prefix.dartString.source;
+        Token prefixToken = prefix.getBeginToken();
+        Token token = new StringToken.fromSource(IDENTIFIER_INFO, source,
+                                                 prefixToken.charOffset);
+        token.next = prefixToken.next;
+        prefixNode = new Identifier(token);
+      }
+      return new Import(tag.token, argument, prefixNode, null, null);
+    } else if (isLibrary()) {
+      return new LibraryName(tag.token, argument, null);
+    } else if (isSource()) {
+      return new Part(tag.token, argument, null);
+    } else {
+      throw 'Unknown script tag ${tag.token.slowToString()}';
+    }
+  }
+}
+
+abstract class LibraryTag extends Node {
+  final Link<MetadataAnnotation> metadata;
+
+  LibraryTag(this.metadata);
+
+  bool get isLibraryName => false;
+  bool get isImport => false;
+  bool get isExport => false;
+  bool get isPart => false;
+  bool get isPartOf => false;
+}
+
+class LibraryName extends LibraryTag {
+  final Expression name;
+
+  final Token libraryKeyword;
+
+  LibraryName(this.libraryKeyword,
+              this.name,
+              Link<MetadataAnnotation> metadata)
+    : super(metadata);
+
+  bool get isLibraryName => true;
+
+  LibraryName asLibraryName() => this;
+
+  accept(Visitor visitor) => visitor.visitLibraryName(this);
+
+  visitChildren(Visitor visitor) => name.accept(visitor);
+
+  Token getBeginToken() => libraryKeyword;
+
+  Token getEndToken() => name.getEndToken().next;
+}
+
+/**
+ * This tag describes a dependency between one library and the exported
+ * identifiers of another library. The other library is specified by the [uri].
+ * Combinators filter away some identifiers from the other library.
+ */
+abstract class LibraryDependency extends LibraryTag {
+  final StringNode uri;
+  final NodeList combinators;
+
+  LibraryDependency(this.uri,
+                    this.combinators,
+                    Link<MetadataAnnotation> metadata)
+    : super(metadata);
+}
+
+/**
+ * An [:import:] library tag.
+ *
+ * An import tag is dependency on another library where the exported identifiers
+ * are put into the import scope of the importing library. The import scope is
+ * only visible inside the library.
+ */
+class Import extends LibraryDependency {
+  final Identifier prefix;
+  final Token importKeyword;
+
+  Import(this.importKeyword, StringNode uri,
+         this.prefix, NodeList combinators,
+         Link<MetadataAnnotation> metadata)
+      : super(uri, combinators, metadata);
+
+  bool get isImport => true;
+
+  Import asImport() => this;
+
+  Token get asKeyword => prefix == null ? null : uri.getEndToken().next;
+
+  accept(Visitor visitor) => visitor.visitImport(this);
+
+  visitChildren(Visitor visitor) {
+    uri.accept(visitor);
+    if (prefix != null) prefix.accept(visitor);
+    if (combinators != null) combinators.accept(visitor);
+  }
+
+  Token getBeginToken() => importKeyword;
+
+  Token getEndToken() {
+    if (combinators != null) return combinators.getEndToken().next;
+    if (prefix != null) return prefix.getEndToken().next;
+    return uri.getEndToken().next;
+  }
+}
+
+/**
+ * An [:export:] library tag.
+ *
+ * An export tag is dependency on another library where the exported identifiers
+ * are put into the export scope of the exporting library. The export scope is
+ * not visible inside the library.
+ */
+class Export extends LibraryDependency {
+  final Token exportKeyword;
+
+  Export(this.exportKeyword,
+         StringNode uri,
+         NodeList combinators,
+         Link<MetadataAnnotation> metadata)
+      : super(uri, combinators, metadata);
+
+  bool get isExport => true;
+
+  Export asExport() => this;
+
+  accept(Visitor visitor) => visitor.visitExport(this);
+
+  visitChildren(Visitor visitor) {
+    uri.accept(visitor);
+    if (combinators != null) combinators.accept(visitor);
+  }
+
+  Token getBeginToken() => exportKeyword;
+
+  Token getEndToken() {
+    if (combinators != null) return combinators.getEndToken().next;
+    return uri.getEndToken().next;
+  }
+}
+
+class Part extends LibraryTag {
+  final StringNode uri;
+
+  final Token partKeyword;
+
+  Part(this.partKeyword, this.uri, Link<MetadataAnnotation> metadata)
+    : super(metadata);
+
+  bool get isPart => true;
+
+  Part asPart() => this;
+
+  accept(Visitor visitor) => visitor.visitPart(this);
+
+  visitChildren(Visitor visitor) => uri.accept(visitor);
+
+  Token getBeginToken() => partKeyword;
+
+  Token getEndToken() => uri.getEndToken().next;
+}
+
+class PartOf extends Node {
+  final Expression name;
+
+  final Token partKeyword;
+
+  final Link<MetadataAnnotation> metadata;
+
+  PartOf(this.partKeyword, this.name, this.metadata);
+
+  Token get ofKeyword => partKeyword.next;
+
+  bool get isPartOf => true;
+
+  PartOf asPartOf() => this;
+
+  accept(Visitor visitor) => visitor.visitPartOf(this);
+
+  visitChildren(Visitor visitor) => name.accept(visitor);
+
+  Token getBeginToken() => partKeyword;
+
+  Token getEndToken() => name.getEndToken().next;
+}
+
+class Combinator extends Node {
+  final NodeList identifiers;
+
+  final Token keywordToken;
+
+  Combinator(this.identifiers, this.keywordToken);
+
+  bool get isShow => identical(keywordToken.stringValue, 'show');
+
+  bool get isHide => identical(keywordToken.stringValue, 'hide');
+
+  Combinator asCombinator() => this;
+
+  accept(Visitor visitor) => visitor.visitCombinator(this);
+
+  visitChildren(Visitor visitor) => identifiers.accept(visitor);
+
+  Token getBeginToken() => keywordToken;
+
+  Token getEndToken() => identifiers.getEndToken();
+}
+
+class Typedef extends Node {
+  final TypeAnnotation returnType;
+  final Identifier name;
+  final NodeList typeParameters;
+  final NodeList formals;
+
+  final Token typedefKeyword;
+  final Token endToken;
+
+  Typedef(this.returnType, this.name, this.typeParameters, this.formals,
+          this.typedefKeyword, this.endToken);
+
+  Typedef asTypedef() => this;
+
+  accept(Visitor visitor) => visitor.visitTypedef(this);
+
+  visitChildren(Visitor visitor) {
+    if (returnType != null) returnType.accept(visitor);
+    name.accept(visitor);
+    if (typeParameters != null) typeParameters.accept(visitor);
+    formals.accept(visitor);
+  }
+
+  Token getBeginToken() => typedefKeyword;
+
+  Token getEndToken() => endToken;
+}
+
+class TryStatement extends Statement {
+  final Block tryBlock;
+  final NodeList catchBlocks;
+  final Block finallyBlock;
+
+  final Token tryKeyword;
+  final Token finallyKeyword;
+
+  TryStatement(this.tryBlock, this.catchBlocks, this.finallyBlock,
+               this.tryKeyword, this.finallyKeyword);
+
+  TryStatement asTryStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitTryStatement(this);
+
+  visitChildren(Visitor visitor) {
+    tryBlock.accept(visitor);
+    catchBlocks.accept(visitor);
+    if (finallyBlock != null) finallyBlock.accept(visitor);
+  }
+
+  Token getBeginToken() => tryKeyword;
+
+  Token getEndToken() {
+    if (finallyBlock != null) return finallyBlock.getEndToken();
+    if (!catchBlocks.isEmpty) return catchBlocks.getEndToken();
+    return tryBlock.getEndToken();
+  }
+}
+
+class Cascade extends Expression {
+  final Expression expression;
+  Cascade(this.expression);
+
+  Cascade asCascade() => this;
+  accept(Visitor visitor) => visitor.visitCascade(this);
+
+  void visitChildren(Visitor visitor) {
+    expression.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => expression.getEndToken();
+}
+
+class CascadeReceiver extends Expression {
+  final Expression expression;
+  final Token cascadeOperator;
+  CascadeReceiver(this.expression, this.cascadeOperator);
+
+  CascadeReceiver asCascadeReceiver() => this;
+  accept(Visitor visitor) => visitor.visitCascadeReceiver(this);
+
+  void visitChildren(Visitor visitor) {
+    expression.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => expression.getEndToken();
+}
+
+class CatchBlock extends Node {
+  final TypeAnnotation type;
+  final NodeList formals;
+  final Block block;
+
+  final Token onKeyword;
+  final Token catchKeyword;
+
+  CatchBlock(this.type, this.formals, this.block,
+             this.onKeyword, this.catchKeyword);
+
+  CatchBlock asCatchBlock() => this;
+
+  accept(Visitor visitor) => visitor.visitCatchBlock(this);
+
+  Node get exception {
+    if (formals == null || formals.nodes.isEmpty) return null;
+    VariableDefinitions declarations = formals.nodes.head;
+    return declarations.definitions.nodes.head;
+  }
+
+  Node get trace {
+    if (formals == null || formals.nodes.isEmpty) return null;
+    Link<Node> declarations = formals.nodes.tail;
+    if (declarations.isEmpty) return null;
+    VariableDefinitions head = declarations.head;
+    return head.definitions.nodes.head;
+  }
+
+  visitChildren(Visitor visitor) {
+    if (type != null) type.accept(visitor);
+    if (formals != null) formals.accept(visitor);
+    block.accept(visitor);
+  }
+
+  Token getBeginToken() => onKeyword != null ? onKeyword : catchKeyword;
+
+  Token getEndToken() => block.getEndToken();
+}
+
+class Initializers {
+  static bool isSuperConstructorCall(Send node) {
+    return (node.receiver == null &&
+            node.selector.asIdentifier() != null &&
+            node.selector.asIdentifier().isSuper()) ||
+           (node.receiver != null &&
+            node.receiver.asIdentifier() != null &&
+            node.receiver.asIdentifier().isSuper() &&
+            node.selector.asIdentifier() != null);
+  }
+
+  static bool isConstructorRedirect(Send node) {
+    return (node.receiver == null &&
+            node.selector.asIdentifier() != null &&
+            node.selector.asIdentifier().isThis()) ||
+           (node.receiver != null &&
+            node.receiver.asIdentifier() != null &&
+            node.receiver.asIdentifier().isThis() &&
+            node.selector.asIdentifier() != null);
+  }
+}
+
+class GetDartStringVisitor extends Visitor<DartString> {
+  const GetDartStringVisitor();
+  DartString visitNode(Node node) => null;
+  DartString visitStringJuxtaposition(StringJuxtaposition node)
+      => node.dartString;
+  DartString visitLiteralString(LiteralString node) => node.dartString;
+}
+
+class IsInterpolationVisitor extends Visitor<bool> {
+  const IsInterpolationVisitor();
+  bool visitNode(Node node) => false;
+  bool visitStringInterpolation(StringInterpolation node) => true;
+  bool visitStringJuxtaposition(StringJuxtaposition node)
+      => node.isInterpolation;
+}
+
+/**
+ * If the given node is a send set, it visits its initializer (first
+ * argument).
+ *
+ * TODO(ahe): This method is controversial, the team needs to discuss
+ * if top-level methods are acceptable and what naming conventions to
+ * use.
+ */
+initializerDo(Node node, f(Node node)) {
+  SendSet send = node.asSendSet();
+  if (send != null) return f(send.arguments.head);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart
new file mode 100644
index 0000000..129c637
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart
@@ -0,0 +1,480 @@
+// 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.
+
+part of tree;
+
+/**
+ * Pretty-prints Node tree in XML-like format.
+ *
+ * TODO(smok): Add main() to run from command-line to print out tree for given
+ * .dart file.
+ */
+class PrettyPrinter implements Visitor {
+
+  /** String used to represent one level of indent. */
+  static const String INDENT = "  ";
+
+  StringBuffer sb;
+  Link<String> tagStack;
+
+  PrettyPrinter() :
+      sb = new StringBuffer(),
+      tagStack = const Link<String>();
+
+  void pushTag(String tag) {
+    tagStack = tagStack.prepend(tag);
+  }
+
+  String popTag() {
+    assert(!tagStack.isEmpty);
+    String tag = tagStack.head;
+    tagStack = tagStack.tail;
+    return tag;
+  }
+
+  /**
+   * Adds given string to result string.
+   */
+  void add(SourceString string) {
+    string.printOn(sb);
+  }
+
+  void addBeginAndEndTokensToParams(Node node, Map params) {
+    params['getBeginToken'] = tokenToStringOrNull(node.getBeginToken());
+    params['getEndToken'] = tokenToStringOrNull(node.getEndToken());
+  }
+
+  /**
+   * Adds given node type to result string.
+   * The method "opens" the node, meaning that all output after calling
+   * this method and before calling closeNode() will represent contents
+   * of given node.
+   */
+  void openNode(Node node, String type, [Map params]) {
+    if (params == null) params = new Map();
+    addCurrentIndent();
+    sb.add("<");
+    addBeginAndEndTokensToParams(node, params);
+    addTypeWithParams(type, params);
+    sb.add(">\n");
+    pushTag(type);
+  }
+
+  /**
+   * Adds given node to result string.
+   */
+  void openAndCloseNode(Node node, String type, [Map params]) {
+    if (params == null) params = new Map();
+    addCurrentIndent();
+    sb.add("<");
+    addBeginAndEndTokensToParams(node, params);
+    addTypeWithParams(type, params);
+    sb.add("/>\n");
+  }
+
+  /**
+   * Closes current node type.
+   */
+  void closeNode() {
+    String tag = popTag();
+    addCurrentIndent();
+    sb.add("</");
+    addTypeWithParams(tag);
+    sb.add(">\n");
+  }
+
+  void addTypeWithParams(String type, [Map params]) {
+    if (params == null) params = new Map();
+    sb.add("${type}");
+    params.forEach((k, v) {
+      String value;
+      if (v != null) {
+        value = v
+            .replaceAll("<", "&lt;")
+            .replaceAll(">", "&gt;")
+            .replaceAll('"', "'");
+      } else {
+        value = "[null]";
+      }
+      sb.add(' $k="$value"');
+    });
+  }
+
+  void addCurrentIndent() {
+    tagStack.forEach((_) { sb.add(INDENT); });
+  }
+
+  /**
+   * Pretty-prints given node tree into string.
+   */
+  static String prettyPrint(Node node) {
+    var p = new PrettyPrinter();
+    node.accept(p);
+    return p.sb.toString();
+  }
+
+  visitNodeWithChildren(Node node, String type) {
+    openNode(node, type);
+    node.visitChildren(this);
+    closeNode();
+  }
+
+  visitBlock(Block node) {
+    visitNodeWithChildren(node, "Block");
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    visitNodeWithChildren(node, "BreakStatement");
+  }
+
+  visitCascade(Cascade node) {
+    visitNodeWithChildren(node, "Cascade");
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visitNodeWithChildren(node, "CascadeReceiver");
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    visitNodeWithChildren(node, "CaseMatch");
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    visitNodeWithChildren(node, "CatchBlock");
+  }
+
+  visitClassNode(ClassNode node) {
+    openNode(node, "ClassNode", {
+      "extendsKeyword" : tokenToStringOrNull(node.extendsKeyword)
+    });
+    visitChildNode(node.name, "name");
+    visitChildNode(node.superclass, "superclass");
+    visitChildNode(node.interfaces, "interfaces");
+    visitChildNode(node.typeParameters, "typeParameters");
+    visitChildNode(node.defaultClause, "defaultClause");
+    closeNode();
+  }
+
+  visitConditional(Conditional node) {
+    visitNodeWithChildren(node, "Conditional");
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    visitNodeWithChildren(node, "ContinueStatement");
+  }
+
+  visitDoWhile(DoWhile node) {
+    visitNodeWithChildren(node, "DoWhile");
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    visitNodeWithChildren(node, "EmptyStatement");
+  }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    visitNodeWithChildren(node, "ExpressionStatement");
+  }
+
+  visitFor(For node) {
+    visitNodeWithChildren(node, "For");
+  }
+
+  visitForIn(ForIn node) {
+    visitNodeWithChildren(node, "ForIn");
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    visitNodeWithChildren(node, "FunctionDeclaration");
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    openNode(node, "FunctionExpression", {
+      "getOrSet" : tokenToStringOrNull(node.getOrSet)
+    });
+    visitChildNode(node.modifiers, "modifiers");
+    visitChildNode(node.returnType, "returnType");
+    visitChildNode(node.name, "name");
+    visitChildNode(node.parameters, "parameters");
+    visitChildNode(node.initializers, "initializers");
+    visitChildNode(node.body, "body");
+    closeNode();
+  }
+
+  visitIdentifier(Identifier node) {
+    openAndCloseNode(node, "Identifier", {"token" : node.token.slowToString()});
+  }
+
+  visitIf(If node) {
+    visitNodeWithChildren(node, "If");
+  }
+
+  visitLabel(Label node) {
+    visitNodeWithChildren(node, "Label");
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    visitNodeWithChildren(node, "LabeledStatement");
+  }
+
+  // Custom.
+  printLiteral(Literal node, String type) {
+    openAndCloseNode(node, type, {"value" : node.value.toString()});
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    printLiteral(node, "LiteralBool");
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    printLiteral(node, "LiteralDouble");
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    printLiteral(node, "LiteralInt");
+  }
+
+  /** Returns token string value or [null] if token is [null]. */
+  tokenToStringOrNull(Token token) => token == null ? null : token.stringValue;
+
+  visitLiteralList(LiteralList node) {
+    openNode(node, "LiteralList", {
+      "constKeyword" : tokenToStringOrNull(node.constKeyword)
+    });
+    visitChildNode(node.typeArguments, "typeArguments");
+    visitChildNode(node.elements, "elements");
+    closeNode();
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    visitNodeWithChildren(node, "LiteralMap");
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    visitNodeWithChildren(node, "LiteralMapEntry");
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    printLiteral(node, "LiteralNull");
+  }
+
+  visitLiteralString(LiteralString node) {
+    openAndCloseNode(node, "LiteralString",
+        {"value" : node.token.slowToString()});
+  }
+
+  visitMixinApplication(MixinApplication node) {
+    visitNodeWithChildren(node, "MixinApplication");
+  }
+
+  visitModifiers(Modifiers node) {
+    visitNodeWithChildren(node, "Modifiers");
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visitNodeWithChildren(node, "NamedArgument");
+  }
+
+  visitNamedMixinApplication(NamedMixinApplication node) {
+    visitNodeWithChildren(node, "NamedMixinApplication");
+  }
+
+  visitNewExpression(NewExpression node) {
+    visitNodeWithChildren(node, "NewExpression");
+  }
+
+  visitNodeList(NodeList node) {
+    var params = {
+        "delimiter" :
+            node.delimiter != null ? node.delimiter.stringValue : null
+    };
+    if (node.nodes.toList().length == 0) {
+      openAndCloseNode(node, "NodeList", params);
+    } else {
+      openNode(node, "NodeList", params);
+      node.visitChildren(this);
+      closeNode();
+    }
+  }
+
+  visitOperator(Operator node) {
+    openAndCloseNode(node, "Operator", {"value" : node.token.slowToString()});
+  }
+
+  visitParenthesizedExpression(ParenthesizedExpression node) {
+    visitNodeWithChildren(node, "ParenthesizedExpression");
+  }
+
+  visitReturn(Return node) {
+    openNode(node, "Return");
+    visitChildNode(node.expression, "expression");
+    closeNode();
+  }
+
+  visitScriptTag(ScriptTag node) {
+    visitNodeWithChildren(node, "ScriptTag");
+  }
+
+  visitChildNode(Node node, String fieldName) {
+    if (node == null) return;
+    addCurrentIndent();
+    sb.add("<$fieldName>\n");
+    pushTag(fieldName);
+    node.accept(this);
+    popTag();
+    addCurrentIndent();
+    sb.add("</$fieldName>\n");
+  }
+
+  openSendNodeWithFields(Send node, String type) {
+    openNode(node, type, {
+        "isPrefix" : "${node.isPrefix}",
+        "isPostfix" : "${node.isPostfix}",
+        "isIndex" : "${node.isIndex}"
+    });
+    visitChildNode(node.receiver, "receiver");
+    visitChildNode(node.selector, "selector");
+    visitChildNode(node.argumentsNode, "argumentsNode");
+  }
+
+  visitSend(Send node) {
+    openSendNodeWithFields(node, "Send");
+    closeNode();
+  }
+
+  visitSendSet(SendSet node) {
+    openSendNodeWithFields(node, "SendSet");
+    visitChildNode(node.assignmentOperator, "assignmentOperator");
+    closeNode();
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    visitNodeWithChildren(node, "StringInterpolation");
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    visitNodeWithChildren(node, "StringInterpolationPart");
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    visitNodeWithChildren(node, "StringJuxtaposition");
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    visitNodeWithChildren(node, "SwitchCase");
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    visitNodeWithChildren(node, "SwitchStatement");
+  }
+
+  visitThrow(Throw node) {
+    visitNodeWithChildren(node, "Throw");
+  }
+
+  visitTryStatement(TryStatement node) {
+    visitNodeWithChildren(node, "TryStatement");
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    openNode(node, "TypeAnnotation");
+    visitChildNode(node.typeName, "typeName");
+    visitChildNode(node.typeArguments, "typeArguments");
+    closeNode();
+  }
+
+  visitTypedef(Typedef node) {
+    visitNodeWithChildren(node, "Typedef");
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    openNode(node, "TypeVariable");
+    visitChildNode(node.name, "name");
+    visitChildNode(node.bound, "bound");
+    closeNode();
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    openNode(node, "VariableDefinitions");
+    visitChildNode(node.type, "type");
+    visitChildNode(node.modifiers, "modifiers");
+    visitChildNode(node.definitions, "definitions");
+    closeNode();
+  }
+
+  visitWhile(While node) {
+    visitNodeWithChildren(node, "While");
+  }
+
+  visitNode(Node node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitCombinator(Combinator node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitExport(Export node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitExpression(Expression node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitGotoStatement(GotoStatement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitImport(Import node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLibraryDependency(Node node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLibraryName(LibraryName node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLibraryTag(LibraryTag node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLiteral(Literal node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLoop(Loop node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPart(Part node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPartOf(PartOf node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPostfix(Postfix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPrefix(Prefix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitStatement(Statement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitStringNode(StringNode node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  unimplemented(String message, {Node node}) {
+    throw message;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart
new file mode 100644
index 0000000..3faa36c
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart
@@ -0,0 +1,22 @@
+// 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.
+
+library tree;
+
+import 'dart:math';
+import 'dart:collection';
+
+import '../scanner/scannerlib.dart';
+import '../util/util.dart';
+import '../util/characters.dart';
+
+import '../resolution/secret_tree_element.dart' show TreeElementMixin;
+
+import '../elements/elements.dart' show MetadataAnnotation;
+
+part 'dartstring.dart';
+part 'nodes.dart';
+part 'prettyprint.dart';
+part 'unparser.dart';
+part 'visitors.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart
new file mode 100644
index 0000000..9c20f2d
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart
@@ -0,0 +1,627 @@
+// 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.
+
+part of tree;
+
+String unparse(Node node) {
+  Unparser unparser = new Unparser();
+  unparser.unparse(node);
+  return unparser.result;
+}
+
+class Unparser implements Visitor {
+  final StringBuffer sb;
+
+  String get result => sb.toString();
+
+  Unparser() : sb = new StringBuffer();
+
+  void add(SourceString string) {
+    string.printOn(sb);
+  }
+
+  void addToken(Token token) {
+    if (token == null) return;
+    add(token.value);
+    if (identical(token.kind, KEYWORD_TOKEN)
+        || identical(token.kind, IDENTIFIER_TOKEN)) {
+      sb.add(' ');
+    }
+  }
+
+  unparse(Node node) { visit(node); }
+
+  visit(Node node) {
+    if (node != null) node.accept(this);
+  }
+
+  visitBlock(Block node) {
+    visit(node.statements);
+  }
+
+  visitCascade(Cascade node) {
+    visit(node.expression);
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visit(node.expression);
+  }
+
+  unparseClassWithBody(ClassNode node, Iterable<Node> members) {
+    addToken(node.beginToken);
+    if (node.beginToken.stringValue == 'abstract') {
+      addToken(node.beginToken.next);
+    }
+    visit(node.name);
+    if (node.typeParameters != null) {
+      visit(node.typeParameters);
+    }
+    if (node.extendsKeyword != null) {
+      sb.add(' ');
+      addToken(node.extendsKeyword);
+      visit(node.superclass);
+    }
+    if (!node.interfaces.isEmpty) {
+      sb.add(' ');
+      visit(node.interfaces);
+    }
+    if (node.defaultClause != null) {
+      sb.add(' default ');
+      visit(node.defaultClause);
+    }
+    sb.add('{');
+    for (final member in members) {
+      visit(member);
+    }
+    sb.add('}');
+  }
+
+  visitClassNode(ClassNode node) {
+    unparseClassWithBody(node, node.body.nodes);
+  }
+
+  visitMixinApplication(MixinApplication node) {
+    visit(node.superclass);
+    sb.add(' with ');
+    visit(node.mixins);
+  }
+
+  visitNamedMixinApplication(NamedMixinApplication node) {
+    sb.add('typedef ');
+    visit(node.name);
+    if (node.typeParameters != null) {
+      visit(node.typeParameters);
+    }
+    sb.add(' = ');
+    if (!node.modifiers.nodes.isEmpty) {
+      visit(node.modifiers);
+      sb.add(' ');
+    }
+    visit(node.mixinApplication);
+    if (node.interfaces != null) {
+      sb.add(' implements ');
+      visit(node.interfaces);
+    }
+    sb.add(';');
+  }
+
+  visitConditional(Conditional node) {
+    visit(node.condition);
+    add(node.questionToken.value);
+    visit(node.thenExpression);
+    add(node.colonToken.value);
+    visit(node.elseExpression);
+  }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    visit(node.expression);
+    add(node.endToken.value);
+  }
+
+  visitFor(For node) {
+    add(node.forToken.value);
+    sb.add('(');
+    visit(node.initializer);
+    sb.add(';');
+    visit(node.conditionStatement);
+    visit(node.update);
+    sb.add(')');
+    visit(node.body);
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    visit(node.function);
+  }
+
+  void unparseFunctionName(Node name) {
+    // TODO(antonm): that's a workaround as currently FunctionExpression
+    // names are modelled with Send and it emits operator[] as only
+    // operator, without [] which are expected to be emitted with
+    // arguments.
+    if (name is Send) {
+      Send send = name;
+      assert(send is !SendSet);
+      if (!send.isOperator) {
+        // Looks like a factory method.
+        visit(send.receiver);
+        sb.add('.');
+      } else {
+        visit(send.receiver);
+        Identifier identifier = send.selector.asIdentifier();
+        if (identical(identifier.token.kind, KEYWORD_TOKEN)) {
+          sb.add(' ');
+        } else if (identifier.source == const SourceString('negate')) {
+          // TODO(ahe): Remove special case for negate.
+          sb.add(' ');
+        }
+      }
+      visit(send.selector);
+    } else {
+      visit(name);
+    }
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    if (!node.modifiers.nodes.isEmpty) {
+      visit(node.modifiers);
+      sb.add(' ');
+    }
+    if (node.returnType != null) {
+      visit(node.returnType);
+      sb.add(' ');
+    }
+    if (node.getOrSet != null) {
+      add(node.getOrSet.value);
+      sb.add(' ');
+    }
+    unparseFunctionName(node.name);
+    visit(node.parameters);
+    visit(node.initializers);
+    visit(node.body);
+  }
+
+  visitIdentifier(Identifier node) {
+    add(node.token.value);
+  }
+
+  visitIf(If node) {
+    add(node.ifToken.value);
+    visit(node.condition);
+    visit(node.thenPart);
+    if (node.hasElsePart) {
+      add(node.elseToken.value);
+      if (node.elsePart is !Block) sb.add(' ');
+      visit(node.elsePart);
+    }
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    add(node.token.value);
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    add(node.token.value);
+    // -Lit is represented as a send.
+    if (node.token.kind == PLUS_TOKEN) add(node.token.next.value);
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    add(node.token.value);
+    // -Lit is represented as a send.
+    if (node.token.kind == PLUS_TOKEN) add(node.token.next.value);
+  }
+
+  visitLiteralString(LiteralString node) {
+    add(node.token.value);
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    visit(node.first);
+    sb.add(" ");
+    visit(node.second);
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    add(node.token.value);
+  }
+
+  visitNewExpression(NewExpression node) {
+    addToken(node.newToken);
+    visit(node.send);
+  }
+
+  visitLiteralList(LiteralList node) {
+    if (node.constKeyword != null) add(node.constKeyword.value);
+    visit(node.typeArguments);
+    visit(node.elements);
+    // If list is empty, emit space after [] to disambiguate cases like []==[].
+    if (node.elements.isEmpty) sb.add(' ');
+  }
+
+  visitModifiers(Modifiers node) => node.visitChildren(this);
+
+  /**
+   * Unparses given NodeList starting from specific node.
+   */
+  unparseNodeListFrom(NodeList node, Link<Node> from) {
+    if (from.isEmpty) return;
+    String delimiter = (node.delimiter == null) ? "" : "${node.delimiter}";
+    visit(from.head);
+    for (Link link = from.tail; !link.isEmpty; link = link.tail) {
+      sb.add(delimiter);
+      visit(link.head);
+    }
+  }
+
+  visitNodeList(NodeList node) {
+    addToken(node.beginToken);
+    if (node.nodes != null) {
+      unparseNodeListFrom(node, node.nodes);
+    }
+    if (node.endToken != null) add(node.endToken.value);
+  }
+
+  visitOperator(Operator node) {
+    visitIdentifier(node);
+  }
+
+  visitReturn(Return node) {
+    if (node.isRedirectingFactoryBody) {
+      sb.add(' ');
+    }
+    add(node.beginToken.value);
+    if (node.hasExpression && node.beginToken.stringValue != '=>') {
+      sb.add(' ');
+    }
+    visit(node.expression);
+    if (node.endToken != null) add(node.endToken.value);
+  }
+
+  unparseSendReceiver(Send node, {bool spacesNeeded: false}) {
+    if (node.receiver == null) return;
+    visit(node.receiver);
+    CascadeReceiver asCascadeReceiver = node.receiver.asCascadeReceiver();
+    if (asCascadeReceiver != null) {
+      add(asCascadeReceiver.cascadeOperator.value);
+    } else if (node.selector.asOperator() == null) {
+      sb.add('.');
+    } else if (spacesNeeded) {
+      sb.add(' ');
+    }
+  }
+
+  visitSend(Send node) {
+    Operator op = node.selector.asOperator();
+    String opString = op != null ? op.source.stringValue : null;
+    bool spacesNeeded = identical(opString, 'is') || identical(opString, 'as');
+
+    if (node.isPrefix) visit(node.selector);
+    unparseSendReceiver(node, spacesNeeded: spacesNeeded);
+    if (!node.isPrefix && !node.isIndex) visit(node.selector);
+    if (spacesNeeded) sb.add(' ');
+    // Also add a space for sequences like x + +1 and y - -y.
+    // TODO(ahe): remove case for '+' when we drop the support for it.
+    if (node.argumentsNode != null && (identical(opString, '-')
+        || identical(opString, '+'))) {
+      Token beginToken = node.argumentsNode.getBeginToken();
+      if (beginToken != null && identical(beginToken.stringValue, opString)) {
+        sb.add(' ');
+      }
+    }
+    visit(node.argumentsNode);
+  }
+
+  visitSendSet(SendSet node) {
+    if (node.isPrefix) {
+      sb.add(' ');
+      visit(node.assignmentOperator);
+    }
+    unparseSendReceiver(node);
+    if (node.isIndex) {
+      sb.add('[');
+      visit(node.arguments.head);
+      sb.add(']');
+      if (!node.isPrefix) visit(node.assignmentOperator);
+      unparseNodeListFrom(node.argumentsNode, node.argumentsNode.nodes.tail);
+    } else {
+      visit(node.selector);
+      if (!node.isPrefix) {
+        visit(node.assignmentOperator);
+        if (node.assignmentOperator.source.slowToString() != '=') sb.add(' ');
+      }
+      visit(node.argumentsNode);
+    }
+  }
+
+  visitThrow(Throw node) {
+    add(node.throwToken.value);
+    if (node.expression != null) {
+      sb.add(' ');
+      visit(node.expression);
+    }
+    node.endToken.value.printOn(sb);
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    visit(node.typeName);
+    visit(node.typeArguments);
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    visit(node.name);
+    if (node.bound != null) {
+      sb.add(' extends ');
+      visit(node.bound);
+    }
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    visit(node.modifiers);
+    if (!node.modifiers.nodes.isEmpty) {
+      sb.add(' ');
+    }
+    if (node.type != null) {
+      visit(node.type);
+      sb.add(' ');
+    }
+    visit(node.definitions);
+  }
+
+  visitDoWhile(DoWhile node) {
+    add(node.doKeyword.value);
+    if (node.body is !Block) sb.add(' ');
+    visit(node.body);
+    add(node.whileKeyword.value);
+    visit(node.condition);
+    sb.add(node.endToken.value);
+  }
+
+  visitWhile(While node) {
+    addToken(node.whileKeyword);
+    visit(node.condition);
+    visit(node.body);
+  }
+
+  visitParenthesizedExpression(ParenthesizedExpression node) {
+    add(node.getBeginToken().value);
+    visit(node.expression);
+    add(node.getEndToken().value);
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    visit(node.string);
+    visit(node.parts);
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    sb.add('\${'); // TODO(ahe): Preserve the real tokens.
+    visit(node.expression);
+    sb.add('}');
+    visit(node.string);
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    add(node.semicolonToken.value);
+  }
+
+  visitGotoStatement(GotoStatement node) {
+    add(node.keywordToken.value);
+    if (node.target != null) {
+      sb.add(' ');
+      visit(node.target);
+    }
+    add(node.semicolonToken.value);
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    visitGotoStatement(node);
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    visitGotoStatement(node);
+  }
+
+  visitForIn(ForIn node) {
+    add(node.forToken.value);
+    sb.add('(');
+    visit(node.declaredIdentifier);
+    sb.add(' ');
+    addToken(node.inToken);
+    visit(node.expression);
+    sb.add(')');
+    visit(node.body);
+  }
+
+  visitLabel(Label node) {
+    visit(node.identifier);
+    add(node.colonToken.value);
+   }
+
+  visitLabeledStatement(LabeledStatement node) {
+    visit(node.labels);
+    visit(node.statement);
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    if (node.constKeyword != null) add(node.constKeyword.value);
+    if (node.typeArguments != null) visit(node.typeArguments);
+    visit(node.entries);
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    visit(node.key);
+    add(node.colonToken.value);
+    visit(node.value);
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visit(node.name);
+    add(node.colonToken.value);
+    visit(node.expression);
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    addToken(node.switchKeyword);
+    visit(node.parenthesizedExpression);
+    visit(node.cases);
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    visit(node.labelsAndCases);
+    if (node.isDefaultCase) {
+      sb.add('default:');
+    }
+    visit(node.statements);
+  }
+
+  unparseImportTag(String uri, [String prefix]) {
+    final suffix = prefix == null ? '' : ' as $prefix';
+    sb.add('import "$uri"$suffix;');
+  }
+
+  visitScriptTag(ScriptTag node) {
+    add(node.beginToken.value);
+    visit(node.tag);
+    sb.add('(');
+    visit(node.argument);
+    if (node.prefixIdentifier != null) {
+      visit(node.prefixIdentifier);
+      sb.add(':');
+      visit(node.prefix);
+    }
+    sb.add(')');
+    add(node.endToken.value);
+  }
+
+  visitTryStatement(TryStatement node) {
+    addToken(node.tryKeyword);
+    visit(node.tryBlock);
+    visit(node.catchBlocks);
+    if (node.finallyKeyword != null) {
+      addToken(node.finallyKeyword);
+      visit(node.finallyBlock);
+    }
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    add(node.caseKeyword.value);
+    sb.add(" ");
+    visit(node.expression);
+    add(node.colonToken.value);
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    addToken(node.onKeyword);
+    if (node.type != null) {
+      visit(node.type);
+      sb.add(' ');
+    }
+    addToken(node.catchKeyword);
+    visit(node.formals);
+    visit(node.block);
+  }
+
+  visitTypedef(Typedef node) {
+    addToken(node.typedefKeyword);
+    if (node.returnType != null) {
+      visit(node.returnType);
+      sb.add(' ');
+    }
+    visit(node.name);
+    if (node.typeParameters != null) {
+      visit(node.typeParameters);
+    }
+    visit(node.formals);
+    add(node.endToken.value);
+  }
+
+  visitLibraryName(LibraryName node) {
+    addToken(node.libraryKeyword);
+    node.visitChildren(this);
+    add(node.getEndToken().value);
+  }
+
+  visitImport(Import node) {
+    addToken(node.importKeyword);
+    visit(node.uri);
+    if (node.prefix != null) {
+      sb.add(' ');
+      addToken(node.asKeyword);
+      visit(node.prefix);
+    }
+    if (node.combinators != null) {
+      sb.add(' ');
+      visit(node.combinators);
+    }
+    add(node.getEndToken().value);
+  }
+
+  visitExport(Export node) {
+    addToken(node.exportKeyword);
+    visit(node.uri);
+    if (node.combinators != null) {
+      sb.add(' ');
+      visit(node.combinators);
+    }
+    add(node.getEndToken().value);
+  }
+
+  visitPart(Part node) {
+    addToken(node.partKeyword);
+    visit(node.uri);
+    add(node.getEndToken().value);
+  }
+
+  visitPartOf(PartOf node) {
+    addToken(node.partKeyword);
+    addToken(node.ofKeyword);
+    visit(node.name);
+    add(node.getEndToken().value);
+  }
+
+  visitCombinator(Combinator node) {
+    addToken(node.keywordToken);
+    visit(node.identifiers);
+  }
+
+  visitNode(Node node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitExpression(Expression node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLibraryTag(LibraryTag node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLibraryDependency(Node node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLiteral(Literal node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLoop(Loop node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitPostfix(Postfix node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitPrefix(Prefix node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitStatement(Statement node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitStringNode(StringNode node) {
+    throw 'internal error'; // Should not be called.
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart
new file mode 100644
index 0000000..9096cff
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2011, 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.
+
+part of tree;
+
+/**
+ * This visitor takes another visitor and applies it to every
+ * node in the tree. There is currently no way to control the
+ * traversal.
+ */
+class TraversingVisitor extends Visitor {
+  final Visitor visitor;
+
+  TraversingVisitor(Visitor this.visitor);
+
+  visitNode(Node node) {
+    node.accept(visitor);
+    node.visitChildren(this);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart b/pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart
new file mode 100644
index 0000000..6a3b93f
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2011, 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.
+
+part of dart2js;
+
+class TreeValidatorTask extends CompilerTask {
+  TreeValidatorTask(Compiler compiler) : super(compiler);
+
+  void validate(Node tree) {
+    assert(check(tree));
+  }
+
+  bool check(Node tree) {
+    List<InvalidNodeError> errors = [];
+    void report(node, message) {
+      final error = new InvalidNodeError(node, message);
+      errors.add(error);
+      compiler.reportWarning(node, message);
+    };
+    final validator = new ValidatorVisitor(report);
+    tree.accept(new TraversingVisitor(validator));
+
+    return errors.isEmpty;
+  }
+}
+
+class ValidatorVisitor extends Visitor {
+  final Function reportInvalidNode;
+
+  ValidatorVisitor(Function this.reportInvalidNode);
+
+  expect(Node node, bool test, [message]) {
+    if (!test) reportInvalidNode(node, message);
+  }
+
+  visitNode(Node node) {}
+
+  visitSendSet(SendSet node) {
+    final selector = node.selector;
+    final name = node.assignmentOperator.source.stringValue;
+    final arguments = node.arguments;
+
+    expect(node, arguments != null);
+    expect(node, selector is Identifier, 'selector is not assignable');
+    if (identical(name, '++') || identical(name, '--')) {
+      expect(node, node.assignmentOperator is Operator);
+      if (node.isIndex) {
+        expect(node.arguments.tail.head, node.arguments.tail.isEmpty);
+      } else {
+        expect(node.arguments.head, node.arguments.isEmpty);
+      }
+    } else {
+      expect(node, !node.arguments.isEmpty);
+    }
+  }
+
+  visitReturn(Return node) {
+    if (!node.isRedirectingFactoryBody && node.hasExpression) {
+      // We allow non-expression expressions in Return nodes, but only when
+      // using them to hold redirecting factory constructors.
+      expect(node, node.expression.asExpression() != null);
+    }
+  }
+}
+
+class InvalidNodeError {
+  final Node node;
+  final String message;
+  InvalidNodeError(this.node, [this.message]);
+
+  toString() {
+    String nodeString = node.toDebugString();
+    String result = 'invalid node: $nodeString';
+    if (message != null) result = '$result ($message)';
+    return result;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/typechecker.dart b/pkgs/markdown/lib/src/compiler/implementation/typechecker.dart
new file mode 100644
index 0000000..01fa607
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/typechecker.dart
@@ -0,0 +1,751 @@
+// 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.
+
+part of dart2js;
+
+class TypeCheckerTask extends CompilerTask {
+  TypeCheckerTask(Compiler compiler) : super(compiler);
+  String get name => "Type checker";
+
+  static const bool LOG_FAILURES = false;
+
+  void check(Node tree, TreeElements elements) {
+    measure(() {
+      Visitor visitor =
+          new TypeCheckerVisitor(compiler, elements, compiler.types);
+      try {
+        tree.accept(visitor);
+      } on CancelTypeCheckException catch (e) {
+        if (LOG_FAILURES) {
+          // Do not warn about unimplemented features; log message instead.
+          compiler.log("'${e.node}': ${e.reason}");
+        }
+      }
+    });
+  }
+}
+
+class CancelTypeCheckException {
+  final Node node;
+  final String reason;
+
+  CancelTypeCheckException(this.node, this.reason);
+}
+
+class TypeCheckerVisitor implements Visitor<DartType> {
+  final Compiler compiler;
+  final TreeElements elements;
+  final Types types;
+
+  Node lastSeenNode;
+  DartType expectedReturnType;
+  ClassElement currentClass;
+
+  Link<DartType> cascadeTypes = const Link<DartType>();
+
+  DartType intType;
+  DartType doubleType;
+  DartType boolType;
+  DartType stringType;
+  DartType objectType;
+  DartType listType;
+
+  TypeCheckerVisitor(this.compiler, this.elements, this.types) {
+    intType = compiler.intClass.computeType(compiler);
+    doubleType = compiler.doubleClass.computeType(compiler);
+    boolType = compiler.boolClass.computeType(compiler);
+    stringType = compiler.stringClass.computeType(compiler);
+    objectType = compiler.objectClass.computeType(compiler);
+    listType = compiler.listClass.computeType(compiler);
+  }
+
+  DartType fail(node, [reason]) {
+    String message = 'cannot type-check';
+    if (reason != null) {
+      message = '$message: $reason';
+    }
+    throw new CancelTypeCheckException(node, message);
+  }
+
+  reportTypeWarning(Node node, MessageKind kind, [Map arguments = const {}]) {
+    compiler.reportWarning(node, new TypeWarning(kind, arguments));
+  }
+
+  // TODO(karlklose): remove these functions.
+  DartType unhandledStatement() => StatementType.NOT_RETURNING;
+  DartType unhandledExpression() => types.dynamicType;
+
+  DartType analyzeNonVoid(Node node) {
+    DartType type = analyze(node);
+    if (type == types.voidType) {
+      reportTypeWarning(node, MessageKind.VOID_EXPRESSION);
+    }
+    return type;
+  }
+
+  DartType analyzeWithDefault(Node node, DartType defaultValue) {
+    return node != null ? analyze(node) : defaultValue;
+  }
+
+  DartType analyze(Node node) {
+    if (node == null) {
+      final String error = 'internal error: unexpected node: null';
+      if (lastSeenNode != null) {
+        fail(null, error);
+      } else {
+        compiler.cancel(error);
+      }
+    } else {
+      lastSeenNode = node;
+    }
+    DartType result = node.accept(this);
+    // TODO(karlklose): record type?
+    if (result == null) {
+      fail(node, 'internal error: type is null');
+    }
+    return result;
+  }
+
+  /**
+   * Check if a value of type t can be assigned to a variable,
+   * parameter or return value of type s.
+   */
+  checkAssignable(Node node, DartType s, DartType t) {
+    if (!types.isAssignable(s, t)) {
+      reportTypeWarning(node, MessageKind.NOT_ASSIGNABLE,
+                        {'fromType': s, 'toType': t});
+    }
+  }
+
+  checkCondition(Expression condition) {
+    checkAssignable(condition, boolType, analyze(condition));
+  }
+
+  void pushCascadeType(DartType type) {
+    cascadeTypes = cascadeTypes.prepend(type);
+  }
+
+  DartType popCascadeType() {
+    DartType type = cascadeTypes.head;
+    cascadeTypes = cascadeTypes.tail;
+    return type;
+  }
+
+  DartType visitBlock(Block node) {
+    return analyze(node.statements);
+  }
+
+  DartType visitCascade(Cascade node) {
+    analyze(node.expression);
+    return popCascadeType();
+  }
+
+  DartType visitCascadeReceiver(CascadeReceiver node) {
+    DartType type = analyze(node.expression);
+    pushCascadeType(type);
+    return type;
+  }
+
+  DartType visitClassNode(ClassNode node) {
+    fail(node);
+  }
+
+  DartType visitMixinApplication(MixinApplication node) {
+    fail(node);
+  }
+
+  DartType visitNamedMixinApplication(NamedMixinApplication node) {
+    fail(node);
+  }
+
+  DartType visitDoWhile(DoWhile node) {
+    StatementType bodyType = analyze(node.body);
+    checkCondition(node.condition);
+    return bodyType.join(StatementType.NOT_RETURNING);
+  }
+
+  DartType visitExpressionStatement(ExpressionStatement node) {
+    analyze(node.expression);
+    return StatementType.NOT_RETURNING;
+  }
+
+  /** Dart Programming Language Specification: 11.5.1 For Loop */
+  DartType visitFor(For node) {
+    analyzeWithDefault(node.initializer, StatementType.NOT_RETURNING);
+    checkCondition(node.condition);
+    analyzeWithDefault(node.update, StatementType.NOT_RETURNING);
+    StatementType bodyType = analyze(node.body);
+    return bodyType.join(StatementType.NOT_RETURNING);
+  }
+
+  DartType visitFunctionDeclaration(FunctionDeclaration node) {
+    analyze(node.function);
+    return StatementType.NOT_RETURNING;
+  }
+
+  DartType visitFunctionExpression(FunctionExpression node) {
+    DartType type;
+    DartType returnType;
+    DartType previousType;
+    final FunctionElement element = elements[node];
+    if (Elements.isUnresolved(element)) return types.dynamicType;
+    if (identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR) ||
+        identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY)) {
+      type = types.dynamicType;
+      returnType = types.voidType;
+    } else {
+      FunctionType functionType = computeType(element);
+      returnType = functionType.returnType;
+      type = functionType;
+    }
+    DartType previous = expectedReturnType;
+    expectedReturnType = returnType;
+    if (element.isMember()) currentClass = element.getEnclosingClass();
+    StatementType bodyType = analyze(node.body);
+    if (returnType != types.voidType && returnType != types.dynamicType
+        && bodyType != StatementType.RETURNING) {
+      MessageKind kind;
+      if (bodyType == StatementType.MAYBE_RETURNING) {
+        kind = MessageKind.MAYBE_MISSING_RETURN;
+      } else {
+        kind = MessageKind.MISSING_RETURN;
+      }
+      reportTypeWarning(node.name, kind);
+    }
+    expectedReturnType = previous;
+    return type;
+  }
+
+  DartType visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      return currentClass.computeType(compiler);
+    } else {
+      // This is an identifier of a formal parameter.
+      return types.dynamicType;
+    }
+  }
+
+  DartType visitIf(If node) {
+    checkCondition(node.condition);
+    StatementType thenType = analyze(node.thenPart);
+    StatementType elseType = node.hasElsePart ? analyze(node.elsePart)
+                                              : StatementType.NOT_RETURNING;
+    return thenType.join(elseType);
+  }
+
+  DartType visitLoop(Loop node) {
+    return unhandledStatement();
+  }
+
+  DartType lookupMethodType(Node node, ClassElement classElement,
+                            SourceString name) {
+    Element member = classElement.lookupLocalMember(name);
+    if (member == null) {
+      classElement.ensureResolved(compiler);
+      for (Link<DartType> supertypes = classElement.allSupertypes;
+           !supertypes.isEmpty && member == null;
+           supertypes = supertypes.tail) {
+        ClassElement lookupTarget = supertypes.head.element;
+        member = lookupTarget.lookupLocalMember(name);
+      }
+    }
+    if (member != null && member.kind == ElementKind.FUNCTION) {
+      return computeType(member);
+    }
+    reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND,
+                      {'className': classElement.name, 'methodName': name});
+    return types.dynamicType;
+  }
+
+  // TODO(johnniwinther): Provide the element from which the type came in order
+  // to give better error messages.
+  void analyzeArguments(Send send, DartType type) {
+    Link<Node> arguments = send.arguments;
+    if (type == null || identical(type, types.dynamicType)) {
+      while(!arguments.isEmpty) {
+        analyze(arguments.head);
+        arguments = arguments.tail;
+      }
+    } else {
+      FunctionType funType = type;
+      Link<DartType> parameterTypes = funType.parameterTypes;
+      Link<DartType> optionalParameterTypes = funType.optionalParameterTypes;
+      while (!arguments.isEmpty) {
+        Node argument = arguments.head;
+        NamedArgument namedArgument = argument.asNamedArgument();
+        if (namedArgument != null) {
+          argument = namedArgument.expression;
+          SourceString argumentName = namedArgument.name.source;
+          DartType namedParameterType =
+              funType.getNamedParameterType(argumentName);
+          if (namedParameterType == null) {
+            // TODO(johnniwinther): Provide better information on the called
+            // function.
+            reportTypeWarning(argument, MessageKind.NAMED_ARGUMENT_NOT_FOUND,
+                {'argumentName': argumentName});
+
+            analyze(argument);
+          } else {
+            checkAssignable(argument, namedParameterType, analyze(argument));
+          }
+        } else {
+          if (parameterTypes.isEmpty) {
+            if (optionalParameterTypes.isEmpty) {
+              // TODO(johnniwinther): Provide better information on the
+              // called function.
+              reportTypeWarning(argument, MessageKind.ADDITIONAL_ARGUMENT);
+
+              analyze(argument);
+            } else {
+              checkAssignable(argument, optionalParameterTypes.head,
+                              analyze(argument));
+              optionalParameterTypes = optionalParameterTypes.tail;
+            }
+          } else {
+            checkAssignable(argument, parameterTypes.head, analyze(argument));
+            parameterTypes = parameterTypes.tail;
+          }
+        }
+        arguments = arguments.tail;
+      }
+      if (!parameterTypes.isEmpty) {
+        // TODO(johnniwinther): Provide better information on the called
+        // function.
+        reportTypeWarning(send, MessageKind.MISSING_ARGUMENT,
+            {'argumentType': parameterTypes.head});
+      }
+    }
+  }
+
+  DartType visitSend(Send node) {
+    Element element = elements[node];
+
+    if (Elements.isClosureSend(node, element)) {
+      // TODO(karlklose): Finish implementation.
+      return types.dynamicType;
+    }
+
+    Identifier selector = node.selector.asIdentifier();
+    String name = selector.source.stringValue;
+
+    if (node.isOperator && identical(name, 'is')) {
+      analyze(node.receiver);
+      return boolType;
+    } else if (node.isOperator) {
+      final Node firstArgument = node.receiver;
+      final DartType firstArgumentType = analyze(node.receiver);
+      final arguments = node.arguments;
+      final Node secondArgument = arguments.isEmpty ? null : arguments.head;
+      final DartType secondArgumentType =
+          analyzeWithDefault(secondArgument, null);
+
+      if (identical(name, '+') || identical(name, '=') || identical(name, '-')
+          || identical(name, '*') || identical(name, '/') || identical(name, '%')
+          || identical(name, '~/') || identical(name, '|') || identical(name, '&')
+          || identical(name, '^') || identical(name, '~')|| identical(name, '<<')
+          || identical(name, '>>') || identical(name, '[]')) {
+        return types.dynamicType;
+      } else if (identical(name, '<') || identical(name, '>') || identical(name, '<=')
+                 || identical(name, '>=') || identical(name, '==') || identical(name, '!=')
+                 || identical(name, '===') || identical(name, '!==')) {
+        return boolType;
+      } else if (identical(name, '||') || identical(name, '&&') || identical(name, '!')) {
+        checkAssignable(firstArgument, boolType, firstArgumentType);
+        if (!arguments.isEmpty) {
+          // TODO(karlklose): check number of arguments in validator.
+          checkAssignable(secondArgument, boolType, secondArgumentType);
+        }
+        return boolType;
+      }
+      fail(selector, 'unexpected operator ${name}');
+
+    } else if (node.isPropertyAccess) {
+      if (node.receiver != null) {
+        // TODO(karlklose): we cannot handle fields.
+        return unhandledExpression();
+      }
+      if (element == null) return types.dynamicType;
+      return computeType(element);
+
+    } else if (node.isFunctionObjectInvocation) {
+      fail(node.receiver, 'function object invocation unimplemented');
+
+    } else {
+      FunctionType computeFunType() {
+        if (node.receiver != null) {
+          DartType receiverType = analyze(node.receiver);
+          if (receiverType.element == compiler.dynamicClass) return null;
+          if (receiverType == null) {
+            fail(node.receiver, 'receivertype is null');
+          }
+          if (identical(receiverType.element.kind, ElementKind.GETTER)) {
+            FunctionType getterType  = receiverType;
+            receiverType = getterType.returnType;
+          }
+          ElementKind receiverKind = receiverType.element.kind;
+          if (identical(receiverKind, ElementKind.TYPEDEF)) {
+            // TODO(karlklose): handle typedefs.
+            return null;
+          }
+          if (identical(receiverKind, ElementKind.TYPE_VARIABLE)) {
+            // TODO(karlklose): handle type variables.
+            return null;
+          }
+          if (!identical(receiverKind, ElementKind.CLASS)) {
+            fail(node.receiver, 'unexpected receiver kind: ${receiverKind}');
+          }
+          ClassElement classElement = receiverType.element;
+          // TODO(karlklose): substitute type arguments.
+          DartType memberType =
+            lookupMethodType(selector, classElement, selector.source);
+          if (identical(memberType.element, compiler.dynamicClass)) return null;
+          return memberType;
+        } else {
+          if (Elements.isUnresolved(element)) {
+            fail(node, 'unresolved ${node.selector}');
+          } else if (identical(element.kind, ElementKind.FUNCTION)) {
+            return computeType(element);
+          } else if (element.isForeign(compiler)) {
+            return null;
+          } else if (identical(element.kind, ElementKind.VARIABLE)
+                     || identical(element.kind, ElementKind.FIELD)) {
+            // TODO(karlklose): handle object invocations.
+            return null;
+          } else {
+            fail(node, 'unexpected element kind ${element.kind}');
+          }
+        }
+      }
+      FunctionType funType = computeFunType();
+      analyzeArguments(node, funType);
+      return (funType != null) ? funType.returnType : types.dynamicType;
+    }
+  }
+
+  visitSendSet(SendSet node) {
+    Identifier selector = node.selector;
+    final name = node.assignmentOperator.source.stringValue;
+    if (identical(name, '++') || identical(name, '--')) {
+      final Element element = elements[node.selector];
+      final DartType receiverType = computeType(element);
+      // TODO(karlklose): this should be the return type instead of int.
+      return node.isPrefix ? intType : receiverType;
+    } else {
+      DartType targetType = computeType(elements[node]);
+      Node value = node.arguments.head;
+      checkAssignable(value, targetType, analyze(value));
+      return targetType;
+    }
+  }
+
+  DartType visitLiteralInt(LiteralInt node) {
+    return intType;
+  }
+
+  DartType visitLiteralDouble(LiteralDouble node) {
+    return doubleType;
+  }
+
+  DartType visitLiteralBool(LiteralBool node) {
+    return boolType;
+  }
+
+  DartType visitLiteralString(LiteralString node) {
+    return stringType;
+  }
+
+  DartType visitStringJuxtaposition(StringJuxtaposition node) {
+    analyze(node.first);
+    analyze(node.second);
+    return stringType;
+  }
+
+  DartType visitLiteralNull(LiteralNull node) {
+    return types.dynamicType;
+  }
+
+  DartType visitNewExpression(NewExpression node) {
+    Element element = elements[node.send];
+    analyzeArguments(node.send, computeType(element));
+    return analyze(node.send.selector);
+  }
+
+  DartType visitLiteralList(LiteralList node) {
+    return listType;
+  }
+
+  DartType visitNodeList(NodeList node) {
+    DartType type = StatementType.NOT_RETURNING;
+    bool reportedDeadCode = false;
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      DartType nextType = analyze(link.head);
+      if (type == StatementType.RETURNING) {
+        if (!reportedDeadCode) {
+          reportTypeWarning(link.head, MessageKind.UNREACHABLE_CODE);
+          reportedDeadCode = true;
+        }
+      } else if (type == StatementType.MAYBE_RETURNING){
+        if (nextType == StatementType.RETURNING) {
+          type = nextType;
+        }
+      } else {
+        type = nextType;
+      }
+    }
+    return type;
+  }
+
+  DartType visitOperator(Operator node) {
+    fail(node, 'internal error');
+  }
+
+  /** Dart Programming Language Specification: 11.10 Return */
+  DartType visitReturn(Return node) {
+    if (identical(node.getBeginToken().stringValue, 'native')) {
+      return StatementType.RETURNING;
+    }
+    if (node.isRedirectingFactoryBody) {
+      // TODO(lrn): Typecheck the body. It must refer to the constructor
+      // of a subtype.
+      return StatementType.RETURNING;
+    }
+
+    final expression = node.expression;
+    final isVoidFunction = (identical(expectedReturnType, types.voidType));
+
+    // Executing a return statement return e; [...] It is a static type warning
+    // if the type of e may not be assigned to the declared return type of the
+    // immediately enclosing function.
+    if (expression != null) {
+      final expressionType = analyze(expression);
+      if (isVoidFunction
+          && !types.isAssignable(expressionType, types.voidType)) {
+        reportTypeWarning(expression, MessageKind.RETURN_VALUE_IN_VOID);
+      } else {
+        checkAssignable(expression, expectedReturnType, expressionType);
+      }
+
+    // Let f be the function immediately enclosing a return statement of the
+    // form 'return;' It is a static warning if both of the following conditions
+    // hold:
+    // - f is not a generative constructor.
+    // - The return type of f may not be assigned to void.
+    } else if (!types.isAssignable(expectedReturnType, types.voidType)) {
+      reportTypeWarning(node, MessageKind.RETURN_NOTHING,
+                        {'returnType': expectedReturnType});
+    }
+    return StatementType.RETURNING;
+  }
+
+  DartType visitThrow(Throw node) {
+    if (node.expression != null) analyze(node.expression);
+    return StatementType.RETURNING;
+  }
+
+  DartType computeType(Element element) {
+    if (Elements.isUnresolved(element)) return types.dynamicType;
+    DartType result = element.computeType(compiler);
+    return (result != null) ? result : types.dynamicType;
+  }
+
+  DartType visitTypeAnnotation(TypeAnnotation node) {
+    return elements.getType(node);
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    return types.dynamicType;
+  }
+
+  DartType visitVariableDefinitions(VariableDefinitions node) {
+    DartType type = analyzeWithDefault(node.type, types.dynamicType);
+    if (type == types.voidType) {
+      reportTypeWarning(node.type, MessageKind.VOID_VARIABLE);
+      type = types.dynamicType;
+    }
+    for (Link<Node> link = node.definitions.nodes; !link.isEmpty;
+         link = link.tail) {
+      Node initialization = link.head;
+      compiler.ensure(initialization is Identifier
+                      || initialization is Send);
+      if (initialization is Send) {
+        DartType initializer = analyzeNonVoid(link.head);
+        checkAssignable(node, type, initializer);
+      }
+    }
+    return StatementType.NOT_RETURNING;
+  }
+
+  DartType visitWhile(While node) {
+    checkCondition(node.condition);
+    StatementType bodyType = analyze(node.body);
+    Expression cond = node.condition.asParenthesizedExpression().expression;
+    if (cond.asLiteralBool() != null && cond.asLiteralBool().value == true) {
+      // If the condition is a constant boolean expression denoting true,
+      // control-flow always enters the loop body.
+      // TODO(karlklose): this should be StatementType.RETURNING unless there
+      // is a break in the loop body that has the loop or a label outside the
+      // loop as a target.
+      return bodyType;
+    } else {
+      return bodyType.join(StatementType.NOT_RETURNING);
+    }
+  }
+
+  DartType visitParenthesizedExpression(ParenthesizedExpression node) {
+    return analyze(node.expression);
+  }
+
+  DartType visitConditional(Conditional node) {
+    checkCondition(node.condition);
+    DartType thenType = analyzeNonVoid(node.thenExpression);
+    DartType elseType = analyzeNonVoid(node.elseExpression);
+    if (types.isSubtype(thenType, elseType)) {
+      return thenType;
+    } else if (types.isSubtype(elseType, thenType)) {
+      return elseType;
+    } else {
+      return objectType;
+    }
+  }
+
+  DartType visitModifiers(Modifiers node) {}
+
+  visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    return stringType;
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    node.visitChildren(this);
+    return stringType;
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    return StatementType.NOT_RETURNING;
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    return StatementType.NOT_RETURNING;
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    return StatementType.NOT_RETURNING;
+  }
+
+  visitForIn(ForIn node) {
+    analyze(node.expression);
+    StatementType bodyType = analyze(node.body);
+    return bodyType.join(StatementType.NOT_RETURNING);
+  }
+
+  visitLabel(Label node) { }
+
+  visitLabeledStatement(LabeledStatement node) {
+    return node.statement.accept(this);
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    return unhandledExpression();
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    return unhandledExpression();
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    return unhandledExpression();
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    return unhandledStatement();
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    return unhandledStatement();
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    return unhandledStatement();
+  }
+
+  visitTryStatement(TryStatement node) {
+    return unhandledStatement();
+  }
+
+  visitScriptTag(ScriptTag node) {
+    return unhandledExpression();
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    return unhandledStatement();
+  }
+
+  visitTypedef(Typedef node) {
+    return unhandledStatement();
+  }
+
+  DartType visitNode(Node node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitCombinator(Combinator node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitExport(Export node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitExpression(Expression node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitGotoStatement(GotoStatement node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitImport(Import node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLibraryName(LibraryName node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLibraryTag(LibraryTag node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLiteral(Literal node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPart(Part node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPartOf(PartOf node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPostfix(Postfix node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPrefix(Prefix node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitStatement(Statement node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitStringNode(StringNode node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLibraryDependency(LibraryDependency node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart b/pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart
new file mode 100644
index 0000000..6c05203
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart
@@ -0,0 +1,1678 @@
+// 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.
+
+part of types;
+
+class CancelTypeInferenceException {
+  final Node node;
+  final String reason;
+
+  CancelTypeInferenceException(this.node, this.reason);
+}
+
+/**
+ * A singleton concrete type. More precisely, a [BaseType] is one of the
+ * following:
+ *
+ *   - a non-asbtract class like [: int :] or [: Uri :] but not [: List :]
+ *   - the null base type
+ *   - the unknown base type
+ */
+abstract class BaseType {
+  bool isClass();
+  bool isUnknown();
+  bool isNull();
+}
+
+/**
+ * A non-asbtract class like [: int :] or [: Uri :] but not [: List :].
+ */
+class ClassBaseType implements BaseType {
+  final ClassElement element;
+
+  ClassBaseType(this.element);
+
+  bool operator ==(BaseType other) {
+    if (identical(this, other)) return true;
+    if (other is! ClassBaseType) return false;
+    return element == other.element;
+  }
+  int get hashCode => element.hashCode;
+  String toString() => element.name.slowToString();
+  bool isClass() => true;
+  bool isUnknown() => false;
+  bool isNull() => false;
+}
+
+/**
+ * The unknown base type.
+ */
+class UnknownBaseType implements BaseType {
+  const UnknownBaseType();
+  bool operator ==(BaseType other) => other is UnknownBaseType;
+  int get hashCode => 0;
+  bool isClass() => false;
+  bool isUnknown() => true;
+  bool isNull() => false;
+  toString() => "unknown";
+}
+
+/**
+ * The null base type.
+ */
+class NullBaseType implements BaseType {
+  const NullBaseType();
+  bool operator ==(BaseType other) => other is NullBaseType;
+  int get hashCode => 1;
+  bool isClass() => false;
+  bool isUnknown() => false;
+  bool isNull() => true;
+  toString() => "null";
+}
+
+/**
+ * An immutable set of base types, like [: {int, bool} :] or the unknown
+ * concrete type.
+ */
+abstract class ConcreteType {
+  factory ConcreteType.empty() {
+    return new UnionType(new Set<BaseType>());
+  }
+
+  /**
+   * The singleton constituted of the unknown base type is the unknown concrete
+   * type.
+   */
+  factory ConcreteType.singleton(int maxConcreteTypeSize, BaseType baseType) {
+    if (baseType.isUnknown() || maxConcreteTypeSize < 1) {
+      return new UnknownConcreteType();
+    }
+    Set<BaseType> singletonSet = new Set<BaseType>();
+    singletonSet.add(baseType);
+    return new UnionType(singletonSet);
+  }
+
+  factory ConcreteType.unknown() {
+    return const UnknownConcreteType();
+  }
+
+  ConcreteType union(int maxConcreteTypeSize, ConcreteType other);
+  bool isUnkown();
+  bool isEmpty();
+  Set<BaseType> get baseTypes;
+
+  /**
+   * Returns the unique element of [: this :] if [: this :] is a singleton,
+   * null otherwise.
+   */
+  ClassElement getUniqueType();
+}
+
+/**
+ * The unkown concrete type: it is absorbing for the union.
+ */
+class UnknownConcreteType implements ConcreteType {
+  const UnknownConcreteType();
+  bool isUnkown() => true;
+  bool isEmpty() => false;
+  bool operator ==(ConcreteType other) => identical(this, other);
+  Set<BaseType> get baseTypes =>
+      new Set<BaseType>.from([const UnknownBaseType()]);
+  int get hashCode => 0;
+  ConcreteType union(int maxConcreteTypeSize, ConcreteType other) => this;
+  ClassElement getUniqueType() => null;
+  toString() => "unknown";
+}
+
+/**
+ * An immutable set of base types, like [: {int, bool} :].
+ */
+class UnionType implements ConcreteType {
+  final Set<BaseType> baseTypes;
+
+  /**
+   * The argument should NOT be mutated later. Do not call directly, use
+   * ConcreteType.singleton instead.
+   */
+  UnionType(this.baseTypes);
+
+  bool isUnkown() => false;
+  bool isEmpty() => baseTypes.isEmpty;
+
+  bool operator ==(ConcreteType other) {
+    if (other is! UnionType) return false;
+    if (baseTypes.length != other.baseTypes.length) return false;
+    return baseTypes.containsAll(other.baseTypes);
+  }
+
+  int get hashCode {
+    int result = 1;
+    for (final baseType in baseTypes) {
+      result = 31 * result + baseType.hashCode;
+    }
+    return result;
+  }
+
+  // TODO(polux): Collapse {num, int, ...}, {num, double, ...} and
+  // {int, double,...} into {num, ...} as an optimization. It will require
+  // UnionType to know about these class elements, which is cumbersome because
+  // there are no nested classes. We need factory methods instead.
+  ConcreteType union(int maxConcreteTypeSize, ConcreteType other) {
+    if (other.isUnkown()) {
+      return const UnknownConcreteType();
+    }
+    UnionType otherUnion = other;  // cast
+    Set<BaseType> newBaseTypes = new Set<BaseType>.from(baseTypes);
+    newBaseTypes.addAll(otherUnion.baseTypes);
+    return newBaseTypes.length > maxConcreteTypeSize
+        ? const UnknownConcreteType()
+        : new UnionType(newBaseTypes);
+  }
+
+  ClassElement getUniqueType() {
+    if (baseTypes.length == 1) {
+      var iterator = baseTypes.iterator;
+      iterator.moveNext();
+      BaseType uniqueBaseType = iterator.current;
+      if (uniqueBaseType.isClass()) {
+        ClassBaseType uniqueClassType = uniqueBaseType;
+        return uniqueClassType.element;
+      }
+    }
+    return null;
+  }
+
+  String toString() => baseTypes.toString();
+}
+
+/**
+ * The cartesian product of concrete types: an iterable of [BaseTypeTuple]s. For
+ * instance, the cartesian product of the concrete types [: {A, B} :] and
+ * [: {C, D} :] is an itearble whose iterators will yield [: (A, C) :],
+ * [: (A, D) :], [: (B, C) :] and finally [: (B, D) :].
+ */
+class ConcreteTypeCartesianProduct
+    extends Iterable<ConcreteTypesEnvironment> {
+  final ConcreteTypesInferrer inferrer;
+  final ClassElement typeOfThis;
+  final Map<Element, ConcreteType> concreteTypes;
+  ConcreteTypeCartesianProduct(this.inferrer, this.typeOfThis,
+                               this.concreteTypes);
+  Iterator get iterator => concreteTypes.isEmpty
+      ? [new ConcreteTypesEnvironment(inferrer, new ClassBaseType(typeOfThis))]
+            .iterator
+      : new ConcreteTypeCartesianProductIterator(inferrer,
+            new ClassBaseType(typeOfThis), concreteTypes);
+  String toString() {
+    List<ConcreteTypesEnvironment> cartesianProduct =
+        new List<ConcreteTypesEnvironment>.from(this);
+    return cartesianProduct.toString();
+  }
+}
+
+/**
+ * An helper class for [ConcreteTypeCartesianProduct].
+ */
+class ConcreteTypeCartesianProductIterator
+    implements Iterator<ConcreteTypesEnvironment> {
+  final ConcreteTypesInferrer inferrer;
+  final BaseType baseTypeOfThis;
+  final Map<Element, ConcreteType> concreteTypes;
+  final Map<Element, BaseType> nextValues;
+  final Map<Element, Iterator> state;
+  int size = 1;
+  int counter = 0;
+  ConcreteTypesEnvironment _current;
+
+  ConcreteTypeCartesianProductIterator(this.inferrer, this.baseTypeOfThis,
+      Map<Element, ConcreteType> concreteTypes)
+      : this.concreteTypes = concreteTypes,
+        nextValues = new Map<Element, BaseType>(),
+        state = new Map<Element, Iterator>() {
+    if (concreteTypes.isEmpty) {
+      size = 0;
+      return;
+    }
+    for (final e in concreteTypes.keys) {
+      final baseTypes = concreteTypes[e].baseTypes;
+      size *= baseTypes.length;
+    }
+  }
+
+  ConcreteTypesEnvironment get current => _current;
+
+  ConcreteTypesEnvironment takeSnapshot() {
+    Map<Element, ConcreteType> result = new Map<Element, ConcreteType>();
+    nextValues.forEach((k, v) {
+      result[k] = inferrer.singletonConcreteType(v);
+    });
+    return new ConcreteTypesEnvironment.of(inferrer, result, baseTypeOfThis);
+  }
+
+  bool moveNext() {
+    if (counter >= size) {
+      _current = null;
+      return false;
+    }
+    Element keyToIncrement = null;
+    for (final key in concreteTypes.keys) {
+      final iterator = state[key];
+      if (iterator != null && iterator.moveNext()) {
+        nextValues[key] = state[key].current;
+        break;
+      }
+      Iterator newIterator = concreteTypes[key].baseTypes.iterator;
+      state[key] = newIterator;
+      newIterator.moveNext();
+      nextValues[key] = newIterator.current;
+    }
+    counter++;
+    _current = takeSnapshot();
+    return true;
+  }
+}
+
+/**
+ * [BaseType] Constants.
+ */
+class BaseTypes {
+  final ClassBaseType intBaseType;
+  final ClassBaseType doubleBaseType;
+  final ClassBaseType numBaseType;
+  final ClassBaseType boolBaseType;
+  final ClassBaseType stringBaseType;
+  final ClassBaseType listBaseType;
+  final ClassBaseType mapBaseType;
+  final ClassBaseType objectBaseType;
+  final ClassBaseType typeBaseType;
+
+  static _getNativeListClass(Compiler compiler) {
+    // TODO(polux): switch to other implementations on other backends
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsArrayClass;
+  }
+
+  BaseTypes(Compiler compiler) :
+    intBaseType = new ClassBaseType(compiler.intClass),
+    doubleBaseType = new ClassBaseType(compiler.doubleClass),
+    numBaseType = new ClassBaseType(compiler.numClass),
+    boolBaseType = new ClassBaseType(compiler.boolClass),
+    stringBaseType = new ClassBaseType(compiler.stringClass),
+    // in the Javascript backend, lists are implemented by JsArray
+    listBaseType = new ClassBaseType(_getNativeListClass(compiler)),
+    mapBaseType = new ClassBaseType(compiler.mapClass),
+    objectBaseType = new ClassBaseType(compiler.objectClass),
+    typeBaseType = new ClassBaseType(compiler.typeClass);
+}
+
+/**
+ * A method-local immutable mapping from variables to their inferred
+ * [ConcreteTypes]. Each visitor owns one.
+ */
+class ConcreteTypesEnvironment {
+  final ConcreteTypesInferrer inferrer;
+  final Map<Element, ConcreteType> environment;
+  final BaseType typeOfThis;
+
+  ConcreteTypesEnvironment(this.inferrer, [this.typeOfThis]) :
+    this.environment = new Map<Element, ConcreteType>();
+  ConcreteTypesEnvironment.of(this.inferrer, this.environment, this.typeOfThis);
+
+  ConcreteType lookupType(Element element) => environment[element];
+  ConcreteType lookupTypeOfThis() {
+    return (typeOfThis == null)
+        ? null
+        : inferrer.singletonConcreteType(typeOfThis);
+  }
+
+  ConcreteTypesEnvironment put(Element element, ConcreteType type) {
+    Map<Element, ConcreteType> newMap =
+        new Map<Element, ConcreteType>.from(environment);
+    newMap[element] = type;
+    return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis);
+  }
+
+  ConcreteTypesEnvironment join(ConcreteTypesEnvironment other) {
+    if (typeOfThis != other.typeOfThis) {
+      throw "trying to join incompatible environments";
+    }
+    Map<Element, ConcreteType> newMap =
+        new Map<Element, ConcreteType>.from(environment);
+    other.environment.forEach((element, type) {
+      ConcreteType currentType = newMap[element];
+      if (element == null) {
+        newMap[element] = type;
+      } else {
+        newMap[element] = inferrer.union(currentType, type);
+      }
+    });
+    return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis);
+  }
+
+  bool operator ==(ConcreteTypesEnvironment other) {
+    if (other is! ConcreteTypesEnvironment) return false;
+    if (typeOfThis != other.typeOfThis) return false;
+    if (environment.length != other.environment.length) return false;
+    for (Element key in environment.keys) {
+      if (!other.environment.containsKey(key)
+          || (environment[key] != other.environment[key])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  int get hashCode {
+    int result = (typeOfThis != null) ? typeOfThis.hashCode : 1;
+    environment.forEach((element, concreteType) {
+      result = 31 * (31 * result + element.hashCode) +
+          concreteType.hashCode;
+    });
+    return result;
+  }
+
+  String toString() => "{ this: $typeOfThis, env: ${environment.toString()} }";
+}
+
+/**
+ * A work item for the type inference queue.
+ */
+class InferenceWorkItem {
+  FunctionElement method;
+  ConcreteTypesEnvironment environment;
+  InferenceWorkItem(this.method, this.environment);
+
+  toString() => "{ method = ${method.name.slowToString()}, "
+                "environment = $environment }";
+}
+
+/**
+ * A task which conservatively infers a [ConcreteType] for each sub expression
+ * of the program. The entry point is [analyzeMain].
+ */
+class ConcreteTypesInferrer {
+  static final bool LOG_FAILURES = true;
+
+  final String name = "Type inferrer";
+
+  final Compiler compiler;
+
+  /**
+   * When true, the string literal [:"__dynamic_for_test":] is inferred to
+   * have the unknown type.
+   */
+  // TODO(polux): get rid of this hack once we have a natural way of inferring
+  // the unknown type.
+  bool testMode = false;
+
+  /**
+   * Constants representing builtin base types. Initialized in [initialize]
+   * and not in the constructor because the compiler elements are not yet
+   * populated.
+   */
+  BaseTypes baseTypes;
+
+  /**
+   * Constant representing [:ConcreteList#[]:] where [:ConcreteList:] is the
+   * concrete implmentation of lists for the selected backend.
+   */
+  FunctionElement listIndex;
+
+  /**
+   * Constant representing [:ConcreteList#[]=:] where [:ConcreteList:] is the
+   * concrete implmentation of lists for the selected backend.
+   */
+  FunctionElement listIndexSet;
+
+  /**
+   * Constant representing [:List():].
+   */
+  FunctionElement listConstructor;
+
+  /**
+   * A cache from (function x argument base types) to concrete types,
+   * used to memoize [analyzeMonoSend]. Another way of seeing [cache] is as a
+   * map from [FunctionElement]s to "templates" in the sense of "The Cartesian
+   * Product Algorithm - Simple and Precise Type Inference of Parametric
+   * Polymorphism" by Ole Agesen.
+   */
+  final Map<FunctionElement, Map<ConcreteTypesEnvironment, ConcreteType>> cache;
+
+  /** A map from expressions to their inferred concrete types. */
+  final Map<Node, ConcreteType> inferredTypes;
+
+  /** A map from fields to their inferred concrete types. */
+  final Map<Element, ConcreteType> inferredFieldTypes;
+
+  /** The work queue consumed by [analyzeMain]. */
+  final Queue<InferenceWorkItem> workQueue;
+
+  /** [: callers[f] :] is the list of [: f :]'s possible callers. */
+  final Map<FunctionElement, Set<FunctionElement>> callers;
+
+  /** [: readers[field] :] is the list of [: field :]'s possible readers. */
+  final Map<Element, Set<FunctionElement>> readers;
+
+  /** The inferred type of elements stored in Lists. */
+  ConcreteType listElementType;
+
+  /**
+   * A map from parameters to their inferred concrete types. It plays no role
+   * in the analysis, it is write only.
+   */
+  final Map<VariableElement, ConcreteType> inferredParameterTypes;
+
+  ConcreteTypesInferrer(Compiler compiler)
+      : this.compiler = compiler,
+        cache = new Map<FunctionElement,
+            Map<ConcreteTypesEnvironment, ConcreteType>>(),
+        inferredTypes = new Map<Node, ConcreteType>(),
+        inferredFieldTypes = new Map<Element, ConcreteType>(),
+        inferredParameterTypes = new Map<VariableElement, ConcreteType>(),
+        workQueue = new Queue<InferenceWorkItem>(),
+        callers = new Map<FunctionElement, Set<FunctionElement>>(),
+        readers = new Map<Element, Set<FunctionElement>>(),
+        listElementType = new ConcreteType.empty() {
+    unknownConcreteType = new ConcreteType.unknown();
+    emptyConcreteType = new ConcreteType.empty();
+  }
+
+  /**
+   * Populates [cache] with ad hoc rules like:
+   *
+   *     {int} + {int}    -> {int}
+   *     {int} + {double} -> {num}
+   *     {int} + {num}    -> {double}
+   *     ...
+   */
+  populateCacheWithBuiltinRules() {
+    // Builds the environment that would be looked up if we were to analyze
+    // o.method(arg) where o has concrete type {receiverType} and arg has
+    // concrete type {argumentType}.
+    ConcreteTypesEnvironment makeEnvironment(BaseType receiverType,
+                                             FunctionElement method,
+                                             BaseType argumentType) {
+      ArgumentsTypes argumentsTypes = new ArgumentsTypes(
+          [singletonConcreteType(argumentType)],
+          new Map());
+      Map<Element, ConcreteType> argumentMap =
+          associateArguments(method, argumentsTypes);
+      return new ConcreteTypesEnvironment.of(this, argumentMap, receiverType);
+    }
+
+    // Adds the rule {receiverType}.method({argumentType}) -> {returnType}
+    // to cache.
+    void rule(ClassBaseType receiverType, String method,
+              BaseType argumentType, BaseType returnType) {
+      // The following line shouldn't be needed but the mock compiler doesn't
+      // resolve num for some reason.
+      receiverType.element.ensureResolved(compiler);
+      FunctionElement methodElement =
+          receiverType.element.lookupMember(new SourceString(method));
+      ConcreteTypesEnvironment environment =
+          makeEnvironment(receiverType, methodElement, argumentType);
+      Map<ConcreteTypesEnvironment, ConcreteType> map =
+          cache.containsKey(methodElement)
+              ? cache[methodElement]
+              : new Map<ConcreteTypesEnvironment, ConcreteType>();
+      map[environment] = singletonConcreteType(returnType);
+      cache[methodElement] = map;
+    }
+
+    // The hardcoded typing rules.
+    final ClassBaseType int = baseTypes.intBaseType;
+    final ClassBaseType double = baseTypes.doubleBaseType;
+    final ClassBaseType num = baseTypes.numBaseType;
+    for (String method in ['+', '*', '-']) {
+      rule(int, method, int, int);
+      rule(int, method, double, num);
+      rule(int, method, num, num);
+
+      rule(double, method, double, double);
+      rule(double, method, int, num);
+      rule(double, method, num, num);
+
+      rule(num, method, int, num);
+      rule(num, method, double, num);
+      rule(num, method, num, num);
+    }
+  }
+
+  // --- utility methods ---
+
+  /** The unknown concrete type */
+  ConcreteType unknownConcreteType;
+
+  /** The empty concrete type */
+  ConcreteType emptyConcreteType;
+
+  /** Creates a singleton concrete type containing [baseType]. */
+  ConcreteType singletonConcreteType(BaseType baseType) {
+    return new ConcreteType.singleton(compiler.maxConcreteTypeSize, baseType);
+  }
+
+  /** Returns the union of its two arguments */
+  ConcreteType union(ConcreteType concreteType1, ConcreteType concreteType2) {
+    return concreteType1.union(compiler.maxConcreteTypeSize, concreteType2);
+  }
+
+  /**
+   * Returns all the members with name [methodName].
+   */
+  List<Element> getMembersByName(SourceString methodName) {
+    // TODO(polux): memoize?
+    var result = new List<Element>();
+    for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) {
+      Element elem = cls.lookupLocalMember(methodName);
+      if (elem != null) {
+        result.add(elem);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Sets the concrete type associated to [node] to the union of the inferred
+   * concrete type so far and [type].
+   */
+  void augmentInferredType(Node node, ConcreteType type) {
+    ConcreteType currentType = inferredTypes[node];
+    inferredTypes[node] = (currentType == null)
+        ? type
+        : union(currentType, type);
+  }
+
+  /**
+   * Returns the current inferred concrete type of [field].
+   */
+  ConcreteType getFieldType(Element field) {
+    ConcreteType result = inferredFieldTypes[field];
+    return (result == null) ? emptyConcreteType : result;
+  }
+
+  /**
+   * Sets the concrete type associated to [field] to the union of the inferred
+   * concrete type so far and [type].
+   */
+  void augmentFieldType(Element field, ConcreteType type) {
+    ConcreteType oldType = inferredFieldTypes[field];
+    ConcreteType newType = (oldType != null)
+        ? union(oldType, type)
+        : type;
+    if (oldType != newType) {
+      inferredFieldTypes[field] = newType;
+      final fieldReaders = readers[field];
+      if (fieldReaders != null) {
+        for (final reader in fieldReaders) {
+          final readerInstances = cache[reader];
+          if (readerInstances != null) {
+            readerInstances.forEach((environment, _) {
+              workQueue.addLast(new InferenceWorkItem(reader, environment));
+            });
+          }
+        }
+      }
+    }
+  }
+
+  /// Augment the inferred type of elements stored in Lists.
+  void augmentListElementType(ConcreteType type) {
+    ConcreteType newType = union(listElementType, type);
+    if (newType != listElementType) {
+      invalidateCallers(listIndex);
+      listElementType = newType;
+    }
+  }
+
+  /**
+   * Sets the concrete type associated to [parameter] to the union of the
+   * inferred concrete type so far and [type].
+   */
+  void augmentParameterType(VariableElement parameter, ConcreteType type) {
+    ConcreteType oldType = inferredParameterTypes[parameter];
+    inferredParameterTypes[parameter] =
+        (oldType == null) ? type : union(oldType, type);
+  }
+
+  /**
+   * Add [caller] to the set of [callee]'s callers.
+   */
+  void addCaller(FunctionElement callee, FunctionElement caller) {
+    Set<FunctionElement> current = callers[callee];
+    if (current != null) {
+      current.add(caller);
+    } else {
+      Set<FunctionElement> newSet = new Set<FunctionElement>();
+      newSet.add(caller);
+      callers[callee] = newSet;
+    }
+  }
+
+  /**
+   * Add [reader] to the set of [field]'s readers.
+   */
+  void addReader(Element field, FunctionElement reader) {
+    Set<FunctionElement> current = readers[field];
+    if (current != null) {
+      current.add(reader);
+    } else {
+      Set<FunctionElement> newSet = new Set<FunctionElement>();
+      newSet.add(reader);
+      readers[field] = newSet;
+    }
+  }
+
+  /**
+   * Add callers of [function] to the workqueue.
+   */
+  void invalidateCallers(FunctionElement function) {
+    Set<FunctionElement> methodCallers = callers[function];
+    if (methodCallers == null) return;
+    for (FunctionElement caller in methodCallers) {
+      Map<ConcreteTypesEnvironment, ConcreteType> callerInstances =
+          cache[caller];
+      if (callerInstances != null) {
+        callerInstances.forEach((environment, _) {
+          workQueue.addLast(
+              new InferenceWorkItem(caller, environment));
+        });
+      }
+    }
+  }
+
+  // -- query --
+
+  /**
+   * Get the inferred concrete type of [node].
+   */
+  ConcreteType getConcreteTypeOfNode(Node node) => inferredTypes[node];
+
+  /**
+   * Get the inferred concrete type of [parameter].
+   */
+  ConcreteType getConcreteTypeOfParameter(VariableElement parameter) {
+    return inferredParameterTypes[parameter];
+  }
+
+  // --- analysis ---
+
+  /**
+   * Returns the concrete type returned by [function] given arguments of
+   * concrete types [argumentsTypes]. If [function] is static then
+   * [receiverType] must be null, else [function] must be a member of the class
+   * of [receiverType].
+   */
+  ConcreteType getSendReturnType(FunctionElement function,
+                                 ClassElement receiverType,
+                                 ArgumentsTypes argumentsTypes) {
+    ConcreteType result = emptyConcreteType;
+    Map<Element, ConcreteType> argumentMap =
+        associateArguments(function, argumentsTypes);
+    // if the association failed, this send will never occur or will fail
+    if (argumentMap == null) {
+      return emptyConcreteType;
+    }
+
+    argumentMap.forEach(augmentParameterType);
+    ConcreteTypeCartesianProduct product =
+        new ConcreteTypeCartesianProduct(this, receiverType, argumentMap);
+    for (ConcreteTypesEnvironment environment in product) {
+      result = union(result,
+                     getMonomorphicSendReturnType(function, environment));
+    }
+    return result;
+  }
+
+  /**
+   * Given a method signature and a list of concrete types, builds a map from
+   * formals to their corresponding concrete types. Returns null if the
+   * association is impossible (for instance: too many arguments).
+   */
+  Map<Element, ConcreteType> associateArguments(FunctionElement function,
+                                                ArgumentsTypes argumentsTypes) {
+    final Map<Element, ConcreteType> result = new Map<Element, ConcreteType>();
+    final FunctionSignature signature = function.computeSignature(compiler);
+
+    // guard 1: too many arguments
+    if (argumentsTypes.length > signature.parameterCount) {
+      return null;
+    }
+    // guard 2: not enough arguments
+    if (argumentsTypes.positional.length < signature.requiredParameterCount) {
+      return null;
+    }
+    // guard 3: too many positional arguments
+    if (signature.optionalParametersAreNamed &&
+        argumentsTypes.positional.length > signature.requiredParameterCount) {
+      return null;
+    }
+
+    handleLeftoverOptionalParameter(Element parameter) {
+      // TODO(polux): use default value whenever available
+      // TODO(polux): add a marker to indicate whether an argument was provided
+      //     in order to handle "?parameter" tests
+      result[parameter] = singletonConcreteType(const NullBaseType());
+    }
+
+    final Iterator<ConcreteType> remainingPositionalArguments =
+        argumentsTypes.positional.iterator;
+    // we attach each positional parameter to its corresponding positional
+    // argument
+    for (Link<Element> requiredParameters = signature.requiredParameters;
+        !requiredParameters.isEmpty;
+        requiredParameters = requiredParameters.tail) {
+      final Element requiredParameter = requiredParameters.head;
+      // we know moveNext() succeeds because of guard 2
+      remainingPositionalArguments.moveNext();
+      result[requiredParameter] = remainingPositionalArguments.current;
+    }
+    if (signature.optionalParametersAreNamed) {
+      // we build a map out of the remaining named parameters
+      Link<Element> remainingOptionalParameters = signature.optionalParameters;
+      final Map<SourceString, Element> leftOverNamedParameters =
+          new Map<SourceString, Element>();
+      for (;
+           !remainingOptionalParameters.isEmpty;
+           remainingOptionalParameters = remainingOptionalParameters.tail) {
+        final Element namedParameter = remainingOptionalParameters.head;
+        leftOverNamedParameters[namedParameter.name] = namedParameter;
+      }
+      // we attach the named arguments to their corresponding optional
+      // parameters
+      for (Identifier identifier in argumentsTypes.named.keys) {
+        final ConcreteType concreteType = argumentsTypes.named[identifier];
+        SourceString source = identifier.source;
+        final Element namedParameter = leftOverNamedParameters[source];
+        // unexisting or already used named parameter
+        if (namedParameter == null) return null;
+        result[namedParameter] = concreteType;
+        leftOverNamedParameters.remove(source);
+      }
+      leftOverNamedParameters.forEach((_, Element parameter) {
+        handleLeftoverOptionalParameter(parameter);
+      });
+    } else { // optional parameters are positional
+      // we attach the remaining positional arguments to their corresponding
+      // optional parameters
+      Link<Element> remainingOptionalParameters = signature.optionalParameters;
+      while (remainingPositionalArguments.moveNext()) {
+        final Element optionalParameter = remainingOptionalParameters.head;
+        result[optionalParameter] = remainingPositionalArguments.current;
+        // we know tail is defined because of guard 1
+        remainingOptionalParameters = remainingOptionalParameters.tail;
+      }
+      for (;
+           !remainingOptionalParameters.isEmpty;
+           remainingOptionalParameters = remainingOptionalParameters.tail) {
+        handleLeftoverOptionalParameter(remainingOptionalParameters.head);
+      }
+    }
+    return result;
+  }
+
+  ConcreteType getMonomorphicSendReturnType(
+      FunctionElement function,
+      ConcreteTypesEnvironment environment) {
+    ConcreteType specialType = getSpecialCaseReturnType(function, environment);
+    if (specialType != null) return specialType;
+
+    Map<ConcreteTypesEnvironment, ConcreteType> template = cache[function];
+    if (template == null) {
+      template = new Map<ConcreteTypesEnvironment, ConcreteType>();
+      cache[function] = template;
+    }
+    ConcreteType type = template[environment];
+    if (type != null) {
+      return type;
+    } else {
+      workQueue.addLast(
+        new InferenceWorkItem(function, environment));
+      // in case of a constructor, optimize by returning the class
+      return emptyConcreteType;
+    }
+  }
+
+  /**
+   * Handles external methods that cannot be cached because they depend on some
+   * other state of [ConcreteTypesInferrer] like [:List#[]:] and
+   * [:List#[]=:]. Returns null if [function] and [environment] don't form a
+   * special case
+   */
+  ConcreteType getSpecialCaseReturnType(FunctionElement function,
+                                        ConcreteTypesEnvironment environment) {
+    if (function == listIndex) {
+      ConcreteType indexType = environment.lookupType(
+          listIndex.functionSignature.requiredParameters.head);
+      if (!indexType.baseTypes.contains(baseTypes.intBaseType)) {
+        return new ConcreteType.empty();
+      }
+      return listElementType;
+    } else if (function == listIndexSet) {
+      Link<Element> parameters =
+          listIndexSet.functionSignature.requiredParameters;
+      ConcreteType indexType = environment.lookupType(parameters.head);
+      if (!indexType.baseTypes.contains(baseTypes.intBaseType)) {
+        return new ConcreteType.empty();
+      }
+      ConcreteType elementType = environment.lookupType(parameters.tail.head);
+      augmentListElementType(elementType);
+      return new ConcreteType.empty();
+    }
+    return null;
+  }
+
+  ConcreteType analyze(FunctionElement element,
+                       ConcreteTypesEnvironment environment) {
+    return element.isGenerativeConstructor()
+        ? analyzeConstructor(element, environment)
+        : analyzeMethod(element, environment);
+  }
+
+  ConcreteType analyzeMethod(FunctionElement element,
+                             ConcreteTypesEnvironment environment) {
+    TreeElements elements =
+        compiler.enqueuer.resolution.resolvedElements[element];
+    ConcreteType specialResult = handleSpecialMethod(element, environment);
+    if (specialResult != null) return specialResult;
+    FunctionExpression tree = element.parseNode(compiler);
+    if (tree.hasBody()) {
+      Visitor visitor =
+          new TypeInferrerVisitor(elements, element, this, environment);
+      return tree.accept(visitor);
+    } else {
+      // TODO(polux): implement visitForeingCall and always use the
+      // implementation element instead of this hack
+      return new ConcreteType.unknown();
+    }
+  }
+
+  ConcreteType analyzeConstructor(FunctionElement element,
+                                  ConcreteTypesEnvironment environment) {
+    ClassElement enclosingClass = element.enclosingElement;
+    FunctionExpression tree = compiler.parser.parse(element);
+    TreeElements elements =
+        compiler.enqueuer.resolution.resolvedElements[element];
+    Visitor visitor =
+        new TypeInferrerVisitor(elements, element, this, environment);
+
+    // handle initializing formals
+    element.functionSignature.forEachParameter((param) {
+      if (param.kind == ElementKind.FIELD_PARAMETER) {
+        FieldParameterElement fieldParam = param;
+        augmentFieldType(fieldParam.fieldElement,
+            environment.lookupType(param));
+      }
+    });
+
+    // analyze initializers, including a possible call to super or a redirect
+    bool foundSuperOrRedirect = false;
+    if (tree.initializers != null) {
+      // we look for a possible call to super in the initializer list
+      for (final init in tree.initializers) {
+        init.accept(visitor);
+        if (init.asSendSet() == null) {
+          foundSuperOrRedirect = true;
+        }
+      }
+    }
+
+    // if no call to super or redirect has been found, call the default
+    // constructor (if the current class is not Object).
+    if (!foundSuperOrRedirect) {
+      ClassElement superClass = enclosingClass.superclass;
+      if (enclosingClass != compiler.objectClass) {
+        FunctionElement target = superClass.lookupConstructor(
+          new Selector.callDefaultConstructor(enclosingClass.getLibrary()));
+        final superClassConcreteType = singletonConcreteType(
+            new ClassBaseType(enclosingClass));
+        getSendReturnType(target, enclosingClass,
+            new ArgumentsTypes(new List(), new Map()));
+      }
+    }
+
+    tree.accept(visitor);
+    return singletonConcreteType(new ClassBaseType(enclosingClass));
+  }
+
+  /**
+   * Hook that performs side effects on some special method calls (like
+   * [:List(length):]) and possibly returns a concrete type
+   * (like [:{JsArray}:]).
+   */
+  ConcreteType handleSpecialMethod(FunctionElement element,
+                                   ConcreteTypesEnvironment environment) {
+    // When List([length]) is called with some length, we must augment
+    // listElementType with {null}.
+    if (element == listConstructor) {
+      Link<Element> parameters =
+          listConstructor.functionSignature.optionalParameters;
+      ConcreteType lengthType = environment.lookupType(parameters.head);
+      if (lengthType.baseTypes.contains(baseTypes.intBaseType)) {
+        augmentListElementType(singletonConcreteType(new NullBaseType()));
+      }
+      return singletonConcreteType(baseTypes.listBaseType);
+    }
+  }
+
+  /* Initialization code that cannot be run in the constructor because it
+   * requires the compiler's elements to be populated.
+   */
+  void initialize() {
+    baseTypes = new BaseTypes(compiler);
+    ClassElement jsArrayClass = baseTypes.listBaseType.element;
+    listIndex = jsArrayClass.lookupMember(const SourceString('[]'));
+    listIndexSet =
+        jsArrayClass.lookupMember(const SourceString('[]='));
+    listConstructor =
+        compiler.listClass.lookupConstructor(
+            new Selector.callConstructor(const SourceString(''),
+                                         compiler.listClass.getLibrary()));
+  }
+
+  /**
+   * Performs concrete type inference of the code reachable from [element].
+   * Returns [:true:] if and only if analysis succeeded.
+   */
+  bool analyzeMain(Element element) {
+    initialize();
+    cache[element] = new Map<ConcreteTypesEnvironment, ConcreteType>();
+    populateCacheWithBuiltinRules();
+    try {
+      workQueue.addLast(
+          new InferenceWorkItem(element, new ConcreteTypesEnvironment(this)));
+      while (!workQueue.isEmpty) {
+        InferenceWorkItem item = workQueue.removeFirst();
+        ConcreteType concreteType = analyze(item.method, item.environment);
+        var template = cache[item.method];
+        if (template[item.environment] == concreteType) continue;
+        template[item.environment] = concreteType;
+        invalidateCallers(item.method);
+      }
+      return true;
+    } on CancelTypeInferenceException catch(e) {
+      if (LOG_FAILURES) {
+        compiler.log(e.reason);
+      }
+      return false;
+    }
+  }
+
+  /**
+   * Dumps debugging information on the standard output.
+   */
+  void debug() {
+    print("callers :");
+    callers.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("readers :");
+    readers.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("inferredFieldTypes:");
+    inferredFieldTypes.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("inferredParameterTypes:");
+    inferredParameterTypes.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("cache:");
+    cache.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("inferred expression types: ");
+    inferredTypes.forEach((k,v) {
+      print("  $k: $v");
+    });
+  }
+
+  /**
+   * Fail with a message and abort.
+   */
+  void fail(node, [reason]) {
+    String message = 'cannot infer types';
+    if (reason != null) {
+      message = '$message: $reason';
+    }
+    throw new CancelTypeInferenceException(node, message);
+  }
+}
+
+/**
+ * Represents the concrete types of the arguments of a send, indexed by
+ * position or name.
+ */
+class ArgumentsTypes {
+  final List<ConcreteType> positional;
+  final Map<Identifier, ConcreteType> named;
+  ArgumentsTypes(this.positional, this.named);
+  int get length => positional.length + named.length;
+  toString() => "{ positional = $positional, named = $named }";
+}
+
+/**
+ * The core logic of the type inference algorithm.
+ */
+class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
+  final ConcreteTypesInferrer inferrer;
+
+  final FunctionElement currentMethod;
+  ConcreteTypesEnvironment environment;
+  Node lastSeenNode;
+
+  TypeInferrerVisitor(TreeElements elements, this.currentMethod, this.inferrer,
+                      this.environment)
+      : super(elements);
+
+  ArgumentsTypes analyzeArguments(Link<Node> arguments) {
+    final positional = new List<ConcreteType>();
+    final named = new Map<Identifier, ConcreteType>();
+    for(Link<Node> iterator = arguments;
+        !iterator.isEmpty;
+        iterator = iterator.tail) {
+      Node node = iterator.head;
+      NamedArgument namedArgument = node.asNamedArgument();
+      if (namedArgument != null) {
+        named[namedArgument.name] = analyze(namedArgument.expression);
+      } else {
+        positional.add(analyze(node));
+      }
+    }
+    return new ArgumentsTypes(positional, named);
+  }
+
+  /**
+   * A proxy to accept which does book keeping and error reporting. Returns null
+   * if [node] is a non-returning statement, its inferred concrete type
+   * otherwise.
+   */
+  ConcreteType analyze(Node node) {
+    if (node == null) {
+      final String error = 'internal error: unexpected node: null';
+      inferrer.fail(lastSeenNode, error);
+    } else {
+      lastSeenNode = node;
+    }
+    ConcreteType result = node.accept(this);
+    if (result == null) {
+      inferrer.fail(node, 'internal error: inferred type is null');
+    }
+    inferrer.augmentInferredType(node, result);
+    return result;
+  }
+
+  ConcreteType visitBlock(Block node) {
+    return analyze(node.statements);
+  }
+
+  ConcreteType visitCascade(Cascade node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitCascadeReceiver(CascadeReceiver node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitClassNode(ClassNode node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitDoWhile(DoWhile node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitExpressionStatement(ExpressionStatement node) {
+    analyze(node.expression);
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitFor(For node) {
+    if (node.initializer != null) {
+      analyze(node.initializer);
+    }
+    analyze(node.conditionStatement);
+    ConcreteType result = inferrer.emptyConcreteType;
+    ConcreteTypesEnvironment oldEnvironment;
+    do {
+      oldEnvironment = environment;
+      analyze(node.conditionStatement);
+      analyze(node.body);
+      analyze(node.update);
+      environment = oldEnvironment.join(environment);
+    // TODO(polux): Maybe have a destructive join-method that returns a boolean
+    // value indicating whether something changed to avoid performing this
+    // comparison twice.
+    } while (oldEnvironment != environment);
+    return result;
+  }
+
+  ConcreteType visitFunctionDeclaration(FunctionDeclaration node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitFunctionExpression(FunctionExpression node) {
+    return analyze(node.body);
+  }
+
+  ConcreteType visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      ConcreteType result = environment.lookupTypeOfThis();
+      if (result == null) {
+        inferrer.fail(node, '"this" has no type');
+      }
+      return result;
+    }
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitIf(If node) {
+    analyze(node.condition);
+    ConcreteType thenType = analyze(node.thenPart);
+    ConcreteTypesEnvironment snapshot = environment;
+    ConcreteType elseType = node.hasElsePart ? analyze(node.elsePart)
+                                             : inferrer.emptyConcreteType;
+    environment = environment.join(snapshot);
+    return inferrer.union(thenType, elseType);
+  }
+
+  ConcreteType visitLoop(Loop node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType analyzeSetElement(Element receiver, ConcreteType argumentType) {
+    environment = environment.put(receiver, argumentType);
+    if (receiver.isField()) {
+      inferrer.augmentFieldType(receiver, argumentType);
+    } else if (receiver.isSetter()){
+      FunctionElement setter = receiver;
+      // TODO(polux): A setter always returns void so there's no need to
+      // invalidate its callers even if it is called with new arguments.
+      // However, if we start to record more than returned types, like
+      // exceptions for instance, we need to do it by uncommenting the following
+      // line.
+      // inferrer.addCaller(setter, currentMethod);
+      inferrer.getSendReturnType(setter, receiver.enclosingElement,
+          new ArgumentsTypes([argumentType], new Map()));
+    }
+    return argumentType;
+  }
+
+  ConcreteType analyzeSetNode(Node receiver, ConcreteType argumentType,
+                              SourceString name) {
+    ConcreteType receiverType = analyze(receiver);
+
+    void augmentField(ClassElement receiverType, Element member) {
+      if (member.isField()) {
+        inferrer.augmentFieldType(member, argumentType);
+      } else if (member.isAbstractField()){
+        AbstractFieldElement abstractField = member;
+        FunctionElement setter = abstractField.setter;
+        // TODO(polux): A setter always returns void so there's no need to
+        // invalidate its callers even if it is called with new arguments.
+        // However, if we start to record more than returned types, like
+        // exceptions for instance, we need to do it by uncommenting the
+        // following line.
+        // inferrer.addCaller(setter, currentMethod);
+        inferrer.getSendReturnType(setter, receiverType,
+            new ArgumentsTypes([argumentType], new Map()));
+      }
+      // since this is a sendSet we ignore non-fields
+    }
+
+    if (receiverType.isUnkown()) {
+      for (Element member in inferrer.getMembersByName(name)) {
+        if (!(member.isField() || member.isAbstractField())) continue;
+        Element cls = member.getEnclosingClass();
+        augmentField(cls, member);
+      }
+    } else {
+      for (BaseType baseReceiverType in receiverType.baseTypes) {
+        if (!baseReceiverType.isClass()) continue;
+        ClassBaseType baseReceiverClassType = baseReceiverType;
+        Element member = baseReceiverClassType.element.lookupMember(name);
+        if (member != null) {
+          augmentField(baseReceiverClassType.element, member);
+        }
+      }
+    }
+    return argumentType;
+  }
+
+  SourceString canonicalizeCompoundOperator(SourceString op) {
+    // TODO(ahe): This class should work on elements or selectors, not
+    // names.  Otherwise, it is repeating work the resolver has
+    // already done (or should have done).  In this case, the problem
+    // is that the resolver is not recording the selectors it is
+    // registering in registerBinaryOperator in
+    // ResolverVisitor.visitSendSet.
+    String stringValue = op.stringValue;
+    if (stringValue == '++') return const SourceString(r'+');
+    else if (stringValue == '--') return const SourceString(r'-');
+    else return Elements.mapToUserOperatorOrNull(op);
+  }
+
+  ConcreteType visitSendSet(SendSet node) {
+    // Operator []= has a different behaviour than other send sets: it is
+    // actually a send whose return type is that of its second argument.
+    if (node.selector.asIdentifier().source.stringValue == '[]') {
+      ConcreteType receiverType = analyze(node.receiver);
+      ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments);
+      analyzeDynamicSend(receiverType, const SourceString('[]='),
+                         argumentsTypes);
+      return argumentsTypes.positional[1];
+    }
+
+    // All other operators have a single argument (++ and -- have an implicit
+    // argument: 1). We will store its type in argumentType.
+    ConcreteType argumentType;
+    SourceString operatorName = node.assignmentOperator.source;
+    SourceString compoundOperatorName =
+        canonicalizeCompoundOperator(node.assignmentOperator.source);
+    // ++, --, +=, -=, ...
+    if (compoundOperatorName != null) {
+      ConcreteType receiverType = visitGetterSend(node);
+      // argumentsTypes is either computed from the actual arguments or [{int}]
+      // in case of ++ or --.
+      ArgumentsTypes argumentsTypes;
+      if (operatorName.stringValue == '++'
+          || operatorName.stringValue == '--') {
+        List<ConcreteType> positionalArguments = <ConcreteType>[
+            inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType)];
+        argumentsTypes = new ArgumentsTypes(positionalArguments, new Map());
+      } else {
+        argumentsTypes = analyzeArguments(node.arguments);
+      }
+      argumentType = analyzeDynamicSend(receiverType, compoundOperatorName,
+                                        argumentsTypes);
+    // The simple assignment case: receiver = argument.
+    } else {
+      argumentType = analyze(node.argumentsNode);
+    }
+
+    Element element = elements[node];
+    if (element != null) {
+      return analyzeSetElement(element, argumentType);
+    } else {
+      return analyzeSetNode(node.receiver, argumentType,
+                            node.selector.asIdentifier().source);
+    }
+  }
+
+  ConcreteType visitLiteralInt(LiteralInt node) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType);
+  }
+
+  ConcreteType visitLiteralDouble(LiteralDouble node) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.doubleBaseType);
+  }
+
+  ConcreteType visitLiteralBool(LiteralBool node) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType);
+  }
+
+  ConcreteType visitLiteralString(LiteralString node) {
+    // TODO(polux): get rid of this hack once we have a natural way of inferring
+    // the unknown type.
+    if (inferrer.testMode
+        && node.dartString.slowToString() == "__dynamic_for_test") {
+      return inferrer.unknownConcreteType;
+    }
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitStringJuxtaposition(StringJuxtaposition node) {
+    analyze(node.first);
+    analyze(node.second);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitLiteralNull(LiteralNull node) {
+    return inferrer.singletonConcreteType(const NullBaseType());
+  }
+
+  ConcreteType visitNewExpression(NewExpression node) {
+    Element constructor = elements[node.send];
+    inferrer.addCaller(constructor, currentMethod);
+    ClassElement cls = constructor.enclosingElement;
+    return inferrer.getSendReturnType(constructor, cls,
+                                      analyzeArguments(node.send.arguments));
+  }
+
+  ConcreteType visitLiteralList(LiteralList node) {
+    ConcreteType elementsType = new ConcreteType.empty();
+    // We compute the union of the types of the list literal's elements.
+    for (Link<Node> link = node.elements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      elementsType = inferrer.union(elementsType, analyze(link.head));
+    }
+    inferrer.augmentListElementType(elementsType);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.listBaseType);
+  }
+
+  ConcreteType visitNodeList(NodeList node) {
+    ConcreteType type = inferrer.emptyConcreteType;
+    // The concrete type of a sequence of statements is the union of the
+    // statement's types.
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      type = inferrer.union(type, analyze(link.head));
+    }
+    return type;
+  }
+
+  ConcreteType visitOperator(Operator node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitReturn(Return node) {
+    final expression = node.expression;
+    return (expression == null)
+        ? inferrer.singletonConcreteType(const NullBaseType())
+        : analyze(expression);
+  }
+
+  ConcreteType visitThrow(Throw node) {
+    if (node.expression != null) analyze(node.expression);
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitTypeAnnotation(TypeAnnotation node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitTypeVariable(TypeVariable node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitVariableDefinitions(VariableDefinitions node) {
+    for (Link<Node> link = node.definitions.nodes; !link.isEmpty;
+         link = link.tail) {
+      analyze(link.head);
+    }
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitWhile(While node) {
+    analyze(node.condition);
+    ConcreteType result = inferrer.emptyConcreteType;
+    ConcreteTypesEnvironment oldEnvironment;
+    do {
+      oldEnvironment = environment;
+      analyze(node.condition);
+      analyze(node.body);
+      environment = oldEnvironment.join(environment);
+    } while (oldEnvironment != environment);
+    return result;
+  }
+
+  ConcreteType visitParenthesizedExpression(ParenthesizedExpression node) {
+    return analyze(node.expression);
+  }
+
+  ConcreteType visitConditional(Conditional node) {
+    analyze(node.condition);
+    ConcreteType thenType = analyze(node.thenExpression);
+    ConcreteType elseType = analyze(node.elseExpression);
+    return inferrer.union(thenType, elseType);
+  }
+
+  ConcreteType visitModifiers(Modifiers node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitStringInterpolationPart(StringInterpolationPart node) {
+    node.visitChildren(this);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitEmptyStatement(EmptyStatement node) {
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitBreakStatement(BreakStatement node) {
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitContinueStatement(ContinueStatement node) {
+    // TODO(polux): we can be more precise
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitForIn(ForIn node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitLabel(Label node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitLabeledStatement(LabeledStatement node) {
+    return analyze(node.statement);
+  }
+
+  ConcreteType visitLiteralMap(LiteralMap node) {
+    visitNodeList(node.entries);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.mapBaseType);
+  }
+
+  ConcreteType visitLiteralMapEntry(LiteralMapEntry node) {
+    // We don't need to visit the key, it's always a string.
+    return analyze(node.value);
+  }
+
+  ConcreteType visitNamedArgument(NamedArgument node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitSwitchStatement(SwitchStatement node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitSwitchCase(SwitchCase node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitCaseMatch(CaseMatch node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitTryStatement(TryStatement node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitScriptTag(ScriptTag node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitCatchBlock(CatchBlock node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitTypedef(Typedef node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitSuperSend(Send node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitOperatorSend(Send node) {
+    SourceString name =
+        canonicalizeMethodName(node.selector.asIdentifier().source);
+    if (name == const SourceString('is')) {
+      return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType);
+    }
+    return visitDynamicSend(node);
+  }
+
+  ConcreteType analyzeFieldRead(Element field) {
+    inferrer.addReader(field, currentMethod);
+    return inferrer.getFieldType(field);
+  }
+
+  ConcreteType analyzeGetterSend(ClassElement receiverType,
+                                 FunctionElement getter) {
+      inferrer.addCaller(getter, currentMethod);
+      return inferrer.getSendReturnType(getter,
+                                        receiverType,
+                                        new ArgumentsTypes([], new Map()));
+  }
+
+  ConcreteType visitGetterSend(Send node) {
+    Element element = elements[node];
+    if (element != null) {
+      // node is a local variable or a field of this
+      ConcreteType result = environment.lookupType(element);
+      if (result != null) {
+        // node is a local variable
+        return result;
+      } else {
+        // node is a field or a getter of this
+        if (element.isField()) {
+          return analyzeFieldRead(element);
+        } else {
+          assert(element.isGetter());
+          ClassElement receiverType = element.enclosingElement;
+          return analyzeGetterSend(receiverType, element);
+        }
+      }
+    } else {
+      // node is a field of not(this)
+      assert(node.receiver != null);
+
+      ConcreteType result = inferrer.emptyConcreteType;
+      void augmentResult(ClassElement baseReceiverType, Element member) {
+        if (member.isField()) {
+          result = inferrer.union(result, analyzeFieldRead(member));
+        } else if (member.isAbstractField()){
+          // call to a getter
+          AbstractFieldElement abstractField = member;
+          result = inferrer.union(
+              result,
+              analyzeGetterSend(baseReceiverType, abstractField.getter));
+        }
+        // since this is a get we ignore non-fields
+      }
+
+      ConcreteType receiverType = analyze(node.receiver);
+      if (receiverType.isUnkown()) {
+        List<Element> members =
+            inferrer.getMembersByName(node.selector.asIdentifier().source);
+        for (Element member in members) {
+          if (!(member.isField() || member.isAbstractField())) continue;
+          Element cls = member.getEnclosingClass();
+          augmentResult(cls, member);
+        }
+      } else {
+        for (BaseType baseReceiverType in receiverType.baseTypes) {
+          if (!baseReceiverType.isNull()) {
+            ClassBaseType classBaseType = baseReceiverType;
+            ClassElement cls = classBaseType.element;
+            Element getterOrField =
+                cls.lookupMember(node.selector.asIdentifier().source);
+            if (getterOrField != null) {
+              augmentResult(cls, getterOrField);
+            }
+          }
+        }
+      }
+      return result;
+    }
+  }
+
+  ConcreteType visitClosureSend(Send node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType analyzeDynamicSend(ConcreteType receiverType,
+                                  SourceString canonicalizedMethodName,
+                                  ArgumentsTypes argumentsTypes) {
+    ConcreteType result = inferrer.emptyConcreteType;
+
+    if (receiverType.isUnkown()) {
+      List<Element> methods =
+          inferrer.getMembersByName(canonicalizedMethodName);
+      for (Element element in methods) {
+        // TODO(polux): when we handle closures, we must handle sends to fields
+        // that are closures.
+        if (!element.isFunction()) continue;
+        FunctionElement method = element;
+        inferrer.addCaller(method, currentMethod);
+        Element cls = method.enclosingElement;
+        result = inferrer.union(
+            result,
+            inferrer.getSendReturnType(method, cls, argumentsTypes));
+      }
+
+    } else {
+      for (BaseType baseReceiverType in receiverType.baseTypes) {
+        if (!baseReceiverType.isNull()) {
+          ClassBaseType classBaseReceiverType = baseReceiverType;
+          ClassElement cls = classBaseReceiverType.element;
+          FunctionElement method = cls.lookupMember(canonicalizedMethodName);
+          if (method != null) {
+            inferrer.addCaller(method, currentMethod);
+            result = inferrer.union(
+                result,
+                inferrer.getSendReturnType(method, cls, argumentsTypes));
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  SourceString canonicalizeMethodName(SourceString name) {
+    // TODO(polux): handle unary-
+    SourceString operatorName =
+        Elements.constructOperatorNameOrNull(name, false);
+    if (operatorName != null) return operatorName;
+    return name;
+  }
+
+  ConcreteType visitDynamicSend(Send node) {
+    ConcreteType receiverType = (node.receiver != null)
+        ? analyze(node.receiver)
+        : inferrer.singletonConcreteType(
+            new ClassBaseType(currentMethod.getEnclosingClass()));
+    SourceString name =
+        canonicalizeMethodName(node.selector.asIdentifier().source);
+    ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments);
+    if (name.stringValue == '!=') {
+      ConcreteType returnType = analyzeDynamicSend(receiverType,
+                                                   const SourceString('=='),
+                                                   argumentsTypes);
+      return returnType.isEmpty()
+          ? returnType
+          : inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType);
+    } else {
+      return analyzeDynamicSend(receiverType, name, argumentsTypes);
+    }
+  }
+
+  ConcreteType visitForeignSend(Send node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitStaticSend(Send node) {
+    Element element = elements[node];
+    inferrer.addCaller(element, currentMethod);
+    return inferrer.getSendReturnType(element, null,
+        analyzeArguments(node.arguments));
+  }
+
+  void internalError(String reason, {Node node}) {
+    inferrer.fail(node, reason);
+  }
+
+  ConcreteType visitTypeReferenceSend(Send) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.typeBaseType);
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/types/types.dart b/pkgs/markdown/lib/src/compiler/implementation/types/types.dart
new file mode 100644
index 0000000..fdce427
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/types/types.dart
@@ -0,0 +1,260 @@
+// 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.
+
+library types;
+
+import 'dart:collection' show Queue;
+
+import '../dart2jslib.dart' hide Selector;
+import '../js_backend/js_backend.dart' show JavaScriptBackend;
+import '../tree/tree.dart';
+import '../elements/elements.dart';
+import '../util/util.dart';
+import '../universe/universe.dart';
+
+part 'concrete_types_inferrer.dart';
+
+/**
+ * The types task infers guaranteed types globally.
+ */
+class TypesTask extends CompilerTask {
+  final String name = 'Type inference';
+  final Set<Element> untypedElements;
+  final Map<Element, Link<Element>> typedSends;
+  ConcreteTypesInferrer concreteTypesInferrer;
+
+  TypesTask(Compiler compiler)
+    : untypedElements = new Set<Element>(),
+      typedSends = new Map<Element, Link<Element>>(),
+      concreteTypesInferrer = compiler.enableConcreteTypeInference
+          ? new ConcreteTypesInferrer(compiler) : null,
+      super(compiler);
+
+  /**
+   * Called once for each method during the resolution phase of the
+   * compiler.
+   */
+  void analyze(Node node, TreeElements elements) {
+    measure(() {
+      node.accept(new ConcreteTypeInferencer(this, elements));
+    });
+  }
+
+  /**
+   * Called when resolution is complete.
+   */
+  void onResolutionComplete(Element mainElement) {
+    measure(() {
+      if (concreteTypesInferrer != null) {
+        bool success = concreteTypesInferrer.analyzeMain(mainElement);
+        if (!success) {
+          // If the concrete type inference bailed out, we pretend it didn't
+          // happen. In the future we might want to record that it failed but
+          // use the partial results as hints.
+          concreteTypesInferrer = null;
+        }
+      }
+    });
+  }
+
+  /**
+   * Return the (inferred) guaranteed concrete type of [element] or null.
+   */
+  ConcreteType getGuaranteedTypeOfElement(Element element) {
+    return measure(() {
+      if (!element.isParameter()) return null;
+      if (concreteTypesInferrer != null) {
+        ConcreteType guaranteedType = concreteTypesInferrer
+            .getConcreteTypeOfParameter(element);
+        if (guaranteedType != null) return guaranteedType;
+      }
+      Element holder = element.enclosingElement;
+      Link<Element> types = typedSends[holder];
+      if (types == null) return null;
+      if (!holder.isFunction()) return null;
+      if (untypedElements.contains(holder)) return null;
+      FunctionElement function = holder;
+      FunctionSignature signature = function.computeSignature(compiler);
+      for (Element parameter in signature.requiredParameters) {
+        if (types.isEmpty) return null;
+        if (element == parameter) {
+          return new ConcreteType.singleton(compiler.maxConcreteTypeSize,
+                                            new ClassBaseType(types.head));
+        }
+        types = types.tail;
+      }
+      return null;
+    });
+  }
+
+  /**
+   * Return the (inferred) guaranteed concrete type of [node] or null.
+   * [node] must be an AST node of [owner].
+   */
+  ConcreteType getGuaranteedTypeOfNode(Node node, Element owner) {
+    return measure(() {
+      if (concreteTypesInferrer != null) {
+        return concreteTypesInferrer.getConcreteTypeOfNode(node);
+      }
+      return null;
+    });
+  }
+}
+
+/**
+ * Infers concrete types for a single method or expression.
+ */
+class ConcreteTypeInferencer extends Visitor {
+  final TypesTask task;
+  final TreeElements elements;
+  final ClassElement boolClass;
+  final ClassElement doubleClass;
+  final ClassElement intClass;
+  final ClassElement listClass;
+  final ClassElement nullClass;
+  final ClassElement stringClass;
+
+  final Map<Node, ClassElement> concreteTypes;
+
+  ConcreteTypeInferencer(TypesTask task, this.elements)
+    : this.task = task,
+      this.boolClass = task.compiler.boolClass,
+      this.doubleClass = task.compiler.doubleClass,
+      this.intClass = task.compiler.intClass,
+      this.listClass = task.compiler.listClass,
+      this.nullClass = task.compiler.nullClass,
+      this.stringClass = task.compiler.stringClass,
+      this.concreteTypes = new Map<Node, ClassElement>();
+
+  visitNode(Node node) => node.visitChildren(this);
+
+  visitLiteralString(LiteralString node) {
+    recordConcreteType(node, stringClass);
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    recordConcreteType(node, stringClass);
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    node.visitChildren(this);
+    recordConcreteType(node, stringClass);
+  }
+
+  recordConcreteType(Node node, ClassElement cls) {
+    concreteTypes[node] = cls;
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    recordConcreteType(node, boolClass);
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    recordConcreteType(node, doubleClass);
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    recordConcreteType(node, intClass);
+  }
+
+  visitLiteralList(LiteralList node) {
+    node.visitChildren(this);
+    recordConcreteType(node, listClass);
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    node.visitChildren(this);
+    // TODO(ahe): map class?
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    recordConcreteType(node, nullClass);
+  }
+
+  Link<Element> computeConcreteSendArguments(Send node) {
+    if (node.argumentsNode == null) return null;
+    if (node.arguments.isEmpty) return const Link<Element>();
+    if (node.receiver != null && concreteTypes[node.receiver] == null) {
+      return null;
+    }
+    LinkBuilder<Element> types = new LinkBuilder<Element>();
+    for (Node argument in node.arguments) {
+      Element type = concreteTypes[argument];
+      if (type == null) return null;
+      types.addLast(type);
+    }
+    return types.toLink();
+  }
+
+  visitSend(Send node) {
+    node.visitChildren(this);
+    Element element = elements[node.selector];
+    if (element == null) return;
+    if (!Elements.isStaticOrTopLevelFunction(element)) return;
+    if (node.argumentsNode == null) {
+      // interest(node, 'closurized method');
+      task.untypedElements.add(element);
+      return;
+    }
+    Link<Element> types = computeConcreteSendArguments(node);
+    if (types != null) {
+      Link<Element> existing = task.typedSends[element];
+      if (existing == null) {
+        task.typedSends[element] = types;
+      } else {
+        // interest(node, 'multiple invocations');
+        Link<Element> lub = computeLubs(existing, types);
+        if (lub == null) {
+          task.untypedElements.add(element);
+        } else {
+          task.typedSends[element] = lub;
+        }
+      }
+    } else {
+      // interest(node, 'dynamically typed invocation');
+      task.untypedElements.add(element);
+    }
+  }
+
+  visitSendSet(SendSet node) {
+    // TODO(ahe): Implement this. For now, overridden to avoid calling
+    // visitSend through super.
+    node.visitChildren(this);
+  }
+
+  void interest(Node node, String note) {
+    var message = MessageKind.GENERIC.message({'text': note});
+    task.compiler.reportWarning(node, message);
+  }
+
+  /**
+   * Computes the pairwise Least Upper Bound (LUB) of the elements of
+   * [a] and [b]. Returns [:null:] if it gives up, or if the lists
+   * aren't the same length.
+   */
+  Link<Element> computeLubs(Link<Element> a, Link<Element> b) {
+    LinkBuilder<Element> lubs = new LinkBuilder<Element>();
+    while (!a.isEmpty && !b.isEmpty) {
+      Element lub = computeLub(a.head, b.head);
+      if (lub == null) return null;
+      lubs.addLast(lub);
+      a = a.tail;
+      b = b.tail;
+    }
+    return (a.isEmpty && b.isEmpty) ? lubs.toLink() : null;
+  }
+
+  /**
+   * Computes the Least Upper Bound (LUB) of [a] and [b]. Returns
+   * [:null:] if it gives up.
+   */
+  Element computeLub(Element a, Element b) {
+    // Fast common case, but also simple initial implementation.
+    if (identical(a, b)) return a;
+
+    // TODO(ahe): Improve the following "computation"...
+    return null;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart
new file mode 100644
index 0000000..23bc782
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart
@@ -0,0 +1,140 @@
+// 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.
+
+part of universe;
+
+// TODO(kasperl): This actually holds getters and setters just fine
+// too and stricly they aren't functions. Maybe this needs a better
+// name -- something like ElementSet seems a bit too generic.
+class FunctionSet extends PartialTypeTree {
+
+  FunctionSet(Compiler compiler) : super(compiler);
+
+  FunctionSetNode newSpecializedNode(ClassElement type)
+      => new FunctionSetNode(type);
+
+  // TODO(kasperl): Allow static members too?
+  void add(Element element) {
+    assert(element.isMember());
+    FunctionSetNode node = findNode(element.getEnclosingClass(), true);
+    node.membersByName[element.name] = element;
+  }
+
+  // TODO(kasperl): Allow static members too?
+  void remove(Element element) {
+    assert(element.isMember());
+    FunctionSetNode node = findNode(element.getEnclosingClass(), false);
+    if (node != null) node.membersByName.remove(element.name);
+  }
+
+  // TODO(kasperl): Allow static members too?
+  bool contains(Element element) {
+    assert(element.isMember());
+    FunctionSetNode node = findNode(element.getEnclosingClass(), false);
+    return (node != null)
+        ? node.membersByName.containsKey(element.name)
+        : false;
+  }
+
+  /**
+   * Returns all elements that may be invoked with the given [selector].
+   */
+  Set<Element> filterBySelector(Selector selector) {
+    // TODO(kasperl): For now, we use a different implementation for
+    // filtering if the tree contains interface subtypes.
+    return containsInterfaceSubtypes
+        ? filterAllBySelector(selector)
+        : filterHierarchyBySelector(selector);
+  }
+
+  /**
+   * Returns whether the set has any element matching the given
+   * [selector].
+   */
+  bool hasAnyElementMatchingSelector(Selector selector) {
+    // TODO(kasperl): For now, we use a different implementation for
+    // filtering if the tree contains interface subtypes.
+    return containsInterfaceSubtypes
+        ? hasAnyInAll(selector)
+        : hasAnyInHierarchy(selector);
+  }
+
+  Set<Element> filterAllBySelector(Selector selector) {
+    Set<Element> result = new Set<Element>();
+    if (root == null) return result;
+    root.visitRecursively((FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      // Since we're running through the entire tree we have to use
+      // the applies method that takes types into account.
+      if (member != null && selector.appliesUnnamed(member, compiler)) {
+        result.add(member);
+      }
+      return true;
+    });
+    return result;
+  }
+
+  Set<Element> filterHierarchyBySelector(Selector selector) {
+    Set<Element> result = new Set<Element>();
+    if (root == null) return result;
+    visitHierarchy(selectorType(selector), (FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      if (member != null && selector.appliesUntyped(member, compiler)) {
+        result.add(member);
+      }
+      return true;
+    });
+    return result;
+  }
+
+  bool hasAnyInAll(Selector selector) {
+    bool result = false;
+    if (root == null) return result;
+    root.visitRecursively((FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      // Since we're running through the entire tree we have to use
+      // the applies method that takes types into account.
+      if (member != null && selector.appliesUnnamed(member, compiler)) {
+        result = true;
+        // End the traversal.
+        return false;
+      }
+      return true;
+    });
+    return result;
+  }
+
+  bool hasAnyInHierarchy(Selector selector) {
+    bool result = false;
+    if (root == null) return result;
+    visitHierarchy(selectorType(selector), (FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      if (member != null && selector.appliesUntyped(member, compiler)) {
+        result = true;
+        // End the traversal.
+        return false;
+      }
+      return true;
+    });
+    return result;
+  }
+
+  void forEach(Function f) {
+    if (root == null) return;
+    root.visitRecursively((FunctionSetNode node) {
+      node.membersByName.forEach(
+          (SourceString _, Element element) => f(element));
+      return true;
+    });
+  }
+}
+
+class FunctionSetNode extends PartialTypeTreeNode {
+
+  final Map<SourceString, Element> membersByName;
+
+  FunctionSetNode(ClassElement type) : super(type),
+      membersByName = new Map<SourceString, Element>();
+
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart
new file mode 100644
index 0000000..f310174
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart
@@ -0,0 +1,190 @@
+// 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.
+
+part of universe;
+
+abstract class PartialTypeTree {
+
+  final Compiler compiler;
+  PartialTypeTreeNode root;
+
+  // TODO(kasperl): This should be final but the VM will not allow
+  // that without making the map a compile-time constant.
+  Map<ClassElement, PartialTypeTreeNode> nodes =
+      new Map<ClassElement, PartialTypeTreeNode>();
+
+  // TODO(kasperl): For now, we keep track of whether or not the tree
+  // contains two classes with a subtype relationship that isn't a
+  // subclass relationship.
+  bool containsInterfaceSubtypes = false;
+
+  // TODO(kasperl): This should be final but the VM will not allow
+  // that without making the set a compile-time constant.
+  Set<ClassElement> unseenInterfaceSubtypes =
+      new Set<ClassElement>();
+
+  PartialTypeTree(this.compiler);
+
+  PartialTypeTreeNode newSpecializedNode(ClassElement type);
+
+  PartialTypeTreeNode newNode(ClassElement type) {
+    PartialTypeTreeNode node = newSpecializedNode(type);
+    nodes[type] = node;
+    if (containsInterfaceSubtypes) return node;
+
+    // Check if the implied interface of the new class is implemented
+    // by another class that is already in the tree.
+    if (unseenInterfaceSubtypes.contains(type)) {
+      containsInterfaceSubtypes = true;
+      unseenInterfaceSubtypes.clear();
+      return node;
+    }
+
+    // Run through all the implied interfaces the class that we're
+    // adding implements and see if any of them are already in the
+    // tree. If so, we have a tree with interface subtypes. If not,
+    // keep track of them so we can deal with it if the interface is
+    // added to the tree later.
+    for (Link link = type.interfaces; !link.isEmpty; link = link.tail) {
+      InterfaceType superType = link.head;
+      ClassElement superTypeElement = superType.element;
+      if (nodes.containsKey(superTypeElement)) {
+        containsInterfaceSubtypes = true;
+        unseenInterfaceSubtypes.clear();
+        break;
+      } else {
+        unseenInterfaceSubtypes.add(superTypeElement);
+      }
+    }
+    return node;
+  }
+
+  // TODO(kasperl): Move this to the Selector class?
+  /**
+   * Returns a [ClassElement] that is an upper bound of the receiver type on
+   * [selector].
+   */
+  ClassElement selectorType(Selector selector) {
+    // TODO(ngeoffray): Should the tree be specialized with DartType?
+    DartType type = selector.receiverType;
+    if (type == null) return compiler.objectClass;
+    // TODO(kasperl): Should [dynamic] return Object?
+    if (identical(type.kind, TypeKind.MALFORMED_TYPE))
+        return compiler.objectClass;
+    // TODO(johnniwinther): Change to use [DartType.unalias].
+    if (type.element.isTypedef()) return compiler.functionClass;
+    return type.element;
+  }
+
+  /**
+   * Finds the tree node corresponding to the given [type]. If [insert]
+   * is true, we always return a node that matches the type by
+   * inserting a new node if necessary. If [insert] is false, we
+   * return null if we cannot find a node that matches the [type].
+   */
+  PartialTypeTreeNode findNode(ClassElement type, bool insert) {
+    if (root == null) {
+      if (!insert) return null;
+      root = newNode(compiler.objectClass);
+    }
+
+    PartialTypeTreeNode current = root;
+    L: while (!identical(current.type, type)) {
+      assert(type.isSubclassOf(current.type));
+
+      // Run through the children. If we find a subtype of the type
+      // we are looking for we go that way. If not, we keep track of
+      // the subtypes so we can move them from being children of the
+      // current node to being children of a new node if we need
+      // to insert that.
+      Link<PartialTypeTreeNode> subtypes = const Link();
+      for (Link link = current.children; !link.isEmpty; link = link.tail) {
+        PartialTypeTreeNode child = link.head;
+        ClassElement childType = child.type;
+        if (type.isSubclassOf(childType)) {
+          assert(subtypes.isEmpty);
+          current = child;
+          continue L;
+        } else if (childType.isSubclassOf(type)) {
+          if (insert) subtypes = subtypes.prepend(child);
+        }
+      }
+
+      // If we are not inserting any nodes, we are done.
+      if (!insert) return null;
+
+      // Create a new node and move the children of the current node
+      // that are subtypes of the type of the new node below the new
+      // node in the hierarchy.
+      PartialTypeTreeNode node = newNode(type);
+      if (!subtypes.isEmpty) {
+        node.children = subtypes;
+        Link<PartialTypeTreeNode> remaining = const Link();
+        for (Link link = current.children; !link.isEmpty; link = link.tail) {
+          PartialTypeTreeNode child = link.head;
+          if (!child.type.isSubclassOf(type)) {
+            remaining = remaining.prepend(child);
+          }
+        }
+        current.children = remaining;
+      }
+
+      // Add the new node as a child node of the current node and return it.
+      current.children = current.children.prepend(node);
+      return node;
+    }
+
+    // We found an exact match. No need to insert new nodes.
+    assert(identical(current.type, type));
+    return current;
+  }
+
+  /**
+   * Visits all superclass and subclass nodes for the given [type]. If
+   * the [visit] function ever returns false, we abort the traversal.
+   */
+  void visitHierarchy(ClassElement type, bool visit(PartialTypeTreeNode node)) {
+    assert(!containsInterfaceSubtypes);
+    PartialTypeTreeNode current = root;
+    L: while (!identical(current.type, type)) {
+      assert(type.isSubclassOf(current.type));
+      if (!visit(current)) return;
+      for (Link link = current.children; !link.isEmpty; link = link.tail) {
+        PartialTypeTreeNode child = link.head;
+        ClassElement childType = child.type;
+        if (type.isSubclassOf(childType)) {
+          current = child;
+          continue L;
+        } else if (childType.isSubclassOf(type)) {
+          if (!child.visitRecursively(visit)) return;
+        }
+      }
+      return;
+    }
+    current.visitRecursively(visit);
+  }
+
+}
+
+class PartialTypeTreeNode {
+
+  final ClassElement type;
+  Link<PartialTypeTreeNode> children;
+
+  PartialTypeTreeNode(this.type) : children = const Link();
+
+  /**
+   * Visits this node and its children recursively. If the visit
+   * callback ever returns false, the visiting stops early.
+   */
+  bool visitRecursively(bool visit(PartialTypeTreeNode node)) {
+    if (!visit(this)) return false;
+    for (Link link = children; !link.isEmpty; link = link.tail) {
+      PartialTypeTreeNode child = link.head;
+      if (!child.visitRecursively(visit)) return false;
+    }
+    return true;
+  }
+
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart
new file mode 100644
index 0000000..cf98a10
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart
@@ -0,0 +1,132 @@
+// 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.
+
+part of universe;
+
+class SelectorMap<T> extends PartialTypeTree {
+
+  SelectorMap(Compiler compiler) : super(compiler);
+
+  SelectorMapNode<T> newSpecializedNode(ClassElement type)
+      => new SelectorMapNode<T>(type);
+
+  T operator [](Selector selector) {
+    SelectorMapNode<T> node = findNode(selectorType(selector), false);
+    if (node == null) return null;
+    Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name];
+    if (selectors == null) return null;
+    for (Link link = selectors; !link.isEmpty; link = link.tail) {
+      SelectorValue<T> existing = link.head;
+      if (existing.selector.equalsUntyped(selector)) return existing.value;
+    }
+    return null;
+  }
+
+  void operator []=(Selector selector, T value) {
+    SelectorMapNode<T> node = findNode(selectorType(selector), true);
+    Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name];
+    if (selectors == null) {
+      // No existing selectors with the given name. Create a new
+      // linked list.
+      SelectorValue<T> head = new SelectorValue<T>(selector, value);
+      node.selectorsByName[selector.name] =
+          new Link<SelectorValue<T>>().prepend(head);
+    } else {
+      // Run through the linked list of selectors with the same name. If
+      // we find one that matches, we update the value in the mapping.
+      for (Link link = selectors; !link.isEmpty; link = link.tail) {
+        SelectorValue<T> existing = link.head;
+        // It is safe to ignore the type here, because all selector
+        // mappings that are stored in a single node have the same type.
+        if (existing.selector.equalsUntyped(selector)) {
+          existing.value = value;
+          return;
+        }
+      }
+      // We could not find an existing mapping for the selector, so
+      // we add a new one to the existing linked list.
+      SelectorValue<T> head = new SelectorValue<T>(selector, value);
+      node.selectorsByName[selector.name] = selectors.prepend(head);
+    }
+  }
+
+  // TODO(kasperl): Share code with the [] operator?
+  bool containsKey(Selector selector) {
+    SelectorMapNode<T> node = findNode(selectorType(selector), false);
+    if (node == null) return false;
+    Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name];
+    if (selectors == null) return false;
+    for (Link link = selectors; !link.isEmpty; link = link.tail) {
+      SelectorValue<T> existing = link.head;
+      if (existing.selector.equalsUntyped(selector)) return true;
+    }
+    return false;
+  }
+
+  /**
+   * Visits all mappings for selectors that may be used to invoke the
+   * given [member] element. If the [visit] function ever returns false,
+   * we abort the traversal early.
+   */
+  void visitMatching(Element member, bool visit(Selector selector, T value)) {
+    assert(member.isMember());
+    if (root == null) return;
+    // TODO(kasperl): For now, we use a different implementation for
+    // visiting if the tree contains interface subtypes.
+    if (containsInterfaceSubtypes) {
+      visitAllMatching(member, visit);
+    } else {
+      visitHierarchyMatching(member, visit);
+    }
+  }
+
+  void visitAllMatching(Element member, bool visit(selector, value)) {
+    root.visitRecursively((SelectorMapNode<T> node) {
+      Link<SelectorValue<T>> selectors = node.selectorsByName[member.name];
+      if (selectors == null) return true;
+      for (Link link = selectors; !link.isEmpty; link = link.tail) {
+        SelectorValue<T> existing = link.head;
+        Selector selector = existing.selector;
+        // Since we're running through the entire tree we have to use
+        // the applies method that takes types into account.
+        if (selector.appliesUnnamed(member, compiler)) {
+          if (!visit(selector, existing.value)) return false;
+        }
+      }
+      return true;
+    });
+  }
+
+  void visitHierarchyMatching(Element member, bool visit(selector, value)) {
+    visitHierarchy(member.getEnclosingClass(), (SelectorMapNode<T> node) {
+      Link<SelectorValue<T>> selectors = node.selectorsByName[member.name];
+      if (selectors == null) return true;
+      for (Link link = selectors; !link.isEmpty; link = link.tail) {
+        SelectorValue<T> existing = link.head;
+        Selector selector = existing.selector;
+        if (selector.appliesUntyped(member, compiler)) {
+          if (!visit(selector, existing.value)) return false;
+        }
+      }
+      return true;
+    });
+  }
+
+}
+
+class SelectorMapNode<T> extends PartialTypeTreeNode {
+
+  final Map<SourceString, Link<SelectorValue<T>>> selectorsByName;
+
+  SelectorMapNode(ClassElement type) : super(type),
+      selectorsByName = new Map<SourceString, Link<SelectorValue<T>>>();
+
+}
+
+class SelectorValue<T> {
+  final Selector selector;
+  T value;
+  SelectorValue(this.selector, this.value);
+  toString() => "$selector -> $value";
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart
new file mode 100644
index 0000000..6b64382
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart
@@ -0,0 +1,495 @@
+// 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.
+
+library universe;
+
+import '../closure.dart';
+import '../elements/elements.dart';
+import '../dart2jslib.dart';
+import '../dart_types.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../js/js.dart' as js;
+
+part 'function_set.dart';
+part 'partial_type_tree.dart';
+part 'selector_map.dart';
+
+class Universe {
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements are declaration elements.
+   */
+  // TODO(karlklose): these sets should be merged.
+  final Set<ClassElement> instantiatedClasses;
+  final Set<DartType> instantiatedTypes;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements are declaration elements.
+   */
+  final Set<FunctionElement> staticFunctionsNeedingGetter;
+  final Map<SourceString, Set<Selector>> invokedNames;
+  final Map<SourceString, Set<Selector>> invokedGetters;
+  final Map<SourceString, Set<Selector>> invokedSetters;
+
+  /**
+   * Fields accessed. Currently only the codegen knows this
+   * information. The resolver is too conservative when seeing a
+   * getter and only registers an invoked getter.
+   */
+  final Map<SourceString, Set<Selector>> fieldGetters;
+
+  /**
+   * Fields set. See comment in [fieldGetters].
+   */
+  final Map<SourceString, Set<Selector>> fieldSetters;
+  final Set<DartType> isChecks;
+
+  Universe() : instantiatedClasses = new Set<ClassElement>(),
+               instantiatedTypes = new Set<DartType>(),
+               staticFunctionsNeedingGetter = new Set<FunctionElement>(),
+               invokedNames = new Map<SourceString, Set<Selector>>(),
+               invokedGetters = new Map<SourceString, Set<Selector>>(),
+               fieldGetters = new Map<SourceString, Set<Selector>>(),
+               fieldSetters = new Map<SourceString, Set<Selector>>(),
+               invokedSetters = new Map<SourceString, Set<Selector>>(),
+               isChecks = new Set<DartType>();
+
+  bool hasMatchingSelector(Set<Selector> selectors,
+                           Element member,
+                           Compiler compiler) {
+    if (selectors == null) return false;
+    for (Selector selector in selectors) {
+      if (selector.appliesUnnamed(member, compiler)) return true;
+    }
+    return false;
+  }
+
+  bool hasInvocation(Element member, Compiler compiler) {
+    return hasMatchingSelector(invokedNames[member.name], member, compiler);
+  }
+
+  bool hasInvokedGetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(invokedGetters[member.name], member, compiler);
+  }
+
+  bool hasInvokedSetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(invokedSetters[member.name], member, compiler);
+  }
+
+  bool hasFieldGetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(fieldGetters[member.name], member, compiler);
+  }
+
+  bool hasFieldSetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(fieldSetters[member.name], member, compiler);
+  }
+}
+
+class SelectorKind {
+  final String name;
+  const SelectorKind(this.name);
+
+  static const SelectorKind GETTER = const SelectorKind('getter');
+  static const SelectorKind SETTER = const SelectorKind('setter');
+  static const SelectorKind CALL = const SelectorKind('call');
+  static const SelectorKind OPERATOR = const SelectorKind('operator');
+  static const SelectorKind INDEX = const SelectorKind('index');
+
+  toString() => name;
+}
+
+class Selector {
+  final SelectorKind kind;
+  final SourceString name;
+  final LibraryElement library; // Library is null for non-private selectors.
+
+  // The numbers of arguments of the selector. Includes named arguments.
+  final int argumentCount;
+  final List<SourceString> namedArguments;
+  final List<SourceString> orderedNamedArguments;
+
+  Selector(
+      this.kind,
+      SourceString name,
+      LibraryElement library,
+      this.argumentCount,
+      [List<SourceString> namedArguments = const <SourceString>[]])
+    : this.name = name,
+      this.library = name.isPrivate() ? library : null,
+      this.namedArguments = namedArguments,
+      this.orderedNamedArguments = namedArguments.isEmpty
+          ? namedArguments
+          : <SourceString>[] {
+    assert(!name.isPrivate() || library != null);
+  }
+
+  Selector.getter(SourceString name, LibraryElement library)
+      : this(SelectorKind.GETTER, name, library, 0);
+
+  Selector.getterFrom(Selector selector)
+      : this(SelectorKind.GETTER, selector.name, selector.library, 0);
+
+  Selector.setter(SourceString name, LibraryElement library)
+      : this(SelectorKind.SETTER, name, library, 1);
+
+  Selector.unaryOperator(SourceString name)
+      : this(SelectorKind.OPERATOR,
+             Elements.constructOperatorName(name, true),
+             null, 0);
+
+  Selector.binaryOperator(SourceString name)
+      : this(SelectorKind.OPERATOR,
+             Elements.constructOperatorName(name, false),
+             null, 1);
+
+  Selector.index()
+      : this(SelectorKind.INDEX,
+             Elements.constructOperatorName(const SourceString("[]"), false),
+             null, 1);
+
+  Selector.indexSet()
+      : this(SelectorKind.INDEX,
+             Elements.constructOperatorName(const SourceString("[]="), false),
+             null, 2);
+
+  Selector.call(SourceString name,
+                LibraryElement library,
+                int arity,
+                [List<SourceString> named = const []])
+      : this(SelectorKind.CALL, name, library, arity, named);
+
+  Selector.callClosure(int arity, [List<SourceString> named = const []])
+      : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null,
+             arity, named);
+
+  Selector.callClosureFrom(Selector selector)
+      : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null,
+             selector.argumentCount, selector.namedArguments);
+
+  Selector.callConstructor(SourceString constructorName,
+                           LibraryElement library)
+      : this(SelectorKind.CALL,
+             constructorName,
+             library,
+             0,
+             const []);
+
+  Selector.callDefaultConstructor(LibraryElement library)
+      : this(SelectorKind.CALL, const SourceString(""), library, 0, const []);
+
+  // TODO(kasperl): This belongs somewhere else.
+  Selector.noSuchMethod()
+      : this(SelectorKind.CALL, Compiler.NO_SUCH_METHOD, null,
+             Compiler.NO_SUCH_METHOD_ARG_COUNT);
+
+  bool isGetter() => identical(kind, SelectorKind.GETTER);
+  bool isSetter() => identical(kind, SelectorKind.SETTER);
+  bool isCall() => identical(kind, SelectorKind.CALL);
+  bool isClosureCall() {
+    SourceString callName = Compiler.CALL_OPERATOR_NAME;
+    return isCall() && name == callName;
+  }
+
+  bool isIndex() => identical(kind, SelectorKind.INDEX) && argumentCount == 1;
+  bool isIndexSet() => identical(kind, SelectorKind.INDEX) && argumentCount == 2;
+
+  bool isOperator() => identical(kind, SelectorKind.OPERATOR);
+  bool isUnaryOperator() => isOperator() && argumentCount == 0;
+  bool isBinaryOperator() => isOperator() && argumentCount == 1;
+
+  /** Check whether this is a call to 'assert'. */
+  bool isAssert() => isCall() && identical(name.stringValue, "assert");
+
+  int get hashCode => argumentCount + 1000 * namedArguments.length;
+  int get namedArgumentCount => namedArguments.length;
+  int get positionalArgumentCount => argumentCount - namedArgumentCount;
+  DartType get receiverType => null;
+
+  Selector get asUntyped => this;
+
+  /**
+   * The member name for invocation mirrors created from this selector.
+   */
+  String get invocationMirrorMemberName =>
+      isSetter() ? '${name.slowToString()}=' : name.slowToString();
+
+  int get invocationMirrorKind {
+    const int METHOD = 0;
+    const int GETTER = 1;
+    const int SETTER = 2;
+    int kind = METHOD;
+    if (isGetter()) {
+      kind = GETTER;
+    } else if (isSetter()) {
+      kind = SETTER;
+    }
+    return kind;
+  }
+
+  bool appliesUnnamed(Element element, Compiler compiler) {
+    assert(sameNameHack(element, compiler));
+    return appliesUntyped(element, compiler);
+  }
+
+  bool appliesUntyped(Element element, Compiler compiler) {
+    assert(sameNameHack(element, compiler));
+    if (Elements.isUnresolved(element)) return false;
+    if (name.isPrivate() && library != element.getLibrary()) return false;
+    if (element.isForeign(compiler)) return true;
+    if (element.isSetter()) return isSetter();
+    if (element.isGetter()) return isGetter() || isCall();
+    if (element.isField()) return isGetter() || isSetter() || isCall();
+    if (isGetter()) return true;
+    if (isSetter()) return false;
+
+    FunctionElement function = element;
+    FunctionSignature parameters = function.computeSignature(compiler);
+    if (argumentCount > parameters.parameterCount) return false;
+    int requiredParameterCount = parameters.requiredParameterCount;
+    int optionalParameterCount = parameters.optionalParameterCount;
+    if (positionalArgumentCount < requiredParameterCount) return false;
+
+    if (!parameters.optionalParametersAreNamed) {
+      // We have already checked that the number of arguments are
+      // not greater than the number of parameters. Therefore the
+      // number of positional arguments are not greater than the
+      // number of parameters.
+      assert(positionalArgumentCount <= parameters.parameterCount);
+      return namedArguments.isEmpty;
+    } else {
+      if (positionalArgumentCount > requiredParameterCount) return false;
+      assert(positionalArgumentCount == requiredParameterCount);
+      if (namedArgumentCount > optionalParameterCount) return false;
+      Set<SourceString> nameSet = new Set<SourceString>();
+      parameters.optionalParameters.forEach((Element element) {
+        nameSet.add(element.name);
+      });
+      for (SourceString name in namedArguments) {
+        if (!nameSet.contains(name)) return false;
+        // TODO(5213): By removing from the set we are checking
+        // that we are not passing the name twice. We should have this
+        // check in the resolver also.
+        nameSet.remove(name);
+      }
+      return true;
+    }
+  }
+
+  bool sameNameHack(Element element, Compiler compiler) {
+    // TODO(ngeoffray): Remove workaround checks.
+    return element == compiler.assertMethod
+        || element.isConstructor()
+        || name == element.name;
+  }
+
+  bool applies(Element element, Compiler compiler) {
+    if (!sameNameHack(element, compiler)) return false;
+    return appliesUnnamed(element, compiler);
+  }
+
+  /**
+   * Fills [list] with the arguments in a defined order.
+   *
+   * [compileArgument] is a function that returns a compiled version
+   * of an argument located in [arguments].
+   *
+   * [compileConstant] is a function that returns a compiled constant
+   * of an optional argument that is not in [arguments.
+   *
+   * Returns [:true:] if the selector and the [element] match; [:false:]
+   * otherwise.
+   *
+   * Invariant: [element] must be the implementation element.
+   */
+  bool addArgumentsToList(Link<Node> arguments,
+                          List list,
+                          FunctionElement element,
+                          compileArgument(Node argument),
+                          compileConstant(Element element),
+                          Compiler compiler) {
+    assert(invariant(element, element.isImplementation));
+    if (!this.applies(element, compiler)) return false;
+
+    FunctionSignature parameters = element.computeSignature(compiler);
+    parameters.forEachRequiredParameter((element) {
+      list.add(compileArgument(arguments.head));
+      arguments = arguments.tail;
+    });
+
+    if (!parameters.optionalParametersAreNamed) {
+      parameters.forEachOptionalParameter((element) {
+        if (!arguments.isEmpty) {
+          list.add(compileArgument(arguments.head));
+          arguments = arguments.tail;
+        } else {
+          list.add(compileConstant(element));
+        }
+      });
+    } else {
+      // Visit named arguments and add them into a temporary list.
+      List compiledNamedArguments = [];
+      for (; !arguments.isEmpty; arguments = arguments.tail) {
+        NamedArgument namedArgument = arguments.head;
+        compiledNamedArguments.add(compileArgument(namedArgument.expression));
+      }
+      // Iterate over the optional parameters of the signature, and try to
+      // find them in [compiledNamedArguments]. If found, we use the
+      // value in the temporary list, otherwise the default value.
+      parameters.orderedOptionalParameters.forEach((element) {
+        int foundIndex = namedArguments.indexOf(element.name);
+        if (foundIndex != -1) {
+          list.add(compiledNamedArguments[foundIndex]);
+        } else {
+          list.add(compileConstant(element));
+        }
+      });
+    }
+    return true;
+  }
+
+  static bool sameNames(List<SourceString> first, List<SourceString> second) {
+    for (int i = 0; i < first.length; i++) {
+      if (first[i] != second[i]) return false;
+    }
+    return true;
+  }
+
+  bool operator ==(other) {
+    if (other is !Selector) return false;
+    return identical(receiverType, other.receiverType)
+        && equalsUntyped(other);
+  }
+
+  bool equalsUntyped(Selector other) {
+    return name == other.name
+           && kind == other.kind
+           && identical(library, other.library)
+           && argumentCount == other.argumentCount
+           && namedArguments.length == other.namedArguments.length
+           && sameNames(namedArguments, other.namedArguments);
+  }
+
+  List<SourceString> getOrderedNamedArguments() {
+    if (namedArguments.isEmpty) return namedArguments;
+    if (!orderedNamedArguments.isEmpty) return orderedNamedArguments;
+
+    orderedNamedArguments.addAll(namedArguments);
+    orderedNamedArguments.sort((SourceString first, SourceString second) {
+      return first.slowToString().compareTo(second.slowToString());
+    });
+    return orderedNamedArguments;
+  }
+
+  String namedArgumentsToString() {
+    if (namedArgumentCount > 0) {
+      StringBuffer result = new StringBuffer();
+      for (int i = 0; i < namedArgumentCount; i++) {
+        if (i != 0) result.add(', ');
+        result.add(namedArguments[i].slowToString());
+      }
+      return "[$result]";
+    }
+    return '';
+  }
+
+  String toString() {
+    String named = '';
+    String type = '';
+    if (namedArgumentCount > 0) named = ', named=${namedArgumentsToString()}';
+    if (receiverType != null) type = ', type=$receiverType';
+    return 'Selector($kind, ${name.slowToString()}, '
+           'arity=$argumentCount$named$type)';
+  }
+}
+
+class TypedSelector extends Selector {
+  /**
+   * The type of the receiver. Any subtype of that type can be the
+   * target of the invocation.
+   */
+  final DartType receiverType;
+
+  final Selector asUntyped;
+
+  TypedSelector(DartType this.receiverType, Selector selector)
+      : asUntyped = selector.asUntyped,
+        super(selector.kind,
+              selector.name,
+              selector.library,
+              selector.argumentCount,
+              selector.namedArguments) {
+    // Invariant: Typed selector can not be based on a malformed type.
+    assert(!identical(receiverType.kind, TypeKind.MALFORMED_TYPE));
+    assert(asUntyped.receiverType == null);
+  }
+
+  /**
+   * Check if [element] will be the one used at runtime when being
+   * invoked on an instance of [cls].
+   */
+  bool hasElementIn(ClassElement cls, Element element) {
+    // Use the selector for the lookup instead of [:element.name:]
+    // because the selector has the right privacy information.
+    Element resolved = cls.lookupSelector(this);
+    if (resolved == element) return true;
+    if (resolved == null) return false;
+    if (resolved.isAbstractField()) {
+      AbstractFieldElement field = resolved;
+      if (element == field.getter || element == field.setter) {
+        return true;
+      } else {
+        ClassElement otherCls = field.getEnclosingClass();
+        // We have not found a match, but another class higher in the
+        // hierarchy may define the getter or the setter.
+        return hasElementIn(otherCls.superclass, element);
+      }
+    }
+    return false;
+  }
+
+  bool appliesUnnamed(Element element, Compiler compiler) {
+    assert(sameNameHack(element, compiler));
+    // [TypedSelector] are only used when compiling.
+    assert(compiler.phase == Compiler.PHASE_COMPILING);
+    if (!element.isMember()) return false;
+
+    // A closure can be called through any typed selector:
+    // class A {
+    //   get foo => () => 42;
+    //   bar() => foo(); // The call to 'foo' is a typed selector.
+    // }
+    ClassElement other = element.getEnclosingClass();
+    if (identical(other.superclass, compiler.closureClass)) {
+      return appliesUntyped(element, compiler);
+    }
+
+    Element self = receiverType.element;
+    if (self.isTypedef()) {
+      // A typedef is a function type that doesn't have any
+      // user-defined members.
+      return false;
+    }
+
+    if (other.implementsInterface(self)
+        || other.isSubclassOf(self)
+        || compiler.world.hasAnySubclassThatImplements(other, receiverType)) {
+      return appliesUntyped(element, compiler);
+    }
+
+    // If [self] is a subclass of [other], it inherits the
+    // implementation of [element].
+    ClassElement cls = self;
+    if (cls.isSubclassOf(other)) {
+      // Resolve an invocation of [element.name] on [self]. If it
+      // is found, this selector is a candidate.
+      return hasElementIn(self, element) && appliesUntyped(element, compiler);
+    }
+
+    return false;
+  }
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/characters.dart b/pkgs/markdown/lib/src/compiler/implementation/util/characters.dart
new file mode 100644
index 0000000..5961d07
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/util/characters.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2011, 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.
+
+library characters;
+
+const int $EOF = 0;
+const int $STX = 2;
+const int $BS  = 8;
+const int $TAB = 9;
+const int $LF = 10;
+const int $VTAB = 11;
+const int $FF = 12;
+const int $CR = 13;
+const int $SPACE = 32;
+const int $BANG = 33;
+const int $DQ = 34;
+const int $HASH = 35;
+const int $$ = 36;
+const int $PERCENT = 37;
+const int $AMPERSAND = 38;
+const int $SQ = 39;
+const int $OPEN_PAREN = 40;
+const int $CLOSE_PAREN = 41;
+const int $STAR = 42;
+const int $PLUS = 43;
+const int $COMMA = 44;
+const int $MINUS = 45;
+const int $PERIOD = 46;
+const int $SLASH = 47;
+const int $0 = 48;
+const int $1 = 49;
+const int $2 = 50;
+const int $3 = 51;
+const int $4 = 52;
+const int $5 = 53;
+const int $6 = 54;
+const int $7 = 55;
+const int $8 = 56;
+const int $9 = 57;
+const int $COLON = 58;
+const int $SEMICOLON = 59;
+const int $LT = 60;
+const int $EQ = 61;
+const int $GT = 62;
+const int $QUESTION = 63;
+const int $AT = 64;
+const int $A = 65;
+const int $B = 66;
+const int $C = 67;
+const int $D = 68;
+const int $E = 69;
+const int $F = 70;
+const int $G = 71;
+const int $H = 72;
+const int $I = 73;
+const int $J = 74;
+const int $K = 75;
+const int $L = 76;
+const int $M = 77;
+const int $N = 78;
+const int $O = 79;
+const int $P = 80;
+const int $Q = 81;
+const int $R = 82;
+const int $S = 83;
+const int $T = 84;
+const int $U = 85;
+const int $V = 86;
+const int $W = 87;
+const int $X = 88;
+const int $Y = 89;
+const int $Z = 90;
+const int $OPEN_SQUARE_BRACKET = 91;
+const int $BACKSLASH = 92;
+const int $CLOSE_SQUARE_BRACKET = 93;
+const int $CARET = 94;
+const int $_ = 95;
+const int $BACKPING = 96;
+const int $a = 97;
+const int $b = 98;
+const int $c = 99;
+const int $d = 100;
+const int $e = 101;
+const int $f = 102;
+const int $g = 103;
+const int $h = 104;
+const int $i = 105;
+const int $j = 106;
+const int $k = 107;
+const int $l = 108;
+const int $m = 109;
+const int $n = 110;
+const int $o = 111;
+const int $p = 112;
+const int $q = 113;
+const int $r = 114;
+const int $s = 115;
+const int $t = 116;
+const int $u = 117;
+const int $v = 118;
+const int $w = 119;
+const int $x = 120;
+const int $y = 121;
+const int $z = 122;
+const int $OPEN_CURLY_BRACKET = 123;
+const int $BAR = 124;
+const int $CLOSE_CURLY_BRACKET = 125;
+const int $TILDE = 126;
+const int $DEL = 127;
+const int $NBSP = 160;
+const int $LS = 0x2028;
+const int $PS = 0x2029;
+
+const int $FIRST_SURROGATE = 0xd800;
+const int $LAST_SURROGATE = 0xdfff;
+const int $LAST_CODE_POINT = 0x10ffff;
+
+bool isHexDigit(int characterCode) {
+  if (characterCode <= $9) return $0 <= characterCode;
+  characterCode |= $a ^ $A;
+  return ($a <= characterCode && characterCode <= $f);
+}
+
+int hexDigitValue(int hexDigit) {
+  assert(isHexDigit(hexDigit));
+  // hexDigit is one of '0'..'9', 'A'..'F' and 'a'..'f'.
+  if (hexDigit <= $9) return hexDigit - $0;
+  return (hexDigit | ($a ^ $A)) - ($a - 10);
+}
+
+bool isUnicodeScalarValue(int value) {
+  return value < $FIRST_SURROGATE ||
+      (value > $LAST_SURROGATE && value <= $LAST_CODE_POINT);
+}
+
+bool isUtf16LeadSurrogate(int value) {
+  return value >= 0xd800 && value <= 0xdbff;
+}
+
+bool isUtf16TrailSurrogate(int value) {
+  return value >= 0xdc00 && value <= 0xdfff;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/link.dart b/pkgs/markdown/lib/src/compiler/implementation/util/link.dart
new file mode 100644
index 0000000..9cc81cc
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/util/link.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2011, 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.
+
+part of org_dartlang_compiler_util;
+
+class Link<T> extends Iterable<T> {
+  T get head => null;
+  Link<T> get tail => null;
+
+  factory Link.fromList(List<T> list) {
+    switch (list.length) {
+      case 0:
+        return new Link<T>();
+      case 1:
+        return new LinkEntry<T>(list[0]);
+      case 2:
+        return new LinkEntry<T>(list[0], new LinkEntry<T>(list[1]));
+      case 3:
+        return new LinkEntry<T>(
+            list[0], new LinkEntry<T>(list[1], new LinkEntry<T>(list[2])));
+    }
+    Link link = new Link<T>();
+    for (int i = list.length ; i > 0; i--) {
+      link = link.prepend(list[i - 1]);
+    }
+    return link;
+  }
+
+  const Link();
+
+  Link<T> prepend(T element) {
+    return new LinkEntry<T>(element, this);
+  }
+
+  Iterator<T> get iterator => new LinkIterator<T>(this);
+
+  void printOn(StringBuffer buffer, [separatedBy]) {
+  }
+
+  List toList() => new List<T>.fixedLength(0);
+
+  bool get isEmpty => true;
+
+  Link<T> reverse() => this;
+
+  Link<T> reversePrependAll(Link<T> from) {
+    if (from.isEmpty) return this;
+    return this.prepend(from.head).reversePrependAll(from.tail);
+  }
+
+  Link<T> skip(int n) {
+    if (n == 0) return this;
+    throw new RangeError('Index $n out of range');
+  }
+
+  void forEach(void f(T element)) {}
+
+  bool operator ==(other) {
+    if (other is !Link<T>) return false;
+    return other.isEmpty;
+  }
+
+  String toString() => "[]";
+
+  get length {
+    throw new UnsupportedError('get:length');
+  }
+
+  int slowLength() => 0;
+}
+
+abstract class LinkBuilder<T> {
+  factory LinkBuilder() = LinkBuilderImplementation;
+
+  Link<T> toLink();
+  void addLast(T t);
+
+  final int length;
+  final bool isEmpty;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart b/pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart
new file mode 100644
index 0000000..d00ec86
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2011, 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.
+
+part of util_implementation;
+
+class LinkIterator<T> implements Iterator<T> {
+  T _current;
+  Link<T> _link;
+
+  LinkIterator(Link<T> this._link);
+
+  T get current => _current;
+
+  bool moveNext() {
+    if (_link.isEmpty) {
+      _current = null;
+      return false;
+    }
+    _current = _link.head;
+    _link = _link.tail;
+    return true;
+  }
+}
+
+class LinkEntry<T> extends Link<T> {
+  final T head;
+  Link<T> tail;
+
+  LinkEntry(T this.head, [Link<T> tail])
+    : this.tail = ((tail == null) ? new Link<T>() : tail);
+
+  Link<T> prepend(T element) {
+    // TODO(ahe): Use new Link<T>, but this cost 8% performance on VM.
+    return new LinkEntry<T>(element, this);
+  }
+
+  void printOn(StringBuffer buffer, [separatedBy]) {
+    buffer.add(head);
+    if (separatedBy == null) separatedBy = '';
+    for (Link link = tail; !link.isEmpty; link = link.tail) {
+      buffer.add(separatedBy);
+      buffer.add(link.head);
+    }
+  }
+
+  String toString() {
+    StringBuffer buffer = new StringBuffer();
+    buffer.add('[ ');
+    printOn(buffer, ', ');
+    buffer.add(' ]');
+    return buffer.toString();
+  }
+
+  Link<T> reverse() {
+    Link<T> result = const Link();
+    for (Link<T> link = this; !link.isEmpty; link = link.tail) {
+      result = result.prepend(link.head);
+    }
+    return result;
+  }
+
+  Link<T> reversePrependAll(Link<T> from) {
+    Link<T> result;
+    for (result = this; !from.isEmpty; from = from.tail) {
+      result = result.prepend(from.head);
+    }
+    return result;
+  }
+
+  Link<T> skip(int n) {
+    Link<T> link = this;
+    for (int i = 0 ; i < n ; i++) {
+      if (link.isEmpty) {
+        throw new RangeError('Index $n out of range');
+      }
+      link = link.tail;
+    }
+    return link;
+  }
+
+  bool get isEmpty => false;
+
+  List<T> toList() {
+    List<T> list = new List<T>();
+    for (Link<T> link = this; !link.isEmpty; link = link.tail) {
+      list.addLast(link.head);
+    }
+    return list;
+  }
+
+  void forEach(void f(T element)) {
+    for (Link<T> link = this; !link.isEmpty; link = link.tail) {
+      f(link.head);
+    }
+  }
+
+  bool operator ==(other) {
+    if (other is !Link<T>) return false;
+    Link<T> myElements = this;
+    while (!myElements.isEmpty && !other.isEmpty) {
+      if (myElements.head != other.head) {
+        return false;
+      }
+      myElements = myElements.tail;
+      other = other.tail;
+    }
+    return myElements.isEmpty && other.isEmpty;
+  }
+
+  int slowLength() => 1 + tail.slowLength();
+}
+
+class LinkBuilderImplementation<T> implements LinkBuilder<T> {
+  LinkEntry<T> head = null;
+  LinkEntry<T> lastLink = null;
+  int length = 0;
+
+  LinkBuilderImplementation();
+
+  Link<T> toLink() {
+    if (head == null) return const Link();
+    lastLink.tail = const Link();
+    Link<T> link = head;
+    lastLink = null;
+    head = null;
+    return link;
+  }
+
+  void addLast(T t) {
+    length++;
+    LinkEntry<T> entry = new LinkEntry<T>(t, null);
+    if (head == null) {
+      head = entry;
+    } else {
+      lastLink.tail = entry;
+    }
+    lastLink = entry;
+  }
+
+  bool get isEmpty => length == 0;
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart b/pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart
new file mode 100644
index 0000000..fed5f15
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart
@@ -0,0 +1,65 @@
+// 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.
+
+library uri_extras;
+
+import 'dart:math';
+import 'dart:uri';
+
+String relativize(Uri base, Uri uri, bool isWindows) {
+  if (!base.path.startsWith('/')) {
+    // Also throw an exception if [base] or base.path is null.
+    throw new ArgumentError('Expected absolute path: ${base.path}');
+  }
+  if (!uri.path.startsWith('/')) {
+    // Also throw an exception if [uri] or uri.path is null.
+    throw new ArgumentError('Expected absolute path: ${uri.path}');
+  }
+  bool equalsNCS(String a, String b) {
+    return a.toLowerCase() == b.toLowerCase();
+  }
+
+  String normalize(String path) {
+    if (isWindows) {
+      return path.toLowerCase();
+    } else {
+      return path;
+    }
+  }
+
+  if (equalsNCS(base.scheme, 'file') &&
+      equalsNCS(base.scheme, uri.scheme) &&
+      base.userInfo == uri.userInfo &&
+      equalsNCS(base.domain, uri.domain) &&
+      base.port == uri.port &&
+      uri.query == "" && uri.fragment == "") {
+    if (normalize(uri.path).startsWith(normalize(base.path))) {
+      return uri.path.substring(base.path.length);
+    }
+    List<String> uriParts = uri.path.split('/');
+    List<String> baseParts = base.path.split('/');
+    int common = 0;
+    int length = min(uriParts.length, baseParts.length);
+    while (common < length &&
+           normalize(uriParts[common]) == normalize(baseParts[common])) {
+      common++;
+    }
+    if (common == 1 || (isWindows && common == 2)) {
+      // The first part will always be an empty string because the
+      // paths are absolute. On Windows, we must also consider drive
+      // letters or hostnames.
+      return uri.path;
+    }
+    StringBuffer sb = new StringBuffer();
+    for (int i = common + 1; i < baseParts.length; i++) {
+      sb.add('../');
+    }
+    for (int i = common; i < uriParts.length - 1; i++) {
+      sb.add('${uriParts[i]}/');
+    }
+    sb.add('${uriParts.last}');
+    return sb.toString();
+  }
+  return uri.toString();
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/util.dart b/pkgs/markdown/lib/src/compiler/implementation/util/util.dart
new file mode 100644
index 0000000..8a07687
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/util/util.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2011, 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.
+
+library org_dartlang_compiler_util;
+
+import 'util_implementation.dart';
+import 'characters.dart';
+
+part 'link.dart';
+
+/**
+ * Tagging interface for classes from which source spans can be generated.
+ */
+// TODO(johnniwinther): Find a better name.
+// TODO(ahe): How about "Bolt"?
+abstract class Spannable {}
+
+class _SpannableSentinel implements Spannable {
+  final String name;
+
+  const _SpannableSentinel(this.name);
+
+  String toString() => name;
+}
+
+const Spannable CURRENT_ELEMENT_SPANNABLE =
+    const _SpannableSentinel("Current element");
+
+class SpannableAssertionFailure {
+  final Spannable node;
+  final String message;
+  SpannableAssertionFailure(this.node, this.message);
+
+  String toString() => 'Compiler crashed: $message.';
+}
+
+/// Writes the characters of [string] on [buffer].  The characters
+/// are escaped as suitable for JavaScript and JSON.  [buffer] is
+/// anything which supports [:add:] and [:addCharCode:], for example,
+/// [StringBuffer].  Note that JS supports \xnn and \unnnn whereas JSON only
+/// supports the \unnnn notation.  Therefore we use the \unnnn notation.
+void writeJsonEscapedCharsOn(String string, buffer) {
+  void addCodeUnitEscaped(var buffer, int code) {
+    assert(code < 0x10000);
+    buffer.add(r'\u');
+    if (code < 0x1000) {
+      buffer.add('0');
+      if (code < 0x100) {
+        buffer.add('0');
+        if (code < 0x10) {
+          buffer.add('0');
+        }
+      }
+    }
+    buffer.add(code.toRadixString(16));
+  }
+
+  void writeEscapedOn(String string, var buffer) {
+    for (int i = 0; i < string.length; i++) {
+      int code = string.charCodeAt(i);
+      if (code == $DQ) {
+        buffer.add(r'\"');
+      } else if (code == $TAB) {
+        buffer.add(r'\t');
+      } else if (code == $LF) {
+        buffer.add(r'\n');
+      } else if (code == $CR) {
+        buffer.add(r'\r');
+      } else if (code == $DEL) {
+        addCodeUnitEscaped(buffer, $DEL);
+      } else if (code == $LS) {
+        // This Unicode line terminator and $PS are invalid in JS string
+        // literals.
+        addCodeUnitEscaped(buffer, $LS);  // 0x2028.
+      } else if (code == $PS) {
+        addCodeUnitEscaped(buffer, $PS);  // 0x2029.
+      } else if (code == $BACKSLASH) {
+        buffer.add(r'\\');
+      } else {
+        if (code < 0x20) {
+          addCodeUnitEscaped(buffer, code);
+          // We emit DEL (ASCII 0x7f) as an escape because it would be confusing
+          // to have it unescaped in a string literal.  We also escape
+          // everything above 0x7f because that means we don't have to worry
+          // about whether the web server serves it up as Latin1 or UTF-8.
+        } else if (code < 0x7f) {
+          buffer.addCharCode(code);
+        } else {
+          // This will output surrogate pairs in the form \udxxx\udyyy, rather
+          // than the more logical \u{zzzzzz}.  This should work in JavaScript
+          // (especially old UCS-2 based implementations) and is the only
+          // format that is allowed in JSON.
+          addCodeUnitEscaped(buffer, code);
+        }
+      }
+    }
+  }
+
+  for (int i = 0; i < string.length; i++) {
+    int code = string.charCodeAt(i);
+    if (code < 0x20 || code == $DEL || code == $DQ || code == $LS ||
+        code == $PS || code == $BACKSLASH || code >= 0x80) {
+      writeEscapedOn(string, buffer);
+      return;
+    }
+  }
+  buffer.add(string);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart b/pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart
new file mode 100644
index 0000000..bbb852a
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2011, 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.
+
+library util_implementation;
+
+import 'util.dart';
+
+part 'link_implementation.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/warnings.dart b/pkgs/markdown/lib/src/compiler/implementation/warnings.dart
new file mode 100644
index 0000000..56f3698
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/warnings.dart
@@ -0,0 +1,568 @@
+// Copyright (c) 2011, 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.
+
+part of dart2js;
+
+class MessageKind {
+  final String template;
+  const MessageKind(this.template);
+
+  static const GENERIC = const MessageKind('#{text}');
+
+  static const NOT_ASSIGNABLE = const MessageKind(
+      '#{fromType} is not assignable to #{toType}');
+  static const VOID_EXPRESSION = const MessageKind(
+      'expression does not yield a value');
+  static const VOID_VARIABLE = const MessageKind(
+      'variable cannot be of type void');
+  static const RETURN_VALUE_IN_VOID = const MessageKind(
+      'cannot return value from void function');
+  static const RETURN_NOTHING = const MessageKind(
+      'value of type #{returnType} expected');
+  static const MISSING_ARGUMENT = const MessageKind(
+      'missing argument of type #{argumentType}');
+  static const ADDITIONAL_ARGUMENT = const MessageKind(
+      'additional argument');
+  static const NAMED_ARGUMENT_NOT_FOUND = const MessageKind(
+      "no named argument '#{argumentName}' found on method");
+  static const METHOD_NOT_FOUND = const MessageKind(
+      'no method named #{methodName} in class #{className}');
+  static const MEMBER_NOT_STATIC = const MessageKind(
+      '#{className}.#{memberName} is not static');
+  static const NO_INSTANCE_AVAILABLE = const MessageKind(
+      '#{name} is only available in instance methods');
+
+  static const UNREACHABLE_CODE = const MessageKind(
+      'unreachable code');
+  static const MISSING_RETURN = const MessageKind(
+      'missing return');
+  static const MAYBE_MISSING_RETURN = const MessageKind(
+      'not all paths lead to a return or throw statement');
+
+  static const CANNOT_RESOLVE = const MessageKind(
+      'cannot resolve #{name}');
+  static const CANNOT_RESOLVE_CONSTRUCTOR = const MessageKind(
+      'cannot resolve constructor #{constructorName}');
+  static const CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind(
+      'cannot resolve constructor #{constructorName} for implicit super call');
+  static const CANNOT_RESOLVE_TYPE = const MessageKind(
+      'cannot resolve type #{typeName}');
+  static const DUPLICATE_DEFINITION = const MessageKind(
+      'duplicate definition of #{name}');
+  static const DUPLICATE_IMPORT = const MessageKind(
+      'duplicate import of #{name}');
+  static const DUPLICATE_EXPORT = const MessageKind(
+      'duplicate export of #{name}');
+  static const NOT_A_TYPE = const MessageKind(
+      '#{node} is not a type');
+  static const NOT_A_PREFIX = const MessageKind(
+      '#{node} is not a prefix');
+  static const NO_SUPER_IN_OBJECT = const MessageKind(
+      "'Object' does not have a superclass");
+  static const CANNOT_FIND_CONSTRUCTOR = const MessageKind(
+      'cannot find constructor #{constructorName}');
+  static const CANNOT_FIND_CONSTRUCTOR2 = const MessageKind(
+      'cannot find constructor #{constructorName} in #{className}');
+  static const CYCLIC_CLASS_HIERARCHY = const MessageKind(
+      '#{className} creates a cycle in the class hierarchy');
+  static const INVALID_RECEIVER_IN_INITIALIZER = const MessageKind(
+      'field initializer expected');
+  static const NO_SUPER_IN_STATIC = const MessageKind(
+      "'super' is only available in instance methods");
+  static const DUPLICATE_INITIALIZER = const MessageKind(
+      'field #{fieldName} is initialized more than once');
+  static const ALREADY_INITIALIZED = const MessageKind(
+      '#{fieldName} was already initialized here');
+  static const INIT_STATIC_FIELD = const MessageKind(
+      'cannot initialize static field #{fieldName}');
+  static const NOT_A_FIELD = const MessageKind(
+      '#{fieldName} is not a field');
+  static const CONSTRUCTOR_CALL_EXPECTED = const MessageKind(
+      "only call to 'this' or 'super' constructor allowed");
+  static const INVALID_FOR_IN = const MessageKind(
+      'invalid for-in variable declaration.');
+  static const INVALID_INITIALIZER = const MessageKind(
+      'invalid initializer');
+  static const FUNCTION_WITH_INITIALIZER = const MessageKind(
+      'only constructors can have initializers');
+  static const REDIRECTING_CONSTRUCTOR_CYCLE = const MessageKind(
+      'cyclic constructor redirection');
+  static const REDIRECTING_CONSTRUCTOR_HAS_BODY = const MessageKind(
+      'redirecting constructor cannot have a body');
+  static const REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER = const MessageKind(
+      'redirecting constructor cannot have other initializers');
+  static const SUPER_INITIALIZER_IN_OBJECT = const MessageKind(
+      "'Object' cannot have a super initializer");
+  static const DUPLICATE_SUPER_INITIALIZER = const MessageKind(
+      'cannot have more than one super initializer');
+  static const INVALID_ARGUMENTS = const MessageKind(
+      "arguments do not match the expected parameters of #{methodName}");
+  static const NO_MATCHING_CONSTRUCTOR = const MessageKind(
+      "super call arguments and constructor parameters don't match");
+  static const NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind(
+      "implicit super call arguments and constructor parameters don't match");
+  static const FIELD_PARAMETER_NOT_ALLOWED = const MessageKind(
+      'a field parameter is only allowed in generative constructors');
+  static const INVALID_PARAMETER = const MessageKind(
+      "cannot resolve parameter");
+  static const NOT_INSTANCE_FIELD = const MessageKind(
+      '#{fieldName} is not an instance field');
+  static const NO_CATCH_NOR_FINALLY = const MessageKind(
+      "expected 'catch' or 'finally'");
+  static const EMPTY_CATCH_DECLARATION = const MessageKind(
+      'expected an identifier in catch declaration');
+  static const EXTRA_CATCH_DECLARATION = const MessageKind(
+      'extra parameter in catch declaration');
+  static const PARAMETER_WITH_TYPE_IN_CATCH = const MessageKind(
+      'cannot use type annotations in catch');
+  static const PARAMETER_WITH_MODIFIER_IN_CATCH = const MessageKind(
+      'cannot use modifiers in catch');
+  static const OPTIONAL_PARAMETER_IN_CATCH = const MessageKind(
+      'cannot use optional parameters in catch');
+  static const THROW_WITHOUT_EXPRESSION = const MessageKind(
+      'cannot use re-throw outside of catch block (expression expected after '
+      '"throw")');
+  static const UNBOUND_LABEL = const MessageKind(
+      'cannot resolve label #{labelName}');
+  static const NO_BREAK_TARGET = const MessageKind(
+      'break statement not inside switch or loop');
+  static const NO_CONTINUE_TARGET = const MessageKind(
+      'continue statement not inside loop');
+  static const EXISTING_LABEL = const MessageKind(
+      'original declaration of duplicate label #{labelName}');
+  static const DUPLICATE_LABEL = const MessageKind(
+      'duplicate declaration of label #{labelName}');
+  static const UNUSED_LABEL = const MessageKind(
+      'unused label #{labelName}');
+  static const INVALID_CONTINUE = const MessageKind(
+      'target of continue is not a loop or switch case');
+  static const INVALID_BREAK = const MessageKind(
+      'target of break is not a statement');
+
+  static const TYPE_VARIABLE_AS_CONSTRUCTOR = const MessageKind(
+      'cannot use type variable as constructor');
+  static const DUPLICATE_TYPE_VARIABLE_NAME = const MessageKind(
+      'type variable #{typeVariableName} already declared');
+  static const TYPE_VARIABLE_WITHIN_STATIC_MEMBER = const MessageKind(
+      'cannot refer to type variable #{typeVariableName} '
+      'within a static member');
+
+  static const INVALID_USE_OF_SUPER = const MessageKind(
+      'super not allowed here');
+  static const INVALID_CASE_DEFAULT = const MessageKind(
+      'default only allowed on last case of a switch');
+
+  static const SWITCH_CASE_TYPES_NOT_EQUAL = const MessageKind(
+      "case expressions don't all have the same type.");
+  static const SWITCH_CASE_VALUE_OVERRIDES_EQUALS = const MessageKind(
+      "case expression value overrides 'operator=='.");
+  static const SWITCH_INVALID = const MessageKind(
+      "switch cases contain invalid expressions.");
+
+  static const INVALID_ARGUMENT_AFTER_NAMED = const MessageKind(
+      'non-named argument after named argument');
+
+  static const NOT_A_COMPILE_TIME_CONSTANT = const MessageKind(
+      'not a compile-time constant');
+  static const CYCLIC_COMPILE_TIME_CONSTANTS = const MessageKind(
+      'cycle in the compile-time constant computation');
+  static const CONSTRUCTOR_IS_NOT_CONST = const MessageKind(
+      'constructor is not a const constructor');
+
+  static const KEY_NOT_A_STRING_LITERAL = const MessageKind(
+      'map-literal key not a string literal');
+
+  static const NO_SUCH_LIBRARY_MEMBER = const MessageKind(
+      '#{libraryName} has no member named #{memberName}');
+
+  static const CANNOT_INSTANTIATE_INTERFACE = const MessageKind(
+      "cannot instantiate interface '#{interfaceName}'");
+
+  static const CANNOT_INSTANTIATE_TYPEDEF = const MessageKind(
+      "cannot instantiate typedef '#{typedefName}'");
+
+  static const CANNOT_INSTANTIATE_TYPE_VARIABLE = const MessageKind(
+      "cannot instantiate type variable '#{typeVariableName}'");
+
+  static const NO_DEFAULT_CLASS = const MessageKind(
+      "no default class on enclosing interface '#{interfaceName}'");
+
+  static const CYCLIC_TYPE_VARIABLE = const MessageKind(
+      "cyclic reference to type variable #{typeVariableName}");
+
+  static const CLASS_NAME_EXPECTED = const MessageKind(
+      "class name expected");
+
+  static const INTERFACE_TYPE_EXPECTED = const MessageKind(
+      "interface type expected");
+
+  static const CANNOT_EXTEND = const MessageKind(
+      "#{type} cannot be extended");
+
+  static const CANNOT_IMPLEMENT = const MessageKind(
+      "#{type} cannot be implemented");
+
+  static const DUPLICATE_EXTENDS_IMPLEMENTS = const MessageKind(
+      "Error: #{type} can not be both extended and implemented.");
+
+  static const DUPLICATE_IMPLEMENTS = const MessageKind(
+      "Error: #{type} must not occur more than once "
+      "in the implements clause.");
+
+  static const ILLEGAL_SUPER_SEND = const MessageKind(
+      "#{name} cannot be called on super");
+
+  static const ADDITIONAL_TYPE_ARGUMENT = const MessageKind(
+      "additional type argument");
+
+  static const MISSING_TYPE_ARGUMENT = const MessageKind(
+      "missing type argument");
+
+  // TODO(johnniwinther): Use ADDITIONAL_TYPE_ARGUMENT or MISSING_TYPE_ARGUMENT
+  // instead.
+  static const TYPE_ARGUMENT_COUNT_MISMATCH = const MessageKind(
+      "incorrect number of type arguments on #{type}");
+
+  static const MISSING_ARGUMENTS_TO_ASSERT = const MessageKind(
+      "missing arguments to assert");
+
+  static const GETTER_MISMATCH = const MessageKind(
+      "Error: setter disagrees on: #{modifiers}.");
+
+  static const SETTER_MISMATCH = const MessageKind(
+      "Error: getter disagrees on: #{modifiers}.");
+
+  static const ILLEGAL_SETTER_FORMALS = const MessageKind(
+      "Error: a setter must have exactly one argument.");
+
+  static const NO_STATIC_OVERRIDE = const MessageKind(
+      "Error: static member cannot override instance member '#{memberName}' of "
+      "'#{className}'.");
+
+  static const NO_STATIC_OVERRIDE_CONT = const MessageKind(
+      "Info: this is the instance member that cannot be overridden "
+      "by a static member.");
+
+  static const CANNOT_OVERRIDE_FIELD_WITH_METHOD = const MessageKind(
+      "Error: method cannot override field '#{memberName}' of '#{className}'.");
+
+  static const CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT = const MessageKind(
+      "Info: this is the field that cannot be overridden by a method.");
+
+  static const CANNOT_OVERRIDE_METHOD_WITH_FIELD = const MessageKind(
+      "Error: field cannot override method '#{memberName}' of '#{className}'.");
+
+  static const CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT = const MessageKind(
+      "Info: this is the method that cannot be overridden by a field.");
+
+  static const BAD_ARITY_OVERRIDE = const MessageKind(
+      "Error: cannot override method '#{memberName}' in '#{className}'; "
+      "the parameters do not match.");
+
+  static const BAD_ARITY_OVERRIDE_CONT = const MessageKind(
+      "Info: this is the method whose parameters do not match.");
+
+  static const MISSING_FORMALS = const MessageKind(
+      "Error: Formal parameters are missing.");
+
+  static const EXTRA_FORMALS = const MessageKind(
+      "Error: Formal parameters are not allowed here.");
+
+  static const UNARY_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator #{operatorName} must have no parameters.");
+
+  static const MINUS_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator - must have 0 or 1 parameters.");
+
+  static const BINARY_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator #{operatorName} must have exactly 1 parameter.");
+
+  static const TERNARY_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator #{operatorName} must have exactly 2 parameters.");
+
+  static const OPERATOR_OPTIONAL_PARAMETERS = const MessageKind(
+      "Error: Operator #{operatorName} cannot have optional parameters.");
+
+  static const OPERATOR_NAMED_PARAMETERS = const MessageKind(
+      "Error: Operator #{operatorName} cannot have named parameters.");
+
+  // TODO(ahe): This message is hard to localize.  This is acceptable,
+  // as it will be removed when we ship Dart version 1.0.
+  static const DEPRECATED_FEATURE_WARNING = const MessageKind(
+      "Warning: deprecated language feature, #{featureName}, "
+      "will be removed in a future Dart milestone.");
+
+  // TODO(ahe): This message is hard to localize.  This is acceptable,
+  // as it will be removed when we ship Dart version 1.0.
+  static const DEPRECATED_FEATURE_ERROR = const MessageKind(
+      "Error: #{featureName} are not legal "
+      "due to option --reject-deprecated-language-features.");
+
+  static const CONSTRUCTOR_WITH_RETURN_TYPE = const MessageKind(
+      "Error: cannot have return type for constructor.");
+
+  static const ILLEGAL_FINAL_METHOD_MODIFIER = const MessageKind(
+      "Error: cannot have final modifier on method.");
+
+  static const ILLEGAL_CONSTRUCTOR_MODIFIERS = const MessageKind(
+      "Error: illegal constructor modifiers: #{modifiers}.");
+
+  static const ILLEGAL_MIXIN_APPLICATION_MODIFIERS = const MessageKind(
+      "Error: illegal mixin application modifiers: #{modifiers}.");
+
+  static const ILLEGAL_MIXIN_SUPERCLASS = const MessageKind(
+      "Error: class used as mixin must have Object as superclass.");
+
+  static const ILLEGAL_MIXIN_CONSTRUCTOR = const MessageKind(
+      "Error: class used as mixin cannot have non-factory constructor.");
+
+  static const ILLEGAL_MIXIN_CYCLE = const MessageKind(
+      "Error: class used as mixin introduces mixin cycle: "
+      "#{mixinName1} <-> #{mixinName2}.");
+
+  static const ILLEGAL_MIXIN_WITH_SUPER = const MessageKind(
+      "Error: cannot use class #{className} as a mixin because it uses super.");
+
+  static const ILLEGAL_MIXIN_SUPER_USE = const MessageKind(
+      "Use of super in class used as mixin.");
+
+  static const PARAMETER_NAME_EXPECTED = const MessageKind(
+      "Error: parameter name expected.");
+
+  static const CANNOT_RESOLVE_GETTER = const MessageKind(
+      'cannot resolve getter.');
+
+  static const CANNOT_RESOLVE_SETTER = const MessageKind(
+      'cannot resolve setter.');
+
+  static const VOID_NOT_ALLOWED = const MessageKind(
+      'type void is only allowed in a return type.');
+
+  static const BEFORE_TOP_LEVEL = const MessageKind(
+      'Error: part header must come before top-level definitions.');
+
+  static const LIBRARY_NAME_MISMATCH = const MessageKind(
+      'Warning: expected part of library name "#{libraryName}".');
+
+  static const MISSING_PART_OF_TAG = const MessageKind(
+      'Note: This file has no part-of tag, but it is being used as a part.');
+
+  static const DUPLICATED_PART_OF = const MessageKind(
+      'Error: duplicated part-of directive.');
+
+  static const ILLEGAL_DIRECTIVE = const MessageKind(
+      'Error: directive not allowed here.');
+
+  static const DUPLICATED_LIBRARY_NAME = const MessageKind(
+      'Warning: duplicated library name "#{libraryName}".');
+
+  static const INVALID_SOURCE_FILE_LOCATION = const MessageKind('''
+Invalid offset (#{offset}) in source map.
+File: #{fileName}
+Length: #{length}''');
+
+  static const TOP_LEVEL_VARIABLE_DECLARED_STATIC = const MessageKind(
+      "Top-level variable cannot be declared static.");
+
+  static const WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT = const MessageKind(
+      "Wrong number of arguments to assert. Should be 1, but given "
+      "#{argumentCount}.");
+
+  static const ASSERT_IS_GIVEN_NAMED_ARGUMENTS = const MessageKind(
+      "assert takes no named arguments, but given #{argumentCount}.");
+
+  static const FACTORY_REDIRECTION_IN_NON_FACTORY = const MessageKind(
+      "Error: Factory redirection only allowed in factories.");
+
+  static const MISSING_FACTORY_KEYWORD = const MessageKind(
+      "Did you forget a factory keyword here?");
+
+  static const COMPILER_CRASHED = const MessageKind(
+      "Error: The compiler crashed when compiling this element.");
+
+  static const PLEASE_REPORT_THE_CRASH = const MessageKind('''
+The compiler is broken.
+
+When compiling the above element, the compiler crashed. It is not
+possible to tell if this is caused by a problem in your program or
+not. Regardless, the compiler should not crash.
+
+The Dart team would greatly appreciate if you would take a moment to
+report this problem at http://dartbug.com/new.
+
+Please include the following information:
+
+* the name and version of your operating system,
+
+* the Dart SDK build number (#{buildId}), and
+
+* the entire message you see here (including the full stack trace
+  below as well as the source location above).
+''');
+
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Patch errors start.
+  //////////////////////////////////////////////////////////////////////////////
+
+  static const PATCH_RETURN_TYPE_MISMATCH = const MessageKind(
+      "Patch return type '#{patchReturnType}' doesn't match "
+      "'#{originReturnType}' on origin method '#{methodName}'.");
+
+  static const PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH = const MessageKind(
+      "Required parameter count of patch method (#{patchParameterCount}) "
+      "doesn't match parameter count on origin method '#{methodName}' "
+      "(#{originParameterCount}).");
+
+  static const PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH = const MessageKind(
+      "Optional parameter count of patch method (#{patchParameterCount}) "
+      "doesn't match parameter count on origin method '#{methodName}' "
+      "(#{originParameterCount}).");
+
+  static const PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH = const MessageKind(
+      "Optional parameters of origin and patch method '#{methodName}' must "
+      "both be either named or positional.");
+
+  static const PATCH_PARAMETER_MISMATCH = const MessageKind(
+      "Patch method parameter '#{patchParameter}' doesn't match "
+      "'#{originParameter}' on origin method #{methodName}.");
+
+  static const PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION = const MessageKind(
+      "External method without an implementation.");
+
+  static const PATCH_POINT_TO_FUNCTION = const MessageKind(
+      "Info: This is the function patch '#{functionName}'.");
+
+  static const PATCH_POINT_TO_CLASS = const MessageKind(
+      "Info: This is the class patch '#{className}'.");
+
+  static const PATCH_POINT_TO_GETTER = const MessageKind(
+      "Info: This is the getter patch '#{getterName}'.");
+
+  static const PATCH_POINT_TO_SETTER = const MessageKind(
+      "Info: This is the setter patch '#{setterName}'.");
+
+  static const PATCH_POINT_TO_CONSTRUCTOR = const MessageKind(
+      "Info: This is the constructor patch '#{constructorName}'.");
+
+  static const PATCH_NON_EXISTING = const MessageKind(
+      "Error: Origin does not exist for patch '#{name}'.");
+
+  static const PATCH_NONPATCHABLE = const MessageKind(
+      "Error: Only classes and functions can be patched.");
+
+  static const PATCH_NON_EXTERNAL = const MessageKind(
+      "Error: Only external functions can be patched.");
+
+  static const PATCH_NON_CLASS = const MessageKind(
+      "Error: Patching non-class with class patch '#{className}'.");
+
+  static const PATCH_NON_GETTER = const MessageKind(
+      "Error: Cannot patch non-getter '#{name}' with getter patch.");
+
+  static const PATCH_NO_GETTER = const MessageKind(
+      "Error: No getter found for getter patch '#{getterName}'.");
+
+  static const PATCH_NON_SETTER = const MessageKind(
+      "Error: Cannot patch non-setter '#{name}' with setter patch.");
+
+  static const PATCH_NO_SETTER = const MessageKind(
+      "Error: No setter found for setter patch '#{setterName}'.");
+
+  static const PATCH_NON_CONSTRUCTOR = const MessageKind(
+      "Error: Cannot patch non-constructor with constructor patch "
+      "'#{constructorName}'.");
+
+  static const PATCH_NON_FUNCTION = const MessageKind(
+      "Error: Cannot patch non-function with function patch "
+      "'#{functionName}'.");
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Patch errors end.
+  //////////////////////////////////////////////////////////////////////////////
+
+  toString() => template;
+
+  Message message([Map arguments = const {}]) {
+    return new Message(this, arguments);
+  }
+
+  CompilationError error([Map arguments = const {}]) {
+    return new CompilationError(this, arguments);
+  }
+}
+
+class Message {
+  final kind;
+  final Map arguments;
+  String message;
+
+  Message(this.kind, this.arguments) {
+    assert(() { computeMessage(); return true; });
+  }
+
+  String computeMessage() {
+    if (message == null) {
+      message = kind.template;
+      arguments.forEach((key, value) {
+        String string = slowToString(value);
+        message = message.replaceAll('#{${key}}', string);
+      });
+      assert(invariant(
+          CURRENT_ELEMENT_SPANNABLE,
+          !message.contains(new RegExp(r"#\{.+\}")),
+          message: 'Missing arguments in error message: "$message"'));
+    }
+    return message;
+  }
+
+  String toString() {
+    return computeMessage();
+  }
+
+  bool operator==(other) {
+    if (other is !Message) return false;
+    return (kind == other.kind) && (toString() == other.toString());
+  }
+
+  String slowToString(object) {
+    if (object is SourceString) {
+      return object.slowToString();
+    } else {
+      return object.toString();
+    }
+  }
+}
+
+class Diagnostic {
+  final Message message;
+  Diagnostic(MessageKind kind, [Map arguments = const {}])
+      : message = new Message(kind, arguments);
+  String toString() => message.toString();
+}
+
+class TypeWarning extends Diagnostic {
+  TypeWarning(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
+
+class ResolutionError extends Diagnostic {
+  ResolutionError(MessageKind kind, [Map arguments = const {}])
+      : super(kind, arguments);
+}
+
+class ResolutionWarning extends Diagnostic {
+  ResolutionWarning(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
+
+class CompileTimeConstantError extends Diagnostic {
+  CompileTimeConstantError(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
+
+class CompilationError extends Diagnostic {
+  CompilationError(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/world.dart b/pkgs/markdown/lib/src/compiler/implementation/world.dart
new file mode 100644
index 0000000..a40050e
--- /dev/null
+++ b/pkgs/markdown/lib/src/compiler/implementation/world.dart
@@ -0,0 +1,232 @@
+// 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.
+
+part of dart2js;
+
+class World {
+  final Compiler compiler;
+  final Map<ClassElement, Set<ClassElement>> subtypes;
+  final Map<ClassElement, Set<MixinApplicationElement>> mixinUses;
+  final Map<ClassElement, Set<ClassElement>> typesImplementedBySubclasses;
+  final Set<ClassElement> classesNeedingRti;
+  final Map<ClassElement, Set<ClassElement>> rtiDependencies;
+  final FunctionSet userDefinedGetters;
+  final FunctionSet userDefinedSetters;
+
+  World(Compiler compiler)
+      : subtypes = new Map<ClassElement, Set<ClassElement>>(),
+        mixinUses = new Map<ClassElement, Set<MixinApplicationElement>>(),
+        typesImplementedBySubclasses =
+            new Map<ClassElement, Set<ClassElement>>(),
+        userDefinedGetters = new FunctionSet(compiler),
+        userDefinedSetters = new FunctionSet(compiler),
+        classesNeedingRti = new Set<ClassElement>(),
+        rtiDependencies = new Map<ClassElement, Set<ClassElement>>(),
+        this.compiler = compiler;
+
+  void populate() {
+    void addSubtypes(ClassElement cls) {
+      if (cls.resolutionState != STATE_DONE) {
+        compiler.internalErrorOnElement(
+            cls, 'Class "${cls.name.slowToString()}" is not resolved.');
+      }
+
+      for (DartType type in cls.allSupertypes) {
+        Set<Element> subtypesOfCls =
+          subtypes.putIfAbsent(type.element, () => new Set<ClassElement>());
+        subtypesOfCls.add(cls);
+      }
+
+      // Walk through the superclasses, and record the types
+      // implemented by that type on the superclasses.
+      DartType type = cls.supertype;
+      while (type != null) {
+        Set<Element> typesImplementedBySubclassesOfCls =
+          typesImplementedBySubclasses.putIfAbsent(
+              type.element, () => new Set<ClassElement>());
+        for (DartType current in cls.allSupertypes) {
+          typesImplementedBySubclassesOfCls.add(current.element);
+        }
+        ClassElement classElement = type.element;
+        type = classElement.supertype;
+      }
+    }
+
+    compiler.resolverWorld.instantiatedClasses.forEach(addSubtypes);
+
+    // Find the classes that need runtime type information. Such
+    // classes are:
+    // (1) used in a is check with type variables,
+    // (2) dependencies of classes in (1),
+    // (3) subclasses of (2) and (3).
+
+    void potentiallyAddForRti(ClassElement cls) {
+      if (cls.typeVariables.isEmpty) return;
+      if (classesNeedingRti.contains(cls)) return;
+      classesNeedingRti.add(cls);
+
+      Set<ClassElement> classes = subtypes[cls];
+      if (classes != null) {
+        classes.forEach((ClassElement sub) {
+          potentiallyAddForRti(sub);
+        });
+      }
+
+      Set<ClassElement> dependencies = rtiDependencies[cls];
+      if (dependencies != null) {
+        dependencies.forEach((ClassElement other) {
+          potentiallyAddForRti(other);
+        });
+      }
+    }
+
+    compiler.resolverWorld.isChecks.forEach((DartType type) {
+      if (type is InterfaceType) {
+        InterfaceType itf = type;
+        if (!itf.isRaw) {
+          potentiallyAddForRti(itf.element);
+        }
+      }
+    });
+  }
+
+  bool needsRti(ClassElement cls) {
+    return classesNeedingRti.contains(cls) || compiler.enabledRuntimeType;
+  }
+
+  void registerMixinUse(MixinApplicationElement mixinApplication,
+                        ClassElement mixin) {
+    Set<MixinApplicationElement> users =
+        mixinUses.putIfAbsent(mixin, () =>
+                              new Set<MixinApplicationElement>());
+    users.add(mixinApplication);
+  }
+
+  void registerRtiDependency(Element element, Element dependency) {
+    // We're not dealing with typedef for now.
+    if (!element.isClass() || !dependency.isClass()) return;
+    Set<ClassElement> classes =
+        rtiDependencies.putIfAbsent(element, () => new Set<ClassElement>());
+    classes.add(dependency);
+  }
+
+  void recordUserDefinedGetter(Element element) {
+    assert(element.isGetter());
+    userDefinedGetters.add(element);
+  }
+
+  void recordUserDefinedSetter(Element element) {
+    assert(element.isSetter());
+    userDefinedSetters.add(element);
+  }
+
+  bool hasAnyUserDefinedGetter(Selector selector) {
+    return userDefinedGetters.hasAnyElementMatchingSelector(selector);
+  }
+
+  bool hasAnyUserDefinedSetter(Selector selector) {
+    return userDefinedSetters.hasAnyElementMatchingSelector(selector);
+  }
+
+  // Returns whether a subclass of [superclass] implements [type].
+  bool hasAnySubclassThatImplements(ClassElement superclass, DartType type) {
+    Set<ClassElement> subclasses= typesImplementedBySubclasses[superclass];
+    if (subclasses == null) return false;
+    return subclasses.contains(type.element);
+  }
+
+  bool hasNoOverridingMember(Element element) {
+    ClassElement cls = element.getEnclosingClass();
+    Set<ClassElement> subclasses = compiler.world.subtypes[cls];
+    // TODO(ngeoffray): Implement the full thing.
+    return subclasses == null || subclasses.isEmpty;
+  }
+
+  void registerUsedElement(Element element) {
+    if (element.isMember()) {
+      if (element.isGetter()) {
+        // We're collecting user-defined getters to let the codegen know which
+        // field accesses might have side effects.
+        recordUserDefinedGetter(element);
+      } else if (element.isSetter()) {
+        recordUserDefinedSetter(element);
+      }
+    }
+  }
+
+  /**
+   * Returns a [MemberSet] that contains the possible targets of the given
+   * [selector] on a receiver with the given [type]. This includes all sub
+   * types.
+   */
+  MemberSet _memberSetFor(DartType type, Selector selector) {
+    assert(compiler != null);
+    ClassElement cls = type.element;
+    SourceString name = selector.name;
+    LibraryElement library = selector.library;
+    MemberSet result = new MemberSet(name);
+    Element element = cls.implementation.lookupSelector(selector);
+    if (element != null) result.add(element);
+
+    bool isPrivate = name.isPrivate();
+    Set<ClassElement> subtypesOfCls = subtypes[cls];
+    if (subtypesOfCls != null) {
+      for (ClassElement sub in subtypesOfCls) {
+        // Private members from a different library are not visible.
+        if (isPrivate && sub.getLibrary() != library) continue;
+        element = sub.implementation.lookupLocalMember(name);
+        if (element != null) result.add(element);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the field in [type] described by the given [selector].
+   * If no such field exists, or a subclass overrides the field
+   * returns [:null:].
+   */
+  VariableElement locateSingleField(DartType type, Selector selector) {
+    MemberSet memberSet = _memberSetFor(type, selector);
+    ClassElement cls = type.element;
+    Element result = cls.implementation.lookupSelector(selector);
+    if (result == null) return null;
+    if (!result.isField()) return null;
+
+    // Verify that no subclass overrides the field.
+    if (memberSet.elements.length != 1) return null;
+    assert(memberSet.elements.contains(result));
+    return result;
+  }
+
+  Set<ClassElement> findNoSuchMethodHolders(DartType type) {
+    Set<ClassElement> result = new Set<ClassElement>();
+    Selector noSuchMethodSelector = new Selector.noSuchMethod();
+    MemberSet memberSet = _memberSetFor(type, noSuchMethodSelector);
+    for (Element element in memberSet.elements) {
+      ClassElement holder = element.getEnclosingClass();
+      if (!identical(holder, compiler.objectClass) &&
+          noSuchMethodSelector.applies(element, compiler)) {
+        result.add(holder);
+      }
+    }
+    return result;
+  }
+}
+
+/**
+ * A [MemberSet] contains all the possible targets for a selector.
+ */
+class MemberSet {
+  final Set<Element> elements;
+  final SourceString name;
+
+  MemberSet(SourceString this.name) : elements = new Set<Element>();
+
+  void add(Element element) {
+    elements.add(element);
+  }
+
+  bool get isEmpty => elements.isEmpty;
+}
diff --git a/pkgs/markdown/lib/src/libraries.dart b/pkgs/markdown/lib/src/libraries.dart
new file mode 100644
index 0000000..5c35483
--- /dev/null
+++ b/pkgs/markdown/lib/src/libraries.dart
@@ -0,0 +1,200 @@
+// 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.
+
+library libraries;
+
+/**
+ * A bit flag used by [LibraryInfo] indicating that a library is used by dart2js
+ */
+const int DART2JS_PLATFORM = 1;
+
+/**
+ * A bit flag used by [LibraryInfo] indicating that a library is used by the VM
+ */
+const int VM_PLATFORM = 2;
+
+/**
+ * Mapping of "dart:" library name (e.g. "core") to information about that library.
+ * This information is structured such that Dart Editor can parse this file
+ * and extract the necessary information without executing it
+ * while other tools can access via execution.
+ */
+const Map<String, LibraryInfo> LIBRARIES = const {
+
+  "async": const LibraryInfo(
+      "async/async.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/async_patch.dart"),
+
+  "chrome": const LibraryInfo(
+      "chrome/dartium/chrome_dartium.dart",
+      category: "Client",
+      dart2jsPath: "chrome/dart2js/chrome_dart2js.dart",
+      documented: false,
+      implementation: true), // Not really, just hiding it for now.
+
+  "collection": const LibraryInfo("collection/collection.dart"),
+
+  "core": const LibraryInfo(
+      "core/core.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/core_patch.dart"),
+
+  "crypto": const LibraryInfo(
+      "crypto/crypto.dart"),
+
+  "html": const LibraryInfo(
+      "html/dartium/html_dartium.dart",
+      category: "Client",
+      dart2jsPath: "html/dart2js/html_dart2js.dart"),
+
+  "html_common": const LibraryInfo(
+      "html/html_common/html_common.dart",
+      category: "Client",
+      dart2jsPath: "html/html_common/html_common_dart2js.dart",
+      documented: false,
+      implementation: true),
+
+  "indexed_db": const LibraryInfo(
+      "indexed_db/dartium/indexed_db_dartium.dart",
+      category: "Client",
+      dart2jsPath: "indexed_db/dart2js/indexed_db_dart2js.dart"),
+
+  "io": const LibraryInfo(
+      "io/io.dart",
+      category: "Server",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/io_patch.dart"),
+
+  "isolate": const LibraryInfo(
+      "isolate/isolate.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/isolate_patch.dart"),
+
+  "json": const LibraryInfo(
+      "json/json.dart"),
+
+  "math": const LibraryInfo(
+      "math/math.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/math_patch.dart"),
+
+  "mirrors": const LibraryInfo(
+      "mirrors/mirrors.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/mirrors_patch.dart"),
+
+  "nativewrappers": const LibraryInfo(
+      "html/dartium/nativewrappers.dart",
+      category: "Client",
+      implementation: true,
+      documented: false,
+      platforms: VM_PLATFORM),
+
+  "scalarlist": const LibraryInfo(
+      "scalarlist/scalarlist.dart",
+      category: "Server",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/scalarlist_patch.dart"),
+
+  "svg": const LibraryInfo(
+        "svg/dartium/svg_dartium.dart",
+        category: "Client",
+        dart2jsPath: "svg/dart2js/svg_dart2js.dart"),
+
+  "uri": const LibraryInfo(
+      "uri/uri.dart"),
+
+  "utf": const LibraryInfo(
+      "utf/utf.dart"),
+
+  "web_audio": const LibraryInfo(
+        "web_audio/dartium/web_audio_dartium.dart",
+        category: "Client",
+        dart2jsPath: "web_audio/dart2js/web_audio_dart2js.dart"),
+
+  "_collection-dev": const LibraryInfo(
+      "_collection_dev/collection_dev.dart",
+      category: "Internal",
+      documented: false),
+
+  "_js_helper": const LibraryInfo(
+      "_internal/compiler/implementation/lib/js_helper.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+
+  "_interceptors": const LibraryInfo(
+      "_internal/compiler/implementation/lib/interceptors.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+
+  "_foreign_helper": const LibraryInfo(
+      "_internal/compiler/implementation/lib/foreign_helper.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+
+  "_isolate_helper": const LibraryInfo(
+      "_internal/compiler/implementation/lib/isolate_helper.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+};
+
+/**
+ * Information about a "dart:" library.
+ */
+class LibraryInfo {
+
+  /**
+   * Path to the library's *.dart file relative to this file.
+   */
+  final String path;
+
+  /**
+   * The category in which the library should appear in the editor
+   * (e.g. "Common", "Client", "Server", ...).
+   */
+  final String category;
+
+  /**
+   * Path to the dart2js library's *.dart file relative to this file
+   * or null if dart2js uses the common library path defined above.
+   * Access using the [#getDart2JsPath()] method.
+   */
+  final String dart2jsPath;
+
+  /**
+   * Path to the dart2js library's patch file relative to this file
+   * or null if no dart2js patch file associated with this library.
+   * Access using the [#getDart2JsPatchPath()] method.
+   */
+  final String dart2jsPatchPath;
+
+  /**
+   * True if this library is documented and should be shown to the user.
+   */
+  final bool documented;
+
+  /**
+   * Bit flags indicating which platforms consume this library.
+   * See [DART2JS_LIBRARY] and [VM_LIBRARY].
+   */
+  final int platforms;
+
+  /**
+   * True if the library contains implementation details for another library.
+   * The implication is that these libraries are less commonly used
+   * and that tools like Dart Editor should not show these libraries
+   * in a list of all libraries unless the user specifically asks the tool to
+   * do so.
+   */
+  final bool implementation;
+
+  const LibraryInfo(this.path, {
+                    this.category: "Shared",
+                    this.dart2jsPath,
+                    this.dart2jsPatchPath,
+                    this.implementation: false,
+                    this.documented: true,
+                    this.platforms: DART2JS_PLATFORM | VM_PLATFORM});
+
+  bool get isDart2jsLibrary => (platforms & DART2JS_PLATFORM) != 0;
+  bool get isVmLibrary => (platforms & VM_PLATFORM) != 0;
+}
diff --git a/pkgs/markdown/lib/src/markdown/ast.dart b/pkgs/markdown/lib/src/markdown/ast.dart
new file mode 100644
index 0000000..c966cea
--- /dev/null
+++ b/pkgs/markdown/lib/src/markdown/ast.dart
@@ -0,0 +1,65 @@
+// 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.
+
+part of markdown;
+
+/// Base class for any AST item. Roughly corresponds to Node in the DOM. Will
+/// be either an Element or Text.
+abstract class Node {
+  void accept(NodeVisitor visitor);
+}
+
+/// A named tag that can contain other nodes.
+class Element implements Node {
+  final String tag;
+  final List<Node> children;
+  final Map<String, String> attributes;
+
+  Element(this.tag, this.children)
+    : attributes = <String, String>{};
+
+  Element.empty(this.tag)
+    : children = null,
+      attributes = <String, String>{};
+
+  Element.withTag(this.tag)
+    : children = [],
+      attributes = <String, String>{};
+
+  Element.text(this.tag, String text)
+    : children = [new Text(text)],
+      attributes = <String, String>{};
+
+  bool get isEmpty => children == null;
+
+  void accept(NodeVisitor visitor) {
+    if (visitor.visitElementBefore(this)) {
+      for (final child in children) child.accept(visitor);
+      visitor.visitElementAfter(this);
+    }
+  }
+}
+
+/// A plain text element.
+class Text implements Node {
+  final String text;
+  Text(this.text);
+
+  void accept(NodeVisitor visitor) => visitor.visitText(this);
+}
+
+/// Visitor pattern for the AST. Renderers or other AST transformers should
+/// implement this.
+abstract class NodeVisitor {
+  /// Called when a Text node has been reached.
+  void visitText(Text text);
+
+  /// Called when an Element has been reached, before its children have been
+  /// visited. Return `false` to skip its children.
+  bool visitElementBefore(Element element);
+
+  /// Called when an Element has been reached, after its children have been
+  /// visited. Will not be called if [visitElementBefore] returns `false`.
+  void visitElementAfter(Element element);
+}
diff --git a/pkgs/markdown/lib/src/markdown/block_parser.dart b/pkgs/markdown/lib/src/markdown/block_parser.dart
new file mode 100644
index 0000000..67109a4
--- /dev/null
+++ b/pkgs/markdown/lib/src/markdown/block_parser.dart
@@ -0,0 +1,464 @@
+// 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.
+
+part of markdown;
+
+/// The line contains only whitespace or is empty.
+final _RE_EMPTY = new RegExp(r'^([ \t]*)$');
+
+/// A series of `=` or `-` (on the next line) define setext-style headers.
+final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$');
+
+/// Leading (and trailing) `#` define atx-style headers.
+final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$');
+
+/// The line starts with `>` with one optional space after.
+final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
+
+/// A line indented four spaces. Used for code blocks and lists.
+final _RE_INDENT = new RegExp(r'^(?:    |\t)(.*)$');
+
+/// Three or more hyphens, asterisks or underscores by themselves. Note that
+/// a line like `----` is valid as both HR and SETEXT. In case of a tie,
+/// SETEXT should win.
+final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|'
+                                 r'(_+[ ]{0,2}){3,}|'
+                                 r'(\*+[ ]{0,2}){3,})$');
+
+/// Really hacky way to detect block-level embedded HTML. Just looks for
+/// "<somename".
+final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]');
+
+/// A line starting with one of these markers: `-`, `*`, `+`. May have up to
+/// three leading spaces before the marker and any number of spaces or tabs
+/// after.
+final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
+
+/// A line starting with a number like `123.`. May have up to three leading
+/// spaces before the marker and any number of spaces or tabs after.
+final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
+
+/// Maintains the internal state needed to parse a series of lines into blocks
+/// of markdown suitable for further inline parsing.
+class BlockParser {
+  final List<String> lines;
+
+  /// The markdown document this parser is parsing.
+  final Document document;
+
+  /// Index of the current line.
+  int pos;
+
+  BlockParser(this.lines, this.document)
+    : pos = 0;
+
+  /// Gets the current line.
+  String get current => lines[pos];
+
+  /// Gets the line after the current one or `null` if there is none.
+  String get next {
+    // Don't read past the end.
+    if (pos >= lines.length - 1) return null;
+    return lines[pos + 1];
+  }
+
+  void advance() {
+    pos++;
+  }
+
+  bool get isDone => pos >= lines.length;
+
+  /// Gets whether or not the current line matches the given pattern.
+  bool matches(RegExp regex) {
+    if (isDone) return false;
+    return regex.firstMatch(current) != null;
+  }
+
+  /// Gets whether or not the current line matches the given pattern.
+  bool matchesNext(RegExp regex) {
+    if (next == null) return false;
+    return regex.firstMatch(next) != null;
+  }
+}
+
+abstract class BlockSyntax {
+  /// Gets the collection of built-in block parsers. To turn a series of lines
+  /// into blocks, each of these will be tried in turn. Order matters here.
+  static List<BlockSyntax> get syntaxes {
+    // Lazy initialize.
+    if (_syntaxes == null) {
+      _syntaxes = [
+          new EmptyBlockSyntax(),
+          new BlockHtmlSyntax(),
+          new SetextHeaderSyntax(),
+          new HeaderSyntax(),
+          new CodeBlockSyntax(),
+          new BlockquoteSyntax(),
+          new HorizontalRuleSyntax(),
+          new UnorderedListSyntax(),
+          new OrderedListSyntax(),
+          new ParagraphSyntax()
+        ];
+    }
+
+    return _syntaxes;
+  }
+
+  static List<BlockSyntax> _syntaxes;
+
+  /// Gets the regex used to identify the beginning of this block, if any.
+  RegExp get pattern => null;
+
+  bool get canEndBlock => true;
+
+  bool canParse(BlockParser parser) {
+    return pattern.firstMatch(parser.current) != null;
+  }
+
+  Node parse(BlockParser parser);
+
+  List<String> parseChildLines(BlockParser parser) {
+    // Grab all of the lines that form the blockquote, stripping off the ">".
+    final childLines = <String>[];
+
+    while (!parser.isDone) {
+      final match = pattern.firstMatch(parser.current);
+      if (match == null) break;
+      childLines.add(match[1]);
+      parser.advance();
+    }
+
+    return childLines;
+  }
+
+  /// Gets whether or not [parser]'s current line should end the previous block.
+  static bool isAtBlockEnd(BlockParser parser) {
+    if (parser.isDone) return true;
+    return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock);
+  }
+}
+
+class EmptyBlockSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_EMPTY;
+
+  Node parse(BlockParser parser) {
+    parser.advance();
+
+    // Don't actually emit anything.
+    return null;
+  }
+}
+
+/// Parses setext-style headers.
+class SetextHeaderSyntax extends BlockSyntax {
+  bool canParse(BlockParser parser) {
+    // Note: matches *next* line, not the current one. We're looking for the
+    // underlining after this line.
+    return parser.matchesNext(_RE_SETEXT);
+  }
+
+  Node parse(BlockParser parser) {
+    final match = _RE_SETEXT.firstMatch(parser.next);
+
+    final tag = (match[1][0] == '=') ? 'h1' : 'h2';
+    final contents = parser.document.parseInline(parser.current);
+    parser.advance();
+    parser.advance();
+
+    return new Element(tag, contents);
+  }
+}
+
+/// Parses atx-style headers: `## Header ##`.
+class HeaderSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_HEADER;
+
+  Node parse(BlockParser parser) {
+    final match = pattern.firstMatch(parser.current);
+    parser.advance();
+    final level = match[1].length;
+    final contents = parser.document.parseInline(match[2].trim());
+    return new Element('h$level', contents);
+  }
+}
+
+/// Parses email-style blockquotes: `> quote`.
+class BlockquoteSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_BLOCKQUOTE;
+
+  Node parse(BlockParser parser) {
+    final childLines = parseChildLines(parser);
+
+    // Recursively parse the contents of the blockquote.
+    final children = parser.document.parseLines(childLines);
+
+    return new Element('blockquote', children);
+  }
+}
+
+/// Parses preformatted code blocks that are indented four spaces.
+class CodeBlockSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_INDENT;
+
+  List<String> parseChildLines(BlockParser parser) {
+    final childLines = <String>[];
+
+    while (!parser.isDone) {
+      var match = pattern.firstMatch(parser.current);
+      if (match != null) {
+        childLines.add(match[1]);
+        parser.advance();
+      } else {
+        // If there's a codeblock, then a newline, then a codeblock, keep the
+        // code blocks together.
+        var nextMatch = parser.next != null ?
+            pattern.firstMatch(parser.next) : null;
+        if (parser.current.trim() == '' && nextMatch != null) {
+          childLines.add('');
+          childLines.add(nextMatch[1]);
+          parser.advance();
+          parser.advance();
+        } else {
+          break;
+        }
+      }
+    }
+    return childLines;
+  }
+
+  Node parse(BlockParser parser) {
+    final childLines = parseChildLines(parser);
+
+    // The Markdown tests expect a trailing newline.
+    childLines.add('');
+
+    // Escape the code.
+    final escaped = classifySource(Strings.join(childLines, '\n'));
+
+    return new Element.text('pre', escaped);
+  }
+}
+
+/// Parses horizontal rules like `---`, `_ _ _`, `*  *  *`, etc.
+class HorizontalRuleSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_HR;
+
+  Node parse(BlockParser parser) {
+    final match = pattern.firstMatch(parser.current);
+    parser.advance();
+    return new Element.empty('hr');
+  }
+}
+
+/// Parses inline HTML at the block level. This differs from other markdown
+/// implementations in several ways:
+///
+/// 1.  This one is way way WAY simpler.
+/// 2.  All HTML tags at the block level will be treated as blocks. If you
+///     start a paragraph with `<em>`, it will not wrap it in a `<p>` for you.
+///     As soon as it sees something like HTML, it stops mucking with it until
+///     it hits the next block.
+/// 3.  Absolutely no HTML parsing or validation is done. We're a markdown
+///     parser not an HTML parser!
+class BlockHtmlSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_HTML;
+
+  bool get canEndBlock => false;
+
+  Node parse(BlockParser parser) {
+    final childLines = [];
+
+    // Eat until we hit a blank line.
+    while (!parser.isDone && !parser.matches(_RE_EMPTY)) {
+      childLines.add(parser.current);
+      parser.advance();
+    }
+
+    return new Text(Strings.join(childLines, '\n'));
+  }
+}
+
+class ListItem {
+  bool forceBlock = false;
+  final List<String> lines;
+
+  ListItem(this.lines);
+}
+
+/// Base class for both ordered and unordered lists.
+abstract class ListSyntax extends BlockSyntax {
+  bool get canEndBlock => false;
+
+  String get listTag;
+
+  Node parse(BlockParser parser) {
+    final items = <ListItem>[];
+    var childLines = <String>[];
+
+    endItem() {
+      if (childLines.length > 0) {
+        items.add(new ListItem(childLines));
+        childLines = <String>[];
+      }
+    }
+
+    var match;
+    tryMatch(RegExp pattern) {
+      match = pattern.firstMatch(parser.current);
+      return match != null;
+    }
+
+    bool afterEmpty = false;
+    while (!parser.isDone) {
+      if (tryMatch(_RE_EMPTY)) {
+        // Add a blank line to the current list item.
+        childLines.add('');
+      } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) {
+        // End the current list item and start a new one.
+        endItem();
+        childLines.add(match[1]);
+      } else if (tryMatch(_RE_INDENT)) {
+        // Strip off indent and add to current item.
+        childLines.add(match[1]);
+      } else if (BlockSyntax.isAtBlockEnd(parser)) {
+        // Done with the list.
+        break;
+      } else {
+        // Anything else is paragraph text or other stuff that can be in a list
+        // item. However, if the previous item is a blank line, this means we're
+        // done with the list and are starting a new top-level paragraph.
+        if ((childLines.length > 0) && (childLines.last == '')) break;
+        childLines.add(parser.current);
+      }
+      parser.advance();
+    }
+
+    endItem();
+
+    // Markdown, because it hates us, specifies two kinds of list items. If you
+    // have a list like:
+    //
+    // * one
+    // * two
+    //
+    // Then it will insert the conents of the lines directly in the <li>, like:
+    // <ul>
+    //   <li>one</li>
+    //   <li>two</li>
+    // <ul>
+    //
+    // If, however, there are blank lines between the items, each is wrapped in
+    // paragraphs:
+    //
+    // * one
+    //
+    // * two
+    //
+    // <ul>
+    //   <li><p>one</p></li>
+    //   <li><p>two</p></li>
+    // <ul>
+    //
+    // In other words, sometimes we parse the contents of a list item like a
+    // block, and sometimes line an inline. The rules our parser implements are:
+    //
+    // - If it has more than one line, it's a block.
+    // - If the line matches any block parser (BLOCKQUOTE, HEADER, HR, INDENT,
+    //   UL, OL) it's a block. (This is for cases like "* > quote".)
+    // - If there was a blank line between this item and the previous one, it's
+    //   a block.
+    // - If there was a blank line between this item and the next one, it's a
+    //   block.
+    // - Otherwise, parse it as an inline.
+
+    // Remove any trailing empty lines and note which items are separated by
+    // empty lines. Do this before seeing which items are single-line so that
+    // trailing empty lines on the last item don't force it into being a block.
+    for (int i = 0; i < items.length; i++) {
+      for (int j = items[i].lines.length - 1; j > 0; j--) {
+        if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) {
+          // Found an empty line. Item and one after it are blocks.
+          if (i < items.length - 1) {
+            items[i].forceBlock = true;
+            items[i + 1].forceBlock = true;
+          }
+          items[i].lines.removeLast();
+        } else {
+          break;
+        }
+      }
+    }
+
+    // Convert the list items to Nodes.
+    final itemNodes = <Node>[];
+    for (final item in items) {
+      bool blockItem = item.forceBlock || (item.lines.length > 1);
+
+      // See if it matches some block parser.
+      final blocksInList = [
+        _RE_BLOCKQUOTE,
+        _RE_HEADER,
+        _RE_HR,
+        _RE_INDENT,
+        _RE_UL,
+        _RE_OL
+      ];
+
+      if (!blockItem) {
+        for (final pattern in blocksInList) {
+          if (pattern.firstMatch(item.lines[0]) != null) {
+            blockItem = true;
+            break;
+          }
+        }
+      }
+
+      // Parse the item as a block or inline.
+      if (blockItem) {
+        // Block list item.
+        final children = parser.document.parseLines(item.lines);
+        itemNodes.add(new Element('li', children));
+      } else {
+        // Raw list item.
+        final contents = parser.document.parseInline(item.lines[0]);
+        itemNodes.add(new Element('li', contents));
+      }
+    }
+
+    return new Element(listTag, itemNodes);
+  }
+}
+
+/// Parses unordered lists.
+class UnorderedListSyntax extends ListSyntax {
+  RegExp get pattern => _RE_UL;
+  String get listTag => 'ul';
+}
+
+/// Parses ordered lists.
+class OrderedListSyntax extends ListSyntax {
+  RegExp get pattern => _RE_OL;
+  String get listTag => 'ol';
+}
+
+/// Parses paragraphs of regular text.
+class ParagraphSyntax extends BlockSyntax {
+  bool get canEndBlock => false;
+
+  bool canParse(BlockParser parser) => true;
+
+  Node parse(BlockParser parser) {
+    final childLines = [];
+
+    // Eat until we hit something that ends a paragraph.
+    while (!BlockSyntax.isAtBlockEnd(parser)) {
+      childLines.add(parser.current);
+      parser.advance();
+    }
+
+    final contents = parser.document.parseInline(
+        Strings.join(childLines, '\n'));
+    return new Element('p', contents);
+  }
+}
diff --git a/pkgs/markdown/lib/src/markdown/html_renderer.dart b/pkgs/markdown/lib/src/markdown/html_renderer.dart
new file mode 100644
index 0000000..1695e96
--- /dev/null
+++ b/pkgs/markdown/lib/src/markdown/html_renderer.dart
@@ -0,0 +1,61 @@
+// 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.
+
+part of markdown;
+
+String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes);
+
+/// Translates a parsed AST to HTML.
+class HtmlRenderer implements NodeVisitor {
+  static final _BLOCK_TAGS = new RegExp(
+      'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
+
+  StringBuffer buffer;
+
+  HtmlRenderer();
+
+  String render(List<Node> nodes) {
+    buffer = new StringBuffer();
+
+    for (final node in nodes) node.accept(this);
+
+    return buffer.toString();
+  }
+
+  void visitText(Text text) {
+    buffer.add(text.text);
+  }
+
+  bool visitElementBefore(Element element) {
+    // Hackish. Separate block-level elements with newlines.
+    if (!buffer.isEmpty &&
+        _BLOCK_TAGS.firstMatch(element.tag) != null) {
+      buffer.add('\n');
+    }
+
+    buffer.add('<${element.tag}');
+
+    // Sort the keys so that we generate stable output.
+    // TODO(rnystrom): This assumes keys returns a fresh mutable
+    // collection.
+    final attributeNames = element.attributes.keys.toList();
+    attributeNames.sort((a, b) => a.compareTo(b));
+    for (final name in attributeNames) {
+      buffer.add(' $name="${element.attributes[name]}"');
+    }
+
+    if (element.isEmpty) {
+      // Empty element like <hr/>.
+      buffer.add(' />');
+      return false;
+    } else {
+      buffer.add('>');
+      return true;
+    }
+  }
+
+  void visitElementAfter(Element element) {
+    buffer.add('</${element.tag}>');
+  }
+}
diff --git a/pkgs/markdown/lib/src/markdown/inline_parser.dart b/pkgs/markdown/lib/src/markdown/inline_parser.dart
new file mode 100644
index 0000000..af42e3e
--- /dev/null
+++ b/pkgs/markdown/lib/src/markdown/inline_parser.dart
@@ -0,0 +1,410 @@
+// 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.
+
+part of markdown;
+
+/// Maintains the internal state needed to parse inline span elements in
+/// markdown.
+class InlineParser {
+  static List<InlineSyntax> get syntaxes {
+    // Lazy initialize.
+    if (_syntaxes == null) {
+      _syntaxes = <InlineSyntax>[
+        // This first regexp matches plain text to accelerate parsing.  It must
+        // be written so that it does not match any prefix of any following
+        // syntax.  Most markdown is plain text, so it is faster to match one
+        // regexp per 'word' rather than fail to match all the following regexps
+        // at each non-syntax character position.  It is much more important
+        // that the regexp is fast than complete (for example, adding grouping
+        // is likely to slow the regexp down enough to negate its benefit).
+        // Since it is purely for optimization, it can be removed for debugging.
+        new TextSyntax(r'\s*[A-Za-z0-9]+'),
+
+        // The real syntaxes.
+
+        new AutolinkSyntax(),
+        new LinkSyntax(),
+        // "*" surrounded by spaces is left alone.
+        new TextSyntax(r' \* '),
+        // "_" surrounded by spaces is left alone.
+        new TextSyntax(r' _ '),
+        // Leave already-encoded HTML entities alone. Ensures we don't turn
+        // "&amp;" into "&amp;amp;"
+        new TextSyntax(r'&[#a-zA-Z0-9]*;'),
+        // Encode "&".
+        new TextSyntax(r'&', sub: '&amp;'),
+        // Encode "<". (Why not encode ">" too? Gruber is toying with us.)
+        new TextSyntax(r'<', sub: '&lt;'),
+        // Parse "**strong**" tags.
+        new TagSyntax(r'\*\*', tag: 'strong'),
+        // Parse "__strong__" tags.
+        new TagSyntax(r'__', tag: 'strong'),
+        // Parse "*emphasis*" tags.
+        new TagSyntax(r'\*', tag: 'em'),
+        // Parse "_emphasis_" tags.
+        // TODO(rnystrom): Underscores in the middle of a word should not be
+        // parsed as emphasis like_in_this.
+        new TagSyntax(r'_', tag: 'em'),
+        // Parse inline code within double backticks: "``code``".
+        new CodeSyntax(r'``\s?((?:.|\n)*?)\s?``'),
+        // Parse inline code within backticks: "`code`".
+        new CodeSyntax(r'`([^`]*)`')
+      ];
+    }
+
+    return _syntaxes;
+  }
+
+  static List<InlineSyntax> _syntaxes;
+
+  /// The string of markdown being parsed.
+  final String source;
+
+  /// The markdown document this parser is parsing.
+  final Document document;
+
+  /// The current read position.
+  int pos = 0;
+
+  /// Starting position of the last unconsumed text.
+  int start = 0;
+
+  final List<TagState> _stack;
+
+  InlineParser(this.source, this.document)
+    : _stack = <TagState>[];
+
+  List<Node> parse() {
+    // Make a fake top tag to hold the results.
+    _stack.add(new TagState(0, 0, null));
+
+    while (!isDone) {
+      bool matched = false;
+
+      // See if any of the current tags on the stack match. We don't allow tags
+      // of the same kind to nest, so this takes priority over other possible // matches.
+      for (int i = _stack.length - 1; i > 0; i--) {
+        if (_stack[i].tryMatch(this)) {
+          matched = true;
+          break;
+        }
+      }
+      if (matched) continue;
+
+      // See if the current text matches any defined markdown syntax.
+      for (final syntax in syntaxes) {
+        if (syntax.tryMatch(this)) {
+          matched = true;
+          break;
+        }
+      }
+      if (matched) continue;
+
+      // If we got here, it's just text.
+      advanceBy(1);
+    }
+
+    // Unwind any unmatched tags and get the results.
+    return _stack[0].close(this, null);
+  }
+
+  writeText() {
+    writeTextRange(start, pos);
+    start = pos;
+  }
+
+  writeTextRange(int start, int end) {
+    if (end > start) {
+      final text = source.substring(start, end);
+      final nodes = _stack.last.children;
+
+      // If the previous node is text too, just append.
+      if ((nodes.length > 0) && (nodes.last is Text)) {
+        final newNode = new Text('${nodes.last.text}$text');
+        nodes[nodes.length - 1] = newNode;
+      } else {
+        nodes.add(new Text(text));
+      }
+    }
+  }
+
+  addNode(Node node) {
+    _stack.last.children.add(node);
+  }
+
+  // TODO(rnystrom): Only need this because RegExp doesn't let you start
+  // searching from a given offset.
+  String get currentSource => source.substring(pos, source.length);
+
+  bool get isDone => pos == source.length;
+
+  void advanceBy(int length) {
+    pos += length;
+  }
+
+  void consume(int length) {
+    pos += length;
+    start = pos;
+  }
+}
+
+/// Represents one kind of markdown tag that can be parsed.
+abstract class InlineSyntax {
+  final RegExp pattern;
+
+  InlineSyntax(String pattern)
+    : pattern = new RegExp(pattern, multiLine: true);
+
+  bool tryMatch(InlineParser parser) {
+    final startMatch = pattern.firstMatch(parser.currentSource);
+    if ((startMatch != null) && (startMatch.start == 0)) {
+      // Write any existing plain text up to this point.
+      parser.writeText();
+
+      if (onMatch(parser, startMatch)) {
+        parser.consume(startMatch[0].length);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  bool onMatch(InlineParser parser, Match match);
+}
+
+/// Matches stuff that should just be passed through as straight text.
+class TextSyntax extends InlineSyntax {
+  String substitute;
+  TextSyntax(String pattern, {String sub})
+    : super(pattern),
+      substitute = sub;
+
+  bool onMatch(InlineParser parser, Match match) {
+    if (substitute == null) {
+      // Just use the original matched text.
+      parser.advanceBy(match[0].length);
+      return false;
+    }
+
+    // Insert the substitution.
+    parser.addNode(new Text(substitute));
+    return true;
+  }
+}
+
+/// Matches autolinks like `<http://foo.com>`.
+class AutolinkSyntax extends InlineSyntax {
+  AutolinkSyntax()
+    : super(r'<((http|https|ftp)://[^>]*)>');
+  // TODO(rnystrom): Make case insensitive.
+
+  bool onMatch(InlineParser parser, Match match) {
+    final url = match[1];
+
+    final anchor = new Element.text('a', escapeHtml(url));
+    anchor.attributes['href'] = url;
+    parser.addNode(anchor);
+
+    return true;
+  }
+}
+
+/// Matches syntax that has a pair of tags and becomes an element, like `*` for
+/// `<em>`. Allows nested tags.
+class TagSyntax extends InlineSyntax {
+  final RegExp endPattern;
+  final String tag;
+
+  TagSyntax(String pattern, {String tag, String end})
+    : super(pattern),
+      endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
+      tag = tag;
+    // TODO(rnystrom): Doing this.field doesn't seem to work with named args.
+
+  bool onMatch(InlineParser parser, Match match) {
+    parser._stack.add(new TagState(parser.pos,
+      parser.pos + match[0].length, this));
+    return true;
+  }
+
+  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
+    parser.addNode(new Element(tag, state.children));
+    return true;
+  }
+}
+
+/// Matches inline links like `[blah] [id]` and `[blah] (url)`.
+class LinkSyntax extends TagSyntax {
+  /// The regex for the end of a link needs to handle both reference style and
+  /// inline styles as well as optional titles for inline links. To make that
+  /// a bit more palatable, this breaks it into pieces.
+  static get linkPattern {
+    final refLink    = r'\s?\[([^\]]*)\]';        // "[id]" reflink id.
+    final title      = r'(?:[ ]*"([^"]+)"|)';     // Optional title in quotes.
+    final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
+    return '\](?:($refLink|$inlineLink)|)';
+
+    // The groups matched by this are:
+    // 1: Will be non-empty if it's either a ref or inline link. Will be empty
+    //    if it's just a bare pair of square brackets with nothing after them.
+    // 2: Contains the id inside [] for a reference-style link.
+    // 3: Contains the URL for an inline link.
+    // 4: Contains the title, if present, for an inline link.
+  }
+
+  LinkSyntax()
+    : super(r'\[', end: linkPattern);
+
+  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
+    var url;
+    var title;
+
+    // If we didn't match refLink or inlineLink, then it means there was
+    // nothing after the first square bracket, so it isn't a normal markdown
+    // link at all. Instead, we allow users of the library to specify a special
+    // resolver function ([setImplicitLinkResolver]) that may choose to handle
+    // this. Otherwise, it's just treated as plain text.
+    if ((match[1] == null) || (match[1] == '')) {
+      if (_implicitLinkResolver == null) return false;
+
+      // Only allow implicit links if the content is just text.
+      // TODO(rnystrom): Do we want to relax this?
+      if (state.children.length != 1) return false;
+      if (state.children[0] is! Text) return false;
+
+      Text link = state.children[0];
+
+      // See if we have a resolver that will generate a link for us.
+      final node = _implicitLinkResolver(link.text);
+      if (node == null) return false;
+
+      parser.addNode(node);
+      return true;
+    }
+
+    if ((match[3] != null) && (match[3] != '')) {
+      // Inline link like [foo](url).
+      url = match[3];
+      title = match[4];
+
+      // For whatever reason, markdown allows angle-bracketed URLs here.
+      if (url.startsWith('<') && url.endsWith('>')) {
+        url = url.substring(1, url.length - 1);
+      }
+    } else {
+      // Reference link like [foo] [bar].
+      var id = match[2];
+      if (id == '') {
+        // The id is empty ("[]") so infer it from the contents.
+        id = parser.source.substring(state.startPos + 1, parser.pos);
+      }
+
+      // References are case-insensitive.
+      id = id.toLowerCase();
+
+      // Look up the link.
+      final link = parser.document.refLinks[id];
+      // If it's an unknown link just emit plaintext.
+      if (link == null) return false;
+
+      url = link.url;
+      title = link.title;
+    }
+
+    final anchor = new Element('a', state.children);
+    anchor.attributes['href'] = escapeHtml(url);
+    if ((title != null) && (title != '')) {
+      anchor.attributes['title'] = escapeHtml(title);
+    }
+
+    parser.addNode(anchor);
+    return true;
+  }
+}
+
+/// Matches backtick-enclosed inline code blocks.
+class CodeSyntax extends InlineSyntax {
+  CodeSyntax(String pattern)
+    : super(pattern);
+
+  bool onMatch(InlineParser parser, Match match) {
+    parser.addNode(new Element.text('code', escapeHtml(match[1])));
+    return true;
+  }
+}
+
+/// Keeps track of a currently open tag while it is being parsed. The parser
+/// maintains a stack of these so it can handle nested tags.
+class TagState {
+  /// The point in the original source where this tag started.
+  int startPos;
+
+  /// The point in the original source where open tag ended.
+  int endPos;
+
+  /// The syntax that created this node.
+  final TagSyntax syntax;
+
+  /// The children of this node. Will be `null` for text nodes.
+  final List<Node> children;
+
+  TagState(this.startPos, this.endPos, this.syntax)
+    : children = <Node>[];
+
+  /// Attempts to close this tag by matching the current text against its end
+  /// pattern.
+  bool tryMatch(InlineParser parser) {
+    Match endMatch = syntax.endPattern.firstMatch(parser.currentSource);
+    if ((endMatch != null) && (endMatch.start == 0)) {
+      // Close the tag.
+      close(parser, endMatch);
+      return true;
+    }
+
+    return false;
+  }
+
+  /// Pops this tag off the stack, completes it, and adds it to the output.
+  /// Will discard any unmatched tags that happen to be above it on the stack.
+  /// If this is the last node in the stack, returns its children.
+  List<Node> close(InlineParser parser, Match endMatch) {
+    // If there are unclosed tags on top of this one when it's closed, that
+    // means they are mismatched. Mismatched tags are treated as plain text in
+    // markdown. So for each tag above this one, we write its start tag as text
+    // and then adds its children to this one's children.
+    int index = parser._stack.indexOf(this);
+
+    // Remove the unmatched children.
+    final unmatchedTags = parser._stack.getRange(index + 1,
+        parser._stack.length - index - 1);
+    parser._stack.removeRange(index + 1, parser._stack.length - index - 1);
+
+    // Flatten them out onto this tag.
+    for (final unmatched in unmatchedTags) {
+      // Write the start tag as text.
+      parser.writeTextRange(unmatched.startPos, unmatched.endPos);
+
+      // Bequeath its children unto this tag.
+      children.addAll(unmatched.children);
+    }
+
+    // Pop this off the stack.
+    parser.writeText();
+    parser._stack.removeLast();
+
+    // If the stack is empty now, this is the special "results" node.
+    if (parser._stack.length == 0) return children;
+
+    // We are still parsing, so add this to its parent's children.
+    if (syntax.onMatchEnd(parser, endMatch, this)) {
+      parser.consume(endMatch[0].length);
+    } else {
+      // Didn't close correctly so revert to text.
+      parser.start = startPos;
+      parser.advanceBy(endMatch[0].length);
+    }
+
+    return null;
+  }
+}
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml
new file mode 100644
index 0000000..f45db3a
--- /dev/null
+++ b/pkgs/markdown/pubspec.yaml
@@ -0,0 +1,7 @@
+name: markdown
+author: "Dart Team <misc@dartlang.org>"
+# homepage: https://github.com/dart-lang/csslib
+description: A library for converting markdown to HTML.
+version: 0.3.4
+dependencies:
+  unittest: any
\ No newline at end of file
diff --git a/pkgs/markdown/test/LICENSE b/pkgs/markdown/test/LICENSE
new file mode 100644
index 0000000..81764fd
--- /dev/null
+++ b/pkgs/markdown/test/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2012, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/pkgs/markdown/test/lib/classify.dart b/pkgs/markdown/test/lib/classify.dart
new file mode 100644
index 0000000..2838fbd
--- /dev/null
+++ b/pkgs/markdown/test/lib/classify.dart
@@ -0,0 +1,209 @@
+// 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.
+
+library classify;
+
+import 'src/compiler/implementation/scanner/scannerlib.dart';
+// TODO(rnystrom): Use "package:" URL (#4968).
+import 'markdown.dart' as md;
+
+/**
+ * Kinds of tokens that we care to highlight differently. The values of the
+ * fields here will be used as CSS class names for the generated spans.
+ */
+class Classification {
+  static const NONE = null;
+  static const ERROR = "e";
+  static const COMMENT = "c";
+  static const IDENTIFIER = "i";
+  static const KEYWORD = "k";
+  static const OPERATOR = "o";
+  static const STRING = "s";
+  static const NUMBER = "n";
+  static const PUNCTUATION = "p";
+
+  // A few things that are nice to make different:
+  static const TYPE_IDENTIFIER = "t";
+
+  // Between a keyword and an identifier
+  static const SPECIAL_IDENTIFIER = "r";
+
+  static const ARROW_OPERATOR = "a";
+
+  static const STRING_INTERPOLATION = 'si';
+}
+
+/// Returns a marked up HTML string. If the code does not appear to be valid
+/// Dart code, returns the original [text].
+String classifySource(String text) {
+  try {
+    var html = new StringBuffer();
+    var tokenizer = new StringScanner(text, includeComments: true);
+
+    var whitespaceOffset = 0;
+    var token = tokenizer.tokenize();
+    var inString = false;
+    while (token.kind != EOF_TOKEN) {
+      html.add(text.substring(whitespaceOffset, token.charOffset));
+      whitespaceOffset = token.charOffset + token.slowCharCount;
+
+      // Track whether or not we're in a string.
+      switch (token.kind) {
+        case STRING_TOKEN:
+        case STRING_INTERPOLATION_TOKEN:
+          inString = true;
+          break;
+      }
+
+      final kind = classify(token);
+      final escapedText = md.escapeHtml(token.slowToString());
+      if (kind != null) {
+        // Add a secondary class to tokens appearing within a string so that
+        // we can highlight tokens in an interpolation specially.
+        var stringClass = inString ? Classification.STRING_INTERPOLATION : '';
+        html.add('<span class="$kind $stringClass">$escapedText</span>');
+      } else {
+        html.add(escapedText);
+      }
+
+      // Track whether or not we're in a string.
+      if (token.kind == STRING_TOKEN) {
+        inString = false;
+      }
+      token = token.next;
+    }
+    return html.toString();
+  } catch (e) {
+    return text;
+  }
+}
+
+bool _looksLikeType(String name) {
+  // If the name looks like an UppercaseName, assume it's a type.
+  return _looksLikePublicType(name) || _looksLikePrivateType(name);
+}
+
+bool _looksLikePublicType(String name) {
+  // If the name looks like an UppercaseName, assume it's a type.
+  return name.length >= 2 && isUpper(name[0]) && isLower(name[1]);
+}
+
+bool _looksLikePrivateType(String name) {
+  // If the name looks like an _UppercaseName, assume it's a type.
+  return (name.length >= 3 && name[0] == '_' && isUpper(name[1])
+    && isLower(name[2]));
+}
+
+// These ensure that they don't return "true" if the string only has symbols.
+bool isUpper(String s) => s.toLowerCase() != s;
+bool isLower(String s) => s.toUpperCase() != s;
+
+String classify(Token token) {
+  switch (token.kind) {
+    case UNKNOWN_TOKEN:
+      return Classification.ERROR;
+
+    case IDENTIFIER_TOKEN:
+      // Special case for names that look like types.
+      final text = token.slowToString();
+      if (_looksLikeType(text)
+          || text == 'num'
+          || text == 'bool'
+          || text == 'int'
+          || text == 'double') {
+        return Classification.TYPE_IDENTIFIER;
+      }
+      return Classification.IDENTIFIER;
+
+    case STRING_TOKEN:
+    case STRING_INTERPOLATION_TOKEN:
+      return Classification.STRING;
+
+    case INT_TOKEN:
+    case HEXADECIMAL_TOKEN:
+    case DOUBLE_TOKEN:
+      return Classification.NUMBER;
+
+    case COMMENT_TOKEN:
+      return Classification.COMMENT;
+
+    // => is so awesome it is in a class of its own.
+    case FUNCTION_TOKEN:
+      return Classification.ARROW_OPERATOR;
+
+    case OPEN_PAREN_TOKEN:
+    case CLOSE_PAREN_TOKEN:
+    case OPEN_SQUARE_BRACKET_TOKEN:
+    case CLOSE_SQUARE_BRACKET_TOKEN:
+    case OPEN_CURLY_BRACKET_TOKEN:
+    case CLOSE_CURLY_BRACKET_TOKEN:
+    case COLON_TOKEN:
+    case SEMICOLON_TOKEN:
+    case COMMA_TOKEN:
+    case PERIOD_TOKEN:
+    case PERIOD_PERIOD_TOKEN:
+      return Classification.PUNCTUATION;
+
+    case PLUS_PLUS_TOKEN:
+    case MINUS_MINUS_TOKEN:
+    case TILDE_TOKEN:
+    case BANG_TOKEN:
+    case EQ_TOKEN:
+    case BAR_EQ_TOKEN:
+    case CARET_EQ_TOKEN:
+    case AMPERSAND_EQ_TOKEN:
+    case LT_LT_EQ_TOKEN:
+    case GT_GT_EQ_TOKEN:
+    case PLUS_EQ_TOKEN:
+    case MINUS_EQ_TOKEN:
+    case STAR_EQ_TOKEN:
+    case SLASH_EQ_TOKEN:
+    case TILDE_SLASH_EQ_TOKEN:
+    case PERCENT_EQ_TOKEN:
+    case QUESTION_TOKEN:
+    case BAR_BAR_TOKEN:
+    case AMPERSAND_AMPERSAND_TOKEN:
+    case BAR_TOKEN:
+    case CARET_TOKEN:
+    case AMPERSAND_TOKEN:
+    case LT_LT_TOKEN:
+    case GT_GT_TOKEN:
+    case PLUS_TOKEN:
+    case MINUS_TOKEN:
+    case STAR_TOKEN:
+    case SLASH_TOKEN:
+    case TILDE_SLASH_TOKEN:
+    case PERCENT_TOKEN:
+    case EQ_EQ_TOKEN:
+    case BANG_EQ_TOKEN:
+    case EQ_EQ_EQ_TOKEN:
+    case BANG_EQ_EQ_TOKEN:
+    case LT_TOKEN:
+    case GT_TOKEN:
+    case LT_EQ_TOKEN:
+    case GT_EQ_TOKEN:
+    case INDEX_TOKEN:
+    case INDEX_EQ_TOKEN:
+      return Classification.OPERATOR;
+
+    // Color keyword token. Most are colored as keywords.
+    case HASH_TOKEN:
+    case KEYWORD_TOKEN:
+      if (token.stringValue == 'void') {
+        // Color "void" as a type.
+        return Classification.TYPE_IDENTIFIER;
+      }
+      if (token.stringValue == 'this' || token.stringValue == 'super') {
+        // Color "this" and "super" as identifiers.
+        return Classification.SPECIAL_IDENTIFIER;
+      }
+      return Classification.KEYWORD;
+
+    case EOF_TOKEN:
+      return Classification.NONE;
+
+    default:
+      return Classification.NONE;
+  }
+}
diff --git a/pkgs/markdown/test/lib/markdown.dart b/pkgs/markdown/test/lib/markdown.dart
new file mode 100644
index 0000000..ef111fb
--- /dev/null
+++ b/pkgs/markdown/test/lib/markdown.dart
@@ -0,0 +1,118 @@
+// 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.
+
+/// Parses text in a markdown-like format and renders to HTML.
+library markdown;
+
+import 'classify.dart';
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+part 'src/markdown/ast.dart';
+part 'src/markdown/block_parser.dart';
+part 'src/markdown/html_renderer.dart';
+part 'src/markdown/inline_parser.dart';
+
+/// Converts the given string of markdown to HTML.
+String markdownToHtml(String markdown) {
+  final document = new Document();
+
+  // Replace windows line endings with unix line endings, and split.
+  final lines = markdown.replaceAll('\r\n','\n').split('\n');
+  document.parseRefLinks(lines);
+  final blocks = document.parseLines(lines);
+  return renderToHtml(blocks);
+}
+
+/// Replaces `<`, `&`, and `>`, with their HTML entity equivalents.
+String escapeHtml(String html) {
+  return html.replaceAll('&', '&amp;')
+             .replaceAll('<', '&lt;')
+             .replaceAll('>', '&gt;');
+}
+
+var _implicitLinkResolver;
+
+Node setImplicitLinkResolver(Node resolver(String text)) {
+  _implicitLinkResolver = resolver;
+}
+
+/// Maintains the context needed to parse a markdown document.
+class Document {
+  final Map<String, Link> refLinks;
+
+  Document()
+    : refLinks = <String, Link>{};
+
+  parseRefLinks(List<String> lines) {
+    // This is a hideous regex. It matches:
+    // [id]: http:foo.com "some title"
+    // Where there may whitespace in there, and where the title may be in
+    // single quotes, double quotes, or parentheses.
+    final indent = r'^[ ]{0,3}'; // Leading indentation.
+    final id = r'\[([^\]]+)\]';  // Reference id in [brackets].
+    final quote = r'"[^"]+"';    // Title in "double quotes".
+    final apos = r"'[^']+'";     // Title in 'single quotes'.
+    final paren = r"\([^)]+\)";  // Title in (parentheses).
+    final pattern = new RegExp(
+        '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
+
+    for (int i = 0; i < lines.length; i++) {
+      final match = pattern.firstMatch(lines[i]);
+      if (match != null) {
+        // Parse the link.
+        var id = match[1];
+        var url = match[2];
+        var title = match[3];
+
+        if (title == '') {
+          // No title.
+          title = null;
+        } else {
+          // Remove "", '', or ().
+          title = title.substring(1, title.length - 1);
+        }
+
+        // References are case-insensitive.
+        id = id.toLowerCase();
+
+        refLinks[id] = new Link(id, url, title);
+
+        // Remove it from the output. We replace it with a blank line which will
+        // get consumed by later processing.
+        lines[i] = '';
+      }
+    }
+  }
+
+  /// Parse the given [lines] of markdown to a series of AST nodes.
+  List<Node> parseLines(List<String> lines) {
+    final parser = new BlockParser(lines, this);
+
+    final blocks = [];
+    while (!parser.isDone) {
+      for (final syntax in BlockSyntax.syntaxes) {
+        if (syntax.canParse(parser)) {
+          final block = syntax.parse(parser);
+          if (block != null) blocks.add(block);
+          break;
+        }
+      }
+    }
+
+    return blocks;
+  }
+
+  /// Takes a string of raw text and processes all inline markdown tags,
+  /// returning a list of AST nodes. For example, given ``"*this **is** a*
+  /// `markdown`"``, returns:
+  /// `<em>this <strong>is</strong> a</em> <code>markdown</code>`.
+  List<Node> parseInline(String text) => new InlineParser(text, this).parse();
+}
+
+class Link {
+  final String id;
+  final String url;
+  final String title;
+  Link(this.id, this.url, this.title);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/compiler.dart b/pkgs/markdown/test/lib/src/compiler/compiler.dart
new file mode 100644
index 0000000..bbee705
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/compiler.dart
@@ -0,0 +1,177 @@
+// 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.
+
+library compiler;
+
+import 'dart:async';
+import 'dart:uri';
+import 'implementation/apiimpl.dart';
+
+// Unless explicitly allowed, passing [:null:] for any argument to the
+// methods of library will result in an Error being thrown.
+
+/**
+ * Returns a future that completes to the source corresponding to
+ * [uri]. If an exception occurs, the future completes with this
+ * exception.
+ */
+typedef Future<String> CompilerInputProvider(Uri uri);
+
+/// Deprecated, please use [CompilerInputProvider] instead.
+typedef Future<String> ReadStringFromUri(Uri uri);
+
+/**
+ * Returns a [StreamSink] that will serve as compiler output for the given
+ * component.
+ *
+ * Components are identified by [name] and [extension]. By convention,
+ * the empty string [:"":] will represent the main script
+ * (corresponding to the script parameter of [compile]) even if the
+ * main script is a library. For libraries that are compiled
+ * separately, the library name is used.
+ *
+ * At least the following extensions can be expected:
+ *
+ * * "js" for JavaScript output.
+ * * "js.map" for source maps.
+ * * "dart" for Dart output.
+ * * "dart.map" for source maps.
+ *
+ * As more features are added to the compiler, new names and
+ * extensions may be introduced.
+ */
+typedef StreamSink<String> CompilerOutputProvider(String name,
+                                                  String extension);
+
+/**
+ * Invoked by the compiler to report diagnostics. If [uri] is
+ * [:null:], so are [begin] and [end]. No other arguments may be
+ * [:null:]. If [uri] is not [:null:], neither are [begin] and
+ * [end]. [uri] indicates the compilation unit from where the
+ * diagnostic originates. [begin] and [end] are zero-based character
+ * offsets from the beginning of the compilaton unit. [message] is the
+ * diagnostic message, and [kind] indicates indicates what kind of
+ * diagnostic it is.
+ */
+typedef void DiagnosticHandler(Uri uri, int begin, int end,
+                               String message, Diagnostic kind);
+
+/**
+ * Returns a future that completes to a non-null String when [script]
+ * has been successfully compiled.
+ *
+ * The compiler output is obtained by providing an [outputProvider].
+ *
+ * If the compilation fails, the future's value will be [:null:] and
+ * [handler] will have been invoked at least once with [:kind ==
+ * Diagnostic.ERROR:] or [:kind == Diagnostic.CRASH:].
+ *
+ * Deprecated: if no [outputProvider] is given, the future completes
+ * to the compiled script. This behavior will be removed in the future
+ * as the compiler may create multiple files to support lazy loading
+ * of libraries.
+ */
+Future<String> compile(Uri script,
+                       Uri libraryRoot,
+                       Uri packageRoot,
+                       CompilerInputProvider inputProvider,
+                       DiagnosticHandler handler,
+                       [List<String> options = const [],
+                        CompilerOutputProvider outputProvider]) {
+  if (!libraryRoot.path.endsWith("/")) {
+    throw new ArgumentError("libraryRoot must end with a /");
+  }
+  if (packageRoot != null && !packageRoot.path.endsWith("/")) {
+    throw new ArgumentError("packageRoot must end with a /");
+  }
+  // TODO(ahe): Consider completing the future with an exception if
+  // code is null.
+  Compiler compiler = new Compiler(inputProvider,
+                                   outputProvider,
+                                   handler,
+                                   libraryRoot,
+                                   packageRoot,
+                                   options);
+  compiler.run(script);
+  String code = compiler.assembledCode;
+  if (code != null && outputProvider != null) {
+    String outputType = 'js';
+    if (options.contains('--output-type=dart')) {
+      outputType = 'dart';
+    }
+    outputProvider('', outputType)
+        ..add(code)
+        ..close();
+    code = ''; // Non-null signals success.
+  }
+  return new Future.immediate(code);
+}
+
+/**
+ * Kind of diagnostics that the compiler can report.
+ */
+class Diagnostic {
+  /**
+   * An error as identified by the "Dart Programming Language
+   * Specification" [http://www.dartlang.org/docs/spec/].
+   *
+   * Note: the compiler may still produce an executable result after
+   * reporting a compilation error. The specification says:
+   *
+   * "A compile-time error must be reported by a Dart compiler before
+   * the erroneous code is executed." and "If a compile-time error
+   * occurs within the code of a running isolate A, A is immediately
+   * suspended."
+   *
+   * This means that the compiler can generate code that when executed
+   * terminates execution.
+   */
+  static const Diagnostic ERROR = const Diagnostic(1, 'error');
+
+  /**
+   * A warning as identified by the "Dart Programming Language
+   * Specification" [http://www.dartlang.org/docs/spec/].
+   */
+  static const Diagnostic WARNING = const Diagnostic(2, 'warning');
+
+  /**
+   * Any other warning that is not covered by [WARNING].
+   */
+  static const Diagnostic LINT = const Diagnostic(4, 'lint');
+
+  /**
+   * Informational messages.
+   */
+  static const Diagnostic INFO = const Diagnostic(8, 'info');
+
+  /**
+   * Informational messages that shouldn't be printed unless
+   * explicitly requested by the user of a compiler.
+   */
+  static const Diagnostic VERBOSE_INFO = const Diagnostic(16, 'verbose info');
+
+  /**
+   * An internal error in the compiler.
+   */
+  static const Diagnostic CRASH = const Diagnostic(32, 'crash');
+
+  /**
+   * An [int] representation of this kind. The ordinals are designed
+   * to be used as bitsets.
+   */
+  final int ordinal;
+
+  /**
+   * The name of this kind.
+   */
+  final String name;
+
+  /**
+   * This constructor is not private to support user-defined
+   * diagnostic kinds.
+   */
+  const Diagnostic(this.ordinal, this.name);
+
+  String toString() => name;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/README.txt b/pkgs/markdown/test/lib/src/compiler/implementation/README.txt
new file mode 100644
index 0000000..8fe0a29
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/README.txt
@@ -0,0 +1,12 @@
+Dart2JS is the Dart-to-JavaScript compiler for Dart. This compiler
+will provide high-quality translation from Dart to JavaScript.
+
+Some areas that will explored in this project are:
+
+   * high-performance extensible scanner and parser
+   * concrete type inferencing
+   * fancy language tool support
+   * programming environment integration
+   * SSA-based intermediate representation
+   * adaptive compilation on the client
+
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart b/pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart
new file mode 100644
index 0000000..663e003
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart
@@ -0,0 +1,266 @@
+// 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.
+
+library leg_apiimpl;
+
+import 'dart:uri';
+import 'dart:async';
+
+import '../compiler.dart' as api;
+import 'dart2jslib.dart' as leg;
+import 'tree/tree.dart' as tree;
+import 'elements/elements.dart' as elements;
+import 'ssa/tracer.dart' as ssa;
+import '../../libraries.dart';
+import 'source_file.dart';
+
+class Compiler extends leg.Compiler {
+  api.ReadStringFromUri provider;
+  api.DiagnosticHandler handler;
+  final Uri libraryRoot;
+  final Uri packageRoot;
+  List<String> options;
+  bool mockableLibraryUsed = false;
+  final Set<String> allowedLibraryCategories;
+
+  Compiler(this.provider,
+           api.CompilerOutputProvider outputProvider,
+           this.handler,
+           this.libraryRoot,
+           this.packageRoot,
+           List<String> options)
+      : this.options = options,
+        this.allowedLibraryCategories = getAllowedLibraryCategories(options),
+        super(
+            tracer: new ssa.HTracer(),
+            outputProvider: outputProvider,
+            enableTypeAssertions: hasOption(options, '--enable-checked-mode'),
+            enableUserAssertions: hasOption(options, '--enable-checked-mode'),
+            enableMinification: hasOption(options, '--minify'),
+            enableNativeLiveTypeAnalysis:
+                !hasOption(options, '--disable-native-live-type-analysis'),
+            emitJavaScript: !hasOption(options, '--output-type=dart'),
+            disallowUnsafeEval: hasOption(options, '--disallow-unsafe-eval'),
+            analyzeAll: hasOption(options, '--analyze-all'),
+            analyzeOnly: hasOption(options, '--analyze-only'),
+            rejectDeprecatedFeatures:
+                hasOption(options, '--reject-deprecated-language-features'),
+            checkDeprecationInSdk:
+                hasOption(options,
+                          '--report-sdk-use-of-deprecated-language-features'),
+            strips: getStrips(options),
+            enableConcreteTypeInference:
+                hasOption(options, '--enable-concrete-type-inference'),
+            preserveComments: hasOption(options, '--preserve-comments')) {
+    if (!libraryRoot.path.endsWith("/")) {
+      throw new ArgumentError("libraryRoot must end with a /");
+    }
+    if (packageRoot != null && !packageRoot.path.endsWith("/")) {
+      throw new ArgumentError("packageRoot must end with a /");
+    }
+  }
+
+  static List<String> getStrips(List<String> options) {
+    for (String option in options) {
+      if (option.startsWith('--force-strip=')) {
+        return option.substring('--force-strip='.length).split(',');
+      }
+    }
+    return const <String>[];
+  }
+
+  static Set<String> getAllowedLibraryCategories(List<String> options) {
+    for (String option in options) {
+      if (option.startsWith('--categories=')) {
+        var result = option.substring('--categories='.length).split(',');
+        result.add('Shared');
+        result.add('Internal');
+        return new Set<String>.from(result);
+      }
+    }
+    return new Set<String>.from(['Client', 'Shared', 'Internal']);
+  }
+
+  static bool hasOption(List<String> options, String option) {
+    return options.indexOf(option) >= 0;
+  }
+
+  // TODO(johnniwinther): Merge better with [translateDartUri] when
+  // [scanBuiltinLibrary] is removed.
+  String lookupLibraryPath(String dartLibraryName) {
+    LibraryInfo info = LIBRARIES[dartLibraryName];
+    if (info == null) return null;
+    if (!info.isDart2jsLibrary) return null;
+    if (!allowedLibraryCategories.contains(info.category)) return null;
+    String path = info.dart2jsPath;
+    if (path == null) {
+      path = info.path;
+    }
+    return "lib/$path";
+  }
+
+  String lookupPatchPath(String dartLibraryName) {
+    LibraryInfo info = LIBRARIES[dartLibraryName];
+    if (info == null) return null;
+    if (!info.isDart2jsLibrary) return null;
+    String path = info.dart2jsPatchPath;
+    if (path == null) return null;
+    return "lib/$path";
+  }
+
+  elements.LibraryElement scanBuiltinLibrary(String path) {
+    Uri uri = libraryRoot.resolve(lookupLibraryPath(path));
+    Uri canonicalUri = new Uri.fromComponents(scheme: "dart", path: path);
+    elements.LibraryElement library =
+        libraryLoader.loadLibrary(uri, null, canonicalUri);
+    return library;
+  }
+
+  void log(message) {
+    handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO);
+  }
+
+  /// See [leg.Compiler.translateResolvedUri].
+  Uri translateResolvedUri(elements.LibraryElement importingLibrary,
+                           Uri resolvedUri, tree.Node node) {
+    if (resolvedUri.scheme == 'dart') {
+      return translateDartUri(importingLibrary, resolvedUri, node);
+    }
+    return resolvedUri;
+  }
+
+  /**
+   * Reads the script designated by [readableUri].
+   */
+  leg.Script readScript(Uri readableUri, [tree.Node node]) {
+    if (!readableUri.isAbsolute()) {
+      internalError('Relative uri $readableUri provided to readScript(Uri)',
+                    node: node);
+    }
+    return fileReadingTask.measure(() {
+      Uri resourceUri = translateUri(readableUri, node);
+      String text = "";
+      try {
+        // TODO(ahe): We expect the future to be complete and call value
+        // directly. In effect, we don't support truly asynchronous API.
+        text = deprecatedFutureValue(provider(resourceUri));
+      } catch (exception) {
+        if (node != null) {
+          cancel("$exception", node: node);
+        } else {
+          reportDiagnostic(null, "$exception", api.Diagnostic.ERROR);
+          throw new leg.CompilerCancelledException("$exception");
+        }
+      }
+      SourceFile sourceFile = new SourceFile(resourceUri.toString(), text);
+      // We use [readableUri] as the URI for the script since need to preserve
+      // the scheme in the script because [Script.uri] is used for resolving
+      // relative URIs mentioned in the script. See the comment on
+      // [LibraryLoader] for more details.
+      return new leg.Script(readableUri, sourceFile);
+    });
+  }
+
+  /**
+   * Translates a readable URI into a resource URI.
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  Uri translateUri(Uri readableUri, tree.Node node) {
+    switch (readableUri.scheme) {
+      case 'package': return translatePackageUri(readableUri, node);
+      default: return readableUri;
+    }
+  }
+
+  Uri translateDartUri(elements.LibraryElement importingLibrary,
+                       Uri resolvedUri, tree.Node node) {
+    LibraryInfo libraryInfo = LIBRARIES[resolvedUri.path];
+    String path = lookupLibraryPath(resolvedUri.path);
+    if (libraryInfo != null &&
+        libraryInfo.category == "Internal") {
+      bool allowInternalLibraryAccess = false;
+      if (importingLibrary != null) {
+        if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) {
+          allowInternalLibraryAccess = true;
+        } else if (importingLibrary.canonicalUri.path.contains(
+                       'dart/tests/compiler/dart2js_native')) {
+          allowInternalLibraryAccess = true;
+        }
+      }
+      if (!allowInternalLibraryAccess) {
+        if (node != null && importingLibrary != null) {
+          reportDiagnostic(spanFromNode(node),
+              'Error: Internal library $resolvedUri is not accessible from '
+              '${importingLibrary.canonicalUri}.',
+              api.Diagnostic.ERROR);
+        } else {
+          reportDiagnostic(null,
+              'Error: Internal library $resolvedUri is not accessible.',
+              api.Diagnostic.ERROR);
+        }
+        //path = null;
+      }
+    }
+    if (path == null) {
+      if (node != null) {
+        reportError(node, 'library not found ${resolvedUri}');
+      } else {
+        reportDiagnostic(null, 'library not found ${resolvedUri}',
+                         api.Diagnostic.ERROR);
+      }
+      return null;
+    }
+    if (resolvedUri.path == 'html' ||
+        resolvedUri.path == 'io') {
+      // TODO(ahe): Get rid of mockableLibraryUsed when test.dart
+      // supports this use case better.
+      mockableLibraryUsed = true;
+    }
+    return libraryRoot.resolve(path);
+  }
+
+  Uri resolvePatchUri(String dartLibraryPath) {
+    String patchPath = lookupPatchPath(dartLibraryPath);
+    if (patchPath == null) return null;
+    return libraryRoot.resolve(patchPath);
+  }
+
+  translatePackageUri(Uri uri, tree.Node node) => packageRoot.resolve(uri.path);
+
+  bool run(Uri uri) {
+    log('Allowed library categories: $allowedLibraryCategories');
+    bool success = super.run(uri);
+    int cumulated = 0;
+    for (final task in tasks) {
+      cumulated += task.timing;
+      log('${task.name} took ${task.timing}msec');
+    }
+    int total = totalCompileTime.elapsedMilliseconds;
+    log('Total compile-time ${total}msec;'
+        ' unaccounted ${total - cumulated}msec');
+    return success;
+  }
+
+  void reportDiagnostic(leg.SourceSpan span, String message,
+                        api.Diagnostic kind) {
+    if (identical(kind, api.Diagnostic.ERROR)
+        || identical(kind, api.Diagnostic.CRASH)) {
+      compilationFailed = true;
+    }
+    // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For
+    // instance in the [Types] constructor in typechecker.dart.
+    if (span == null || span.uri == null) {
+      handler(null, null, null, message, kind);
+    } else {
+      handler(translateUri(span.uri, null), span.begin, span.end,
+              message, kind);
+    }
+  }
+
+  bool get isMockCompilation {
+    return mockableLibraryUsed
+      && (options.indexOf('--allow-mock-compilation') != -1);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/closure.dart b/pkgs/markdown/test/lib/src/compiler/implementation/closure.dart
new file mode 100644
index 0000000..4ed1564
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/closure.dart
@@ -0,0 +1,663 @@
+// 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.
+
+library closureToClassMapper;
+
+import "elements/elements.dart";
+import "dart2jslib.dart";
+import "dart_types.dart";
+import "scanner/scannerlib.dart" show Token;
+import "tree/tree.dart";
+import "util/util.dart";
+import "elements/modelx.dart" show ElementX, FunctionElementX, ClassElementX;
+
+abstract class ClosureNamer {
+  SourceString getClosureVariableName(SourceString name, int id);
+}
+
+
+class ClosureTask extends CompilerTask {
+  Map<Node, ClosureClassMap> closureMappingCache;
+  ClosureNamer namer;
+  ClosureTask(Compiler compiler, this.namer)
+      : closureMappingCache = new Map<Node, ClosureClassMap>(),
+        super(compiler);
+
+  String get name => "Closure Simplifier";
+
+  ClosureClassMap computeClosureToClassMapping(Element element,
+                                               Expression node,
+                                               TreeElements elements) {
+    return measure(() {
+      ClosureClassMap cached = closureMappingCache[node];
+      if (cached != null) return cached;
+
+      ClosureTranslator translator =
+          new ClosureTranslator(compiler, elements, closureMappingCache, namer);
+
+      // The translator will store the computed closure-mappings inside the
+      // cache. One for given node and one for each nested closure.
+      if (node is FunctionExpression) {
+        translator.translateFunction(element, node);
+      } else {
+        // Must be the lazy initializer of a static.
+        assert(node is SendSet);
+        translator.translateLazyInitializer(element, node);
+      }
+      assert(closureMappingCache[node] != null);
+      return closureMappingCache[node];
+    });
+  }
+
+  ClosureClassMap getMappingForNestedFunction(FunctionExpression node) {
+    return measure(() {
+      ClosureClassMap nestedClosureData = closureMappingCache[node];
+      if (nestedClosureData == null) {
+        compiler.internalError("No closure cache", node: node);
+      }
+      return nestedClosureData;
+    });
+  }
+}
+
+class ClosureFieldElement extends ElementX {
+  ClosureFieldElement(SourceString name, ClassElement enclosing)
+      : super(name, ElementKind.FIELD, enclosing);
+
+  bool isInstanceMember() => true;
+  bool isAssignable() => false;
+  // The names of closure variables don't need renaming, since their use is very
+  // simple and they have 1-character names in the minified mode.
+  bool hasFixedBackendName() => true;
+  String fixedBackendName() => name.slowToString();
+
+  DartType computeType(Compiler compiler) => compiler.types.dynamicType;
+
+  String toString() => "ClosureFieldElement($name)";
+}
+
+class ClosureClassElement extends ClassElementX {
+  ClosureClassElement(SourceString name,
+                      Compiler compiler,
+                      this.methodElement,
+                      Element enclosingElement)
+      : super(name,
+              enclosingElement,
+              // By assigning a fresh class-id we make sure that the hashcode
+              // is unique, but also emit closure classes after all other
+              // classes (since the emitter sorts classes by their id).
+              compiler.getNextFreeClassId(),
+              STATE_DONE) {
+    compiler.closureClass.ensureResolved(compiler);
+    supertype = compiler.closureClass.computeType(compiler);
+    interfaces = const Link<DartType>();
+    allSupertypes = const Link<DartType>().prepend(supertype);
+  }
+
+  bool isClosure() => true;
+
+  /**
+   * The most outer method this closure is declared into.
+   */
+  Element methodElement;
+}
+
+class BoxElement extends ElementX {
+  BoxElement(SourceString name, Element enclosingElement)
+      : super(name, ElementKind.VARIABLE, enclosingElement);
+}
+
+class ThisElement extends ElementX {
+  ThisElement(Element enclosing)
+      : super(const SourceString('this'), ElementKind.PARAMETER, enclosing);
+
+  bool isAssignable() => false;
+
+  // Since there is no declaration corresponding to 'this', use the position of
+  // the enclosing method.
+  Token position() => enclosingElement.position();
+}
+
+class CheckVariableElement extends ElementX {
+  Element parameter;
+  CheckVariableElement(SourceString name, this.parameter, Element enclosing)
+      : super(name, ElementKind.VARIABLE, enclosing);
+
+  // Since there is no declaration for the synthetic 'check' variable, use
+  // parameter.
+  Token position() => parameter.position();
+}
+
+// The box-element for a scope, and the captured variables that need to be
+// stored in the box.
+class ClosureScope {
+  Element boxElement;
+  Map<Element, Element> capturedVariableMapping;
+  // If the scope is attached to a [For] contains the variables that are
+  // declared in the initializer of the [For] and that need to be boxed.
+  // Otherwise contains the empty List.
+  List<Element> boxedLoopVariables;
+
+  ClosureScope(this.boxElement, this.capturedVariableMapping)
+      : boxedLoopVariables = const <Element>[];
+
+  bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty;
+}
+
+class ClosureClassMap {
+  // The closure's element before any translation. Will be null for methods.
+  final Element closureElement;
+  // The closureClassElement will be null for methods that are not local
+  // closures.
+  final ClassElement closureClassElement;
+  // The callElement will be null for methods that are not local closures.
+  final FunctionElement callElement;
+  // The [thisElement] makes handling 'this' easier by treating it like any
+  // other argument. It is only set for instance-members.
+  final ThisElement thisElement;
+
+  // Maps free locals, arguments and function elements to their captured
+  // copies.
+  final Map<Element, Element> freeVariableMapping;
+  // Maps closure-fields to their captured elements. This is somehow the inverse
+  // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does
+  // not deal with boxes, here we map instance-fields (which might represent
+  // boxes) to their boxElement.
+  final Map<Element, Element> capturedFieldMapping;
+
+  // Maps scopes ([Loop] and [FunctionExpression] nodes) to their
+  // [ClosureScope] which contains their box and the
+  // captured variables that are stored in the box.
+  // This map will be empty if the method/closure of this [ClosureData] does not
+  // contain any nested closure.
+  final Map<Node, ClosureScope> capturingScopes;
+
+  final Set<Element> usedVariablesInTry;
+
+  // A map from the parameter element to the variable element that
+  // holds the sentinel check.
+  final Map<Element, Element> parametersWithSentinel;
+
+  ClosureClassMap(this.closureElement,
+                  this.closureClassElement,
+                  this.callElement,
+                  this.thisElement)
+      : this.freeVariableMapping = new Map<Element, Element>(),
+        this.capturedFieldMapping = new Map<Element, Element>(),
+        this.capturingScopes = new Map<Node, ClosureScope>(),
+        this.usedVariablesInTry = new Set<Element>(),
+        this.parametersWithSentinel = new Map<Element, Element>();
+
+  bool isClosure() => closureElement != null;
+}
+
+class ClosureTranslator extends Visitor {
+  final Compiler compiler;
+  final TreeElements elements;
+  int closureFieldCounter = 0;
+  int boxedFieldCounter = 0;
+  bool inTryStatement = false;
+  final Map<Node, ClosureClassMap> closureMappingCache;
+
+  // Map of captured variables. Initially they will map to themselves. If
+  // a variable needs to be boxed then the scope declaring the variable
+  // will update this mapping.
+  Map<Element, Element> capturedVariableMapping;
+  // List of encountered closures.
+  List<Expression> closures;
+
+  // The variables that have been declared in the current scope.
+  List<Element> scopeVariables;
+
+  // Keep track of the mutated variables so that we don't need to box
+  // non-mutated variables.
+  Set<Element> mutatedVariables;
+
+  Element outermostElement;
+  Element currentElement;
+
+  // The closureData of the currentFunctionElement.
+  ClosureClassMap closureData;
+
+  ClosureNamer namer;
+
+  bool insideClosure = false;
+
+  ClosureTranslator(this.compiler, this.elements, this.closureMappingCache,
+                    this.namer)
+      : capturedVariableMapping = new Map<Element, Element>(),
+        closures = <Expression>[],
+        mutatedVariables = new Set<Element>();
+
+  void translateFunction(Element element, FunctionExpression node) {
+    // For constructors the [element] and the [:elements[node]:] may differ.
+    // The [:elements[node]:] always points to the generative-constructor
+    // element, whereas the [element] might be the constructor-body element.
+    visit(node);  // [visitFunctionExpression] will call [visitInvokable].
+    // When variables need to be boxed their [capturedVariableMapping] is
+    // updated, but we delay updating the similar freeVariableMapping in the
+    // closure datas that capture these variables.
+    // The closures don't have their fields (in the closure class) set, either.
+    updateClosures();
+  }
+
+  void translateLazyInitializer(Element element, SendSet node) {
+    assert(node.assignmentOperator.source == const SourceString("="));
+    Expression initialValue = node.argumentsNode.nodes.head;
+    visitInvokable(element, node, () { visit(initialValue); });
+    updateClosures();
+  }
+
+  // This function runs through all of the existing closures and updates their
+  // free variables to the boxed value. It also adds the field-elements to the
+  // class representing the closure. At the same time it fills the
+  // [capturedFieldMapping].
+  void updateClosures() {
+    for (Expression closure in closures) {
+      // The captured variables that need to be stored in a field of the closure
+      // class.
+      Set<Element> fieldCaptures = new Set<Element>();
+      Set<Element> boxes = new Set<Element>();
+      ClosureClassMap data = closureMappingCache[closure];
+      Map<Element, Element> freeVariableMapping = data.freeVariableMapping;
+      // We get a copy of the keys and iterate over it, to avoid modifications
+      // to the map while iterating over it.
+      freeVariableMapping.keys.toList().forEach((Element fromElement) {
+        assert(fromElement == freeVariableMapping[fromElement]);
+        Element updatedElement = capturedVariableMapping[fromElement];
+        assert(updatedElement != null);
+        if (fromElement == updatedElement) {
+          assert(freeVariableMapping[fromElement] == updatedElement);
+          assert(Elements.isLocal(updatedElement)
+                 || updatedElement.isTypeVariable());
+          // The variable has not been boxed.
+          fieldCaptures.add(updatedElement);
+        } else {
+          // A boxed element.
+          freeVariableMapping[fromElement] = updatedElement;
+          Element boxElement = updatedElement.enclosingElement;
+          assert(boxElement.kind == ElementKind.VARIABLE);
+          boxes.add(boxElement);
+        }
+      });
+      ClassElement closureElement = data.closureClassElement;
+      assert(closureElement != null ||
+             (fieldCaptures.isEmpty && boxes.isEmpty));
+      void addElement(Element element, SourceString name) {
+        Element fieldElement = new ClosureFieldElement(name, closureElement);
+        closureElement.addBackendMember(fieldElement);
+        data.capturedFieldMapping[fieldElement] = element;
+        freeVariableMapping[element] = fieldElement;
+      }
+      // Add the box elements first so we get the same ordering.
+      // TODO(sra): What is the canonical order of multiple boxes?
+      for (Element capturedElement in boxes) {
+        addElement(capturedElement, capturedElement.name);
+      }
+      for (Element capturedElement in
+               Elements.sortedByPosition(fieldCaptures)) {
+        int id = closureFieldCounter++;
+        SourceString name =
+            namer.getClosureVariableName(capturedElement.name, id);
+        addElement(capturedElement, name);
+      }
+      closureElement.reverseBackendMembers();
+    }
+  }
+
+  void useLocal(Element element) {
+    // If the element is not declared in the current function and the element
+    // is not the closure itself we need to mark the element as free variable.
+    // Note that the check on [insideClosure] is not just an
+    // optimization: factories have type parameters as function
+    // parameters, and type parameters are declared in the class, not
+    // the factory.
+    if (insideClosure &&
+        element.enclosingElement != currentElement &&
+        element != currentElement) {
+      assert(closureData.freeVariableMapping[element] == null ||
+             closureData.freeVariableMapping[element] == element);
+      closureData.freeVariableMapping[element] = element;
+    } else if (inTryStatement) {
+      // Don't mark the this-element. This would complicate things in the
+      // builder.
+      if (element != closureData.thisElement) {
+        // TODO(ngeoffray): only do this if the variable is mutated.
+        closureData.usedVariablesInTry.add(element);
+      }
+    }
+  }
+
+  void declareLocal(Element element) {
+    scopeVariables.add(element);
+  }
+
+  visit(Node node) => node.accept(this);
+
+  visitNode(Node node) => node.visitChildren(this);
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    for (Link<Node> link = node.definitions.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      Node definition = link.head;
+      Element element = elements[definition];
+      assert(element != null);
+      declareLocal(element);
+      // We still need to visit the right-hand sides of the init-assignments.
+      // For SendSets don't visit the left again. Otherwise it would be marked
+      // as mutated.
+      if (definition is Send) {
+        Send assignment = definition;
+        Node arguments = assignment.argumentsNode;
+        if (arguments != null) {
+          visit(arguments);
+        }
+      } else {
+        visit(definition);
+      }
+    }
+  }
+
+  visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      useLocal(closureData.thisElement);
+    }
+    node.visitChildren(this);
+  }
+
+  visitSend(Send node) {
+    Element element = elements[node];
+    if (Elements.isLocal(element)) {
+      useLocal(element);
+    } else if (node.receiver == null &&
+               Elements.isInstanceSend(node, elements)) {
+      useLocal(closureData.thisElement);
+    } else if (node.isSuperCall) {
+      useLocal(closureData.thisElement);
+    } else if (node.isParameterCheck) {
+      Element parameter = elements[node.receiver];
+      FunctionElement enclosing = parameter.enclosingElement;
+      FunctionExpression function = enclosing.parseNode(compiler);
+      ClosureClassMap cached = closureMappingCache[function];
+      if (!cached.parametersWithSentinel.containsKey(parameter)) {
+        SourceString parameterName = parameter.name;
+        String name = '${parameterName.slowToString()}_check';
+        Element newElement = new CheckVariableElement(new SourceString(name),
+                                                      parameter,
+                                                      enclosing);
+        useLocal(newElement);
+        cached.parametersWithSentinel[parameter] = newElement;
+      }
+    }
+    node.visitChildren(this);
+  }
+
+  visitSendSet(SendSet node) {
+    Element element = elements[node];
+    if (Elements.isLocal(element)) {
+      mutatedVariables.add(element);
+    }
+    super.visitSendSet(node);
+  }
+
+  visitNewExpression(NewExpression node) {
+    DartType type = elements.getType(node);
+
+    bool hasTypeVariable(DartType type) {
+      if (type is TypeVariableType) {
+        return true;
+      } else if (type is InterfaceType) {
+        InterfaceType ifcType = type;
+        for (DartType argument in ifcType.typeArguments) {
+          if (hasTypeVariable(argument)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    void analyzeTypeVariables(DartType type) {
+      if (type is TypeVariableType) {
+        useLocal(type.element);
+      } else if (type is InterfaceType) {
+        InterfaceType ifcType = type;
+        for (DartType argument in ifcType.typeArguments) {
+          analyzeTypeVariables(argument);
+        }
+      }
+    }
+    if (outermostElement.isMember() &&
+        compiler.world.needsRti(outermostElement.getEnclosingClass())) {
+      if (outermostElement.isInstanceMember()
+          || outermostElement.isGenerativeConstructor()) {
+        if (hasTypeVariable(type)) useLocal(closureData.thisElement);
+      } else if (outermostElement.isFactoryConstructor()) {
+        analyzeTypeVariables(type);
+      }
+    }
+
+    node.visitChildren(this);
+  }
+
+  // If variables that are declared in the [node] scope are captured and need
+  // to be boxed create a box-element and update the [capturingScopes] in the
+  // current [closureData].
+  // The boxed variables are updated in the [capturedVariableMapping].
+  void attachCapturedScopeVariables(Node node) {
+    Element box = null;
+    Map<Element, Element> scopeMapping = new Map<Element, Element>();
+    for (Element element in scopeVariables) {
+      // No need to box non-assignable elements.
+      if (!element.isAssignable()) continue;
+      if (!mutatedVariables.contains(element)) continue;
+      if (capturedVariableMapping.containsKey(element)) {
+        if (box == null) {
+          // TODO(floitsch): construct better box names.
+          SourceString boxName =
+              namer.getClosureVariableName(const SourceString('box'),
+                                           closureFieldCounter++);
+          box = new BoxElement(boxName, currentElement);
+        }
+        String elementName = element.name.slowToString();
+        SourceString boxedName =
+            namer.getClosureVariableName(new SourceString(elementName),
+                                         boxedFieldCounter++);
+        // TODO(kasperl): Should this be a FieldElement instead?
+        Element boxed = new ElementX(boxedName, ElementKind.FIELD, box);
+        // No need to rename the fields of a box, so we give them a native name
+        // right now.
+        boxed.setFixedBackendName(boxedName.slowToString());
+        scopeMapping[element] = boxed;
+        capturedVariableMapping[element] = boxed;
+      }
+    }
+    if (!scopeMapping.isEmpty) {
+      ClosureScope scope = new ClosureScope(box, scopeMapping);
+      closureData.capturingScopes[node] = scope;
+    }
+  }
+
+  void inNewScope(Node node, Function action) {
+    List<Element> oldScopeVariables = scopeVariables;
+    scopeVariables = new List<Element>();
+    action();
+    attachCapturedScopeVariables(node);
+    for (Element element in scopeVariables) {
+      mutatedVariables.remove(element);
+    }
+    scopeVariables = oldScopeVariables;
+  }
+
+  visitLoop(Loop node) {
+    inNewScope(node, () {
+      node.visitChildren(this);
+    });
+  }
+
+  visitFor(For node) {
+    visitLoop(node);
+    // See if we have declared loop variables that need to be boxed.
+    if (node.initializer == null) return;
+    VariableDefinitions definitions = node.initializer.asVariableDefinitions();
+    if (definitions == null) return;
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    List<Element> result = <Element>[];
+    for (Link<Node> link = definitions.definitions.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      Node definition = link.head;
+      Element element = elements[definition];
+      if (capturedVariableMapping.containsKey(element)) {
+        result.add(element);
+      };
+    }
+    scopeData.boxedLoopVariables = result;
+  }
+
+  /** Returns a non-unique name for the given closure element. */
+  String computeClosureName(Element element) {
+    Link<String> parts = const Link<String>();
+    SourceString ownName = element.name;
+    if (ownName == null || ownName.stringValue == "") {
+      parts = parts.prepend("anon");
+    } else {
+      parts = parts.prepend(ownName.slowToString());
+    }
+    for (Element enclosingElement = element.enclosingElement;
+         enclosingElement != null &&
+             (identical(enclosingElement.kind,
+                        ElementKind.GENERATIVE_CONSTRUCTOR_BODY)
+              || identical(enclosingElement.kind, ElementKind.CLASS)
+              || identical(enclosingElement.kind, ElementKind.FUNCTION)
+              || identical(enclosingElement.kind, ElementKind.GETTER)
+              || identical(enclosingElement.kind, ElementKind.SETTER));
+         enclosingElement = enclosingElement.enclosingElement) {
+      SourceString surroundingName =
+          Elements.operatorNameToIdentifier(enclosingElement.name);
+      parts = parts.prepend(surroundingName.slowToString());
+    }
+    StringBuffer sb = new StringBuffer();
+    parts.printOn(sb, '_');
+    return sb.toString();
+  }
+
+  ClosureClassMap globalizeClosure(FunctionExpression node, Element element) {
+    SourceString closureName = new SourceString(computeClosureName(element));
+    ClassElement globalizedElement = new ClosureClassElement(
+        closureName, compiler, element, element.getCompilationUnit());
+    FunctionElement callElement =
+        new FunctionElementX.from(Compiler.CALL_OPERATOR_NAME,
+                                  element,
+                                  globalizedElement);
+    globalizedElement.addBackendMember(callElement);
+    // The nested function's 'this' is the same as the one for the outer
+    // function. It could be [null] if we are inside a static method.
+    Element thisElement = closureData.thisElement;
+    return new ClosureClassMap(element, globalizedElement,
+                               callElement, thisElement);
+  }
+
+  void visitInvokable(Element element, Expression node, void visitChildren()) {
+    bool oldInsideClosure = insideClosure;
+    Element oldFunctionElement = currentElement;
+    ClosureClassMap oldClosureData = closureData;
+
+    insideClosure = outermostElement != null;
+    currentElement = element;
+    if (insideClosure) {
+      closures.add(node);
+      closureData = globalizeClosure(node, element);
+    } else {
+      outermostElement = element;
+      Element thisElement = null;
+      if (element.isInstanceMember() || element.isGenerativeConstructor()) {
+        thisElement = new ThisElement(element);
+      }
+      closureData = new ClosureClassMap(null, null, null, thisElement);
+    }
+    closureMappingCache[node] = closureData;
+
+    inNewScope(node, () {
+      // We have to declare the implicit 'this' parameter.
+      if (!insideClosure && closureData.thisElement != null) {
+        declareLocal(closureData.thisElement);
+      }
+      // If we are inside a named closure we have to declare ourselve. For
+      // simplicity we declare the local even if the closure does not have a
+      // name.
+      // It will simply not be used.
+      if (insideClosure) {
+        declareLocal(element);
+      }
+
+      if (currentElement.isFactoryConstructor()
+          && compiler.world.needsRti(currentElement.enclosingElement)) {
+        // Declare the type parameters in the scope. Generative
+        // constructors just use 'this'.
+        ClassElement cls = currentElement.enclosingElement;
+        cls.typeVariables.forEach((TypeVariableType typeVariable) {
+          declareLocal(typeVariable.element);
+        });
+      }
+
+      visitChildren();
+    });
+
+
+    ClosureClassMap savedClosureData = closureData;
+    bool savedInsideClosure = insideClosure;
+
+    // Restore old values.
+    insideClosure = oldInsideClosure;
+    closureData = oldClosureData;
+    currentElement = oldFunctionElement;
+
+    // Mark all free variables as captured and use them in the outer function.
+    Iterable<Element> freeVariables = savedClosureData.freeVariableMapping.keys;
+    assert(freeVariables.isEmpty || savedInsideClosure);
+    for (Element freeElement in freeVariables) {
+      if (capturedVariableMapping[freeElement] != null &&
+          capturedVariableMapping[freeElement] != freeElement) {
+        compiler.internalError('In closure analyzer', node: node);
+      }
+      capturedVariableMapping[freeElement] = freeElement;
+      useLocal(freeElement);
+    }
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    Element element = elements[node];
+
+    if (element.isParameter()) {
+      // TODO(ahe): This is a hack. This method should *not* call
+      // visitChildren.
+      return node.name.accept(this);
+    }
+
+    visitInvokable(element, node, () {
+      // TODO(ahe): This is problematic. The backend should not repeat
+      // the work of the resolver. It is the resolver's job to create
+      // parameters, etc. Other phases should only visit statements.
+      if (node.parameters != null) node.parameters.accept(this);
+      if (node.initializers != null) node.initializers.accept(this);
+      if (node.body != null) node.body.accept(this);
+    });
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    node.visitChildren(this);
+    declareLocal(elements[node]);
+  }
+
+  visitTryStatement(TryStatement node) {
+    // TODO(ngeoffray): implement finer grain state.
+    bool oldInTryStatement = inTryStatement;
+    inTryStatement = true;
+    node.visitChildren(this);
+    inTryStatement = oldInTryStatement;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart
new file mode 100644
index 0000000..c3465e8
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart
@@ -0,0 +1,106 @@
+// 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.
+
+part of dart2js;
+
+class CodeBuffer implements StringBuffer {
+  StringBuffer buffer;
+  List<CodeBufferMarker> markers;
+  int lastBufferOffset = 0;
+  int mappedRangeCounter = 0;
+
+  CodeBuffer()
+      : buffer = new StringBuffer(),
+        markers = new List<CodeBufferMarker>();
+
+  int get length => buffer.length;
+
+  bool get isEmpty {
+    return buffer.isEmpty;
+  }
+
+  /**
+   * Converts [object] to a string and adds it to the buffer. If [object] is a
+   * [CodeBuffer], adds its markers to [markers].
+   */
+  CodeBuffer add(var object) {
+    if (object is CodeBuffer) {
+      return addBuffer(object);
+    }
+    if (mappedRangeCounter == 0) setSourceLocation(null);
+    buffer.add(object.toString());
+    return this;
+  }
+
+  CodeBuffer addBuffer(CodeBuffer other) {
+    if (other.markers.length > 0) {
+      CodeBufferMarker firstMarker = other.markers[0];
+      int offsetDelta =
+          buffer.length + firstMarker.offsetDelta - lastBufferOffset;
+      markers.add(new CodeBufferMarker(offsetDelta,
+                                       firstMarker.sourcePosition));
+      for (int i = 1; i < other.markers.length; ++i) {
+        markers.add(other.markers[i]);
+      }
+      lastBufferOffset = buffer.length + other.lastBufferOffset;
+    }
+    buffer.add(other.getText());
+  }
+
+  CodeBuffer addAll(Iterable<Object> iterable) {
+    for (Object obj in iterable) {
+      add(obj);
+    }
+    return this;
+  }
+
+  CodeBuffer addCharCode(int charCode) {
+    return add(new String.fromCharCodes([charCode]));
+  }
+
+  CodeBuffer clear() {
+    buffer.clear();
+    markers.clear();
+    lastBufferOffset = 0;
+    return this;
+  }
+
+  String toString() {
+    throw "Don't use CodeBuffer.toString() since it drops sourcemap data.";
+  }
+
+  String getText() {
+    return buffer.toString();
+  }
+
+  void beginMappedRange() {
+    ++mappedRangeCounter;
+  }
+
+  void endMappedRange() {
+    assert(mappedRangeCounter > 0);
+    --mappedRangeCounter;
+  }
+
+  void setSourceLocation(var sourcePosition) {
+    int offsetDelta = buffer.length - lastBufferOffset;
+    markers.add(new CodeBufferMarker(offsetDelta, sourcePosition));
+    lastBufferOffset = buffer.length;
+  }
+
+  void forEachSourceLocation(void f(int targetOffset, var sourcePosition)) {
+    int targetOffset = 0;
+    markers.forEach((marker) {
+      targetOffset += marker.offsetDelta;
+      f(targetOffset, marker.sourcePosition);
+    });
+  }
+}
+
+class CodeBufferMarker {
+  final int offsetDelta;
+  final sourcePosition;
+
+  CodeBufferMarker(this.offsetDelta, this.sourcePosition);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/colors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/colors.dart
new file mode 100644
index 0000000..42b3e7f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/colors.dart
@@ -0,0 +1,15 @@
+// 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.
+
+library colors;
+
+const String GREEN_COLOR = '\u001b[32m';
+const String RED_COLOR = '\u001b[31m';
+const String MAGENTA_COLOR = '\u001b[35m';
+const String NO_COLOR = '\u001b[0m';
+
+String wrap(String string, String color) => "${color}$string${NO_COLOR}";
+String green(String string) => wrap(string, GREEN_COLOR);
+String red(String string) => wrap(string, RED_COLOR);
+String magenta(String string) => wrap(string, MAGENTA_COLOR);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart b/pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart
new file mode 100644
index 0000000..691cbac
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart
@@ -0,0 +1,888 @@
+// 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.
+
+part of dart2js;
+
+/**
+ * The [ConstantHandler] keeps track of compile-time constants,
+ * initializations of global and static fields, and default values of
+ * optional parameters.
+ */
+class ConstantHandler extends CompilerTask {
+  final ConstantSystem constantSystem;
+  final bool isMetadata;
+
+  /**
+   * Contains the initial value of fields. Must contain all static and global
+   * initializations of const fields. May contain eagerly compiled values for
+   * statics and instance fields.
+   */
+  final Map<VariableElement, Constant> initialVariableValues;
+
+  /** Set of all registered compiled constants. */
+  final Set<Constant> compiledConstants;
+
+  /** The set of variable elements that are in the process of being computed. */
+  final Set<VariableElement> pendingVariables;
+
+  /** Caches the statics where the initial value cannot be eagerly compiled. */
+  final Set<VariableElement> lazyStatics;
+
+  /** Caches the createRuntimeType function if registered. */
+  Element createRuntimeTypeFunction = null;
+
+  ConstantHandler(Compiler compiler, this.constantSystem,
+                  { bool this.isMetadata: false })
+      : initialVariableValues = new Map<VariableElement, dynamic>(),
+        compiledConstants = new Set<Constant>(),
+        pendingVariables = new Set<VariableElement>(),
+        lazyStatics = new Set<VariableElement>(),
+        super(compiler);
+
+  String get name => 'ConstantHandler';
+
+  void registerCompileTimeConstant(Constant constant) {
+    registerInstantiatedClass(constant.computeType(compiler).element);
+    if (constant.isFunction()) {
+      FunctionConstant function = constant;
+      registerGetOfStaticFunction(function.element);
+    }
+    compiledConstants.add(constant);
+  }
+
+  void registerInstantiatedClass(ClassElement element) {
+    if (isMetadata) return;
+    compiler.enqueuer.codegen.registerInstantiatedClass(element);
+  }
+
+  void registerStaticUse(Element element) {
+    if (isMetadata) return;
+    compiler.enqueuer.codegen.registerStaticUse(element);
+  }
+
+  void registerGetOfStaticFunction(FunctionElement element) {
+    if (isMetadata) return;
+    compiler.enqueuer.codegen.registerGetOfStaticFunction(element);
+  }
+
+  void registerStringInstance() {
+    registerInstantiatedClass(compiler.stringClass);
+  }
+
+  void registerCreateRuntimeTypeFunction() {
+    if (createRuntimeTypeFunction != null) return;
+    SourceString helperName = const SourceString('createRuntimeType');
+    createRuntimeTypeFunction = compiler.findHelper(helperName);
+    registerStaticUse(createRuntimeTypeFunction);
+  }
+
+  /**
+   * Compiles the initial value of the given field and stores it in an internal
+   * map. Returns the initial value (a constant) if it can be computed
+   * statically. Returns [:null:] if the variable must be initialized lazily.
+   *
+   * [work] must contain a [VariableElement] refering to a global or
+   * static field.
+   */
+  Constant compileWorkItem(CodegenWorkItem work) {
+    return measure(() {
+      assert(work.element.kind == ElementKind.FIELD
+             || work.element.kind == ElementKind.PARAMETER
+             || work.element.kind == ElementKind.FIELD_PARAMETER);
+      VariableElement element = work.element;
+      // Shortcut if it has already been compiled.
+      Constant result = initialVariableValues[element];
+      if (result != null) return result;
+      if (lazyStatics.contains(element)) return null;
+      result = compileVariableWithDefinitions(element, work.resolutionTree);
+      assert(pendingVariables.isEmpty);
+      return result;
+    });
+  }
+
+  /**
+   * Returns a compile-time constant, or reports an error if the element is not
+   * a compile-time constant.
+   */
+  Constant compileConstant(VariableElement element) {
+    return compileVariable(element, isConst: true);
+  }
+
+  /**
+   * Returns the a compile-time constant if the variable could be compiled
+   * eagerly. Otherwise returns `null`.
+   */
+  Constant compileVariable(VariableElement element, {bool isConst: false}) {
+    return measure(() {
+      if (initialVariableValues.containsKey(element)) {
+        Constant result = initialVariableValues[element];
+        return result;
+      }
+      return compiler.withCurrentElement(element, () {
+        TreeElements definitions = compiler.analyzeElement(element);
+        Constant constant = compileVariableWithDefinitions(
+            element, definitions, isConst: isConst);
+        return constant;
+      });
+    });
+  }
+
+  /**
+   * Returns the a compile-time constant if the variable could be compiled
+   * eagerly. If the variable needs to be initialized lazily returns `null`.
+   * If the variable is `const` but cannot be compiled eagerly reports an
+   * error.
+   */
+  Constant compileVariableWithDefinitions(VariableElement element,
+                                          TreeElements definitions,
+                                          {bool isConst: false}) {
+    return measure(() {
+      // Initializers for parameters must be const.
+      isConst = isConst || element.modifiers.isConst()
+          || !Elements.isStaticOrTopLevel(element);
+      if (!isConst && lazyStatics.contains(element)) return null;
+
+      Node node = element.parseNode(compiler);
+      if (pendingVariables.contains(element)) {
+        if (isConst) {
+          MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS;
+          compiler.reportError(node,
+                               new CompileTimeConstantError(kind));
+        } else {
+          lazyStatics.add(element);
+          return null;
+        }
+      }
+      pendingVariables.add(element);
+
+      SendSet assignment = node.asSendSet();
+      Constant value;
+      if (assignment == null) {
+        // No initial value.
+        value = new NullConstant();
+      } else {
+        Node right = assignment.arguments.head;
+        value =
+            compileNodeWithDefinitions(right, definitions, isConst: isConst);
+        if (compiler.enableTypeAssertions
+            && value != null
+            && element.isField()) {
+          DartType elementType = element.computeType(compiler);
+          DartType constantType = value.computeType(compiler);
+          if (elementType.isMalformed || constantType.isMalformed ||
+              !constantSystem.isSubtype(compiler, constantType, elementType)) {
+            if (isConst) {
+              compiler.reportError(node, new CompileTimeConstantError(
+                  MessageKind.NOT_ASSIGNABLE,
+                  {'fromType': elementType, 'toType': constantType}));
+            } else {
+              // If the field can be lazily initialized, we will throw
+              // the exception at runtime.
+              value = null;
+            }
+          }
+        }
+      }
+      if (value != null) {
+        initialVariableValues[element] = value;
+      } else {
+        assert(!isConst);
+        lazyStatics.add(element);
+      }
+      pendingVariables.remove(element);
+      return value;
+    });
+  }
+
+  Constant compileNodeWithDefinitions(Node node,
+                                      TreeElements definitions,
+                                      {bool isConst: false}) {
+    return measure(() {
+      assert(node != null);
+      CompileTimeConstantEvaluator evaluator = new CompileTimeConstantEvaluator(
+          this, definitions, compiler, isConst: isConst);
+      return evaluator.evaluate(node);
+    });
+  }
+
+  /** Attempts to compile a constant expression. Returns null if not possible */
+  Constant tryCompileNodeWithDefinitions(Node node, TreeElements definitions) {
+    return measure(() {
+      assert(node != null);
+      try {
+        TryCompileTimeConstantEvaluator evaluator =
+            new TryCompileTimeConstantEvaluator(this, definitions, compiler);
+        return evaluator.evaluate(node);
+      } on CompileTimeConstantError catch (exn) {
+        return null;
+      }
+    });
+  }
+
+  /**
+   * Returns an [Iterable] of static non final fields that need to be
+   * initialized. The fields list must be evaluated in order since they might
+   * depend on each other.
+   */
+  Iterable<VariableElement> getStaticNonFinalFieldsForEmission() {
+    return initialVariableValues.keys.where((element) {
+      return element.kind == ElementKind.FIELD
+          && !element.isInstanceMember()
+          && !element.modifiers.isFinal()
+          // The const fields are all either emitted elsewhere or inlined.
+          && !element.modifiers.isConst();
+    });
+  }
+
+  /**
+   * Returns an [Iterable] of static const fields that need to be initialized.
+   * The fields must be evaluated in order since they might depend on each
+   * other.
+   */
+  Iterable<VariableElement> getStaticFinalFieldsForEmission() {
+    return initialVariableValues.keys.where((element) {
+      return element.kind == ElementKind.FIELD
+          && !element.isInstanceMember()
+          && element.modifiers.isFinal();
+    });
+  }
+
+  List<VariableElement> getLazilyInitializedFieldsForEmission() {
+    return new List<VariableElement>.from(lazyStatics);
+  }
+
+  List<Constant> getConstantsForEmission() {
+    // We must emit dependencies before their uses.
+    Set<Constant> seenConstants = new Set<Constant>();
+    List<Constant> result = new List<Constant>();
+
+    void addConstant(Constant constant) {
+      if (!seenConstants.contains(constant)) {
+        constant.getDependencies().forEach(addConstant);
+        assert(!seenConstants.contains(constant));
+        result.add(constant);
+        seenConstants.add(constant);
+      }
+    }
+
+    compiledConstants.forEach(addConstant);
+    return result;
+  }
+
+  Constant getInitialValueFor(VariableElement element) {
+    Constant initialValue = initialVariableValues[element];
+    if (initialValue == null) {
+      compiler.internalError("No initial value for given element",
+                             element: element);
+    }
+    return initialValue;
+  }
+}
+
+class CompileTimeConstantEvaluator extends Visitor {
+  bool isEvaluatingConstant;
+  final ConstantHandler handler;
+  final TreeElements elements;
+  final Compiler compiler;
+
+  CompileTimeConstantEvaluator(this.handler,
+                               this.elements,
+                               this.compiler,
+                               {bool isConst: false})
+      : this.isEvaluatingConstant = isConst;
+
+  ConstantSystem get constantSystem => handler.constantSystem;
+
+  Constant evaluate(Node node) {
+    return node.accept(this);
+  }
+
+  Constant evaluateConstant(Node node) {
+    bool oldIsEvaluatingConstant = isEvaluatingConstant;
+    isEvaluatingConstant = true;
+    Constant result = node.accept(this);
+    isEvaluatingConstant = oldIsEvaluatingConstant;
+    assert(result != null);
+    return result;
+  }
+
+  Constant visitNode(Node node) {
+    return signalNotCompileTimeConstant(node);
+  }
+
+  Constant visitLiteralBool(LiteralBool node) {
+    handler.registerInstantiatedClass(compiler.boolClass);
+    return constantSystem.createBool(node.value);
+  }
+
+  Constant visitLiteralDouble(LiteralDouble node) {
+    handler.registerInstantiatedClass(compiler.doubleClass);
+    return constantSystem.createDouble(node.value);
+  }
+
+  Constant visitLiteralInt(LiteralInt node) {
+    handler.registerInstantiatedClass(compiler.intClass);
+    return constantSystem.createInt(node.value);
+  }
+
+  Constant visitLiteralList(LiteralList node) {
+    if (!node.isConst())  {
+      return signalNotCompileTimeConstant(node);
+    }
+    List<Constant> arguments = <Constant>[];
+    for (Link<Node> link = node.elements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      arguments.add(evaluateConstant(link.head));
+    }
+    // TODO(floitsch): get type parameters.
+    DartType type = new InterfaceType(compiler.listClass);
+    Constant constant = new ListConstant(type, arguments);
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  Constant visitLiteralMap(LiteralMap node) {
+    if (!node.isConst()) {
+      return signalNotCompileTimeConstant(node);
+    }
+    List<StringConstant> keys = <StringConstant>[];
+    Map<StringConstant, Constant> map = new Map<StringConstant, Constant>();
+    for (Link<Node> link = node.entries.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      LiteralMapEntry entry = link.head;
+      Constant key = evaluateConstant(entry.key);
+      if (!key.isString() || entry.key.asStringNode() == null) {
+        MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL;
+        compiler.reportError(entry.key, new ResolutionError(kind));
+      }
+      StringConstant keyConstant = key;
+      if (!map.containsKey(key)) keys.add(key);
+      map[key] = evaluateConstant(entry.value);
+    }
+    List<Constant> values = <Constant>[];
+    Constant protoValue = null;
+    for (StringConstant key in keys) {
+      if (key.value == MapConstant.PROTO_PROPERTY) {
+        protoValue = map[key];
+      } else {
+        values.add(map[key]);
+      }
+    }
+    bool hasProtoKey = (protoValue != null);
+    // TODO(floitsch): this should be a List<String> type.
+    DartType keysType = new InterfaceType(compiler.listClass);
+    ListConstant keysList = new ListConstant(keysType, keys);
+    handler.registerCompileTimeConstant(keysList);
+    SourceString className = hasProtoKey
+                             ? MapConstant.DART_PROTO_CLASS
+                             : MapConstant.DART_CLASS;
+    ClassElement classElement = compiler.jsHelperLibrary.find(className);
+    classElement.ensureResolved(compiler);
+    // TODO(floitsch): copy over the generic type.
+    DartType type = new InterfaceType(classElement);
+    handler.registerInstantiatedClass(classElement);
+    Constant constant = new MapConstant(type, keysList, values, protoValue);
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  Constant visitLiteralNull(LiteralNull node) {
+    return constantSystem.createNull();
+  }
+
+  Constant visitLiteralString(LiteralString node) {
+    handler.registerStringInstance();
+    return constantSystem.createString(node.dartString, node);
+  }
+
+  Constant visitStringJuxtaposition(StringJuxtaposition node) {
+    StringConstant left = evaluate(node.first);
+    StringConstant right = evaluate(node.second);
+    if (left == null || right == null) return null;
+    handler.registerStringInstance();
+    return constantSystem.createString(
+        new DartString.concat(left.value, right.value), node);
+  }
+
+  Constant visitStringInterpolation(StringInterpolation node) {
+    StringConstant initialString = evaluate(node.string);
+    if (initialString == null) return null;
+    DartString accumulator = initialString.value;
+    for (StringInterpolationPart part in node.parts) {
+      Constant expression = evaluate(part.expression);
+      DartString expressionString;
+      if (expression == null) {
+        return signalNotCompileTimeConstant(part.expression);
+      } else if (expression.isNum() || expression.isBool()) {
+        PrimitiveConstant primitive = expression;
+        expressionString = new DartString.literal(primitive.value.toString());
+      } else if (expression.isString()) {
+        PrimitiveConstant primitive = expression;
+        expressionString = primitive.value;
+      } else {
+        return signalNotCompileTimeConstant(part.expression);
+      }
+      accumulator = new DartString.concat(accumulator, expressionString);
+      StringConstant partString = evaluate(part.string);
+      if (partString == null) return null;
+      accumulator = new DartString.concat(accumulator, partString.value);
+    };
+    handler.registerStringInstance();
+    return constantSystem.createString(accumulator, node);
+  }
+
+  Constant makeTypeConstant(Element element) {
+    DartType elementType = element.computeType(compiler).asRaw();
+    DartType constantType = compiler.typeClass.computeType(compiler);
+    Constant constant = new TypeConstant(elementType, constantType);
+    // If we use a type literal in a constant, the compile time
+    // constant emitter will generate a call to the createRuntimeType
+    // helper so we register a use of that.
+    handler.registerCreateRuntimeTypeFunction();
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  // TODO(floitsch): provide better error-messages.
+  Constant visitSend(Send send) {
+    Element element = elements[send];
+    if (send.isPropertyAccess) {
+      if (Elements.isStaticOrTopLevelFunction(element)) {
+        Constant constant = new FunctionConstant(element);
+        handler.registerCompileTimeConstant(constant);
+        return constant;
+      } else if (Elements.isStaticOrTopLevelField(element)) {
+        Constant result;
+        if (element.modifiers.isConst()) {
+          result = handler.compileConstant(element);
+        } else if (element.modifiers.isFinal() && !isEvaluatingConstant) {
+          result = handler.compileVariable(element);
+        }
+        if (result != null) return result;
+      } else if (Elements.isClass(element) || Elements.isTypedef(element)) {
+        return makeTypeConstant(element);
+      } else if (send.receiver != null) {
+        // Fall through to error handling.
+      } else if (!Elements.isUnresolved(element)
+                 && element.isVariable()
+                 && element.modifiers.isConst()) {
+        Constant result = handler.compileConstant(element);
+        if (result != null) return result;
+      }
+      return signalNotCompileTimeConstant(send);
+    } else if (send.isCall) {
+      if (identical(element, compiler.identicalFunction)
+          && send.argumentCount() == 2) {
+        Constant left = evaluate(send.argumentsNode.nodes.head);
+        Constant right = evaluate(send.argumentsNode.nodes.tail.head);
+        Constant result = constantSystem.identity.fold(left, right);
+        if (result != null) return result;
+      } else if (Elements.isClass(element) || Elements.isTypedef(element)) {
+        return makeTypeConstant(element);
+      }
+      return signalNotCompileTimeConstant(send);
+    } else if (send.isPrefix) {
+      assert(send.isOperator);
+      Constant receiverConstant = evaluate(send.receiver);
+      if (receiverConstant == null) return null;
+      Operator op = send.selector;
+      Constant folded;
+      switch (op.source.stringValue) {
+        case "!":
+          folded = constantSystem.not.fold(receiverConstant);
+          break;
+        case "-":
+          folded = constantSystem.negate.fold(receiverConstant);
+          break;
+        case "~":
+          folded = constantSystem.bitNot.fold(receiverConstant);
+          break;
+        default:
+          compiler.internalError("Unexpected operator.", node: op);
+          break;
+      }
+      if (folded == null) return signalNotCompileTimeConstant(send);
+      return folded;
+    } else if (send.isOperator && !send.isPostfix) {
+      assert(send.argumentCount() == 1);
+      Constant left = evaluate(send.receiver);
+      Constant right = evaluate(send.argumentsNode.nodes.head);
+      if (left == null || right == null) return null;
+      Operator op = send.selector.asOperator();
+      Constant folded = null;
+      switch (op.source.stringValue) {
+        case "+":
+          folded = constantSystem.add.fold(left, right);
+          break;
+        case "-":
+          folded = constantSystem.subtract.fold(left, right);
+          break;
+        case "*":
+          folded = constantSystem.multiply.fold(left, right);
+          break;
+        case "/":
+          folded = constantSystem.divide.fold(left, right);
+          break;
+        case "%":
+          folded = constantSystem.modulo.fold(left, right);
+          break;
+        case "~/":
+          folded = constantSystem.truncatingDivide.fold(left, right);
+          break;
+        case "|":
+          folded = constantSystem.bitOr.fold(left, right);
+          break;
+        case "&":
+          folded = constantSystem.bitAnd.fold(left, right);
+          break;
+        case "^":
+          folded = constantSystem.bitXor.fold(left, right);
+          break;
+        case "||":
+          folded = constantSystem.booleanOr.fold(left, right);
+          break;
+        case "&&":
+          folded = constantSystem.booleanAnd.fold(left, right);
+          break;
+        case "<<":
+          folded = constantSystem.shiftLeft.fold(left, right);
+          break;
+        case ">>":
+          folded = constantSystem.shiftRight.fold(left, right);
+          break;
+        case "<":
+          folded = constantSystem.less.fold(left, right);
+          break;
+        case "<=":
+          folded = constantSystem.lessEqual.fold(left, right);
+          break;
+        case ">":
+          folded = constantSystem.greater.fold(left, right);
+          break;
+        case ">=":
+          folded = constantSystem.greaterEqual.fold(left, right);
+          break;
+        case "==":
+          if (left.isPrimitive() && right.isPrimitive()) {
+            folded = constantSystem.equal.fold(left, right);
+          }
+          break;
+        case "===":
+          folded = constantSystem.identity.fold(left, right);
+          break;
+        case "!=":
+          if (left.isPrimitive() && right.isPrimitive()) {
+            BoolConstant areEquals = constantSystem.equal.fold(left, right);
+            if (areEquals == null) {
+              folded = null;
+            } else {
+              folded = areEquals.negate();
+            }
+          }
+          break;
+        case "!==":
+          BoolConstant areIdentical =
+              constantSystem.identity.fold(left, right);
+          if (areIdentical == null) {
+            folded = null;
+          } else {
+            folded = areIdentical.negate();
+          }
+          break;
+      }
+      if (folded == null) return signalNotCompileTimeConstant(send);
+      return folded;
+    }
+    return signalNotCompileTimeConstant(send);
+  }
+
+  Constant visitSendSet(SendSet node) {
+    return signalNotCompileTimeConstant(node);
+  }
+
+  /**
+   * Returns the list of constants that are passed to the static function.
+   *
+   * Invariant: [target] must be an implementation element.
+   */
+  List<Constant> evaluateArgumentsToConstructor(Node node,
+                                                Selector selector,
+                                                Link<Node> arguments,
+                                                FunctionElement target) {
+    assert(invariant(node, target.isImplementation));
+    List<Constant> compiledArguments = <Constant>[];
+
+    Function compileArgument = evaluateConstant;
+    Function compileConstant = handler.compileConstant;
+    bool succeeded = selector.addArgumentsToList(arguments,
+                                                 compiledArguments,
+                                                 target,
+                                                 compileArgument,
+                                                 compileConstant,
+                                                 compiler);
+    if (!succeeded) {
+      MessageKind kind = MessageKind.INVALID_ARGUMENTS;
+      compiler.reportError(node,
+          new CompileTimeConstantError(kind, {'methodName': target.name}));
+    }
+    return compiledArguments;
+  }
+
+  Constant visitNewExpression(NewExpression node) {
+    if (!node.isConst()) {
+      return signalNotCompileTimeConstant(node);
+    }
+
+    Send send = node.send;
+    FunctionElement constructor = elements[send];
+    constructor = constructor.redirectionTarget;
+    ClassElement classElement = constructor.getEnclosingClass();
+    if (classElement.isInterface()) {
+      compiler.resolver.resolveMethodElement(constructor);
+      constructor = constructor.defaultImplementation;
+      classElement = constructor.getEnclosingClass();
+    }
+    // The constructor must be an implementation to ensure that field
+    // initializers are handled correctly.
+    constructor = constructor.implementation;
+    assert(invariant(node, constructor.isImplementation));
+
+    Selector selector = elements.getSelector(send);
+    List<Constant> arguments = evaluateArgumentsToConstructor(
+        node, selector, send.arguments, constructor);
+    ConstructorEvaluator evaluator =
+        new ConstructorEvaluator(node, constructor, handler, compiler);
+    evaluator.evaluateConstructorFieldValues(arguments);
+    List<Constant> jsNewArguments = evaluator.buildJsNewArguments(classElement);
+
+    handler.registerInstantiatedClass(classElement);
+    // TODO(floitsch): take generic types into account.
+    DartType type = classElement.computeType(compiler);
+    Constant constant = new ConstructedConstant(type, jsNewArguments);
+    handler.registerCompileTimeConstant(constant);
+    return constant;
+  }
+
+  Constant visitParenthesizedExpression(ParenthesizedExpression node) {
+    return node.expression.accept(this);
+  }
+
+  error(Node node) {
+    // TODO(floitsch): get the list of constants that are currently compiled
+    // and present some kind of stack-trace.
+    MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT;
+    compiler.reportError(node, new CompileTimeConstantError(kind));
+  }
+
+  Constant signalNotCompileTimeConstant(Node node) {
+    if (isEvaluatingConstant) {
+      error(node);
+    }
+    // Else we don't need to do anything. The final handler is only
+    // optimistically trying to compile constants. So it is normal that we
+    // sometimes see non-compile time constants.
+    // Simply return [:null:] which is used to propagate a failing
+    // compile-time compilation.
+    return null;
+  }
+}
+
+class TryCompileTimeConstantEvaluator extends CompileTimeConstantEvaluator {
+  TryCompileTimeConstantEvaluator(ConstantHandler handler,
+                                  TreeElements elements,
+                                  Compiler compiler)
+      : super(handler, elements, compiler, isConst: true);
+
+  error(Node node) {
+    // Just fail without reporting it anywhere.
+    throw new CompileTimeConstantError(
+        MessageKind.NOT_A_COMPILE_TIME_CONSTANT);
+  }
+}
+
+class ConstructorEvaluator extends CompileTimeConstantEvaluator {
+  final FunctionElement constructor;
+  final Map<Element, Constant> definitions;
+  final Map<Element, Constant> fieldValues;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [constructor] must be an implementation element.
+   */
+  ConstructorEvaluator(Node node,
+                       FunctionElement constructor,
+                       ConstantHandler handler,
+                       Compiler compiler)
+      : this.constructor = constructor,
+        this.definitions = new Map<Element, Constant>(),
+        this.fieldValues = new Map<Element, Constant>(),
+        super(handler,
+              compiler.resolver.resolveMethodElement(constructor.declaration),
+              compiler,
+              isConst: true) {
+    assert(invariant(node, constructor.isImplementation));
+  }
+
+  Constant visitSend(Send send) {
+    Element element = elements[send];
+    if (Elements.isLocal(element)) {
+      Constant constant = definitions[element];
+      if (constant == null) {
+        compiler.internalError("Local variable without value", node: send);
+      }
+      return constant;
+    }
+    return super.visitSend(send);
+  }
+
+  void potentiallyCheckType(Node node, Element element, Constant constant) {
+    if (compiler.enableTypeAssertions) {
+      DartType elementType = element.computeType(compiler);
+      DartType constantType = constant.computeType(compiler);
+      // TODO(ngeoffray): Handle type parameters.
+      if (elementType.element.isTypeVariable()) return;
+      if (elementType.isMalformed || constantType.isMalformed ||
+          !constantSystem.isSubtype(compiler, constantType, elementType)) {
+        compiler.reportError(node, new CompileTimeConstantError(
+            MessageKind.NOT_ASSIGNABLE,
+            {'fromType': elementType, 'toType': constantType}));
+      }
+    }
+  }
+
+  void updateFieldValue(Node node, Element element, Constant constant) {
+    potentiallyCheckType(node, element, constant);
+    fieldValues[element] = constant;
+  }
+
+  /**
+   * Given the arguments (a list of constants) assigns them to the parameters,
+   * updating the definitions map. If the constructor has field-initializer
+   * parameters (like [:this.x:]), also updates the [fieldValues] map.
+   */
+  void assignArgumentsToParameters(List<Constant> arguments) {
+    // Assign arguments to parameters.
+    FunctionSignature parameters = constructor.computeSignature(compiler);
+    int index = 0;
+    parameters.orderedForEachParameter((Element parameter) {
+      Constant argument = arguments[index++];
+      Node node = parameter.parseNode(compiler);
+      potentiallyCheckType(node, parameter, argument);
+      definitions[parameter] = argument;
+      if (parameter.kind == ElementKind.FIELD_PARAMETER) {
+        FieldParameterElement fieldParameterElement = parameter;
+        updateFieldValue(node, fieldParameterElement.fieldElement, argument);
+      }
+    });
+  }
+
+  void evaluateSuperOrRedirectSend(Node currentNode,
+                                   Selector selector,
+                                   Link<Node> arguments,
+                                   FunctionElement targetConstructor) {
+    List<Constant> compiledArguments = evaluateArgumentsToConstructor(
+        currentNode, selector, arguments, targetConstructor);
+
+    ConstructorEvaluator evaluator = new ConstructorEvaluator(
+        currentNode, targetConstructor, handler, compiler);
+    evaluator.evaluateConstructorFieldValues(compiledArguments);
+    // Copy over the fieldValues from the super/redirect-constructor.
+    // No need to go through [updateFieldValue] because the
+    // assignments have already been checked in checked mode.
+    evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value);
+  }
+
+  /**
+   * Runs through the initializers of the given [constructor] and updates
+   * the [fieldValues] map.
+   */
+  void evaluateConstructorInitializers() {
+    FunctionExpression functionNode = constructor.parseNode(compiler);
+    NodeList initializerList = functionNode.initializers;
+
+    bool foundSuperOrRedirect = false;
+
+    if (initializerList != null) {
+      for (Link<Node> link = initializerList.nodes;
+           !link.isEmpty;
+           link = link.tail) {
+        assert(link.head is Send);
+        if (link.head is !SendSet) {
+          // A super initializer or constructor redirection.
+          Send call = link.head;
+          FunctionElement targetConstructor = elements[call];
+          Selector selector = elements.getSelector(call);
+          Link<Node> arguments = call.arguments;
+          evaluateSuperOrRedirectSend(
+              call, selector, arguments, targetConstructor);
+          foundSuperOrRedirect = true;
+        } else {
+          // A field initializer.
+          SendSet init = link.head;
+          Link<Node> initArguments = init.arguments;
+          assert(!initArguments.isEmpty && initArguments.tail.isEmpty);
+          Constant fieldValue = evaluate(initArguments.head);
+          updateFieldValue(init, elements[init], fieldValue);
+        }
+      }
+    }
+
+    if (!foundSuperOrRedirect) {
+      // No super initializer found. Try to find the default constructor if
+      // the class is not Object.
+      ClassElement enclosingClass = constructor.getEnclosingClass();
+      ClassElement superClass = enclosingClass.superclass;
+      if (enclosingClass != compiler.objectClass) {
+        assert(superClass != null);
+        assert(superClass.resolutionState == STATE_DONE);
+
+        Selector selector =
+            new Selector.callDefaultConstructor(enclosingClass.getLibrary());
+
+        FunctionElement targetConstructor =
+            superClass.lookupConstructor(selector);
+        if (targetConstructor == null) {
+          compiler.internalError("no default constructor available",
+                                 node: functionNode);
+        }
+
+        evaluateSuperOrRedirectSend(functionNode,
+                                    selector,
+                                    const Link<Node>(),
+                                    targetConstructor);
+      }
+    }
+  }
+
+  /**
+   * Simulates the execution of the [constructor] with the given
+   * [arguments] to obtain the field values that need to be passed to the
+   * native JavaScript constructor.
+   */
+  void evaluateConstructorFieldValues(List<Constant> arguments) {
+    compiler.withCurrentElement(constructor, () {
+      assignArgumentsToParameters(arguments);
+      evaluateConstructorInitializers();
+    });
+  }
+
+  List<Constant> buildJsNewArguments(ClassElement classElement) {
+    List<Constant> jsNewArguments = <Constant>[];
+    classElement.implementation.forEachInstanceField(
+        (ClassElement enclosing, Element field) {
+          Constant fieldValue = fieldValues[field];
+          if (fieldValue == null) {
+            // Use the default value.
+            fieldValue = handler.compileConstant(field);
+          }
+          jsNewArguments.add(fieldValue);
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: true);
+    return jsNewArguments;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart b/pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart
new file mode 100644
index 0000000..4fef805
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart
@@ -0,0 +1,1130 @@
+// 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.
+
+part of dart2js;
+
+/**
+ * If true, print a warning for each method that was resolved, but not
+ * compiled.
+ */
+const bool REPORT_EXCESS_RESOLUTION = false;
+
+/**
+ * If true, dump the inferred types after compilation.
+ */
+const bool DUMP_INFERRED_TYPES = false;
+
+/**
+ * A string to identify the revision or build.
+ *
+ * This ID is displayed if the compiler crashes and in verbose mode, and is
+ * an aid in reproducing bug reports.
+ *
+ * The actual string is rewritten during the SDK build process.
+ */
+const String BUILD_ID = '0.3.5.1_r18300';
+
+/**
+ * Contains backend-specific data that is used throughout the compilation of
+ * one work item.
+ */
+class ItemCompilationContext {
+}
+
+abstract class WorkItem {
+  final ItemCompilationContext compilationContext;
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  final Element element;
+  TreeElements get resolutionTree;
+
+  WorkItem(this.element, this.compilationContext) {
+    assert(invariant(element, element.isDeclaration));
+  }
+
+  bool isAnalyzed() => resolutionTree != null;
+
+  void run(Compiler compiler, Enqueuer world);
+}
+
+/// [WorkItem] used exclusively by the [ResolutionEnqueuer].
+class ResolutionWorkItem extends WorkItem {
+  TreeElements resolutionTree;
+
+  ResolutionWorkItem(Element element,
+                     ItemCompilationContext compilationContext)
+      : super(element, compilationContext);
+
+  void run(Compiler compiler, ResolutionEnqueuer world) {
+    resolutionTree = compiler.analyze(this, world);
+  }
+}
+
+/// [WorkItem] used exclusively by the [CodegenEnqueuer].
+class CodegenWorkItem extends WorkItem {
+  final TreeElements resolutionTree;
+
+  bool allowSpeculativeOptimization = true;
+  List<HTypeGuard> guards = const <HTypeGuard>[];
+
+  CodegenWorkItem(Element element,
+                  TreeElements this.resolutionTree,
+                  ItemCompilationContext compilationContext)
+      : super(element, compilationContext) {
+    assert(invariant(element, resolutionTree != null,
+        message: 'Resolution tree is null for $element in codegen work item'));
+  }
+
+  void run(Compiler compiler, CodegenEnqueuer world) {
+    if (world.isProcessed(element)) return;
+    compiler.codegen(this, world);
+  }
+}
+
+class ReadingFilesTask extends CompilerTask {
+  ReadingFilesTask(Compiler compiler) : super(compiler);
+  String get name => 'Reading input files';
+}
+
+abstract class Backend {
+  final Compiler compiler;
+  final ConstantSystem constantSystem;
+
+  Backend(this.compiler,
+          [ConstantSystem constantSystem = DART_CONSTANT_SYSTEM])
+      : this.constantSystem = constantSystem;
+
+  void enqueueAllTopLevelFunctions(LibraryElement lib, Enqueuer world) {
+    lib.forEachExport((Element e) {
+      if (e.isFunction()) world.addToWorkList(e);
+    });
+  }
+
+  void enqueueHelpers(ResolutionEnqueuer world);
+  void codegen(CodegenWorkItem work);
+
+  // The backend determines the native resolution enqueuer, with a no-op
+  // default, so tools like dart2dart can ignore the native classes.
+  native.NativeEnqueuer nativeResolutionEnqueuer(world) {
+    return new native.NativeEnqueuer();
+  }
+  native.NativeEnqueuer nativeCodegenEnqueuer(world) {
+    return new native.NativeEnqueuer();
+  }
+
+  void assembleProgram();
+  List<CompilerTask> get tasks;
+
+  // TODO(ahe,karlklose): rename this?
+  void dumpInferredTypes() {}
+
+  ItemCompilationContext createItemCompilationContext() {
+    return new ItemCompilationContext();
+  }
+
+  SourceString getCheckedModeHelper(DartType type) => null;
+  void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {}
+}
+
+/**
+ * Key class used in [TokenMap] in which the hash code for a token is based
+ * on the [charOffset].
+ */
+class TokenKey {
+  final Token token;
+  TokenKey(this.token);
+  int get hashCode => token.charOffset;
+  operator==(other) => other is TokenKey && token == other.token;
+}
+
+/// Map of tokens and the first associated comment.
+/*
+ * This implementation was chosen among several candidates for its space/time
+ * efficiency by empirical tests of running dartdoc on dartdoc itself. Time
+ * measurements for the use of [Compiler.commentMap]:
+ *
+ * 1) Using [TokenKey] as key (this class): ~80 msec
+ * 2) Using [TokenKey] as key + storing a separate map in each script: ~120 msec
+ * 3) Using [Token] as key in a [Map]: ~38000 msec
+ * 4) Storing comments is new field in [Token]: ~20 msec
+ *    (Abandoned due to the increased memory usage)
+ * 5) Storing comments in an [Expando]: ~14000 msec
+ * 6) Storing token/comments pairs in a linked list: ~5400 msec
+ */
+class TokenMap {
+  Map<TokenKey,Token> comments = new Map<TokenKey,Token>();
+
+  Token operator[] (Token key) {
+    if (key == null) return null;
+    return comments[new TokenKey(key)];
+  }
+
+  void operator[]= (Token key, Token value) {
+    if (key == null) return;
+    comments[new TokenKey(key)] = value;
+  }
+}
+
+abstract class Compiler implements DiagnosticListener {
+  final Map<String, LibraryElement> libraries;
+  final Stopwatch totalCompileTime = new Stopwatch();
+  int nextFreeClassId = 0;
+  World world;
+  String assembledCode;
+  Types types;
+
+  /**
+   * Map from token to the first preceeding comment token.
+   */
+  final TokenMap commentMap = new TokenMap();
+
+  final bool enableMinification;
+  final bool enableTypeAssertions;
+  final bool enableUserAssertions;
+  final bool enableConcreteTypeInference;
+  /**
+   * The maximum size of a concrete type before it widens to dynamic during
+   * concrete type inference.
+   */
+  final int maxConcreteTypeSize;
+  final bool analyzeAll;
+  final bool analyzeOnly;
+  final bool enableNativeLiveTypeAnalysis;
+  final bool rejectDeprecatedFeatures;
+  final bool checkDeprecationInSdk;
+
+  /**
+   * If [:true:], comment tokens are collected in [commentMap] during scanning.
+   */
+  final bool preserveComments;
+
+  final api.CompilerOutputProvider outputProvider;
+
+  bool disableInlining = false;
+
+  List<Uri> librariesToAnalyzeWhenRun;
+
+  final Tracer tracer;
+
+  CompilerTask measuredTask;
+  Element _currentElement;
+  LibraryElement coreLibrary;
+  LibraryElement isolateLibrary;
+  LibraryElement isolateHelperLibrary;
+  LibraryElement jsHelperLibrary;
+  LibraryElement interceptorsLibrary;
+  LibraryElement foreignLibrary;
+  LibraryElement mainApp;
+
+  ClassElement objectClass;
+  ClassElement closureClass;
+  ClassElement dynamicClass;
+  ClassElement boolClass;
+  ClassElement numClass;
+  ClassElement intClass;
+  ClassElement doubleClass;
+  ClassElement stringClass;
+  ClassElement functionClass;
+  ClassElement nullClass;
+  ClassElement listClass;
+  ClassElement typeClass;
+  ClassElement mapClass;
+  ClassElement jsInvocationMirrorClass;
+  /// Document class from dart:mirrors.
+  ClassElement documentClass;
+  Element assertMethod;
+  Element identicalFunction;
+  Element functionApplyMethod;
+  Element invokeOnMethod;
+  Element createInvocationMirrorElement;
+
+  Element get currentElement => _currentElement;
+  withCurrentElement(Element element, f()) {
+    Element old = currentElement;
+    _currentElement = element;
+    try {
+      return f();
+    } on SpannableAssertionFailure catch (ex) {
+      if (!hasCrashed) {
+        SourceSpan span = spanFromSpannable(ex.node);
+        reportDiagnostic(span, ex.message, api.Diagnostic.ERROR);
+        pleaseReportCrash();
+      }
+      hasCrashed = true;
+      throw;
+    } on CompilerCancelledException catch (ex) {
+      throw;
+    } on StackOverflowError catch (ex) {
+      // We cannot report anything useful in this case, because we
+      // do not have enough stack space.
+      throw;
+    } catch (ex) {
+      try {
+        unhandledExceptionOnElement(element);
+      } catch (doubleFault) {
+        // Ignoring exceptions in exception handling.
+      }
+      throw;
+    } finally {
+      _currentElement = old;
+    }
+  }
+
+  List<CompilerTask> tasks;
+  ScannerTask scanner;
+  DietParserTask dietParser;
+  ParserTask parser;
+  PatchParserTask patchParser;
+  LibraryLoader libraryLoader;
+  TreeValidatorTask validator;
+  ResolverTask resolver;
+  closureMapping.ClosureTask closureToClassMapper;
+  TypeCheckerTask checker;
+  ti.TypesTask typesTask;
+  Backend backend;
+  ConstantHandler constantHandler;
+  ConstantHandler metadataHandler;
+  EnqueueTask enqueuer;
+  CompilerTask fileReadingTask;
+
+  static const SourceString MAIN = const SourceString('main');
+  static const SourceString CALL_OPERATOR_NAME = const SourceString('call');
+  static const SourceString NO_SUCH_METHOD = const SourceString('noSuchMethod');
+  static const int NO_SUCH_METHOD_ARG_COUNT = 1;
+  static const SourceString CREATE_INVOCATION_MIRROR =
+      const SourceString('createInvocationMirror');
+  static const SourceString INVOKE_ON = const SourceString('invokeOn');
+  static const SourceString RUNTIME_TYPE = const SourceString('runtimeType');
+  static const SourceString START_ROOT_ISOLATE =
+      const SourceString('startRootIsolate');
+  bool enabledNoSuchMethod = false;
+  bool enabledRuntimeType = false;
+  bool enabledFunctionApply = false;
+  bool enabledInvokeOn = false;
+
+  Stopwatch progress;
+
+  static const int PHASE_SCANNING = 0;
+  static const int PHASE_RESOLVING = 1;
+  static const int PHASE_COMPILING = 2;
+  int phase;
+
+  bool compilationFailed = false;
+
+  bool hasCrashed = false;
+
+  Compiler({this.tracer: const Tracer(),
+            this.enableTypeAssertions: false,
+            this.enableUserAssertions: false,
+            this.enableConcreteTypeInference: false,
+            this.maxConcreteTypeSize: 5,
+            this.enableMinification: false,
+            this.enableNativeLiveTypeAnalysis: false,
+            bool emitJavaScript: true,
+            bool generateSourceMap: true,
+            bool disallowUnsafeEval: false,
+            this.analyzeAll: false,
+            this.analyzeOnly: false,
+            this.rejectDeprecatedFeatures: false,
+            this.checkDeprecationInSdk: false,
+            this.preserveComments: false,
+            outputProvider,
+            List<String> strips: const []})
+      : libraries = new Map<String, LibraryElement>(),
+        progress = new Stopwatch(),
+        this.outputProvider =
+            (outputProvider == null) ? NullSink.outputProvider : outputProvider
+  {
+    progress.start();
+    world = new World(this);
+
+    closureMapping.ClosureNamer closureNamer;
+    if (emitJavaScript) {
+      js_backend.JavaScriptBackend jsBackend =
+          new js_backend.JavaScriptBackend(this, generateSourceMap,
+                                           disallowUnsafeEval);
+      closureNamer = jsBackend.namer;
+      backend = jsBackend;
+    } else {
+      backend = new dart_backend.DartBackend(this, strips);
+    }
+
+    // No-op in production mode.
+    validator = new TreeValidatorTask(this);
+
+    tasks = [
+      fileReadingTask = new ReadingFilesTask(this),
+      libraryLoader = new LibraryLoaderTask(this),
+      scanner = new ScannerTask(this),
+      dietParser = new DietParserTask(this),
+      parser = new ParserTask(this),
+      patchParser = new PatchParserTask(this),
+      resolver = new ResolverTask(this),
+      closureToClassMapper = new closureMapping.ClosureTask(this, closureNamer),
+      checker = new TypeCheckerTask(this),
+      typesTask = new ti.TypesTask(this),
+      constantHandler = new ConstantHandler(this, backend.constantSystem),
+      enqueuer = new EnqueueTask(this)];
+
+    tasks.addAll(backend.tasks);
+    metadataHandler = new ConstantHandler(
+        this, backend.constantSystem, isMetadata: true);
+  }
+
+  Universe get resolverWorld => enqueuer.resolution.universe;
+  Universe get codegenWorld => enqueuer.codegen.universe;
+
+  int getNextFreeClassId() => nextFreeClassId++;
+
+  void ensure(bool condition) {
+    if (!condition) cancel('failed assertion in leg');
+  }
+
+  void unimplemented(String methodName,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element}) {
+    internalError("$methodName not implemented",
+                  node: node, token: token,
+                  instruction: instruction, element: element);
+  }
+
+  void internalError(String message,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element}) {
+    cancel('Internal error: $message',
+           node: node, token: token,
+           instruction: instruction, element: element);
+  }
+
+  void internalErrorOnElement(Element element, String message) {
+    internalError(message, element: element);
+  }
+
+  void unhandledExceptionOnElement(Element element) {
+    if (hasCrashed) return;
+    hasCrashed = true;
+    reportDiagnostic(spanFromElement(element),
+                     MessageKind.COMPILER_CRASHED.error().toString(),
+                     api.Diagnostic.CRASH);
+    pleaseReportCrash();
+  }
+
+  void pleaseReportCrash() {
+    print(MessageKind.PLEASE_REPORT_THE_CRASH.message({'buildId': BUILD_ID}));
+  }
+
+  void cancel(String reason, {Node node, Token token,
+               HInstruction instruction, Element element}) {
+    assembledCode = null; // Compilation failed. Make sure that we
+                          // don't return a bogus result.
+    SourceSpan span = null;
+    if (node != null) {
+      span = spanFromNode(node);
+    } else if (token != null) {
+      span = spanFromTokens(token, token);
+    } else if (instruction != null) {
+      span = spanFromHInstruction(instruction);
+    } else if (element != null) {
+      span = spanFromElement(element);
+    } else {
+      throw 'No error location for error: $reason';
+    }
+    reportDiagnostic(span, reason, api.Diagnostic.ERROR);
+    throw new CompilerCancelledException(reason);
+  }
+
+  SourceSpan spanFromSpannable(Spannable node, [Uri uri]) {
+    if (node == CURRENT_ELEMENT_SPANNABLE) {
+      node = currentElement;
+    }
+    if (node is Node) {
+      return spanFromNode(node, uri);
+    } else if (node is Token) {
+      return spanFromTokens(node, node, uri);
+    } else if (node is HInstruction) {
+      return spanFromHInstruction(node);
+    } else if (node is Element) {
+      return spanFromElement(node);
+    } else if (node is MetadataAnnotation) {
+      return spanFromTokens(node.beginToken, node.endToken);
+    } else {
+      throw 'No error location.';
+    }
+  }
+
+  void reportFatalError(String reason, Element element,
+                        {Node node, Token token, HInstruction instruction}) {
+    withCurrentElement(element, () {
+      cancel(reason, node: node, token: token, instruction: instruction,
+             element: element);
+    });
+  }
+
+  void log(message) {
+    reportDiagnostic(null, message, api.Diagnostic.VERBOSE_INFO);
+  }
+
+  bool run(Uri uri) {
+    totalCompileTime.start();
+    try {
+      runCompiler(uri);
+    } on CompilerCancelledException catch (exception) {
+      log('Error: $exception');
+      return false;
+    } finally {
+      tracer.close();
+      totalCompileTime.stop();
+    }
+    return true;
+  }
+
+  bool hasIsolateSupport() => isolateLibrary != null;
+
+  /**
+   * This method is called before [library] import and export scopes have been
+   * set up.
+   */
+  void onLibraryScanned(LibraryElement library, Uri uri) {
+    if (dynamicClass != null) {
+      // When loading the built-in libraries, dynamicClass is null. We
+      // take advantage of this as core imports js_helper and sees [dynamic]
+      // this way.
+      withCurrentElement(dynamicClass, () {
+        library.addToScope(dynamicClass, this);
+      });
+    }
+  }
+
+  LibraryElement scanBuiltinLibrary(String filename);
+
+  void initializeSpecialClasses() {
+    final List missingCoreClasses = [];
+    ClassElement lookupCoreClass(String name) {
+      ClassElement result = coreLibrary.find(new SourceString(name));
+      if (result == null) {
+        missingCoreClasses.add(name);
+      }
+      return result;
+    }
+    objectClass = lookupCoreClass('Object');
+    boolClass = lookupCoreClass('bool');
+    numClass = lookupCoreClass('num');
+    intClass = lookupCoreClass('int');
+    doubleClass = lookupCoreClass('double');
+    stringClass = lookupCoreClass('String');
+    functionClass = lookupCoreClass('Function');
+    listClass = lookupCoreClass('List');
+    typeClass = lookupCoreClass('Type');
+    mapClass = lookupCoreClass('Map');
+    if (!missingCoreClasses.isEmpty) {
+      internalErrorOnElement(coreLibrary,
+          'dart:core library does not contain required classes: '
+          '$missingCoreClasses');
+    }
+
+    final List missingHelperClasses = [];
+    ClassElement lookupHelperClass(String name) {
+      ClassElement result = jsHelperLibrary.find(new SourceString(name));
+      if (result == null) {
+        missingHelperClasses.add(name);
+      }
+      return result;
+    }
+    jsInvocationMirrorClass = lookupHelperClass('JSInvocationMirror');
+    closureClass = lookupHelperClass('Closure');
+    dynamicClass = lookupHelperClass('Dynamic_');
+    nullClass = lookupHelperClass('Null');
+    if (!missingHelperClasses.isEmpty) {
+      internalErrorOnElement(jsHelperLibrary,
+          'dart:_js_helper library does not contain required classes: '
+          '$missingHelperClasses');
+    }
+
+    types = new Types(this, dynamicClass);
+  }
+
+  void scanBuiltinLibraries() {
+    jsHelperLibrary = scanBuiltinLibrary('_js_helper');
+    interceptorsLibrary = scanBuiltinLibrary('_interceptors');
+    foreignLibrary = scanBuiltinLibrary('_foreign_helper');
+    isolateHelperLibrary = scanBuiltinLibrary('_isolate_helper');
+    // The helper library does not use the native language extension,
+    // so we manually set the native classes this library defines.
+    // TODO(ngeoffray): Enable annotations on these classes.
+    ClassElement cls =
+        isolateHelperLibrary.find(const SourceString('_WorkerStub'));
+    cls.setNative('"*Worker"');
+
+    assertMethod = jsHelperLibrary.find(const SourceString('assertHelper'));
+    identicalFunction = coreLibrary.find(const SourceString('identical'));
+
+    initializeSpecialClasses();
+
+    functionClass.ensureResolved(this);
+    functionApplyMethod =
+        functionClass.lookupLocalMember(const SourceString('apply'));
+    jsInvocationMirrorClass.ensureResolved(this);
+    invokeOnMethod = jsInvocationMirrorClass.lookupLocalMember(
+        const SourceString('invokeOn'));
+
+    if (preserveComments) {
+      var uri = new Uri.fromComponents(scheme: 'dart', path: 'mirrors');
+      LibraryElement libraryElement =
+          libraryLoader.loadLibrary(uri, null, uri);
+      documentClass = libraryElement.find(const SourceString('Comment'));
+    }
+  }
+
+  void importHelperLibrary(LibraryElement library) {
+    if (jsHelperLibrary != null) {
+      libraryLoader.importLibrary(library, jsHelperLibrary, null);
+    }
+  }
+
+  /**
+   * Get an [Uri] pointing to a patch for the dart: library with
+   * the given path. Returns null if there is no patch.
+   */
+  Uri resolvePatchUri(String dartLibraryPath);
+
+  void runCompiler(Uri uri) {
+    assert(uri != null || analyzeOnly);
+    scanBuiltinLibraries();
+    if (librariesToAnalyzeWhenRun != null) {
+      for (Uri libraryUri in librariesToAnalyzeWhenRun) {
+        log('analyzing $libraryUri ($BUILD_ID)');
+        libraryLoader.loadLibrary(libraryUri, null, libraryUri);
+      }
+    }
+    if (uri != null) {
+      if (analyzeOnly) {
+        log('analyzing $uri ($BUILD_ID)');
+      } else {
+        log('compiling $uri ($BUILD_ID)');
+      }
+      mainApp = libraryLoader.loadLibrary(uri, null, uri);
+    }
+    Element main = null;
+    if (mainApp != null) {
+      main = mainApp.find(MAIN);
+      if (main == null) {
+        if (!analyzeOnly) {
+          // Allow analyze only of libraries with no main.
+          reportFatalError('Could not find $MAIN', mainApp);
+        } else if (!analyzeAll) {
+          reportFatalError(
+              "Could not find $MAIN. "
+              "No source will be analyzed. "
+              "Use '--analyze-all' to analyze all code in the library.",
+              mainApp);
+        }
+      } else {
+        if (!main.isFunction()) {
+          reportFatalError('main is not a function', main);
+        }
+        FunctionElement mainMethod = main;
+        FunctionSignature parameters = mainMethod.computeSignature(this);
+        parameters.forEachParameter((Element parameter) {
+          reportFatalError('main cannot have parameters', parameter);
+        });
+      }
+    }
+
+    log('Resolving...');
+    phase = PHASE_RESOLVING;
+    if (analyzeAll) {
+      libraries.forEach((_, lib) => fullyEnqueueLibrary(lib));
+    }
+    backend.enqueueHelpers(enqueuer.resolution);
+    processQueue(enqueuer.resolution, main);
+    enqueuer.resolution.logSummary(log);
+
+    if (compilationFailed) return;
+    if (analyzeOnly) return;
+    assert(main != null);
+
+    log('Inferring types...');
+    typesTask.onResolutionComplete(main);
+
+    // TODO(ahe): Remove this line. Eventually, enqueuer.resolution
+    // should know this.
+    world.populate();
+
+    log('Compiling...');
+    phase = PHASE_COMPILING;
+    // TODO(johnniwinther): Move these to [CodegenEnqueuer].
+    if (hasIsolateSupport()) {
+      enqueuer.codegen.addToWorkList(
+          isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE));
+    }
+    if (enabledNoSuchMethod) {
+      Selector selector = new Selector.noSuchMethod();
+      enqueuer.codegen.registerInvocation(NO_SUCH_METHOD, selector);
+      enqueuer.codegen.addToWorkList(createInvocationMirrorElement);
+    }
+    processQueue(enqueuer.codegen, main);
+    enqueuer.codegen.logSummary(log);
+
+    if (compilationFailed) return;
+
+    backend.assembleProgram();
+
+    checkQueues();
+  }
+
+  void fullyEnqueueLibrary(LibraryElement library) {
+    library.forEachLocalMember(fullyEnqueueTopLevelElement);
+  }
+
+  void fullyEnqueueTopLevelElement(Element element) {
+    if (element.isClass()) {
+      ClassElement cls = element;
+      cls.ensureResolved(this);
+      cls.forEachLocalMember(enqueuer.resolution.addToWorkList);
+    } else {
+      enqueuer.resolution.addToWorkList(element);
+    }
+  }
+
+  void processQueue(Enqueuer world, Element main) {
+    world.nativeEnqueuer.processNativeClasses(libraries.values);
+    if (main != null) {
+      world.addToWorkList(main);
+    }
+    progress.reset();
+    world.forEach((WorkItem work) {
+      withCurrentElement(work.element, () => work.run(this, world));
+    });
+    world.queueIsClosed = true;
+    if (compilationFailed) return;
+    assert(world.checkNoEnqueuedInvokedInstanceMethods());
+    if (DUMP_INFERRED_TYPES && phase == PHASE_COMPILING) {
+      backend.dumpInferredTypes();
+    }
+  }
+
+  /**
+   * Perform various checks of the queues. This includes checking that
+   * the queues are empty (nothing was added after we stopped
+   * processing the queues). Also compute the number of methods that
+   * were resolved, but not compiled (aka excess resolution).
+   */
+  checkQueues() {
+    for (Enqueuer world in [enqueuer.resolution, enqueuer.codegen]) {
+      world.forEach((WorkItem work) {
+        internalErrorOnElement(work.element, "Work list is not empty.");
+      });
+    }
+    if (!REPORT_EXCESS_RESOLUTION) return;
+    var resolved = new Set.from(enqueuer.resolution.resolvedElements.keys);
+    for (Element e in enqueuer.codegen.generatedCode.keys) {
+      resolved.remove(e);
+    }
+    for (Element e in new Set.from(resolved)) {
+      if (e.isClass() ||
+          e.isField() ||
+          e.isTypeVariable() ||
+          e.isTypedef() ||
+          identical(e.kind, ElementKind.ABSTRACT_FIELD)) {
+        resolved.remove(e);
+      }
+      if (identical(e.kind, ElementKind.GENERATIVE_CONSTRUCTOR)) {
+        ClassElement enclosingClass = e.getEnclosingClass();
+        if (enclosingClass.isInterface()) {
+          resolved.remove(e);
+        }
+        resolved.remove(e);
+
+      }
+      if (identical(e.getLibrary(), jsHelperLibrary)) {
+        resolved.remove(e);
+      }
+      if (identical(e.getLibrary(), interceptorsLibrary)) {
+        resolved.remove(e);
+      }
+    }
+    log('Excess resolution work: ${resolved.length}.');
+    for (Element e in resolved) {
+      SourceSpan span = spanFromElement(e);
+      reportDiagnostic(span, 'Warning: $e resolved but not compiled.',
+                       api.Diagnostic.WARNING);
+    }
+  }
+
+  TreeElements analyzeElement(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    TreeElements elements = enqueuer.resolution.getCachedElements(element);
+    if (elements != null) return elements;
+    assert(parser != null);
+    Node tree = parser.parse(element);
+    validator.validate(tree);
+    elements = resolver.resolve(element);
+    if (elements != null) {
+      // Only analyze nodes with a corresponding [TreeElements].
+      checker.check(tree, elements);
+      typesTask.analyze(tree, elements);
+    }
+    return elements;
+  }
+
+  TreeElements analyze(ResolutionWorkItem work, ResolutionEnqueuer world) {
+    assert(invariant(work.element, identical(world, enqueuer.resolution)));
+    assert(invariant(work.element, !work.isAnalyzed(),
+        message: 'Element ${work.element} has already been analyzed'));
+    if (progress.elapsedMilliseconds > 500) {
+      // TODO(ahe): Add structured diagnostics to the compiler API and
+      // use it to separate this from the --verbose option.
+      if (phase == PHASE_RESOLVING) {
+        log('Resolved ${enqueuer.resolution.resolvedElements.length} '
+            'elements.');
+        progress.reset();
+      }
+    }
+    Element element = work.element;
+    TreeElements result = world.getCachedElements(element);
+    if (result != null) return result;
+    result = analyzeElement(element);
+    assert(invariant(element, element.isDeclaration));
+    world.resolvedElements[element] = result;
+    return result;
+  }
+
+  void codegen(CodegenWorkItem work, CodegenEnqueuer world) {
+    assert(invariant(work.element, identical(world, enqueuer.codegen)));
+    if (progress.elapsedMilliseconds > 500) {
+      // TODO(ahe): Add structured diagnostics to the compiler API and
+      // use it to separate this from the --verbose option.
+      log('Compiled ${enqueuer.codegen.generatedCode.length} methods.');
+      progress.reset();
+    }
+    backend.codegen(work);
+  }
+
+  DartType resolveTypeAnnotation(Element element,
+                                 TypeAnnotation annotation) {
+    return resolver.resolveTypeAnnotation(element, annotation);
+  }
+
+  DartType resolveReturnType(Element element,
+                             TypeAnnotation annotation) {
+    return resolver.resolveReturnType(element, annotation);
+  }
+
+  FunctionSignature resolveSignature(FunctionElement element) {
+    return withCurrentElement(element,
+                              () => resolver.resolveSignature(element));
+  }
+
+  FunctionSignature resolveFunctionExpression(Element element,
+                                              FunctionExpression node) {
+    return withCurrentElement(element,
+        () => resolver.resolveFunctionExpression(element, node));
+  }
+
+  void resolveTypedef(TypedefElement element) {
+    withCurrentElement(element,
+                       () => resolver.resolveTypedef(element));
+  }
+
+  FunctionType computeFunctionType(Element element,
+                                   FunctionSignature signature) {
+    return withCurrentElement(element,
+        () => resolver.computeFunctionType(element, signature));
+  }
+
+  reportWarning(Node node, var message) {
+    if (message is TypeWarning) {
+      // TODO(ahe): Don't supress these warning when the type checker
+      // is more complete.
+      if (identical(message.message.kind, MessageKind.NOT_ASSIGNABLE)) return;
+      if (identical(message.message.kind, MessageKind.MISSING_RETURN)) return;
+      if (identical(message.message.kind, MessageKind.MAYBE_MISSING_RETURN)) return;
+      if (identical(message.message.kind, MessageKind.METHOD_NOT_FOUND)) return;
+    }
+    SourceSpan span = spanFromNode(node);
+
+    reportDiagnostic(span, 'Warning: $message', api.Diagnostic.WARNING);
+  }
+
+  // TODO(ahe): Remove this method.
+  reportError(Node node, var message) {
+    SourceSpan span = spanFromNode(node);
+    reportDiagnostic(span, 'Error: $message', api.Diagnostic.ERROR);
+    throw new CompilerCancelledException(message.toString());
+  }
+
+  // TODO(ahe): Rename to reportError when that method has been removed.
+  void reportErrorCode(Spannable node, MessageKind errorCode,
+                       [Map arguments = const {}]) {
+    reportMessage(spanFromSpannable(node),
+                  errorCode.error(arguments),
+                  api.Diagnostic.ERROR);
+  }
+
+  void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) {
+    // TODO(ahe): The names Diagnostic and api.Diagnostic are in
+    // conflict. Fix it.
+    reportDiagnostic(span, "$message", kind);
+  }
+
+  /// Returns true if a diagnostic was emitted.
+  bool onDeprecatedFeature(Spannable span, String feature) {
+    if (currentElement == null)
+      throw new SpannableAssertionFailure(span, feature);
+    if (!checkDeprecationInSdk &&
+        currentElement.getLibrary().isPlatformLibrary) {
+      return false;
+    }
+    var kind = rejectDeprecatedFeatures
+        ? api.Diagnostic.ERROR : api.Diagnostic.WARNING;
+    var message = rejectDeprecatedFeatures
+        ? MessageKind.DEPRECATED_FEATURE_ERROR.error({'featureName': feature})
+        : MessageKind.DEPRECATED_FEATURE_WARNING.error(
+            {'featureName': feature});
+    reportMessage(spanFromSpannable(span), message, kind);
+    return true;
+  }
+
+  void reportDiagnostic(SourceSpan span, String message, api.Diagnostic kind);
+
+  SourceSpan spanFromTokens(Token begin, Token end, [Uri uri]) {
+    if (begin == null || end == null) {
+      // TODO(ahe): We can almost always do better. Often it is only
+      // end that is null. Otherwise, we probably know the current
+      // URI.
+      throw 'Cannot find tokens to produce error message.';
+    }
+    if (uri == null && currentElement != null) {
+      uri = currentElement.getCompilationUnit().script.uri;
+    }
+    return SourceSpan.withCharacterOffsets(begin, end,
+      (beginOffset, endOffset) => new SourceSpan(uri, beginOffset, endOffset));
+  }
+
+  SourceSpan spanFromNode(Node node, [Uri uri]) {
+    return spanFromTokens(node.getBeginToken(), node.getEndToken(), uri);
+  }
+
+  SourceSpan spanFromElement(Element element) {
+    if (Elements.isErroneousElement(element)) {
+      element = element.enclosingElement;
+    }
+    if (element.position() == null && !element.isCompilationUnit()) {
+      // Sometimes, the backend fakes up elements that have no
+      // position. So we use the enclosing element instead. It is
+      // not a good error location, but cancel really is "internal
+      // error" or "not implemented yet", so the vicinity is good
+      // enough for now.
+      element = element.enclosingElement;
+      // TODO(ahe): I plan to overhaul this infrastructure anyways.
+    }
+    if (element == null) {
+      element = currentElement;
+    }
+    Token position = element.position();
+    Uri uri = element.getCompilationUnit().script.uri;
+    return (position == null)
+        ? new SourceSpan(uri, 0, 0)
+        : spanFromTokens(position, position, uri);
+  }
+
+  SourceSpan spanFromHInstruction(HInstruction instruction) {
+    Element element = instruction.sourceElement;
+    if (element == null) element = currentElement;
+    var position = instruction.sourcePosition;
+    if (position == null) return spanFromElement(element);
+    Token token = position.token;
+    if (token == null) return spanFromElement(element);
+    Uri uri = element.getCompilationUnit().script.uri;
+    return spanFromTokens(token, token, uri);
+  }
+
+  /**
+   * Translates the [resolvedUri] into a readable URI.
+   *
+   * The [importingLibrary] holds the library importing [resolvedUri] or
+   * [:null:] if [resolvedUri] is loaded as the main library. The
+   * [importingLibrary] is used to grant access to internal libraries from
+   * platform libraries and patch libraries.
+   *
+   * If the [resolvedUri] is not accessible from [importingLibrary], this method
+   * is responsible for reporting errors.
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  Uri translateResolvedUri(LibraryElement importingLibrary,
+                           Uri resolvedUri, Node node) {
+    unimplemented('Compiler.translateResolvedUri');
+  }
+
+  /**
+   * Reads the script specified by the [readableUri].
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  Script readScript(Uri readableUri, [Node node]) {
+    unimplemented('Compiler.readScript');
+  }
+
+  String get legDirectory {
+    unimplemented('Compiler.legDirectory');
+  }
+
+  // TODO(karlklose): split into findHelperFunction and findHelperClass and
+  // add a check that the element has the expected kind.
+  Element findHelper(SourceString name)
+      => jsHelperLibrary.findLocal(name);
+  Element findInterceptor(SourceString name)
+      => interceptorsLibrary.findLocal(name);
+
+  Element lookupElementIn(ScopeContainerElement container, SourceString name) {
+    Element element = container.localLookup(name);
+    if (element == null) {
+      throw 'Could not find ${name.slowToString()} in $container';
+    }
+    return element;
+  }
+
+  bool get isMockCompilation => false;
+
+  Token processAndStripComments(Token currentToken) {
+    Token firstToken = currentToken;
+    Token prevToken;
+    while (currentToken.kind != EOF_TOKEN) {
+      if (identical(currentToken.kind, COMMENT_TOKEN)) {
+        Token firstCommentToken = currentToken;
+        while (identical(currentToken.kind, COMMENT_TOKEN)) {
+          currentToken = currentToken.next;
+        }
+        commentMap[currentToken] = firstCommentToken;
+        if (prevToken == null) {
+          firstToken = currentToken;
+        } else {
+          prevToken.next = currentToken;
+        }
+      }
+      prevToken = currentToken;
+      currentToken = currentToken.next;
+    }
+    return firstToken;
+  }
+}
+
+class CompilerTask {
+  final Compiler compiler;
+  final Stopwatch watch;
+
+  CompilerTask(this.compiler) : watch = new Stopwatch();
+
+  String get name => 'Unknown task';
+  int get timing => watch.elapsedMilliseconds;
+
+  measure(Function action) {
+    CompilerTask previous = compiler.measuredTask;
+    if (identical(this, previous)) return action();
+    compiler.measuredTask = this;
+    if (previous != null) previous.watch.stop();
+    watch.start();
+    try {
+      return action();
+    } finally {
+      watch.stop();
+      if (previous != null) previous.watch.start();
+      compiler.measuredTask = previous;
+    }
+  }
+}
+
+class CompilerCancelledException implements Exception {
+  final String reason;
+  CompilerCancelledException(this.reason);
+
+  String toString() {
+    String banner = 'compiler cancelled';
+    return (reason != null) ? '$banner: $reason' : '$banner';
+  }
+}
+
+class Tracer {
+  final bool enabled = false;
+
+  const Tracer();
+
+  void traceCompilation(String methodName, ItemCompilationContext context) {
+  }
+
+  void traceGraph(String name, var graph) {
+  }
+
+  void close() {
+  }
+}
+
+class SourceSpan {
+  final Uri uri;
+  final int begin;
+  final int end;
+
+  const SourceSpan(this.uri, this.begin, this.end);
+
+  static withCharacterOffsets(Token begin, Token end,
+                     f(int beginOffset, int endOffset)) {
+    final beginOffset = begin.charOffset;
+    final endOffset = end.charOffset + end.slowCharCount;
+
+    // [begin] and [end] might be the same for the same empty token. This
+    // happens for instance when scanning '$$'.
+    assert(endOffset >= beginOffset);
+    return f(beginOffset, endOffset);
+  }
+
+  String toString() => 'SourceSpan($uri, $begin, $end)';
+}
+
+/**
+ * Throws an [InvariantException] if [condition] is [:false:]. [condition] must
+ * be either a [:bool:] or a no-arg function returning a [:bool:].
+ *
+ * Use this method to provide better information for assertion by calling
+ * [invariant] as the argument to an [:assert:] statement:
+ *
+ *     assert(invariant(position, isValid));
+ *
+ * [spannable] must be non-null and will be used to provide positional
+ * information in the generated error message.
+ */
+bool invariant(Spannable spannable, var condition, {String message: null}) {
+  // TODO(johnniwinther): Use [spannable] and [message] to provide better
+  // information on assertion errors.
+  if (condition is Function){
+    condition = condition();
+  }
+  if (spannable == null || !condition) {
+    throw new SpannableAssertionFailure(spannable, message);
+  }
+  return true;
+}
+
+/// A sink that drains into /dev/null.
+class NullSink extends StreamSink<String> {
+  final String name;
+
+  NullSink(this.name);
+
+  add(String value) {}
+
+  void signalError(AsyncError error) {}
+
+  void close() {}
+
+  toString() => name;
+
+  /// Convenience method for getting an [api.CompilerOutputProvider].
+  static NullSink outputProvider(String name, String extension) {
+    return new NullSink('$name.$extension');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart
new file mode 100644
index 0000000..0fff580
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart
@@ -0,0 +1,80 @@
+// 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.
+
+part of dart2js;
+
+abstract class Operation {
+  SourceString get name;
+  bool isUserDefinable();
+}
+
+abstract class UnaryOperation extends Operation {
+  /** Returns [:null:] if it was unable to fold the operation. */
+  Constant fold(Constant constant);
+  apply(value);
+}
+
+abstract class BinaryOperation extends Operation {
+  /** Returns [:null:] if it was unable to fold the operation. */
+  Constant fold(Constant left, Constant right);
+  apply(left, right);
+}
+
+/**
+ * A [ConstantSystem] is responsible for creating constants and folding them.
+ */
+abstract class ConstantSystem {
+  BinaryOperation get add;
+  BinaryOperation get bitAnd;
+  UnaryOperation get bitNot;
+  BinaryOperation get bitOr;
+  BinaryOperation get bitXor;
+  BinaryOperation get booleanAnd;
+  BinaryOperation get booleanOr;
+  BinaryOperation get divide;
+  BinaryOperation get equal;
+  BinaryOperation get greaterEqual;
+  BinaryOperation get greater;
+  BinaryOperation get identity;
+  BinaryOperation get lessEqual;
+  BinaryOperation get less;
+  BinaryOperation get modulo;
+  BinaryOperation get multiply;
+  UnaryOperation get negate;
+  UnaryOperation get not;
+  BinaryOperation get shiftLeft;
+  BinaryOperation get shiftRight;
+  BinaryOperation get subtract;
+  BinaryOperation get truncatingDivide;
+
+  const ConstantSystem();
+
+  Constant createInt(int i);
+  Constant createDouble(double d);
+  // We need a diagnostic node to report errors in case the string is malformed.
+  Constant createString(DartString string, Node diagnosticNode);
+  Constant createBool(bool value);
+  Constant createNull();
+
+  // We need to special case the subtype check for JavaScript constant
+  // system because an int is a double at runtime.
+  bool isSubtype(Compiler compiler, DartType s, DartType t);
+
+  /** Returns true if the [constant] is an integer at runtime. */
+  bool isInt(Constant constant);
+  /** Returns true if the [constant] is a double at runtime. */
+  bool isDouble(Constant constant);
+  /** Returns true if the [constant] is a string at runtime. */
+  bool isString(Constant constant);
+  /** Returns true if the [constant] is a boolean at runtime. */
+  bool isBool(Constant constant);
+  /** Returns true if the [constant] is null at runtime. */
+  bool isNull(Constant constant);
+
+  Operation lookupUnary(SourceString operator) {
+    if (operator == const SourceString('-')) return negate;
+    if (operator == const SourceString('~')) return bitNot;
+    return null;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart
new file mode 100644
index 0000000..6732b8c
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart
@@ -0,0 +1,380 @@
+// 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.
+
+part of dart2js;
+
+const DART_CONSTANT_SYSTEM = const DartConstantSystem();
+
+class BitNotOperation implements UnaryOperation {
+  final SourceString name = const SourceString('~');
+  bool isUserDefinable() => true;
+  const BitNotOperation();
+  Constant fold(Constant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      return DART_CONSTANT_SYSTEM.createInt(~intConstant.value);
+    }
+    return null;
+  }
+  apply(value) => ~value;
+}
+
+class NegateOperation implements UnaryOperation {
+  final SourceString name = const SourceString('negate');
+  bool isUserDefinable() => true;
+  const NegateOperation();
+  Constant fold(Constant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      return DART_CONSTANT_SYSTEM.createInt(-intConstant.value);
+    }
+    if (constant.isDouble()) {
+      DoubleConstant doubleConstant = constant;
+      return DART_CONSTANT_SYSTEM.createDouble(-doubleConstant.value);
+    }
+    return null;
+  }
+  apply(value) => -value;
+}
+
+class NotOperation implements UnaryOperation {
+  final SourceString name = const SourceString('!');
+  bool isUserDefinable() => true;
+  const NotOperation();
+  Constant fold(Constant constant) {
+    if (constant.isBool()) {
+      BoolConstant boolConstant = constant;
+      return DART_CONSTANT_SYSTEM.createBool(!boolConstant.value);
+    }
+    return null;
+  }
+  apply(value) => !value;
+}
+
+/**
+ * Operations that only work if both arguments are integers.
+ */
+abstract class BinaryBitOperation implements BinaryOperation {
+  bool isUserDefinable() => true;
+  const BinaryBitOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isInt() && right.isInt()) {
+      IntConstant leftInt = left;
+      IntConstant rightInt = right;
+      int resultValue = foldInts(leftInt.value, rightInt.value);
+      if (resultValue == null) return null;
+      return DART_CONSTANT_SYSTEM.createInt(resultValue);
+    }
+    return null;
+  }
+
+  int foldInts(int left, int right);
+}
+
+class BitOrOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('|');
+  const BitOrOperation();
+  int foldInts(int left, int right)  => left | right;
+  apply(left, right) => left | right;
+}
+
+class BitAndOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('&');
+  const BitAndOperation();
+  int foldInts(int left, int right) => left & right;
+  apply(left, right) => left & right;
+}
+
+class BitXorOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('^');
+  const BitXorOperation();
+  int foldInts(int left, int right) => left ^ right;
+  apply(left, right) => left ^ right;
+}
+
+class ShiftLeftOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('<<');
+  const ShiftLeftOperation();
+  int foldInts(int left, int right) {
+    // TODO(floitsch): find a better way to guard against excessive shifts to
+    // the left.
+    if (right > 100 || right < 0) return null;
+    return left << right;
+  }
+  apply(left, right) => left << right;
+}
+
+class ShiftRightOperation extends BinaryBitOperation {
+  final SourceString name = const SourceString('>>');
+  const ShiftRightOperation();
+  int foldInts(int left, int right) {
+    if (right < 0) return null;
+    return left >> right;
+  }
+  apply(left, right) => left >> right;
+}
+
+abstract class BinaryBoolOperation implements BinaryOperation {
+  bool isUserDefinable() => false;
+  const BinaryBoolOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isBool() && right.isBool()) {
+      BoolConstant leftBool = left;
+      BoolConstant rightBool = right;
+      bool resultValue = foldBools(leftBool.value, rightBool.value);
+      return DART_CONSTANT_SYSTEM.createBool(resultValue);
+    }
+    return null;
+  }
+
+  bool foldBools(bool left, bool right);
+}
+
+class BooleanAndOperation extends BinaryBoolOperation {
+  final SourceString name = const SourceString('&&');
+  const BooleanAndOperation();
+  bool foldBools(bool left, bool right) => left && right;
+  apply(left, right) => left && right;
+}
+
+class BooleanOrOperation extends BinaryBoolOperation {
+  final SourceString name = const SourceString('||');
+  const BooleanOrOperation();
+  bool foldBools(bool left, bool right) => left || right;
+  apply(left, right) => left || right;
+}
+
+abstract class ArithmeticNumOperation implements BinaryOperation {
+  bool isUserDefinable() => true;
+  const ArithmeticNumOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      num foldedValue;
+      if (left.isInt() && right.isInt()) {
+        foldedValue = foldInts(leftNum.value, rightNum.value);
+      } else {
+        foldedValue = foldNums(leftNum.value, rightNum.value);
+      }
+      // A division by 0 means that we might not have a folded value.
+      if (foldedValue == null) return null;
+      if (left.isInt() && right.isInt() && !isDivide() ||
+          isTruncatingDivide()) {
+        assert(foldedValue is int);
+        return DART_CONSTANT_SYSTEM.createInt(foldedValue);
+      } else {
+        return DART_CONSTANT_SYSTEM.createDouble(foldedValue);
+      }
+    }
+    return null;
+  }
+
+  bool isDivide() => false;
+  bool isTruncatingDivide() => false;
+  num foldInts(int left, int right) => foldNums(left, right);
+  num foldNums(num left, num right);
+}
+
+class SubtractOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('-');
+  const SubtractOperation();
+  num foldNums(num left, num right) => left - right;
+  apply(left, right) => left - right;
+}
+
+class MultiplyOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('*');
+  const MultiplyOperation();
+  num foldNums(num left, num right) => left * right;
+  apply(left, right) => left * right;
+}
+
+class ModuloOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('%');
+  const ModuloOperation();
+  int foldInts(int left, int right) {
+    if (right == 0) return null;
+    return left % right;
+  }
+  num foldNums(num left, num right) => left % right;
+  apply(left, right) => left % right;
+}
+
+class TruncatingDivideOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('~/');
+  const TruncatingDivideOperation();
+  int foldInts(int left, int right) {
+    if (right == 0) return null;
+    return left ~/ right;
+  }
+  num foldNums(num left, num right) {
+    num ratio = left / right;
+    if (ratio.isNaN || ratio.isInfinite) return null;
+    return ratio.truncate().toInt();
+  }
+  apply(left, right) => left ~/ right;
+  bool isTruncatingDivide() => true;
+}
+
+class DivideOperation extends ArithmeticNumOperation {
+  final SourceString name = const SourceString('/');
+  const DivideOperation();
+  num foldNums(num left, num right) => left / right;
+  bool isDivide() => true;
+  apply(left, right) => left / right;
+}
+
+class AddOperation implements BinaryOperation {
+  final SourceString name = const SourceString('+');
+  bool isUserDefinable() => true;
+  const AddOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isInt() && right.isInt()) {
+      IntConstant leftInt = left;
+      IntConstant rightInt = right;
+      int result = leftInt.value + rightInt.value;
+      return DART_CONSTANT_SYSTEM.createInt(result);
+    } else if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      double result = leftNum.value + rightNum.value;
+      return DART_CONSTANT_SYSTEM.createDouble(result);
+    } else {
+      return null;
+    }
+  }
+  apply(left, right) => left + right;
+}
+
+abstract class RelationalNumOperation implements BinaryOperation {
+  bool isUserDefinable() => true;
+  const RelationalNumOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      bool foldedValue = foldNums(leftNum.value, rightNum.value);
+      assert(foldedValue != null);
+      return DART_CONSTANT_SYSTEM.createBool(foldedValue);
+    }
+  }
+
+  bool foldNums(num left, num right);
+}
+
+class LessOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('<');
+  const LessOperation();
+  bool foldNums(num left, num right) => left < right;
+  apply(left, right) => left < right;
+}
+
+class LessEqualOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('<=');
+  const LessEqualOperation();
+  bool foldNums(num left, num right) => left <= right;
+  apply(left, right) => left <= right;
+}
+
+class GreaterOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('>');
+  const GreaterOperation();
+  bool foldNums(num left, num right) => left > right;
+  apply(left, right) => left > right;
+}
+
+class GreaterEqualOperation extends RelationalNumOperation {
+  final SourceString name = const SourceString('>=');
+  const GreaterEqualOperation();
+  bool foldNums(num left, num right) => left >= right;
+  apply(left, right) => left >= right;
+}
+
+class EqualsOperation implements BinaryOperation {
+  final SourceString name = const SourceString('==');
+  bool isUserDefinable() => true;
+  const EqualsOperation();
+  Constant fold(Constant left, Constant right) {
+    if (left.isNum() && right.isNum()) {
+      // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0,
+      // and 1 == 1.0.
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      bool result = leftNum.value == rightNum.value;
+      return DART_CONSTANT_SYSTEM.createBool(result);
+    }
+    if (left.isConstructedObject()) {
+      // Unless we know that the user-defined object does not implement the
+      // equality operator we cannot fold here.
+      return null;
+    }
+    return DART_CONSTANT_SYSTEM.createBool(left == right);
+  }
+  apply(left, right) => left == right;
+}
+
+class IdentityOperation implements BinaryOperation {
+  final SourceString name = const SourceString('===');
+  bool isUserDefinable() => false;
+  const IdentityOperation();
+  BoolConstant fold(Constant left, Constant right) {
+    // In order to preserve runtime semantics which says that NaN !== NaN don't
+    // constant fold NaN === NaN. Otherwise the output depends on inlined
+    // variables and other optimizations.
+    if (left.isNaN() && right.isNaN()) return null;
+    return DART_CONSTANT_SYSTEM.createBool(left == right);
+  }
+  apply(left, right) => identical(left, right);
+}
+
+/**
+ * A constant system implementing the Dart semantics. This system relies on
+ * the underlying runtime-system. That is, if dart2js is run in an environment
+ * that doesn't correctly implement Dart's semantics this constant system will
+ * not return the correct values.
+ */
+class DartConstantSystem extends ConstantSystem {
+  const add = const AddOperation();
+  const bitAnd = const BitAndOperation();
+  const bitNot = const BitNotOperation();
+  const bitOr = const BitOrOperation();
+  const bitXor = const BitXorOperation();
+  const booleanAnd = const BooleanAndOperation();
+  const booleanOr = const BooleanOrOperation();
+  const divide = const DivideOperation();
+  const equal = const EqualsOperation();
+  const greaterEqual = const GreaterEqualOperation();
+  const greater = const GreaterOperation();
+  const identity = const IdentityOperation();
+  const lessEqual = const LessEqualOperation();
+  const less = const LessOperation();
+  const modulo = const ModuloOperation();
+  const multiply = const MultiplyOperation();
+  const negate = const NegateOperation();
+  const not = const NotOperation();
+  const shiftLeft = const ShiftLeftOperation();
+  const shiftRight = const ShiftRightOperation();
+  const subtract = const SubtractOperation();
+  const truncatingDivide = const TruncatingDivideOperation();
+
+  const DartConstantSystem();
+
+  IntConstant createInt(int i) => new IntConstant(i);
+  DoubleConstant createDouble(double d) => new DoubleConstant(d);
+  StringConstant createString(DartString string, Node diagnosticNode)
+      => new StringConstant(string, diagnosticNode);
+  BoolConstant createBool(bool value) => new BoolConstant(value);
+  NullConstant createNull() => new NullConstant();
+
+  bool isInt(Constant constant) => constant.isInt();
+  bool isDouble(Constant constant) => constant.isDouble();
+  bool isString(Constant constant) => constant.isString();
+  bool isBool(Constant constant) => constant.isBool();
+  bool isNull(Constant constant) => constant.isNull();
+
+  bool isSubtype(Compiler compiler, DartType s, DartType t) {
+    return compiler.types.isSubtype(s, t);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/constants.dart b/pkgs/markdown/test/lib/src/compiler/implementation/constants.dart
new file mode 100644
index 0000000..ad5dc74
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/constants.dart
@@ -0,0 +1,473 @@
+// 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.
+
+part of dart2js;
+
+abstract class ConstantVisitor<R> {
+  R visitSentinel(SentinelConstant constant);
+  R visitFunction(FunctionConstant constant);
+  R visitNull(NullConstant constant);
+  R visitInt(IntConstant constant);
+  R visitDouble(DoubleConstant constant);
+  R visitTrue(TrueConstant constant);
+  R visitFalse(FalseConstant constant);
+  R visitString(StringConstant constant);
+  R visitList(ListConstant constant);
+  R visitMap(MapConstant constant);
+  R visitConstructed(ConstructedConstant constant);
+  R visitType(TypeConstant constant);
+}
+
+abstract class Constant {
+  const Constant();
+
+  bool isNull() => false;
+  bool isBool() => false;
+  bool isTrue() => false;
+  bool isFalse() => false;
+  bool isInt() => false;
+  bool isDouble() => false;
+  bool isNum() => false;
+  bool isString() => false;
+  bool isList() => false;
+  bool isMap() => false;
+  bool isConstructedObject() => false;
+  bool isFunction() => false;
+  /** Returns true if the constant is null, a bool, a number or a string. */
+  bool isPrimitive() => false;
+  /** Returns true if the constant is a list, a map or a constructed object. */
+  bool isObject() => false;
+  bool isType() => false;
+  bool isSentinel() => false;
+
+  bool isNaN() => false;
+  bool isMinusZero() => false;
+
+  DartType computeType(Compiler compiler);
+
+  List<Constant> getDependencies();
+
+  accept(ConstantVisitor visitor);
+}
+
+class SentinelConstant extends Constant {
+  const SentinelConstant();
+  static final SENTINEL = const SentinelConstant();
+
+  List<Constant> getDependencies() => const <Constant>[];
+
+  // Just use a random value.
+  int get hashCode => 24297418;
+
+  bool isSentinel() => true;
+
+  accept(ConstantVisitor visitor) => visitor.visitSentinel(this);
+
+  DartType computeType(Compiler compiler) => compiler.types.dynamicType;
+}
+
+class FunctionConstant extends Constant {
+  Element element;
+
+  FunctionConstant(this.element);
+
+  bool isFunction() => true;
+
+  bool operator ==(var other) {
+    if (other is !FunctionConstant) return false;
+    return identical(other.element, element);
+  }
+
+  String toString() => element.toString();
+  List<Constant> getDependencies() => const <Constant>[];
+  DartString toDartString() {
+    return new DartString.literal(element.name.slowToString());
+  }
+
+  DartType computeType(Compiler compiler) {
+    return compiler.functionClass.computeType(compiler);
+  }
+
+  int get hashCode => (17 * element.hashCode) & 0x7fffffff;
+
+  accept(ConstantVisitor visitor) => visitor.visitFunction(this);
+}
+
+abstract class PrimitiveConstant extends Constant {
+  get value;
+  const PrimitiveConstant();
+  bool isPrimitive() => true;
+
+  bool operator ==(var other) {
+    if (other is !PrimitiveConstant) return false;
+    PrimitiveConstant otherPrimitive = other;
+    // We use == instead of 'identical' so that DartStrings compare correctly.
+    return value == otherPrimitive.value;
+  }
+
+  String toString() => value.toString();
+  // Primitive constants don't have dependencies.
+  List<Constant> getDependencies() => const <Constant>[];
+  DartString toDartString();
+}
+
+class NullConstant extends PrimitiveConstant {
+  /** The value a Dart null is compiled to in JavaScript. */
+  static const String JsNull = "null";
+
+  factory NullConstant() => const NullConstant._internal();
+  const NullConstant._internal();
+  bool isNull() => true;
+  get value => null;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.nullClass.computeType(compiler);
+  }
+
+  void _writeJsCode(CodeBuffer buffer, ConstantHandler handler) {
+    buffer.add(JsNull);
+  }
+
+  // The magic constant has no meaning. It is just a random value.
+  int get hashCode => 785965825;
+  DartString toDartString() => const LiteralDartString("null");
+
+  accept(ConstantVisitor visitor) => visitor.visitNull(this);
+}
+
+abstract class NumConstant extends PrimitiveConstant {
+  num get value;
+  const NumConstant();
+  bool isNum() => true;
+}
+
+class IntConstant extends NumConstant {
+  final int value;
+  factory IntConstant(int value) {
+    switch (value) {
+      case 0: return const IntConstant._internal(0);
+      case 1: return const IntConstant._internal(1);
+      case 2: return const IntConstant._internal(2);
+      case 3: return const IntConstant._internal(3);
+      case 4: return const IntConstant._internal(4);
+      case 5: return const IntConstant._internal(5);
+      case 6: return const IntConstant._internal(6);
+      case 7: return const IntConstant._internal(7);
+      case 8: return const IntConstant._internal(8);
+      case 9: return const IntConstant._internal(9);
+      case 10: return const IntConstant._internal(10);
+      case -1: return const IntConstant._internal(-1);
+      case -2: return const IntConstant._internal(-2);
+      default: return new IntConstant._internal(value);
+    }
+  }
+  const IntConstant._internal(this.value);
+  bool isInt() => true;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.intClass.computeType(compiler);
+  }
+
+  // We have to override the equality operator so that ints and doubles are
+  // treated as separate constants.
+  // The is [:!IntConstant:] check at the beginning of the function makes sure
+  // that we compare only equal to integer constants.
+  bool operator ==(var other) {
+    if (other is !IntConstant) return false;
+    IntConstant otherInt = other;
+    return value == otherInt.value;
+  }
+
+  int get hashCode => value.hashCode;
+  DartString toDartString() => new DartString.literal(value.toString());
+
+  accept(ConstantVisitor visitor) => visitor.visitInt(this);
+}
+
+class DoubleConstant extends NumConstant {
+  final double value;
+  factory DoubleConstant(double value) {
+    if (value.isNaN) {
+      return const DoubleConstant._internal(double.NAN);
+    } else if (value == double.INFINITY) {
+      return const DoubleConstant._internal(double.INFINITY);
+    } else if (value == -double.INFINITY) {
+      return const DoubleConstant._internal(-double.INFINITY);
+    } else if (value == 0.0 && !value.isNegative) {
+      return const DoubleConstant._internal(0.0);
+    } else if (value == 1.0) {
+      return const DoubleConstant._internal(1.0);
+    } else {
+      return new DoubleConstant._internal(value);
+    }
+  }
+  const DoubleConstant._internal(this.value);
+  bool isDouble() => true;
+  bool isNaN() => value.isNaN;
+  // We need to check for the negative sign since -0.0 == 0.0.
+  bool isMinusZero() => value == 0.0 && value.isNegative;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.doubleClass.computeType(compiler);
+  }
+
+  bool operator ==(var other) {
+    if (other is !DoubleConstant) return false;
+    DoubleConstant otherDouble = other;
+    double otherValue = otherDouble.value;
+    if (value == 0.0 && otherValue == 0.0) {
+      return value.isNegative == otherValue.isNegative;
+    } else if (value.isNaN) {
+      return otherValue.isNaN;
+    } else {
+      return value == otherValue;
+    }
+  }
+
+  int get hashCode => value.hashCode;
+  DartString toDartString() => new DartString.literal(value.toString());
+
+  accept(ConstantVisitor visitor) => visitor.visitDouble(this);
+}
+
+abstract class BoolConstant extends PrimitiveConstant {
+  factory BoolConstant(value) {
+    return value ? new TrueConstant() : new FalseConstant();
+  }
+  const BoolConstant._internal();
+  bool isBool() => true;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.boolClass.computeType(compiler);
+  }
+
+  BoolConstant negate();
+}
+
+class TrueConstant extends BoolConstant {
+  final bool value = true;
+
+  factory TrueConstant() => const TrueConstant._internal();
+  const TrueConstant._internal() : super._internal();
+  bool isTrue() => true;
+
+  FalseConstant negate() => new FalseConstant();
+
+  bool operator ==(var other) => identical(this, other);
+  // The magic constant is just a random value. It does not have any
+  // significance.
+  int get hashCode => 499;
+  DartString toDartString() => const LiteralDartString("true");
+
+  accept(ConstantVisitor visitor) => visitor.visitTrue(this);
+}
+
+class FalseConstant extends BoolConstant {
+  final bool value = false;
+
+  factory FalseConstant() => const FalseConstant._internal();
+  const FalseConstant._internal() : super._internal();
+  bool isFalse() => true;
+
+  TrueConstant negate() => new TrueConstant();
+
+  bool operator ==(var other) => identical(this, other);
+  // The magic constant is just a random value. It does not have any
+  // significance.
+  int get hashCode => 536555975;
+  DartString toDartString() => const LiteralDartString("false");
+
+  accept(ConstantVisitor visitor) => visitor.visitFalse(this);
+}
+
+class StringConstant extends PrimitiveConstant {
+  final DartString value;
+  final int hashCode;
+  final Node node;
+
+  // TODO(floitsch): cache StringConstants.
+  // TODO(floitsch): compute hashcode without calling toString() on the
+  // DartString.
+  StringConstant(DartString value, this.node)
+      : this.value = value,
+        this.hashCode = value.slowToString().hashCode;
+  bool isString() => true;
+
+  DartType computeType(Compiler compiler) {
+    return compiler.stringClass.computeType(compiler);
+  }
+
+  bool operator ==(var other) {
+    if (other is !StringConstant) return false;
+    StringConstant otherString = other;
+    return (hashCode == otherString.hashCode) && (value == otherString.value);
+  }
+
+  DartString toDartString() => value;
+  int get length => value.length;
+
+  accept(ConstantVisitor visitor) => visitor.visitString(this);
+}
+
+abstract class ObjectConstant extends Constant {
+  final DartType type;
+
+  ObjectConstant(this.type);
+  bool isObject() => true;
+
+  DartType computeType(Compiler compiler) => type;
+}
+
+class TypeConstant extends ObjectConstant {
+  /// The user type that this constant represents.
+  final DartType representedType;
+
+  TypeConstant(this.representedType, type) : super(type);
+
+  bool isType() => true;
+
+  bool operator ==(other) {
+    return other is TypeConstant && representedType == other.representedType;
+  }
+
+  int get hashCode => representedType.hashCode * 13;
+
+  List<Constant> getDependencies() => const <Constant>[];
+
+  accept(ConstantVisitor visitor) => visitor.visitType(this);
+}
+
+class ListConstant extends ObjectConstant {
+  final List<Constant> entries;
+  final int hashCode;
+
+  ListConstant(DartType type, List<Constant> entries)
+      : this.entries = entries,
+        hashCode = _computeHash(entries),
+        super(type);
+  bool isList() => true;
+
+  static int _computeHash(List<Constant> entries) {
+    // TODO(floitsch): create a better hash.
+    int hash = 0;
+    for (Constant input in entries) hash ^= input.hashCode;
+    return hash;
+  }
+
+  bool operator ==(var other) {
+    if (other is !ListConstant) return false;
+    ListConstant otherList = other;
+    if (hashCode != otherList.hashCode) return false;
+    // TODO(floitsch): verify that the generic types are the same.
+    if (entries.length != otherList.entries.length) return false;
+    for (int i = 0; i < entries.length; i++) {
+      if (entries[i] != otherList.entries[i]) return false;
+    }
+    return true;
+  }
+
+  List<Constant> getDependencies() => entries;
+
+  int get length => entries.length;
+
+  accept(ConstantVisitor visitor) => visitor.visitList(this);
+}
+
+class MapConstant extends ObjectConstant {
+  /**
+   * The [PROTO_PROPERTY] must not be used as normal property in any JavaScript
+   * object. It would change the prototype chain.
+   */
+  static const LiteralDartString PROTO_PROPERTY =
+      const LiteralDartString("__proto__");
+
+  /** The dart class implementing constant map literals. */
+  static const SourceString DART_CLASS = const SourceString("ConstantMap");
+  static const SourceString DART_PROTO_CLASS =
+      const SourceString("ConstantProtoMap");
+  static const SourceString LENGTH_NAME = const SourceString("length");
+  static const SourceString JS_OBJECT_NAME = const SourceString("_jsObject");
+  static const SourceString KEYS_NAME = const SourceString("_keys");
+  static const SourceString PROTO_VALUE = const SourceString("_protoValue");
+
+  final ListConstant keys;
+  final List<Constant> values;
+  final Constant protoValue;
+  final int hashCode;
+
+  MapConstant(DartType type, this.keys, List<Constant> values, this.protoValue)
+      : this.values = values,
+        this.hashCode = computeHash(values),
+        super(type);
+  bool isMap() => true;
+
+  static int computeHash(List<Constant> values) {
+    // TODO(floitsch): create a better hash.
+    int hash = 0;
+    for (Constant value in values) hash ^= value.hashCode;
+    return hash;
+  }
+
+  bool operator ==(var other) {
+    if (other is !MapConstant) return false;
+    MapConstant otherMap = other;
+    if (hashCode != otherMap.hashCode) return false;
+    // TODO(floitsch): verify that the generic types are the same.
+    if (keys != otherMap.keys) return false;
+    for (int i = 0; i < values.length; i++) {
+      if (values[i] != otherMap.values[i]) return false;
+    }
+    return true;
+  }
+
+  List<Constant> getDependencies() {
+    List<Constant> result = <Constant>[keys];
+    result.addAll(values);
+    return result;
+  }
+
+  int get length => keys.length;
+
+  accept(ConstantVisitor visitor) => visitor.visitMap(this);
+}
+
+class ConstructedConstant extends ObjectConstant {
+  final List<Constant> fields;
+  final int hashCode;
+
+  ConstructedConstant(DartType type, List<Constant> fields)
+    : this.fields = fields,
+      hashCode = computeHash(type, fields),
+      super(type) {
+    assert(type != null);
+  }
+  bool isConstructedObject() => true;
+
+  static int computeHash(DartType type, List<Constant> fields) {
+    // TODO(floitsch): create a better hash.
+    int hash = 0;
+    for (Constant field in fields) {
+      hash ^= field.hashCode;
+    }
+    hash ^= type.element.hashCode;
+    return hash;
+  }
+
+  bool operator ==(var otherVar) {
+    if (otherVar is !ConstructedConstant) return false;
+    ConstructedConstant other = otherVar;
+    if (hashCode != other.hashCode) return false;
+    // TODO(floitsch): verify that the (generic) types are the same.
+    if (type.element != other.type.element) return false;
+    if (fields.length != other.fields.length) return false;
+    for (int i = 0; i < fields.length; i++) {
+      if (fields[i] != other.fields[i]) return false;
+    }
+    return true;
+  }
+
+  List<Constant> getDependencies() => fields;
+
+  accept(ConstantVisitor visitor) => visitor.visitConstructed(this);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart
new file mode 100644
index 0000000..ac7d422
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart
@@ -0,0 +1,497 @@
+// 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.
+
+library dart2js;
+
+import 'dart:async';
+import 'dart:collection' show Queue, LinkedHashMap;
+import 'dart:io';
+import 'dart:uri';
+import 'dart:utf';
+
+import '../compiler.dart' as api;
+import 'source_file.dart';
+import 'source_file_provider.dart';
+import 'filenames.dart';
+import 'util/uri_extras.dart';
+import '../../libraries.dart';
+
+const String LIBRARY_ROOT = '../../../../..';
+const String OUTPUT_LANGUAGE_DART = 'Dart';
+
+typedef void HandleOption(String option);
+
+class OptionHandler {
+  String pattern;
+  HandleOption handle;
+
+  OptionHandler(this.pattern, this.handle);
+}
+
+/**
+ * Extract the parameter of an option.
+ *
+ * For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters
+ * are ['fisk.js'] and ['hest.js'], respectively.
+ */
+String extractParameter(String argument) {
+  // m[0] is the entire match (which will be equal to argument). m[1]
+  // is something like "-o" or "--out=", and m[2] is the parameter.
+  Match m = new RegExp('^(-[a-z]|--.+=)(.*)').firstMatch(argument);
+  if (m == null) helpAndFail('Error: Unknown option "$argument".');
+  return m[2];
+}
+
+String extractPath(String argument) {
+  String path = nativeToUriPath(extractParameter(argument));
+  return path.endsWith("/") ? path : "$path/";
+}
+
+void parseCommandLine(List<OptionHandler> handlers, List<String> argv) {
+  // TODO(ahe): Use ../../args/args.dart for parsing options instead.
+  var patterns = <String>[];
+  for (OptionHandler handler in handlers) {
+    patterns.add(handler.pattern);
+  }
+  var pattern = new RegExp('^(${Strings.join(patterns, ")\$|(")})\$');
+  OUTER: for (String argument in argv) {
+    Match match = pattern.firstMatch(argument);
+    assert(match.groupCount == handlers.length);
+    for (int i = 0; i < handlers.length; i++) {
+      if (match[i + 1] != null) {
+        handlers[i].handle(argument);
+        continue OUTER;
+      }
+    }
+    throw 'Internal error: "$argument" did not match';
+  }
+}
+
+void compile(List<String> argv) {
+  bool isWindows = (Platform.operatingSystem == 'windows');
+  Uri cwd = getCurrentDirectory();
+  Uri libraryRoot = cwd;
+  Uri out = cwd.resolve('out.js');
+  Uri sourceMapOut = cwd.resolve('out.js.map');
+  Uri packageRoot = null;
+  List<String> options = new List<String>();
+  bool explicitOut = false;
+  bool wantHelp = false;
+  String outputLanguage = 'JavaScript';
+  bool stripArgumentSet = false;
+  bool analyzeOnly = false;
+  SourceFileProvider inputProvider = new SourceFileProvider();
+  FormattingDiagnosticHandler diagnosticHandler =
+      new FormattingDiagnosticHandler(inputProvider);
+
+  passThrough(String argument) => options.add(argument);
+
+  setLibraryRoot(String argument) {
+    libraryRoot = cwd.resolve(extractPath(argument));
+  }
+
+  setPackageRoot(String argument) {
+    packageRoot = cwd.resolve(extractPath(argument));
+  }
+
+  setOutput(String argument) {
+    explicitOut = true;
+    out = cwd.resolve(nativeToUriPath(extractParameter(argument)));
+    sourceMapOut = Uri.parse('$out.map');
+  }
+
+  setOutputType(String argument) {
+    if (argument == '--output-type=dart') {
+      outputLanguage = OUTPUT_LANGUAGE_DART;
+      if (!explicitOut) {
+        out = cwd.resolve('out.dart');
+        sourceMapOut = cwd.resolve('out.dart.map');
+      }
+    }
+    passThrough(argument);
+  }
+
+  String getDepsOutput(Map<String, SourceFile> sourceFiles) {
+    var filenames = new List.from(sourceFiles.keys);
+    filenames.sort();
+    return Strings.join(filenames, "\n");
+  }
+
+  setStrip(String argument) {
+    stripArgumentSet = true;
+    passThrough(argument);
+  }
+
+  setAnalyzeOnly(String argument) {
+    analyzeOnly = true;
+    passThrough(argument);
+  }
+
+  setCategories(String argument) {
+    List<String> categories = extractParameter(argument).split(',');
+    Set<String> allowedCategories =
+        LIBRARIES.values.map((x) => x.category).toSet();
+    allowedCategories.remove('Shared');
+    allowedCategories.remove('Internal');
+    List<String> allowedCategoriesList =
+        new List<String>.from(allowedCategories);
+    allowedCategoriesList.sort();
+    if (categories.contains('all')) {
+      categories = allowedCategoriesList;
+    } else {
+      String allowedCategoriesString =
+          Strings.join(allowedCategoriesList, ', ');
+      for (String category in categories) {
+        if (!allowedCategories.contains(category)) {
+          fail('Error: unsupported library category "$category", '
+               'supported categories are: $allowedCategoriesString');
+        }
+      }
+    }
+    return passThrough('--categories=${Strings.join(categories, ",")}');
+  }
+
+  handleShortOptions(String argument) {
+    var shortOptions = argument.substring(1).splitChars();
+    for (var shortOption in shortOptions) {
+      switch (shortOption) {
+        case 'v':
+          diagnosticHandler.verbose = true;
+          break;
+        case 'h':
+        case '?':
+          wantHelp = true;
+          break;
+        case 'c':
+          passThrough('--enable-checked-mode');
+          break;
+        default:
+          throw 'Internal error: "$shortOption" did not match';
+      }
+    }
+  }
+
+  List<String> arguments = <String>[];
+  List<OptionHandler> handlers = <OptionHandler>[
+    new OptionHandler('-[chv?]+', handleShortOptions),
+    new OptionHandler('--throw-on-error',
+                      (_) => diagnosticHandler.throwOnError = true),
+    new OptionHandler('--suppress-warnings',
+                      (_) => diagnosticHandler.showWarnings = false),
+    new OptionHandler('--output-type=dart|--output-type=js', setOutputType),
+    new OptionHandler('--verbose', (_) => diagnosticHandler.verbose = true),
+    new OptionHandler('--library-root=.+', setLibraryRoot),
+    new OptionHandler('--out=.+|-o.+', setOutput),
+    new OptionHandler('--allow-mock-compilation', passThrough),
+    new OptionHandler('--minify', passThrough),
+    new OptionHandler('--force-strip=.*', setStrip),
+    // TODO(ahe): Remove the --no-colors option.
+    new OptionHandler('--disable-diagnostic-colors',
+                      (_) => diagnosticHandler.enableColors = false),
+    new OptionHandler('--enable-diagnostic-colors',
+                      (_) => diagnosticHandler.enableColors = true),
+    new OptionHandler('--enable[_-]checked[_-]mode|--checked',
+                      (_) => passThrough('--enable-checked-mode')),
+    new OptionHandler('--enable-concrete-type-inference',
+                      (_) => passThrough('--enable-concrete-type-inference')),
+    new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true),
+    new OptionHandler('--package-root=.+|-p.+', setPackageRoot),
+    new OptionHandler('--disallow-unsafe-eval', passThrough),
+    new OptionHandler('--analyze-all', passThrough),
+    new OptionHandler('--analyze-only', setAnalyzeOnly),
+    new OptionHandler('--disable-native-live-type-analysis', passThrough),
+    new OptionHandler('--reject-deprecated-language-features', passThrough),
+    new OptionHandler('--report-sdk-use-of-deprecated-language-features',
+                      passThrough),
+    new OptionHandler('--categories=.*', setCategories),
+
+    // The following two options must come last.
+    new OptionHandler('-.*', (String argument) {
+      helpAndFail('Error: Unknown option "$argument".');
+    }),
+    new OptionHandler('.*', (String argument) {
+      arguments.add(nativeToUriPath(argument));
+    })
+  ];
+
+  parseCommandLine(handlers, argv);
+  if (wantHelp) helpAndExit(diagnosticHandler.verbose);
+
+  if (outputLanguage != OUTPUT_LANGUAGE_DART && stripArgumentSet) {
+    helpAndFail('Error: --force-strip may only be used with '
+        '--output-type=dart');
+  }
+  if (arguments.isEmpty) {
+    helpAndFail('Error: No Dart file specified.');
+  }
+  if (arguments.length > 1) {
+    var extra = arguments.getRange(1, arguments.length - 1);
+    helpAndFail('Error: Extra arguments: ${Strings.join(extra, " ")}');
+  }
+
+  void handler(Uri uri, int begin, int end, String message,
+               api.Diagnostic kind) {
+    diagnosticHandler.diagnosticHandler(uri, begin, end, message, kind);
+  }
+
+  Uri uri = cwd.resolve(arguments[0]);
+  if (packageRoot == null) {
+    packageRoot = uri.resolve('./packages/');
+  }
+
+  diagnosticHandler.info('package root is $packageRoot');
+
+  int charactersWritten = 0;
+
+  compilationDone(String code) {
+    if (analyzeOnly) return;
+    if (code == null) {
+      fail('Error: Compilation failed.');
+    }
+    writeString(Uri.parse('$out.deps'),
+                getDepsOutput(inputProvider.sourceFiles));
+    diagnosticHandler.info(
+         'compiled ${inputProvider.dartCharactersRead} characters Dart '
+         '-> $charactersWritten characters $outputLanguage '
+         'in ${relativize(cwd, out, isWindows)}');
+    if (!explicitOut) {
+      String input = uriPathToNative(arguments[0]);
+      String output = relativize(cwd, out, isWindows);
+      print('Dart file $input compiled to $outputLanguage: $output');
+    }
+  }
+
+  StreamSink<String> outputProvider(String name, String extension) {
+    Uri uri;
+    String sourceMapFileName;
+    bool isPrimaryOutput = false;
+    if (name == '') {
+      if (extension == 'js' || extension == 'dart') {
+        isPrimaryOutput = true;
+        uri = out;
+        sourceMapFileName =
+            sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1);
+      } else if (extension == 'js.map' || extension == 'dart.map') {
+        uri = sourceMapOut;
+      } else {
+        fail('Error: Unknown extension: $extension');
+      }
+    } else {
+      uri = out.resolve('$name.$extension');
+    }
+
+    if (uri.scheme != 'file') {
+      fail('Error: Unhandled scheme ${uri.scheme} in $uri.');
+    }
+    var outputStream = new File(uriPathToNative(uri.path)).openOutputStream();
+
+    CountingSink sink;
+
+    onDone() {
+      if (sourceMapFileName != null) {
+        String sourceMapTag = '//@ sourceMappingURL=$sourceMapFileName\n';
+        sink.count += sourceMapTag.length;
+        outputStream.writeString(sourceMapTag);
+      }
+      outputStream.close();
+      if (isPrimaryOutput) {
+        charactersWritten += sink.count;
+      }
+    }
+
+    var controller = new StreamController<String>();
+    controller.stream.listen(outputStream.writeString, onDone: onDone);
+    sink = new CountingSink(controller);
+    return sink;
+  }
+
+  api.compile(uri, libraryRoot, packageRoot,
+              inputProvider.readStringFromUri, handler,
+              options, outputProvider)
+      .then(compilationDone);
+}
+
+// TODO(ahe): Get rid of this class if http://dartbug.com/8118 is fixed.
+class CountingSink implements StreamSink<String> {
+  final StreamSink<String> sink;
+  int count = 0;
+
+  CountingSink(this.sink);
+
+  add(String value) {
+    sink.add(value);
+    count += value.length;
+  }
+
+  signalError(AsyncError error) => sink.signalError(error);
+
+  close() => sink.close();
+}
+
+class AbortLeg {
+  final message;
+  AbortLeg(this.message);
+  toString() => 'Aborted due to --throw-on-error: $message';
+}
+
+void writeString(Uri uri, String text) {
+  if (uri.scheme != 'file') {
+    fail('Error: Unhandled scheme ${uri.scheme}.');
+  }
+  var file = new File(uriPathToNative(uri.path)).openSync(FileMode.WRITE);
+  file.writeStringSync(text);
+  file.closeSync();
+}
+
+void fail(String message) {
+  print(message);
+  exit(1);
+}
+
+void compilerMain(Options options) {
+  var root = uriPathToNative("/$LIBRARY_ROOT");
+  List<String> argv = ['--library-root=${options.script}$root'];
+  argv.addAll(options.arguments);
+  compile(argv);
+}
+
+void help() {
+  // This message should be no longer than 20 lines. The default
+  // terminal size normally 80x24. Two lines are used for the prompts
+  // before and after running the compiler. Another two lines may be
+  // used to print an error message.
+  print('''
+Usage: dart2js [options] dartfile
+
+Compiles Dart to JavaScript.
+
+Common options:
+  -o<file> Generate the output into <file>.
+  -c       Insert runtime type checks and enable assertions (checked mode).
+  -h       Display this message (add -v for information about all options).''');
+}
+
+void verboseHelp() {
+  print('''
+Usage: dart2js [options] dartfile
+
+Compiles Dart to JavaScript.
+
+Supported options:
+  -o<file>, --out=<file>
+    Generate the output into <file>.
+
+  -c, --enable-checked-mode, --checked
+    Insert runtime type checks and enable assertions (checked mode).
+
+  -h, /h, /?, --help
+    Display this message (add -v for information about all options).
+
+  -v, --verbose
+    Display verbose information.
+
+  -p<path>, --package-root=<path>
+    Where to find packages, that is, "package:..." imports.
+
+  --analyze-all
+    Analyze all code.  Without this option, the compiler only analyzes
+    code that is reachable from [main].  This option is useful for
+    finding errors in libraries, but using it can result in bigger and
+    slower output.
+
+  --analyze-only
+    Analyze but do not generate code.
+
+  --minify
+    Generate minified output.
+
+  --suppress-warnings
+    Do not display any warnings.
+
+  --enable-diagnostic-colors
+    Add colors to diagnostic messages.
+
+The following options are only used for compiler development and may
+be removed in a future version:
+
+  --output-type=dart
+    Output Dart code instead of JavaScript.
+
+  --throw-on-error
+    Throw an exception if a compile-time error is detected.
+
+  --library-root=<directory>
+    Where to find the Dart platform libraries.
+
+  --allow-mock-compilation
+    Do not generate a call to main if either of the following
+    libraries are used: dart:dom, dart:html dart:io.
+
+  --enable-concrete-type-inference
+    Enable experimental concrete type inference.
+
+  --disable-native-live-type-analysis
+    Disable the optimization that removes unused native types from dart:html
+    and related libraries.
+
+  --disallow-unsafe-eval
+    Disable dynamic generation of code in the generated output. This is
+    necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/).
+    This flag is not continuously tested. Please report breakages and we
+    will fix them as soon as possible.
+
+  --reject-deprecated-language-features
+    Reject deprecated language features.  Without this option, the
+    compiler will accept language features that are no longer valid
+    according to The Dart Programming Language Specification, version
+    0.12, M1.
+
+  --report-sdk-use-of-deprecated-language-features
+    Report use of deprecated features in Dart platform libraries.
+    Without this option, the compiler will silently accept use of
+    deprecated language features from these libraries.  The option
+    --reject-deprecated-language-features controls if these usages are
+    reported as errors or warnings.
+
+  --categories=<categories>
+
+    A comma separated list of allowed library categories.  The default
+    is "Client".  Possible categories can be seen by providing an
+    unsupported category, for example, --categories=help.  To enable
+    all categories, use --categories=all.
+
+'''.trim());
+}
+
+void helpAndExit(bool verbose) {
+  if (verbose) {
+    verboseHelp();
+  } else {
+    help();
+  }
+  exit(0);
+}
+
+void helpAndFail(String message) {
+  help();
+  print('');
+  fail(message);
+}
+
+void main() {
+  try {
+    compilerMain(new Options());
+  } catch (exception, trace) {
+    try {
+      print('Internal error: $exception');
+    } catch (ignored) {
+      print('Internal error: error while printing exception');
+    }
+    try {
+      print(trace);
+    } finally {
+      exit(253); // 253 is recognized as a crash by our test scripts.
+    }
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot
new file mode 100644
index 0000000..758a05a
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot
Binary files differ
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart
new file mode 100644
index 0000000..9b683f2
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart
@@ -0,0 +1,61 @@
+// 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.
+
+library dart2js;
+
+import 'dart:async';
+import 'dart:uri';
+import 'dart:collection' show Queue, LinkedHashMap;
+
+import 'closure.dart' as closureMapping;
+import 'dart_backend/dart_backend.dart' as dart_backend;
+import 'dart_types.dart';
+import 'elements/elements.dart';
+import 'elements/modelx.dart'
+    show ErroneousElementX,
+         CompilationUnitElementX,
+         LibraryElementX,
+         PrefixElementX,
+         VoidElementX;
+import 'js_backend/js_backend.dart' as js_backend;
+import 'native_handler.dart' as native;
+import 'scanner/scanner_implementation.dart';
+import 'scanner/scannerlib.dart';
+import 'ssa/ssa.dart';
+import 'string_validator.dart';
+import 'source_file.dart';
+import 'tree/tree.dart';
+import 'universe/universe.dart';
+import 'util/characters.dart';
+import 'util/util.dart';
+import '../compiler.dart' as api;
+import 'patch_parser.dart';
+import 'types/types.dart' as ti;
+import 'resolution/resolution.dart';
+import 'js/js.dart' as js;
+
+export 'resolution/resolution.dart' show TreeElements, TreeElementMapping;
+export 'scanner/scannerlib.dart' show SourceString,
+                                      isUserDefinableOperator,
+                                      isUnaryOperator,
+                                      isBinaryOperator,
+                                      isTernaryOperator,
+                                      isMinusOperator;
+export 'universe/universe.dart' show Selector;
+
+part 'code_buffer.dart';
+part 'compile_time_constants.dart';
+part 'compiler.dart';
+part 'constants.dart';
+part 'constant_system.dart';
+part 'constant_system_dart.dart';
+part 'diagnostic_listener.dart';
+part 'enqueue.dart';
+part 'library_loader.dart';
+part 'resolved_visitor.dart';
+part 'script.dart';
+part 'tree_validator.dart';
+part 'typechecker.dart';
+part 'warnings.dart';
+part 'world.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart
new file mode 100644
index 0000000..6a3b000
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart
@@ -0,0 +1,593 @@
+// 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.
+
+part of dart_backend;
+
+// TODO(ahe): This class is simply wrong.  This backend should use
+// elements when it can, not AST nodes.  Perhaps a [Map<Element,
+// TreeElements>] is what is needed.
+class ElementAst {
+  final Node ast;
+  final TreeElements treeElements;
+
+  ElementAst(this.ast, this.treeElements);
+
+  factory ElementAst.rewrite(compiler, ast, treeElements, stripAsserts) {
+    final rewriter =
+        new FunctionBodyRewriter(compiler, treeElements, stripAsserts);
+    return new ElementAst(rewriter.visit(ast), rewriter.cloneTreeElements);
+  }
+
+  ElementAst.forClassLike(this.ast)
+      : this.treeElements = new TreeElementMapping(null);
+}
+
+// TODO(ahe): This class should not subclass [TreeElementMapping], if
+// anything, it should implement TreeElements.
+class AggregatedTreeElements extends TreeElementMapping {
+  final List<TreeElements> treeElements;
+
+  AggregatedTreeElements() : treeElements = <TreeElements>[], super(null);
+
+  Element operator[](Node node) {
+    final result = super[node];
+    return result != null ? result : getFirstNotNullResult((e) => e[node]);
+  }
+
+  Selector getSelector(Send send) {
+    final result = super.getSelector(send);
+    return result != null ?
+        result : getFirstNotNullResult((e) => e.getSelector(send));
+  }
+
+  DartType getType(Node node) {
+    final result = super.getType(node);
+    return result != null ?
+        result : getFirstNotNullResult((e) => e.getType(node));
+  }
+
+  getFirstNotNullResult(f(TreeElements element)) {
+    for (final element in treeElements) {
+      final result = f(element);
+      if (result != null) return result;
+    }
+
+    return null;
+  }
+}
+
+class VariableListAst extends ElementAst {
+  VariableListAst(ast) : super(ast, new AggregatedTreeElements());
+
+  add(VariableElement element, TreeElements treeElements) {
+    AggregatedTreeElements e = this.treeElements;
+    e[element.cachedNode] = element;
+    e.treeElements.add(treeElements);
+  }
+}
+
+class FunctionBodyRewriter extends CloningVisitor {
+  final Compiler compiler;
+  final bool stripAsserts;
+
+  FunctionBodyRewriter(this.compiler, originalTreeElements, this.stripAsserts)
+      : super(originalTreeElements);
+
+  visitBlock(Block block) {
+    shouldOmit(Statement statement) {
+      if (statement is EmptyStatement) return true;
+      if (statement is ExpressionStatement) {
+        Send send = statement.expression.asSend();
+        if (send != null) {
+          Element element = originalTreeElements[send];
+          if (stripAsserts && identical(element, compiler.assertMethod)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    rewriteStatement(Statement statement) {
+      if (statement is Block) {
+        Link statements = statement.statements.nodes;
+        if (!statements.isEmpty && statements.tail.isEmpty) {
+          Statement single = statements.head;
+          bool isDeclaration =
+              single is VariableDefinitions || single is FunctionDeclaration;
+          if (!isDeclaration) return single;
+        }
+      }
+      return statement;
+    }
+
+    NodeList statements = block.statements;
+    LinkBuilder<Statement> builder = new LinkBuilder<Statement>();
+    for (Statement statement in statements.nodes) {
+      if (!shouldOmit(statement)) {
+        builder.addLast(visit(rewriteStatement(statement)));
+      }
+    }
+    return new Block(rewriteNodeList(statements, builder.toLink()));
+  }
+}
+
+class DartBackend extends Backend {
+  final List<CompilerTask> tasks;
+  final bool forceStripTypes;
+  final bool stripAsserts;
+  // TODO(antonm): make available from command-line options.
+  final bool outputAst = false;
+
+  Map<Element, TreeElements> get resolvedElements =>
+      compiler.enqueuer.resolution.resolvedElements;
+
+  /**
+   * Tells whether it is safe to remove type declarations from variables,
+   * functions parameters. It becomes not safe if:
+   * 1) TypeError is used somewhere in the code,
+   * 2) The code has typedefs in right hand side of IS checks,
+   * 3) The code has classes which extend typedefs, have type arguments typedefs
+   *    or type variable bounds typedefs.
+   * These restrictions can be less strict.
+   */
+  bool isSafeToRemoveTypeDeclarations(
+      Map<ClassElement, Set<Element>> classMembers) {
+    Set<DartType> processedTypes = new Set<DartType>();
+    List<DartType> workQueue = new List<DartType>();
+    workQueue.addAll(
+        classMembers.keys.map((classElement) => classElement.thisType));
+    workQueue.addAll(compiler.resolverWorld.isChecks);
+    Element typeErrorElement =
+        compiler.coreLibrary.find(new SourceString('TypeError'));
+    DartType typeErrorType = typeErrorElement.computeType(compiler);
+    if (workQueue.indexOf(typeErrorType) != -1) {
+      return false;
+    }
+
+    void processTypeArguments(Element classElement, NodeList typeArguments) {
+      if (typeArguments == null) return;
+      for (Node typeArgument in typeArguments.nodes) {
+        if (typeArgument is TypeVariable) {
+          typeArgument = typeArgument.bound;
+        }
+        if (typeArgument == null) continue;
+        assert(typeArgument is TypeAnnotation);
+        DartType argumentType =
+            compiler.resolveTypeAnnotation(classElement, typeArgument);
+        assert(argumentType != null);
+        workQueue.add(argumentType);
+      }
+    }
+
+    void processTypeAnnotationList(Element classElement, NodeList annotations) {
+      for (Link link = annotations.nodes; !link.isEmpty; link = link.tail) {
+        TypeAnnotation typeAnnotation = link.head;
+        NodeList typeArguments = typeAnnotation.typeArguments;
+        processTypeArguments(classElement, typeArguments);
+      }
+    }
+
+    void processSuperclassTypeArguments(Element classElement, Node superclass) {
+      if (superclass == null) return;
+      MixinApplication superMixinApplication = superclass.asMixinApplication();
+      if (superMixinApplication != null) {
+        processTypeAnnotationList(classElement, superMixinApplication.mixins);
+      } else {
+        TypeAnnotation typeAnnotation = superclass;
+        NodeList typeArguments = typeAnnotation.typeArguments;
+        processTypeArguments(classElement, typeArguments);
+      }
+    }
+
+    while (!workQueue.isEmpty) {
+      DartType type = workQueue.removeLast();
+      if (processedTypes.contains(type)) continue;
+      processedTypes.add(type);
+      if (type is TypedefType) return false;
+      if (type is InterfaceType) {
+        ClassElement element = type.element;
+        Node node = element.parseNode(compiler);
+        if (node is ClassNode) {
+          ClassNode classNode = node;
+          processTypeArguments(element, classNode.typeParameters);
+          processSuperclassTypeArguments(element, classNode.superclass);
+          processTypeAnnotationList(element, classNode.interfaces);
+        } else {
+          MixinApplication mixinNode = node;
+          processSuperclassTypeArguments(element, mixinNode.superclass);
+          if (mixinNode is NamedMixinApplication) {
+            NamedMixinApplication namedMixinNode = mixinNode;
+            processTypeArguments(element, namedMixinNode.typeParameters);
+          }
+        }
+        // Check all supertypes.
+        if (element.allSupertypes != null) {
+          workQueue.addAll(element.allSupertypes.toList());
+        }
+      }
+    }
+    return true;
+  }
+
+  DartBackend(Compiler compiler, List<String> strips)
+      : tasks = <CompilerTask>[],
+        forceStripTypes = strips.indexOf('types') != -1,
+        stripAsserts = strips.indexOf('asserts') != -1,
+        super(compiler);
+
+  void enqueueHelpers(ResolutionEnqueuer world) {
+    // Right now resolver doesn't always resolve interfaces needed
+    // for literals, so force them. TODO(antonm): fix in the resolver.
+    final LITERAL_TYPE_NAMES = const [
+      'Map', 'List', 'num', 'int', 'double', 'bool'
+    ];
+    final coreLibrary = compiler.coreLibrary;
+    for (final name in LITERAL_TYPE_NAMES) {
+      ClassElement classElement = coreLibrary.findLocal(new SourceString(name));
+      classElement.ensureResolved(compiler);
+    }
+  }
+  void codegen(CodegenWorkItem work) { }
+  void processNativeClasses(Enqueuer world,
+                            Iterable<LibraryElement> libraries) { }
+
+  bool isUserLibrary(LibraryElement lib) {
+    final INTERNAL_HELPERS = [
+      compiler.jsHelperLibrary,
+      compiler.interceptorsLibrary,
+    ];
+    return INTERNAL_HELPERS.indexOf(lib) == -1 && !lib.isPlatformLibrary;
+  }
+
+  void assembleProgram() {
+    // Conservatively traverse all platform libraries and collect member names.
+    // TODO(antonm): ideally we should only collect names of used members,
+    // however as of today there are problems with names of some core library
+    // interfaces, most probably for interfaces of literals.
+    final fixedMemberNames = new Set<String>();
+    for (final library in compiler.libraries.values) {
+      if (!library.isPlatformLibrary) continue;
+      library.implementation.forEachLocalMember((Element element) {
+        if (element.isClass()) {
+          ClassElement classElement = element;
+          // Make sure we parsed the class to initialize its local members.
+          // TODO(smok): Figure out if there is a better way to fill local
+          // members.
+          element.parseNode(compiler);
+          classElement.forEachLocalMember((member) {
+            final name = member.name.slowToString();
+            // Skip operator names.
+            if (!name.startsWith(r'operator$')) {
+              // Fetch name of named constructors and factories if any,
+              // otherwise store regular name.
+              // TODO(antonm): better way to analyze the name.
+              fixedMemberNames.add(name.split(r'$').last);
+            }
+          });
+        }
+        // Even class names are added due to a delicate problem we have:
+        // if one imports dart:core with a prefix, we cannot tell prefix.name
+        // from dynamic invocation (alas!).  So we'd better err on preserving
+        // those names.
+        fixedMemberNames.add(element.name.slowToString());
+      });
+    }
+    // The VM will automatically invoke the call method of objects
+    // that are invoked as functions. Make sure to not rename that.
+    fixedMemberNames.add('call');
+    // TODO(antonm): TypeError.srcType and TypeError.dstType are defined in
+    // runtime/lib/error.dart. Overall, all DartVM specific libs should be
+    // accounted for.
+    fixedMemberNames.add('srcType');
+    fixedMemberNames.add('dstType');
+
+    /**
+     * Tells whether we should output given element. Corelib classes like
+     * Object should not be in the resulting code.
+     */
+    bool shouldOutput(Element element) {
+      return !identical(element.kind, ElementKind.VOID)
+          && isUserLibrary(element.getLibrary())
+          && !element.isSynthesized
+          && element is !AbstractFieldElement;
+    }
+
+    final elementAsts = new Map<Element, ElementAst>();
+
+    parse(element) => element.parseNode(compiler);
+
+    Set<Element> topLevelElements = new Set<Element>();
+    Map<ClassElement, Set<Element>> classMembers =
+        new Map<ClassElement, Set<Element>>();
+
+    // Build all top level elements to emit and necessary class members.
+    var newTypedefElementCallback, newClassElementCallback;
+
+    processElement(element, elementAst) {
+      new ReferencedElementCollector(
+          compiler,
+          element, elementAst.treeElements,
+          newTypedefElementCallback, newClassElementCallback).collect();
+      elementAsts[element] = elementAst;
+    }
+
+    addTopLevel(element, elementAst) {
+      if (topLevelElements.contains(element)) return;
+      topLevelElements.add(element);
+      processElement(element, elementAst);
+    }
+
+    addClass(classElement) {
+      addTopLevel(classElement,
+                  new ElementAst.forClassLike(parse(classElement)));
+      classMembers.putIfAbsent(classElement, () => new Set());
+    }
+
+    newTypedefElementCallback = (TypedefElement element) {
+      if (!shouldOutput(element)) return;
+      addTopLevel(element,
+                  new ElementAst.forClassLike(parse(element)));
+    };
+    newClassElementCallback = (ClassElement classElement) {
+      if (!shouldOutput(classElement)) return;
+      addClass(classElement);
+    };
+
+    compiler.resolverWorld.instantiatedClasses.forEach(
+        (ClassElement classElement) {
+      if (shouldOutput(classElement)) addClass(classElement);
+    });
+    resolvedElements.forEach((element, treeElements) {
+      if (!shouldOutput(element) || treeElements == null) return;
+      var elementAst = new ElementAst.rewrite(
+          compiler, parse(element), treeElements, stripAsserts);
+      if (element.isField()) {
+        final list = (element as VariableElement).variables;
+        elementAst = elementAsts.putIfAbsent(
+            list, () => new VariableListAst(parse(list)));
+        (elementAst as VariableListAst).add(element, treeElements);
+        element = list;
+      }
+
+      if (element.isMember()) {
+        ClassElement enclosingClass = element.getEnclosingClass();
+        assert(enclosingClass.isClass());
+        assert(enclosingClass.isTopLevel());
+        assert(shouldOutput(enclosingClass));
+        addClass(enclosingClass);
+        classMembers[enclosingClass].add(element);
+        processElement(element, elementAst);
+      } else {
+        if (!element.isTopLevel()) {
+          compiler.cancel('Cannot process $element', element: element);
+        }
+        addTopLevel(element, elementAst);
+      }
+    });
+
+    // Add synthesized constructors to classes with no resolved constructors,
+    // but which originally had any constructor.  That should prevent
+    // those classes from being instantiable with default constructor.
+    Identifier synthesizedIdentifier =
+        new Identifier(new StringToken(IDENTIFIER_INFO, '', -1));
+
+    NextClassElement:
+    for (ClassElement classElement in classMembers.keys) {
+      for (Element member in classMembers[classElement]) {
+        if (member.isConstructor()) continue NextClassElement;
+      }
+      if (classElement.constructors.isEmpty) continue NextClassElement;
+
+      // TODO(antonm): check with AAR team if there is better approach.
+      // As an idea: provide template as a Dart code---class C { C.name(); }---
+      // and then overwrite necessary parts.
+      ClassNode classNode = classElement.parseNode(compiler);
+      SynthesizedConstructorElementX constructor =
+          new SynthesizedConstructorElementX(classElement);
+      constructor.type = new FunctionType(
+          constructor,
+          compiler.types.voidType,
+          const Link<DartType>(),
+          const Link<DartType>(),
+          const Link<SourceString>(),
+          const Link<DartType>()
+          );
+      constructor.cachedNode = new FunctionExpression(
+          new Send(classNode.name, synthesizedIdentifier),
+          new NodeList(new StringToken(OPEN_PAREN_INFO, '(', -1),
+                       const Link<Node>(),
+                       new StringToken(CLOSE_PAREN_INFO, ')', -1)),
+          new EmptyStatement(new StringToken(SEMICOLON_INFO, ';', -1)),
+          null, Modifiers.EMPTY, null, null);
+
+      classMembers[classElement].add(constructor);
+      elementAsts[constructor] =
+          new ElementAst(constructor.cachedNode, new TreeElementMapping(null));
+    }
+
+    // Create all necessary placeholders.
+    PlaceholderCollector collector =
+        new PlaceholderCollector(compiler, fixedMemberNames, elementAsts);
+    // Add synthesizedIdentifier to set of unresolved names to rename it to
+    // some unused identifier.
+    collector.unresolvedNodes.add(synthesizedIdentifier);
+    makePlaceholders(element) {
+      collector.collect(element);
+      if (element.isClass()) {
+        classMembers[element].forEach(makePlaceholders);
+      }
+    }
+    topLevelElements.forEach(makePlaceholders);
+    // Create renames.
+    Map<Node, String> renames = new Map<Node, String>();
+    Map<LibraryElement, String> imports = new Map<LibraryElement, String>();
+    bool shouldCutDeclarationTypes = forceStripTypes
+        || (compiler.enableMinification
+            && isSafeToRemoveTypeDeclarations(classMembers));
+    renamePlaceholders(
+        compiler, collector, renames, imports,
+        fixedMemberNames, shouldCutDeclarationTypes);
+
+    // Sort elements.
+    final sortedTopLevels = sortElements(topLevelElements);
+    final sortedClassMembers = new Map<ClassElement, List<Element>>();
+    classMembers.forEach((classElement, members) {
+      sortedClassMembers[classElement] = sortElements(members);
+    });
+
+    if (outputAst) {
+      // TODO(antonm): Ideally XML should be a separate backend.
+      // TODO(antonm): obey renames and minification, at least as an option.
+      StringBuffer sb = new StringBuffer();
+      outputElement(element) { sb.add(parse(element).toDebugString()); }
+
+      // Emit XML for AST instead of the program.
+      for (final topLevel in sortedTopLevels) {
+        if (topLevel.isClass()) {
+          // TODO(antonm): add some class info.
+          sortedClassMembers[topLevel].forEach(outputElement);
+        } else {
+          outputElement(topLevel);
+        }
+      }
+      compiler.assembledCode = '<Program>\n$sb</Program>\n';
+      return;
+    }
+
+    final topLevelNodes = <Node>[];
+    final memberNodes = new Map<ClassNode, List<Node>>();
+    for (final element in sortedTopLevels) {
+      topLevelNodes.add(elementAsts[element].ast);
+      if (element.isClass() && !element.isMixinApplication) {
+        final members = <Node>[];
+        for (final member in sortedClassMembers[element]) {
+          members.add(elementAsts[member].ast);
+        }
+        memberNodes[elementAsts[element].ast] = members;
+      }
+    }
+
+    final unparser = new EmitterUnparser(renames);
+    emitCode(unparser, imports, topLevelNodes, memberNodes);
+    compiler.assembledCode = unparser.result;
+
+    // Output verbose info about size ratio of resulting bundle to all
+    // referenced non-platform sources.
+    logResultBundleSizeInfo(topLevelElements);
+  }
+
+  void logResultBundleSizeInfo(Set<Element> topLevelElements) {
+    Iterable<LibraryElement> referencedLibraries =
+        compiler.libraries.values.where(isUserLibrary);
+    // Sum total size of scripts in each referenced library.
+    int nonPlatformSize = 0;
+    for (LibraryElement lib in referencedLibraries) {
+      for (CompilationUnitElement compilationUnit in lib.compilationUnits) {
+        nonPlatformSize += compilationUnit.script.text.length;
+      }
+    }
+    int percentage = compiler.assembledCode.length * 100 ~/ nonPlatformSize;
+    log('Total used non-platform files size: ${nonPlatformSize} bytes, '
+        'bundle size: ${compiler.assembledCode.length} bytes (${percentage}%)');
+  }
+
+  log(String message) => compiler.log('[DartBackend] $message');
+}
+
+class EmitterUnparser extends Unparser {
+  final Map<Node, String> renames;
+
+  EmitterUnparser(this.renames);
+
+  visit(Node node) {
+    if (node != null && renames.containsKey(node)) {
+      sb.add(renames[node]);
+    } else {
+      super.visit(node);
+    }
+  }
+
+  unparseSendReceiver(Send node, {bool spacesNeeded: false}) {
+    // TODO(smok): Remove ugly hack for library prefices.
+    if (node.receiver != null && renames[node.receiver] == '') return;
+    super.unparseSendReceiver(node, spacesNeeded: spacesNeeded);
+  }
+
+  unparseFunctionName(Node name) {
+    if (name != null && renames.containsKey(name)) {
+      sb.add(renames[name]);
+    } else {
+      super.unparseFunctionName(name);
+    }
+  }
+}
+
+
+/**
+ * Some elements are not recorded by resolver now,
+ * for example, typedefs or classes which are only
+ * used in signatures, as/is operators or in super clauses
+ * (just to name a few).  Retraverse AST to pick those up.
+ */
+class ReferencedElementCollector extends Visitor {
+  final Compiler compiler;
+  final Element rootElement;
+  final TreeElements treeElements;
+  final newTypedefElementCallback;
+  final newClassElementCallback;
+
+  ReferencedElementCollector(
+      this.compiler,
+      Element rootElement, this.treeElements,
+      this.newTypedefElementCallback, this.newClassElementCallback)
+      : this.rootElement = (rootElement is VariableElement)
+          ? (rootElement as VariableElement).variables : rootElement;
+
+  visitClassNode(ClassNode node) {
+    super.visitClassNode(node);
+    // Temporary hack which should go away once interfaces
+    // and default clauses are out.
+    if (node.defaultClause != null) {
+      // Resolver cannot resolve parameterized default clauses.
+      TypeAnnotation evilCousine = new TypeAnnotation(
+          node.defaultClause.typeName, null);
+      evilCousine.accept(this);
+    }
+  }
+
+  visitNode(Node node) { node.visitChildren(this); }
+
+  visitTypeAnnotation(TypeAnnotation typeAnnotation) {
+    // We call [resolveReturnType] to allow having 'void'.
+    final type = compiler.resolveReturnType(rootElement, typeAnnotation);
+    Element typeElement = type.element;
+    if (typeElement.isTypedef()) newTypedefElementCallback(typeElement);
+    if (typeElement.isClass()) newClassElementCallback(typeElement);
+    typeAnnotation.visitChildren(this);
+  }
+
+  void collect() {
+    compiler.withCurrentElement(rootElement, () {
+      rootElement.parseNode(compiler).accept(this);
+    });
+  }
+}
+
+compareBy(f) => (x, y) => f(x).compareTo(f(y));
+
+List sorted(Iterable l, comparison) {
+  final result = new List.from(l);
+  result.sort(comparison);
+  return result;
+}
+
+compareElements(e0, e1) {
+  int result = compareBy((e) => e.getLibrary().canonicalUri.toString())(e0, e1);
+  if (result != 0) return result;
+  return compareBy((e) => e.position().charOffset)(e0, e1);
+}
+
+List<Element> sortElements(Iterable<Element> elements) =>
+    sorted(elements, compareElements);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart
new file mode 100644
index 0000000..8a8abe4
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart
@@ -0,0 +1,25 @@
+// 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.
+
+library dart_backend;
+
+import '../elements/elements.dart';
+import '../elements/modelx.dart' show SynthesizedConstructorElementX;
+import '../dart2jslib.dart';
+import '../dart_types.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+
+import '../scanner/scannerlib.dart' show StringToken,
+                                         Keyword,
+                                         OPEN_PAREN_INFO,
+                                         CLOSE_PAREN_INFO,
+                                         SEMICOLON_INFO,
+                                         IDENTIFIER_INFO;
+
+part 'backend.dart';
+part 'emitter.dart';
+part 'renamer.dart';
+part 'placeholder_collector.dart';
+part 'utils.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart
new file mode 100644
index 0000000..c074af4
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart
@@ -0,0 +1,24 @@
+// 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.
+
+part of dart_backend;
+
+String emitCode(
+      Unparser unparser,
+      Map<LibraryElement, String> imports,
+      Collection<Node> topLevelNodes,
+      Map<ClassNode, Collection<Node>> classMembers) {
+  imports.forEach((libraryElement, prefix) {
+    unparser.unparseImportTag('${libraryElement.canonicalUri}', prefix);
+  });
+
+  for (final node in topLevelNodes) {
+    if (node is ClassNode) {
+      // TODO(smok): Filter out default constructors here.
+      unparser.unparseClassWithBody(node, classMembers[node]);
+    } else {
+      unparser.unparse(node);
+    }
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart
new file mode 100644
index 0000000..d184663
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart
@@ -0,0 +1,626 @@
+// 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.
+
+part of dart_backend;
+
+class LocalPlaceholder {
+  final String identifier;
+  final Set<Node> nodes;
+  LocalPlaceholder(this.identifier) : nodes = new Set<Node>();
+  int get hashCode => identifier.hashCode;
+  String toString() =>
+      'local_placeholder[id($identifier), nodes($nodes)]';
+}
+
+class FunctionScope {
+  final Set<String> parameterIdentifiers;
+  final Set<LocalPlaceholder> localPlaceholders;
+  FunctionScope()
+      : parameterIdentifiers = new Set<String>(),
+      localPlaceholders = new Set<LocalPlaceholder>();
+  void registerParameter(Identifier node) {
+    parameterIdentifiers.add(node.source.slowToString());
+  }
+}
+
+class ConstructorPlaceholder {
+  final Node node;
+  final DartType type;
+  final bool isRedirectingCall;
+  ConstructorPlaceholder(this.node, this.type)
+      : this.isRedirectingCall = false;
+  // Note: factory redirection is not redirecting call!
+  ConstructorPlaceholder.redirectingCall(this.node)
+      : this.type = null, this.isRedirectingCall = true;
+}
+
+class DeclarationTypePlaceholder {
+  final TypeAnnotation typeNode;
+  final bool requiresVar;
+  DeclarationTypePlaceholder(this.typeNode, this.requiresVar);
+}
+
+class SendVisitor extends ResolvedVisitor {
+  final PlaceholderCollector collector;
+
+  get compiler => collector.compiler;
+
+  SendVisitor(this.collector, TreeElements elements) : super(elements);
+
+  visitOperatorSend(Send node) {}
+  visitForeignSend(Send node) {}
+
+  visitSuperSend(Send node) {
+    Element element = elements[node];
+    if (element != null && element.isConstructor()) {
+      collector.makeRedirectingConstructorPlaceholder(node.selector, element);
+    } else {
+      collector.tryMakeMemberPlaceholder(node.selector);
+    }
+  }
+
+  visitDynamicSend(Send node) {
+    final element = elements[node];
+    if (element == null || !element.isErroneous()) {
+      collector.tryMakeMemberPlaceholder(node.selector);
+    }
+  }
+
+  visitClosureSend(Send node) {
+    final element = elements[node];
+    if (element != null) {
+      collector.tryMakeLocalPlaceholder(element, node.selector);
+    }
+  }
+
+  visitGetterSend(Send node) {
+    final element = elements[node];
+    // element == null means dynamic property access.
+    if (element == null) {
+      collector.tryMakeMemberPlaceholder(node.selector);
+    } else if (element.isErroneous()) {
+      return;
+    } else if (element.isPrefix()) {
+      // Node is prefix part in case of source 'lib.somesetter = 5;'
+      collector.makeNullPlaceholder(node);
+    } else if (Elements.isStaticOrTopLevel(element)) {
+      // Unqualified or prefixed top level or static.
+      collector.makeElementPlaceholder(node.selector, element);
+    } else if (!element.isTopLevel()) {
+      if (element.isInstanceMember()) {
+        collector.tryMakeMemberPlaceholder(node.selector);
+      } else {
+        // May get FunctionExpression here in selector
+        // in case of A(int this.f());
+        if (node.selector is Identifier) {
+          collector.tryMakeLocalPlaceholder(element, node.selector);
+        } else {
+          assert(node.selector is FunctionExpression);
+        }
+      }
+    }
+  }
+
+  visitStaticSend(Send node) {
+    final element = elements[node];
+    if (Elements.isUnresolved(element)
+        || identical(element, compiler.assertMethod)) {
+      return;
+    }
+    if (element.isConstructor() || element.isFactoryConstructor()) {
+      // Rename named constructor in redirection position:
+      // class C { C.named(); C.redirecting() : this.named(); }
+      if (node.receiver is Identifier
+          && node.receiver.asIdentifier().isThis()) {
+        assert(node.selector is Identifier);
+        collector.makeRedirectingConstructorPlaceholder(node.selector, element);
+      }
+      return;
+    }
+    collector.makeElementPlaceholder(node.selector, element);
+    // Another ugly case: <lib prefix>.<top level> is represented as
+    // receiver: lib prefix, selector: top level.
+    if (element.isTopLevel() && node.receiver != null) {
+      assert(elements[node.receiver].isPrefix());
+      // Hack: putting null into map overrides receiver of original node.
+      collector.makeNullPlaceholder(node.receiver);
+    }
+  }
+
+  internalError(String reason, {Node node}) {
+    collector.internalError(reason, node: node);
+  }
+
+  visitTypeReferenceSend(Send node) {
+    collector.makeElementPlaceholder(node.selector, elements[node]);
+  }
+}
+
+class PlaceholderCollector extends Visitor {
+  final Compiler compiler;
+  final Set<String> fixedMemberNames; // member names which cannot be renamed.
+  final Map<Element, ElementAst> elementAsts;
+  final Set<Node> nullNodes;  // Nodes that should not be in output.
+  final Set<Node> unresolvedNodes;
+  final Map<Element, Set<Node>> elementNodes;
+  final Map<FunctionElement, FunctionScope> functionScopes;
+  final Map<LibraryElement, Set<Identifier>> privateNodes;
+  final List<DeclarationTypePlaceholder> declarationTypePlaceholders;
+  final Map<String, Set<Identifier>> memberPlaceholders;
+  final Map<Element, List<ConstructorPlaceholder>> constructorPlaceholders;
+  Map<String, LocalPlaceholder> currentLocalPlaceholders;
+  Element currentElement;
+  FunctionElement topmostEnclosingFunction;
+  TreeElements treeElements;
+
+  LibraryElement get coreLibrary => compiler.coreLibrary;
+  FunctionElement get entryFunction => compiler.mainApp.find(Compiler.MAIN);
+
+  get currentFunctionScope => functionScopes.putIfAbsent(
+      topmostEnclosingFunction, () => new FunctionScope());
+
+  PlaceholderCollector(this.compiler, this.fixedMemberNames, this.elementAsts) :
+      nullNodes = new Set<Node>(),
+      unresolvedNodes = new Set<Node>(),
+      elementNodes = new Map<Element, Set<Node>>(),
+      functionScopes = new Map<FunctionElement, FunctionScope>(),
+      privateNodes = new Map<LibraryElement, Set<Identifier>>(),
+      declarationTypePlaceholders = new List<DeclarationTypePlaceholder>(),
+      memberPlaceholders = new Map<String, Set<Identifier>>(),
+      constructorPlaceholders =
+          new Map<Element, List<ConstructorPlaceholder>>();
+
+  void collectFunctionDeclarationPlaceholders(
+      FunctionElement element, FunctionExpression node) {
+    if (element.isGenerativeConstructor() || element.isFactoryConstructor()) {
+      DartType type = element.getEnclosingClass().thisType.asRaw();
+      makeConstructorPlaceholder(node.name, element, type);
+      Return bodyAsReturn = node.body.asReturn();
+      if (bodyAsReturn != null && bodyAsReturn.isRedirectingFactoryBody) {
+        // Factory redirection.
+        FunctionElement redirectTarget = element.defaultImplementation;
+        assert(redirectTarget != null && redirectTarget != element);
+        type = redirectTarget.getEnclosingClass().thisType.asRaw();
+        makeConstructorPlaceholder(
+            bodyAsReturn.expression, redirectTarget, type);
+      }
+    } else if (Elements.isStaticOrTopLevel(element)) {
+      // Note: this code should only rename private identifiers for class'
+      // fields/getters/setters/methods.  Top-level identifiers are renamed
+      // just to escape conflicts and that should be enough as we shouldn't
+      // be able to resolve private identifiers for other libraries.
+      makeElementPlaceholder(node.name, element);
+    } else if (element.isMember()) {
+      if (node.name is Identifier) {
+        tryMakeMemberPlaceholder(node.name);
+      } else {
+        assert(node.name.asSend().isOperator);
+      }
+    }
+  }
+
+  void collectFieldDeclarationPlaceholders(Element element, Node node) {
+    Identifier name = node is Identifier ? node : node.asSend().selector;
+    if (Elements.isStaticOrTopLevel(element)) {
+      makeElementPlaceholder(name, element);
+    } else if (Elements.isInstanceField(element)) {
+      tryMakeMemberPlaceholder(name);
+    }
+  }
+
+  void collect(Element element) {
+    this.currentElement = element;
+    this.topmostEnclosingFunction = null;
+    final ElementAst elementAst = elementAsts[element];
+    this.treeElements = elementAst.treeElements;
+    Node elementNode = elementAst.ast;
+    if (element is FunctionElement) {
+      collectFunctionDeclarationPlaceholders(element, elementNode);
+    } else if (element is VariableListElement) {
+      VariableDefinitions definitions = elementNode;
+      for (Node definition in definitions.definitions) {
+        final definitionElement = treeElements[definition];
+        // definitionElement == null if variable is actually unused.
+        if (definitionElement == null) continue;
+        collectFieldDeclarationPlaceholders(definitionElement, definition);
+      }
+      makeVarDeclarationTypePlaceholder(definitions);
+    } else {
+      assert(element is ClassElement || element is TypedefElement);
+    }
+    currentLocalPlaceholders = new Map<String, LocalPlaceholder>();
+    compiler.withCurrentElement(element, () {
+      elementNode.accept(this);
+    });
+  }
+
+  void tryMakeLocalPlaceholder(Element element, Identifier node) {
+    bool isOptionalParameter() {
+      FunctionElement function = element.enclosingElement;
+      for (Element parameter in function.functionSignature.optionalParameters) {
+        if (identical(parameter, element)) return true;
+      }
+      return false;
+    }
+
+    // TODO(smok): Maybe we should rename privates as well, their privacy
+    // should not matter if they are local vars.
+    if (node.source.isPrivate()) return;
+    if (element.isParameter() && isOptionalParameter()) {
+      currentFunctionScope.registerParameter(node);
+    } else if (Elements.isLocal(element)) {
+      makeLocalPlaceholder(node);
+    }
+  }
+
+  void tryMakeMemberPlaceholder(Identifier node) {
+    assert(node != null);
+    if (node.source.isPrivate()) return;
+    if (node is Operator) return;
+    final identifier = node.source.slowToString();
+    if (fixedMemberNames.contains(identifier)) return;
+    memberPlaceholders.putIfAbsent(
+        identifier, () => new Set<Identifier>()).add(node);
+  }
+
+  void makeTypePlaceholder(Node node, DartType type) {
+    if (node is Send) {
+      // Prefix.
+      assert(node.receiver is Identifier);
+      assert(node.selector is Identifier);
+      makeNullPlaceholder(node.receiver);
+      node = node.selector;
+    }
+    makeElementPlaceholder(node, type.element);
+  }
+
+  void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) {
+    if (type == null) return;
+    declarationTypePlaceholders.add(
+        new DeclarationTypePlaceholder(type, false));
+  }
+
+  void makeVarDeclarationTypePlaceholder(VariableDefinitions node) {
+    // TODO(smok): Maybe instead of calling this method and
+    // makeDeclaratioTypePlaceholder have type declaration placeholder
+    // collector logic in visitVariableDefinitions when resolver becomes better
+    // and/or catch syntax changes.
+    if (node.type == null) return;
+    Element definitionElement = treeElements[node.definitions.nodes.head];
+    bool requiresVar = !node.modifiers.isFinalOrConst();
+    declarationTypePlaceholders.add(
+        new DeclarationTypePlaceholder(node.type, requiresVar));
+  }
+
+  void makeNullPlaceholder(Node node) {
+    assert(node is Identifier || node is Send);
+    nullNodes.add(node);
+  }
+
+  void makeElementPlaceholder(Node node, Element element) {
+    assert(element != null);
+    if (identical(element, entryFunction)) return;
+    if (identical(element.getLibrary(), coreLibrary)) return;
+    if (element.getLibrary().isPlatformLibrary && !element.isTopLevel()) {
+      return;
+    }
+    if (element == compiler.types.dynamicType.element) {
+      internalError(
+          'Should never make element placeholder for dynamic type element',
+          node: node);
+    }
+    elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node);
+  }
+
+  void makePrivateIdentifier(Identifier node) {
+    assert(node != null);
+    privateNodes.putIfAbsent(
+        currentElement.getLibrary(), () => new Set<Identifier>()).add(node);
+  }
+
+  void makeUnresolvedPlaceholder(Node node) {
+    unresolvedNodes.add(node);
+  }
+
+  void makeLocalPlaceholder(Identifier identifier) {
+    LocalPlaceholder getLocalPlaceholder() {
+      String name = identifier.source.slowToString();
+      return currentLocalPlaceholders.putIfAbsent(name, () {
+        LocalPlaceholder localPlaceholder = new LocalPlaceholder(name);
+        currentFunctionScope.localPlaceholders.add(localPlaceholder);
+        return localPlaceholder;
+      });
+    }
+
+    getLocalPlaceholder().nodes.add(identifier);
+  }
+
+  void makeConstructorPlaceholder(Node node, Element element, DartType type) {
+    assert(type != null);
+    constructorPlaceholders
+        .putIfAbsent(element, () => <ConstructorPlaceholder>[])
+            .add(new ConstructorPlaceholder(node, type));
+  }
+  void makeRedirectingConstructorPlaceholder(Node node, Element element) {
+    constructorPlaceholders
+        .putIfAbsent(element, () => <ConstructorPlaceholder>[])
+            .add(new ConstructorPlaceholder.redirectingCall(node));
+  }
+
+  void internalError(String reason, {Node node}) {
+    compiler.cancel(reason, node: node);
+  }
+
+  void unreachable() { internalError('Unreachable case'); }
+
+  visit(Node node) => (node == null) ? null : node.accept(this);
+
+  visitNode(Node node) { node.visitChildren(this); }  // We must go deeper.
+
+  visitNewExpression(NewExpression node) {
+    Send send = node.send;
+    InterfaceType type = treeElements.getType(node);
+    assert(type != null);
+    Element constructor = treeElements[send];
+    assert(constructor != null);
+    assert(send.receiver == null);
+    if (!Elements.isErroneousElement(constructor)) {
+      makeConstructorPlaceholder(node.send.selector, constructor, type);
+      // TODO(smok): Should this be in visitNamedArgument?
+      // Field names can be exposed as names of optional arguments, e.g.
+      // class C {
+      //   final field;
+      //   C([this.field]);
+      // }
+      // Do not forget to rename them as well.
+      FunctionElement constructorFunction = constructor;
+      Link<Element> optionalParameters =
+          constructorFunction.functionSignature.optionalParameters;
+      for (final argument in send.argumentsNode) {
+        NamedArgument named = argument.asNamedArgument();
+        if (named == null) continue;
+        Identifier name = named.name;
+        String nameAsString = name.source.slowToString();
+        for (final parameter in optionalParameters) {
+          if (identical(parameter.kind, ElementKind.FIELD_PARAMETER)) {
+            if (parameter.name.slowToString() == nameAsString) {
+              tryMakeMemberPlaceholder(name);
+              break;
+            }
+          }
+        }
+      }
+    } else {
+      makeUnresolvedPlaceholder(node.send.selector);
+    }
+    visit(node.send.argumentsNode);
+  }
+
+  visitSend(Send send) {
+    new SendVisitor(this, treeElements).visitSend(send);
+    send.visitChildren(this);
+  }
+
+  visitSendSet(SendSet send) {
+    Element element = treeElements[send];
+    if (Elements.isErroneousElement(element)) {
+      // Complicated case: constructs like receiver.selector++ can resolve
+      // to ErroneousElement.  Fortunately, receiver.selector still
+      // can be resoved via treeElements[send.selector], that's all
+      // that is needed to rename the construct properly.
+      element = treeElements[send.selector];
+    }
+    if (element == null) {
+      if (send.receiver != null) tryMakeMemberPlaceholder(send.selector);
+    } else if (!element.isErroneous()) {
+      if (Elements.isStaticOrTopLevel(element)) {
+        // TODO(smok): Worth investigating why sometimes we get getter/setter
+        // here and sometimes abstract field.
+        assert(element.isClass() || element is VariableElement ||
+               element.isAccessor() || element.isAbstractField() ||
+               element.isFunction() || element.isTypedef() ||
+               element is TypeVariableElement);
+        makeElementPlaceholder(send.selector, element);
+      } else {
+        assert(send.selector is Identifier);
+        if (Elements.isInstanceField(element)) {
+          tryMakeMemberPlaceholder(send.selector);
+        } else {
+          tryMakeLocalPlaceholder(element, send.selector);
+        }
+      }
+    }
+    send.visitChildren(this);
+  }
+
+  visitIdentifier(Identifier identifier) {
+    if (identifier.source.isPrivate()) makePrivateIdentifier(identifier);
+  }
+
+  static bool isPlainTypeName(TypeAnnotation typeAnnotation) {
+    if (typeAnnotation.typeName is !Identifier) return false;
+    if (typeAnnotation.typeArguments == null) return true;
+    if (typeAnnotation.typeArguments.isEmpty) return true;
+    return false;
+  }
+
+  static bool isDynamicType(TypeAnnotation typeAnnotation) {
+    if (!isPlainTypeName(typeAnnotation)) return false;
+    String name = typeAnnotation.typeName.asIdentifier().source.slowToString();
+    // TODO(aprelev@gmail.com): Removed deprecated Dynamic keyword support.
+    return name == 'Dynamic' || name == 'dynamic';
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    // Poor man generic variables resolution.
+    // TODO(antonm): get rid of it once resolver can deal with it.
+    TypeDeclarationElement typeDeclarationElement;
+    if (currentElement is TypeDeclarationElement) {
+      typeDeclarationElement = currentElement;
+    } else {
+      typeDeclarationElement = currentElement.getEnclosingClass();
+    }
+    if (typeDeclarationElement != null && isPlainTypeName(node)
+        && tryResolveAndCollectTypeVariable(
+               typeDeclarationElement, node.typeName)) {
+      return;
+    }
+    // We call [resolveReturnType] to allow having 'void'.
+    final type = compiler.resolveReturnType(currentElement, node);
+    if (type is InterfaceType || type is TypedefType) {
+      // TODO(antonm): is there a better way to detect unresolved types?
+      // Corner case: dart:core type with a prefix.
+      // Most probably there are some additional problems with
+      // coreLibPrefix.topLevels.
+      if (!identical(type.element, compiler.types.dynamicType.element)) {
+        makeTypePlaceholder(node.typeName, type);
+      } else {
+        if (!isDynamicType(node)) makeUnresolvedPlaceholder(node.typeName);
+      }
+    }
+    // Visit only type arguments, otherwise in case of lib.Class type
+    // annotation typeName is Send and we go to visitGetterSend, as a result
+    // "Class" is added to member placeholders.
+    visit(node.typeArguments);
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    // Collect only local placeholders.
+    for (Node definition in node.definitions.nodes) {
+      Element definitionElement = treeElements[definition];
+      // definitionElement may be null if we're inside variable definitions
+      // of a function that is a parameter of another function.
+      // TODO(smok): Fix this when resolver correctly deals with
+      // such cases.
+      if (definitionElement == null) continue;
+      if (definition is Send) {
+        // May get FunctionExpression here in definition.selector
+        // in case of A(int this.f());
+        if (definition.selector is Identifier) {
+          if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) {
+            tryMakeMemberPlaceholder(definition.selector);
+          } else {
+            tryMakeLocalPlaceholder(definitionElement, definition.selector);
+          }
+        } else {
+          assert(definition.selector is FunctionExpression);
+          if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) {
+            tryMakeMemberPlaceholder(
+                definition.selector.asFunctionExpression().name);
+          }
+        }
+      } else if (definition is Identifier) {
+        tryMakeLocalPlaceholder(definitionElement, definition);
+      } else if (definition is FunctionExpression) {
+        // Skip, it will be processed in visitFunctionExpression.
+      } else {
+        internalError('Unexpected definition structure $definition');
+      }
+    }
+    node.visitChildren(this);
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    bool isKeyword(Identifier id) =>
+        id != null && Keyword.keywords[id.source.slowToString()] != null;
+
+    Element element = treeElements[node];
+    // May get null here in case of A(int this.f());
+    if (element != null) {
+      // Rename only local functions.
+      if (topmostEnclosingFunction == null) {
+        topmostEnclosingFunction = element;
+      }
+      if (!identical(element, currentElement)) {
+        if (node.name != null) {
+          assert(node.name is Identifier);
+          tryMakeLocalPlaceholder(element, node.name);
+        }
+      }
+    }
+    node.visitChildren(this);
+    // Make sure we don't omit return type of methods which names are
+    // identifiers, because the following works fine:
+    // int interface() => 1;
+    // But omitting 'int' makes VM unhappy.
+    // TODO(smok): Remove it when http://dartbug.com/5278 is fixed.
+    if (node.name == null || !isKeyword(node.name.asIdentifier())) {
+      makeOmitDeclarationTypePlaceholder(node.returnType);
+    }
+    collectFunctionParameters(node.parameters);
+  }
+
+  void collectFunctionParameters(NodeList parameters) {
+    if (parameters == null) return;
+    for (Node parameter in parameters.nodes) {
+      if (parameter is NodeList) {
+        // Optional parameter list.
+        collectFunctionParameters(parameter);
+      } else {
+        assert(parameter is VariableDefinitions);
+        makeOmitDeclarationTypePlaceholder(
+            parameter.asVariableDefinitions().type);
+      }
+    }
+  }
+
+  visitClassNode(ClassNode node) {
+    ClassElement classElement = currentElement;
+    makeElementPlaceholder(node.name, classElement);
+    node.visitChildren(this);
+    if (node.defaultClause != null) {
+      // Can't just visit class node's default clause because of the bug in the
+      // resolver, it just crashes when it meets type variable.
+      DartType defaultType = classElement.defaultClass;
+      assert(defaultType != null);
+      makeTypePlaceholder(node.defaultClause.typeName, defaultType);
+      visit(node.defaultClause.typeArguments);
+    }
+  }
+
+  bool tryResolveAndCollectTypeVariable(
+      TypeDeclarationElement typeDeclaration, Identifier name) {
+    // Hack for case when interface and default class are in different
+    // libraries, try to resolve type variable to default class type arg.
+    // Example:
+    // lib1: interface I<K> default C<K> {...}
+    // lib2: class C<K> {...}
+    if (typeDeclaration is ClassElement
+        && (typeDeclaration as ClassElement).defaultClass != null) {
+      typeDeclaration = (typeDeclaration as ClassElement).defaultClass.element;
+    }
+    // Another poor man type resolution.
+    // Find this variable in enclosing type declaration parameters.
+    for (DartType type in typeDeclaration.typeVariables) {
+      if (type.name.slowToString() == name.source.slowToString()) {
+        makeTypePlaceholder(name, type);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    assert(currentElement is TypedefElement || currentElement is ClassElement);
+    tryResolveAndCollectTypeVariable(currentElement, node.name);
+    node.visitChildren(this);
+  }
+
+  visitTypedef(Typedef node) {
+    assert(currentElement is TypedefElement);
+    makeElementPlaceholder(node.name, currentElement);
+    node.visitChildren(this);
+    makeOmitDeclarationTypePlaceholder(node.returnType);
+    collectFunctionParameters(node.formals);
+  }
+
+  visitBlock(Block node) {
+    for (Node statement in node.statements.nodes) {
+      if (statement is VariableDefinitions) {
+        makeVarDeclarationTypePlaceholder(statement);
+      }
+    }
+    node.visitChildren(this);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart
new file mode 100644
index 0000000..886d39e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart
@@ -0,0 +1,359 @@
+// 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.
+
+part of dart_backend;
+
+Function get _compareNodes =>
+    compareBy((n) => n.getBeginToken().charOffset);
+
+typedef String _Renamer(Renamable renamable);
+abstract class Renamable {
+  const int RENAMABLE_TYPE_ELEMENT = 1;
+  const int RENAMABLE_TYPE_MEMBER = 2;
+  const int RENAMABLE_TYPE_LOCAL = 3;
+
+  final Set<Node> nodes;
+  final _Renamer renamer;
+
+  Renamable(this.nodes, this.renamer);
+  int compareTo(Renamable other) {
+    int nodesDiff = other.nodes.length.compareTo(this.nodes.length);
+    if (nodesDiff != 0) return nodesDiff;
+    int typeDiff = this.getTypeId().compareTo(other.getTypeId());
+    return typeDiff != 0 ? typeDiff : compareInternals(other);
+  }
+
+  int compareInternals(Renamable other);
+  int getTypeId();
+
+  String rename() => renamer(this);
+}
+
+class ElementRenamable extends Renamable {
+  final Element element;
+
+  ElementRenamable(this.element, Set<Node> nodes, _Renamer renamer)
+      : super(nodes, renamer);
+
+  int compareInternals(ElementRenamable other) =>
+      compareElements(this.element, other.element);
+  int getTypeId() => RENAMABLE_TYPE_ELEMENT;
+}
+
+class MemberRenamable extends Renamable {
+  final String identifier;
+  MemberRenamable(this.identifier, Set<Node> nodes, _Renamer renamer)
+      : super(nodes, renamer);
+  int compareInternals(MemberRenamable other) =>
+      this.identifier.compareTo(other.identifier);
+  int getTypeId() => RENAMABLE_TYPE_MEMBER;
+}
+
+class LocalRenamable extends Renamable {
+  LocalRenamable(Set<Node> nodes, _Renamer renamer) : super(nodes, renamer);
+  int compareInternals(LocalRenamable other) =>
+      _compareNodes(sorted(this.nodes, _compareNodes)[0],
+          sorted(other.nodes, _compareNodes)[0]);
+  int getTypeId() => RENAMABLE_TYPE_LOCAL;
+}
+
+/**
+ * Renames only top-level elements that would let to ambiguity if not renamed.
+ */
+void renamePlaceholders(
+    Compiler compiler,
+    PlaceholderCollector placeholderCollector,
+    Map<Node, String> renames,
+    Map<LibraryElement, String> imports,
+    Set<String> fixedMemberNames,
+    bool cutDeclarationTypes) {
+  final Map<LibraryElement, Map<String, String>> renamed
+      = new Map<LibraryElement, Map<String, String>>();
+
+  renameNodes(Collection<Node> nodes, renamer) {
+    for (Node node in sorted(nodes, _compareNodes)) {
+      renames[node] = renamer(node);
+    }
+  }
+
+  sortedForEach(Map<Element, dynamic> map, f) {
+    for (Element element in sortElements(map.keys)) {
+      f(element, map[element]);
+    }
+  }
+
+  String renameType(DartType type, Function renameElement) {
+    // TODO(smok): Do not rename type if it is in platform library or
+    // js-helpers.
+    StringBuffer result = new StringBuffer(renameElement(type.element));
+    if (type is InterfaceType) {
+      if (!type.isRaw) {
+        result.add('<');
+        Link<DartType> argumentsLink = type.typeArguments;
+        result.add(renameType(argumentsLink.head, renameElement));
+        for (Link<DartType> link = argumentsLink.tail; !link.isEmpty;
+             link = link.tail) {
+          result.add(',');
+          result.add(renameType(link.head, renameElement));
+        }
+        result.add('>');
+      }
+    }
+    return result.toString();
+  }
+
+  String renameConstructor(Element element, ConstructorPlaceholder placeholder,
+      Function renameString, Function renameElement) {
+    assert(element.isConstructor());
+    StringBuffer result = new StringBuffer();
+    String name = element.name.slowToString();
+    if (element.name != element.getEnclosingClass().name) {
+      // Named constructor or factory. Is there a more reliable way to check
+      // this case?
+      if (!placeholder.isRedirectingCall) {
+        result.add(renameType(placeholder.type, renameElement));
+        result.add('.');
+      }
+      String prefix = '${element.getEnclosingClass().name.slowToString()}\$';
+      if (!name.startsWith(prefix)) {
+        // Factory for another interface (that is going away soon).
+        compiler.internalErrorOnElement(element,
+            "Factory constructors for external interfaces are not supported.");
+      }
+      name = name.substring(prefix.length);
+      if (!element.getLibrary().isPlatformLibrary) {
+        name = renameString(element.getLibrary(), name);
+      }
+      result.add(name);
+    } else {
+      assert(!placeholder.isRedirectingCall);
+      result.add(renameType(placeholder.type, renameElement));
+    }
+    return result.toString();
+  }
+
+  Function makeElementRenamer(rename, generateUniqueName) => (element) {
+    assert(Elements.isErroneousElement(element) ||
+           Elements.isStaticOrTopLevel(element) ||
+           element is TypeVariableElement);
+    // TODO(smok): We may want to reuse class static field and method names.
+    String originalName = element.name.slowToString();
+    LibraryElement library = element.getLibrary();
+    if (identical(element.getLibrary(), compiler.coreLibrary)) {
+      return originalName;
+    }
+    if (library.isPlatformLibrary && !library.isInternalLibrary) {
+      assert(element.isTopLevel());
+      final prefix =
+          imports.putIfAbsent(library, () => generateUniqueName('p'));
+      return '$prefix.$originalName';
+    }
+
+    return rename(library, originalName);
+  };
+
+  Function makeRenamer(generateUniqueName) =>
+      (library, originalName) =>
+          renamed.putIfAbsent(library, () => {})
+              .putIfAbsent(originalName,
+                  () => generateUniqueName(originalName));
+
+  // Renamer function that takes library and original name and returns a new
+  // name for given identifier.
+  Function rename;
+  Function renameElement;
+  // A function that takes original identifier name and generates a new unique
+  // identifier.
+  Function generateUniqueName;
+  if (compiler.enableMinification) {
+    MinifyingGenerator generator = new MinifyingGenerator();
+    Set<String> forbiddenIdentifiers = new Set<String>.from(['main']);
+    forbiddenIdentifiers.addAll(Keyword.keywords.keys);
+    forbiddenIdentifiers.addAll(fixedMemberNames);
+    generateUniqueName = (_) =>
+        generator.generate(forbiddenIdentifiers.contains);
+    rename = makeRenamer(generateUniqueName);
+    renameElement = makeElementRenamer(rename, generateUniqueName);
+
+    Set<String> allParameterIdentifiers = new Set<String>();
+    for (var functionScope in placeholderCollector.functionScopes.values) {
+      allParameterIdentifiers.addAll(functionScope.parameterIdentifiers);
+    }
+    // Build a sorted (by usage) list of local nodes that will be renamed to
+    // the same identifier. So the top-used local variables in all functions
+    // will be renamed first and will all share the same new identifier.
+    List<Set<Node>> allSortedLocals = new List<Set<Node>>();
+    for (var functionScope in placeholderCollector.functionScopes.values) {
+      // Add current sorted local identifiers to the whole sorted list
+      // of all local identifiers for all functions.
+      List<LocalPlaceholder> currentSortedPlaceholders =
+          sorted(functionScope.localPlaceholders,
+              compareBy((LocalPlaceholder ph) => -ph.nodes.length));
+      List<Set<Node>> currentSortedNodes =
+          currentSortedPlaceholders.map((ph) => ph.nodes).toList();
+      // Make room in all sorted locals list for new stuff.
+      while (currentSortedNodes.length > allSortedLocals.length) {
+        allSortedLocals.add(new Set<Node>());
+      }
+      for (int i = 0; i < currentSortedNodes.length; i++) {
+        allSortedLocals[i].addAll(currentSortedNodes[i]);
+      }
+    }
+
+    // Rename elements, members and locals together based on their usage count,
+    // otherwise when we rename elements first there will be no good identifiers
+    // left for members even if they are used often.
+    String elementRenamer(ElementRenamable elementRenamable) =>
+        renameElement(elementRenamable.element);
+    String memberRenamer(MemberRenamable memberRenamable) =>
+        generator.generate(forbiddenIdentifiers.contains);
+    String localRenamer(LocalRenamable localRenamable) =>
+        generator.generate((name) =>
+            allParameterIdentifiers.contains(name)
+            || forbiddenIdentifiers.contains(name));
+    List<Renamable> renamables = [];
+    placeholderCollector.elementNodes.forEach(
+        (Element element, Set<Node> nodes) {
+      renamables.add(new ElementRenamable(element, nodes, elementRenamer));
+    });
+    placeholderCollector.memberPlaceholders.forEach(
+        (String memberName, Set<Identifier> identifiers) {
+      renamables.add(
+          new MemberRenamable(memberName, identifiers, memberRenamer));
+    });
+    for (Set<Node> localIdentifiers in allSortedLocals) {
+      renamables.add(new LocalRenamable(localIdentifiers, localRenamer));
+    }
+    renamables.sort((Renamable renamable1, Renamable renamable2) =>
+        renamable1.compareTo(renamable2));
+    for (Renamable renamable in renamables) {
+      String newName = renamable.rename();
+      renameNodes(renamable.nodes, (_) => newName);
+    }
+  } else {
+    // Never rename anything to 'main'.
+    final usedTopLevelOrMemberIdentifiers = new Set<String>();
+    usedTopLevelOrMemberIdentifiers.add('main');
+    usedTopLevelOrMemberIdentifiers.addAll(fixedMemberNames);
+    generateUniqueName = (originalName) {
+      String newName = conservativeGenerator(
+          originalName, usedTopLevelOrMemberIdentifiers.contains);
+      usedTopLevelOrMemberIdentifiers.add(newName);
+      return newName;
+    };
+    rename = makeRenamer(generateUniqueName);
+    renameElement = makeElementRenamer(rename, generateUniqueName);
+    // Rename elements.
+    sortedForEach(placeholderCollector.elementNodes,
+        (Element element, Set<Node> nodes) {
+      renameNodes(nodes, (_) => renameElement(element));
+    });
+
+    // Rename locals.
+    sortedForEach(placeholderCollector.functionScopes,
+        (functionElement, functionScope) {
+      Set<LocalPlaceholder> placeholders = functionScope.localPlaceholders;
+      Set<String> memberIdentifiers = new Set<String>();
+      if (functionElement.getEnclosingClass() != null) {
+        functionElement.getEnclosingClass().forEachMember(
+            (enclosingClass, member) {
+              memberIdentifiers.add(member.name.slowToString());
+            });
+      }
+      Set<String> usedLocalIdentifiers = new Set<String>();
+      for (LocalPlaceholder placeholder in placeholders) {
+        String nextId =
+            conservativeGenerator(placeholder.identifier, (name) =>
+                functionScope.parameterIdentifiers.contains(name)
+                || usedTopLevelOrMemberIdentifiers.contains(name)
+                || usedLocalIdentifiers.contains(name)
+                || memberIdentifiers.contains(name));
+        usedLocalIdentifiers.add(nextId);
+        renameNodes(placeholder.nodes, (_) => nextId);
+      }
+    });
+
+    final usedMemberIdentifiers = new Set<String>.from(fixedMemberNames);
+    // Do not rename members to top-levels, that allows to avoid renaming
+    // members to constructors.
+    usedMemberIdentifiers.addAll(usedTopLevelOrMemberIdentifiers);
+    placeholderCollector.memberPlaceholders.forEach((identifier, nodes) {
+      String newIdentifier = conservativeGenerator(
+          identifier, usedMemberIdentifiers.contains);
+      renameNodes(nodes, (_) => newIdentifier);
+    });
+  }
+
+  // Rename constructors.
+  sortedForEach(placeholderCollector.constructorPlaceholders,
+      (Element constructor, List<ConstructorPlaceholder> placeholders) {
+        for (ConstructorPlaceholder ph in placeholders) {
+          renames[ph.node] =
+              renameConstructor(constructor, ph, rename, renameElement);
+        }
+  });
+  sortedForEach(placeholderCollector.privateNodes, (library, nodes) {
+    renameNodes(nodes, (node) => rename(library, node.source.slowToString()));
+  });
+  renameNodes(placeholderCollector.unresolvedNodes,
+      (_) => generateUniqueName('Unresolved'));
+  renameNodes(placeholderCollector.nullNodes, (_) => '');
+  if (cutDeclarationTypes) {
+    for (DeclarationTypePlaceholder placeholder in
+         placeholderCollector.declarationTypePlaceholders) {
+      renames[placeholder.typeNode] = placeholder.requiresVar ? 'var' : '';
+    }
+  }
+}
+
+/** Always tries to return original identifier name unless it is forbidden. */
+String conservativeGenerator(
+    String originalName, bool isForbidden(String name)) {
+  String newName = originalName;
+  while (isForbidden(newName)) {
+    newName = 'p_$newName';
+  }
+  return newName;
+}
+
+/** Always tries to generate the most compact identifier. */
+class MinifyingGenerator {
+  static const String firstCharAlphabet =
+      r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+  static const String otherCharsAlphabet =
+      r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$';
+  int nextIdIndex;
+
+  MinifyingGenerator() : nextIdIndex = 0;
+
+  String generate(bool isForbidden(String name)) {
+    String newName;
+    do {
+      newName = getNextId();
+    } while(isForbidden(newName));
+    return newName;
+  }
+
+  /**
+   * Generates next mini ID with current index and alphabet.
+   * Advances current index.
+   * In other words, it converts index to visual representation
+   * as if digits are given characters.
+   */
+  String getNextId() {
+    // It's like converting index in decimal to [chars] radix.
+    int index = nextIdIndex++;
+    StringBuffer resultBuilder = new StringBuffer();
+    if (index < firstCharAlphabet.length) return firstCharAlphabet[index];
+    resultBuilder.add(firstCharAlphabet[index % firstCharAlphabet.length]);
+    index ~/= firstCharAlphabet.length;
+    int length = otherCharsAlphabet.length;
+    while (index >= length) {
+      resultBuilder.add(otherCharsAlphabet[index % length]);
+      index ~/= length;
+    }
+    resultBuilder.add(otherCharsAlphabet[index]);
+    return resultBuilder.toString();
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart
new file mode 100644
index 0000000..c30a2cf
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart
@@ -0,0 +1,292 @@
+// 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.
+
+part of dart_backend;
+
+class CloningVisitor implements Visitor<Node> {
+  final TreeElements originalTreeElements;
+  final TreeElementMapping cloneTreeElements;
+
+  CloningVisitor(originalTreeElements)
+      : cloneTreeElements =
+            new TreeElementMapping(originalTreeElements.currentElement),
+        this.originalTreeElements = originalTreeElements;
+
+  visit(Node node) {
+    if (node == null) return null;
+    final clone = node.accept(this);
+
+    final originalElement = originalTreeElements[node];
+    if (originalElement != null) cloneTreeElements[clone] = originalElement;
+
+    final originalType = originalTreeElements.getType(node);
+    if (originalType != null) cloneTreeElements.setType(clone, originalType);
+    return clone;
+  }
+
+  visitBlock(Block node) => new Block(visit(node.statements));
+
+  visitBreakStatement(BreakStatement node) => new BreakStatement(
+      visit(node.target), node.keywordToken, node.semicolonToken);
+
+  visitCascade(Cascade node) => new Cascade(visit(node.expression));
+
+  visitCascadeReceiver(CascadeReceiver node) => new CascadeReceiver(
+      visit(node.expression), node.cascadeOperator);
+
+  visitCaseMatch(CaseMatch node) => new CaseMatch(
+      node.caseKeyword, visit(node.expression), node.colonToken);
+
+  visitCatchBlock(CatchBlock node) => new CatchBlock(
+      visit(node.type), visit(node.formals), visit(node.block),
+      node.onKeyword, node.catchKeyword);
+
+  visitClassNode(ClassNode node) => new ClassNode(
+      visit(node.modifiers), visit(node.name), visit(node.typeParameters),
+      visit(node.superclass), visit(node.interfaces), visit(node.defaultClause),
+      node.beginToken, node.extendsKeyword, visit(node.body), node.endToken);
+
+  visitConditional(Conditional node) => new Conditional(
+      visit(node.condition), visit(node.thenExpression),
+      visit(node.elseExpression), node.questionToken, node.colonToken);
+
+  visitContinueStatement(ContinueStatement node) => new ContinueStatement(
+      visit(node.target), node.keywordToken, node.semicolonToken);
+
+  visitDoWhile(DoWhile node) => new DoWhile(
+      visit(node.body), visit(node.condition),
+      node.doKeyword, node.whileKeyword, node.endToken);
+
+  visitEmptyStatement(EmptyStatement node) => new EmptyStatement(
+      node.semicolonToken);
+
+  visitExpressionStatement(ExpressionStatement node) => new ExpressionStatement(
+      visit(node.expression), node.endToken);
+
+  visitFor(For node) => new For(
+      visit(node.initializer), visit(node.conditionStatement),
+      visit(node.update), visit(node.body), node.forToken);
+
+  visitForIn(ForIn node) => new ForIn(
+      visit(node.declaredIdentifier), visit(node.expression), visit(node.body),
+      node.forToken, node.inToken);
+
+  visitFunctionDeclaration(FunctionDeclaration node) => new FunctionDeclaration(
+      visit(node.function));
+
+  rewriteFunctionExpression(FunctionExpression node, Statement body) =>
+      new FunctionExpression(
+          visit(node.name), visit(node.parameters), body,
+          visit(node.returnType), visit(node.modifiers),
+          visit(node.initializers), node.getOrSet);
+
+  visitFunctionExpression(FunctionExpression node) =>
+      rewriteFunctionExpression(node, visit(node.body));
+
+  visitIdentifier(Identifier node) => new Identifier(node.token);
+
+  visitIf(If node) => new If(
+      visit(node.condition), visit(node.thenPart), visit(node.elsePart),
+      node.ifToken, node.elseToken);
+
+  visitLabel(Label node) => new Label(visit(node.identifier), node.colonToken);
+
+  visitLabeledStatement(LabeledStatement node) => new LabeledStatement(
+      visit(node.labels), visit(node.statement));
+
+  visitLiteralBool(LiteralBool node) => new LiteralBool(
+      node.token, node.handler);
+
+  visitLiteralDouble(LiteralDouble node) => new LiteralDouble(
+      node.token, node.handler);
+
+  visitLiteralInt(LiteralInt node) => new LiteralInt(node.token, node.handler);
+
+  visitLiteralList(LiteralList node) => new LiteralList(
+      visit(node.typeArguments), visit(node.elements), node.constKeyword);
+
+  visitLiteralMap(LiteralMap node) => new LiteralMap(
+      visit(node.typeArguments), visit(node.entries), node.constKeyword);
+
+  visitLiteralMapEntry(LiteralMapEntry node) => new LiteralMapEntry(
+      visit(node.key), node.colonToken, visit(node.value));
+
+  visitLiteralNull(LiteralNull node) => new LiteralNull(node.token);
+
+  visitLiteralString(LiteralString node) => new LiteralString(
+      node.token, node.dartString);
+
+  visitMixinApplication(MixinApplication node) => new MixinApplication(
+      visit(node.superclass), visit(node.mixins));
+
+  visitNamedMixinApplication(NamedMixinApplication node) =>
+      new NamedMixinApplication(visit(node.name),
+                                visit(node.typeParameters),
+                                visit(node.modifiers),
+                                visit(node.mixinApplication),
+                                visit(node.interfaces),
+                                node.typedefKeyword,
+                                node.endToken);
+
+  visitModifiers(Modifiers node) => new Modifiers(visit(node.nodes));
+
+  visitNamedArgument(NamedArgument node) => new NamedArgument(
+      visit(node.name), node.colonToken, visit(node.expression));
+
+  visitNewExpression(NewExpression node) => new NewExpression(
+      node.newToken, visit(node.send));
+
+  rewriteNodeList(NodeList node, Link link) =>
+      new NodeList(node.beginToken, link, node.endToken, node.delimiter);
+
+  visitNodeList(NodeList node) {
+    // Special case for classes which exist in hierarchy, but not
+    // in the visitor.
+    if (node is Prefix) {
+      return node.nodes.isEmpty ?
+          new Prefix() : new Prefix.singleton(visit(node.nodes.head));
+    }
+    if (node is Postfix) {
+      return node.nodes.isEmpty ?
+          new Postfix() : new Postfix.singleton(visit(node.nodes.head));
+    }
+    LinkBuilder<Node> builder = new LinkBuilder<Node>();
+    for (Node n in node.nodes) {
+      builder.addLast(visit(n));
+    }
+    return rewriteNodeList(node, builder.toLink());
+  }
+
+  visitOperator(Operator node) => new Operator(node.token);
+
+  visitParenthesizedExpression(ParenthesizedExpression node) =>
+      new ParenthesizedExpression(visit(node.expression), node.beginToken);
+
+  visitReturn(Return node) => new Return(
+      node.beginToken, node.endToken, visit(node.expression));
+
+  visitScriptTag(ScriptTag node) => new ScriptTag(
+      visit(node.tag), visit(node.argument),
+      visit(node.prefixIdentifier), visit(node.prefix),
+      node.beginToken, node.endToken);
+
+  visitSend(Send node) => new Send(
+      visit(node.receiver), visit(node.selector), visit(node.argumentsNode));
+
+  visitSendSet(SendSet node) => new SendSet(
+      visit(node.receiver), visit(node.selector),
+      visit(node.assignmentOperator), visit(node.argumentsNode));
+
+  visitStringInterpolation(StringInterpolation node) =>
+      new StringInterpolation(visit(node.string), visit(node.parts));
+
+  visitStringInterpolationPart(StringInterpolationPart node) =>
+      new StringInterpolationPart(visit(node.expression), visit(node.string));
+
+  visitStringJuxtaposition(StringJuxtaposition node) =>
+      new StringJuxtaposition(visit(node.first), visit(node.second));
+
+  visitSwitchCase(SwitchCase node) => new SwitchCase(
+      visit(node.labelsAndCases), node.defaultKeyword, visit(node.statements),
+      node.startToken);
+
+  visitSwitchStatement(SwitchStatement node) => new SwitchStatement(
+      visit(node.parenthesizedExpression), visit(node.cases),
+      node.switchKeyword);
+
+  visitThrow(Throw node) => new Throw(
+      visit(node.expression), node.throwToken, node.endToken);
+
+  visitTryStatement(TryStatement node) => new TryStatement(
+      visit(node.tryBlock), visit(node.catchBlocks), visit(node.finallyBlock),
+      node.tryKeyword, node.finallyKeyword);
+
+  visitTypeAnnotation(TypeAnnotation node) => new TypeAnnotation(
+      visit(node.typeName), visit(node.typeArguments));
+
+  visitTypedef(Typedef node) => new Typedef(
+      visit(node.returnType), visit(node.name), visit(node.typeParameters),
+      visit(node.formals), node.typedefKeyword, node.endToken);
+
+  visitTypeVariable(TypeVariable node) => new TypeVariable(
+      visit(node.name), visit(node.bound));
+
+  visitVariableDefinitions(VariableDefinitions node) => new VariableDefinitions(
+      visit(node.type), visit(node.modifiers), visit(node.definitions));
+
+  visitWhile(While node) => new While(
+      visit(node.condition), visit(node.body), node.whileKeyword);
+
+  Node visitNode(Node node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitCombinator(Combinator node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitExport(Export node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitExpression(Expression node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitGotoStatement(GotoStatement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitImport(Import node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLibraryDependency(LibraryTag node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLibraryName(LibraryName node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLibraryTag(LibraryTag node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLiteral(Literal node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitLoop(Loop node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPart(Part node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPartOf(PartOf node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPostfix(Postfix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitPrefix(Prefix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitStatement(Statement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  Node visitStringNode(StringNode node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  unimplemented(String message, {Node node}) {
+    throw message;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart
new file mode 100644
index 0000000..7c78bd5
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart
@@ -0,0 +1,806 @@
+// 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.
+
+library dart_types;
+
+import 'dart2jslib.dart' show Compiler, invariant, Script, Message;
+import 'elements/modelx.dart' show VoidElementX, LibraryElementX;
+import 'elements/elements.dart';
+import 'scanner/scannerlib.dart' show SourceString;
+import 'util/util.dart' show Link, LinkBuilder;
+
+class TypeKind {
+  final String id;
+
+  const TypeKind(String this.id);
+
+  static const TypeKind FUNCTION = const TypeKind('function');
+  static const TypeKind INTERFACE = const TypeKind('interface');
+  static const TypeKind STATEMENT = const TypeKind('statement');
+  static const TypeKind TYPEDEF = const TypeKind('typedef');
+  static const TypeKind TYPE_VARIABLE = const TypeKind('type variable');
+  static const TypeKind MALFORMED_TYPE = const TypeKind('malformed');
+  static const TypeKind VOID = const TypeKind('void');
+
+  String toString() => id;
+}
+
+abstract class DartType {
+  SourceString get name;
+
+  TypeKind get kind;
+
+  const DartType();
+
+  /**
+   * Returns the [Element] which declared this type.
+   *
+   * This can be [ClassElement] for classes, [TypedefElement] for typedefs,
+   * [TypeVariableElement] for type variables and [FunctionElement] for
+   * function types.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  Element get element;
+
+  /**
+   * Performs the substitution [: [arguments[i]/parameters[i]]this :].
+   *
+   * The notation is known from this lambda calculus rule:
+   *
+   *     (lambda x.e0)e1 -> [e1/x]e0.
+   *
+   * See [TypeVariableType] for a motivation for this method.
+   *
+   * Invariant: There must be the same number of [arguments] and [parameters].
+   */
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters);
+
+  /**
+   * Returns the unaliased type of this type.
+   *
+   * The unaliased type of a typedef'd type is the unaliased type to which its
+   * name is bound. The unaliased version of any other type is the type itself.
+   *
+   * For example, the unaliased type of [: typedef A Func<A,B>(B b) :] is the
+   * function type [: (B) -> A :] and the unaliased type of
+   * [: Func<int,String> :] is the function type [: (String) -> int :].
+   */
+  DartType unalias(Compiler compiler);
+
+  /**
+   * A type is malformed if it is itself a malformed type or contains a
+   * malformed type.
+   */
+  bool get isMalformed => false;
+
+  /**
+   * Calls [f] with each [MalformedType] within this type.
+   *
+   * If [f] returns [: false :], the traversal stops prematurely.
+   *
+   * [forEachMalformedType] returns [: false :] if the traversal was stopped
+   * prematurely.
+   */
+  bool forEachMalformedType(bool f(MalformedType type)) => true;
+
+  bool operator ==(other);
+
+  /**
+   * Is [: true :] if this type has no explict type arguments.
+   */
+  bool get isRaw => true;
+
+  DartType asRaw() => this;
+}
+
+/**
+ * Represents a type variable, that is the type parameters of a class type.
+ *
+ * For example, in [: class Array<E> { ... } :], E is a type variable.
+ *
+ * Each class should have its own unique type variables, one for each type
+ * parameter. A class with type parameters is said to be parameterized or
+ * generic.
+ *
+ * Non-static members, constructors, and factories of generic
+ * class/interface can refer to type variables of the current class
+ * (not of supertypes).
+ *
+ * When using a generic type, also known as an application or
+ * instantiation of the type, the actual type arguments should be
+ * substituted for the type variables in the class declaration.
+ *
+ * For example, given a box, [: class Box<T> { T value; } :], the
+ * type of the expression [: new Box<String>().value :] is
+ * [: String :] because we must substitute [: String :] for the
+ * the type variable [: T :].
+ */
+class TypeVariableType extends DartType {
+  final TypeVariableElement element;
+
+  TypeVariableType(this.element);
+
+  TypeKind get kind => TypeKind.TYPE_VARIABLE;
+
+  SourceString get name => element.name;
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    if (parameters.isEmpty) {
+      assert(arguments.isEmpty);
+      // Return fast on empty substitutions.
+      return this;
+    }
+    Link<DartType> parameterLink = parameters;
+    Link<DartType> argumentLink = arguments;
+    while (!argumentLink.isEmpty && !parameterLink.isEmpty) {
+      TypeVariableType parameter = parameterLink.head;
+      DartType argument = argumentLink.head;
+      if (parameter == this) {
+        assert(argumentLink.tail.isEmpty == parameterLink.tail.isEmpty);
+        return argument;
+      }
+      parameterLink = parameterLink.tail;
+      argumentLink = argumentLink.tail;
+    }
+    assert(argumentLink.isEmpty && parameterLink.isEmpty);
+    // The type variable was not substituted.
+    return this;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  int get hashCode => 17 * element.hashCode;
+
+  bool operator ==(other) {
+    if (other is !TypeVariableType) return false;
+    return identical(other.element, element);
+  }
+
+  String toString() => name.slowToString();
+}
+
+/**
+ * A statement type tracks whether a statement returns or may return.
+ */
+class StatementType extends DartType {
+  final String stringName;
+
+  Element get element => null;
+
+  TypeKind get kind => TypeKind.STATEMENT;
+
+  SourceString get name => new SourceString(stringName);
+
+  const StatementType(this.stringName);
+
+  static const RETURNING = const StatementType('<returning>');
+  static const NOT_RETURNING = const StatementType('<not returning>');
+  static const MAYBE_RETURNING = const StatementType('<maybe returning>');
+
+  /** Combine the information about two control-flow edges that are joined. */
+  StatementType join(StatementType other) {
+    return (identical(this, other)) ? this : MAYBE_RETURNING;
+  }
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    // Statement types are not substitutable.
+    return this;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  int get hashCode => 17 * stringName.hashCode;
+
+  bool operator ==(other) {
+    if (other is !StatementType) return false;
+    return other.stringName == stringName;
+  }
+
+  String toString() => stringName;
+}
+
+class VoidType extends DartType {
+  const VoidType(this.element);
+
+  TypeKind get kind => TypeKind.VOID;
+
+  SourceString get name => element.name;
+
+  final Element element;
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    // Void cannot be substituted.
+    return this;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  int get hashCode => 1729;
+
+  bool operator ==(other) => other is VoidType;
+
+  String toString() => name.slowToString();
+}
+
+class MalformedType extends DartType {
+  final ErroneousElement element;
+
+  /**
+   * [declaredType] holds the type which the user wrote in code.
+   *
+   * For instance, for a resolved but malformed type like [: Map<String> :] the
+   * [declaredType] is [: Map<String> :] whereas for an unresolved type
+   */
+  final DartType userProvidedBadType;
+
+  /**
+   * Type arguments for the malformed typed, if these cannot fit in the
+   * [declaredType].
+   *
+   * This field is for instance used for [: dynamic<int> :] and [: T<int> :]
+   * where [: T :] is a type variable, in which case [declaredType] holds
+   * [: dynamic :] and [: T :], respectively, or for [: X<int> :] where [: X :]
+   * is not resolved or does not imply a type.
+   */
+  final Link<DartType> typeArguments;
+
+  MalformedType(this.element, this.userProvidedBadType,
+                [this.typeArguments = null]);
+
+  TypeKind get kind => TypeKind.MALFORMED_TYPE;
+
+  SourceString get name => element.name;
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    // Malformed types are not substitutable.
+    return this;
+  }
+
+  bool get isMalformed => true;
+
+  bool forEachMalformedType(bool f(MalformedType type)) => f(this);
+
+  DartType unalias(Compiler compiler) => this;
+
+  String toString() {
+    var sb = new StringBuffer();
+    if (typeArguments != null) {
+      if (userProvidedBadType != null) {
+        sb.add(userProvidedBadType.name.slowToString());
+      } else {
+        sb.add(element.name.slowToString());
+      }
+      if (!typeArguments.isEmpty) {
+        sb.add('<');
+        typeArguments.printOn(sb, ', ');
+        sb.add('>');
+      }
+    } else {
+      sb.add(userProvidedBadType.toString());
+    }
+    return sb.toString();
+  }
+}
+
+bool hasMalformed(Link<DartType> types) {
+  for (DartType typeArgument in types) {
+    if (typeArgument.isMalformed) {
+      return true;
+    }
+  }
+  return false;
+}
+
+abstract class GenericType extends DartType {
+  final Link<DartType> typeArguments;
+  final bool isMalformed;
+
+  GenericType(Link<DartType> this.typeArguments, bool this.isMalformed);
+
+  TypeDeclarationElement get element;
+
+  /// Creates a new instance of this type using the provided type arguments.
+  GenericType _createType(Link<DartType> newTypeArguments);
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    if (typeArguments.isEmpty) {
+      // Return fast on non-generic types.
+      return this;
+    }
+    if (parameters.isEmpty) {
+      assert(arguments.isEmpty);
+      // Return fast on empty substitutions.
+      return this;
+    }
+    Link<DartType> newTypeArguments =
+        Types.substTypes(typeArguments, arguments, parameters);
+    if (!identical(typeArguments, newTypeArguments)) {
+      // Create a new type only if necessary.
+      return _createType(newTypeArguments);
+    }
+    return this;
+  }
+
+  bool forEachMalformedType(bool f(MalformedType type)) {
+    for (DartType typeArgument in typeArguments) {
+      if (!typeArgument.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.add(name.slowToString());
+    if (!isRaw) {
+      sb.add('<');
+      typeArguments.printOn(sb, ', ');
+      sb.add('>');
+    }
+    return sb.toString();
+  }
+
+  int get hashCode {
+    int hash = element.hashCode;
+    for (Link<DartType> arguments = this.typeArguments;
+         !arguments.isEmpty;
+         arguments = arguments.tail) {
+      int argumentHash = arguments.head != null ? arguments.head.hashCode : 0;
+      hash = 17 * hash + 3 * argumentHash;
+    }
+    return hash;
+  }
+
+  bool operator ==(other) {
+    if (!identical(element, other.element)) return false;
+    return typeArguments == other.typeArguments;
+  }
+
+  bool get isRaw => typeArguments.isEmpty || identical(this, element.rawType);
+
+  GenericType asRaw() => element.rawType;
+}
+
+// TODO(johnniwinther): Add common supertype for InterfaceType and TypedefType.
+class InterfaceType extends GenericType {
+  final ClassElement element;
+
+  InterfaceType(this.element,
+                [Link<DartType> typeArguments = const Link<DartType>()])
+      : super(typeArguments, hasMalformed(typeArguments)) {
+    assert(invariant(element, element.isDeclaration));
+  }
+
+  TypeKind get kind => TypeKind.INTERFACE;
+
+  SourceString get name => element.name;
+
+  InterfaceType _createType(Link<DartType> newTypeArguments) {
+    return new InterfaceType(element, newTypeArguments);
+  }
+
+  /**
+   * Returns the type as an instance of class [other], if possible, null
+   * otherwise.
+   */
+  DartType asInstanceOf(ClassElement other) {
+    if (element == other) return this;
+    for (InterfaceType supertype in element.allSupertypes) {
+      ClassElement superclass = supertype.element;
+      if (superclass == other) {
+        Link<DartType> arguments = Types.substTypes(supertype.typeArguments,
+                                                    typeArguments,
+                                                    element.typeVariables);
+        return new InterfaceType(superclass, arguments);
+      }
+    }
+    return null;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  bool operator ==(other) {
+    if (other is !InterfaceType) return false;
+    return super == other;
+  }
+
+  InterfaceType asRaw() => super.asRaw();
+}
+
+class FunctionType extends DartType {
+  final Element element;
+  final DartType returnType;
+  final Link<DartType> parameterTypes;
+  final Link<DartType> optionalParameterTypes;
+
+  /**
+   * The names of the named parameters ordered lexicographically.
+   */
+  final Link<SourceString> namedParameters;
+
+  /**
+   * The types of the named parameters in the order corresponding to the
+   * [namedParameters].
+   */
+  final Link<DartType> namedParameterTypes;
+  final bool isMalformed;
+
+  factory FunctionType(Element element,
+                       DartType returnType,
+                       Link<DartType> parameterTypes,
+                       Link<DartType> optionalParameterTypes,
+                       Link<SourceString> namedParameters,
+                       Link<DartType> namedParameterTypes) {
+    // Compute [isMalformed] eagerly since it is faster than a lazy computation
+    // and since [isMalformed] most likely will be accessed in [Types.isSubtype]
+    // anyway.
+    bool isMalformed = returnType != null &&
+                       returnType.isMalformed ||
+                       hasMalformed(parameterTypes) ||
+                       hasMalformed(optionalParameterTypes) ||
+                       hasMalformed(namedParameterTypes);
+    return new FunctionType.internal(element,
+                                     returnType,
+                                     parameterTypes,
+                                     optionalParameterTypes,
+                                     namedParameters,
+                                     namedParameterTypes,
+                                     isMalformed);
+  }
+
+  FunctionType.internal(Element this.element,
+                        DartType this.returnType,
+                        Link<DartType> this.parameterTypes,
+                        Link<DartType> this.optionalParameterTypes,
+                        Link<SourceString> this.namedParameters,
+                        Link<DartType> this.namedParameterTypes,
+                        bool this.isMalformed) {
+    assert(element == null || invariant(element, element.isDeclaration));
+    // Assert that optional and named parameters are not used at the same time.
+    assert(optionalParameterTypes.isEmpty || namedParameterTypes.isEmpty);
+    assert(namedParameters.slowLength() == namedParameterTypes.slowLength());
+  }
+
+  TypeKind get kind => TypeKind.FUNCTION;
+
+  DartType getNamedParameterType(SourceString name) {
+    Link<SourceString> namedParameter = namedParameters;
+    Link<DartType> namedParameterType = namedParameterTypes;
+    while (!namedParameter.isEmpty && !namedParameterType.isEmpty) {
+      if (namedParameter.head == name) {
+        return namedParameterType.head;
+      }
+      namedParameter = namedParameter.tail;
+      namedParameterType = namedParameterType.tail;
+    }
+    return null;
+  }
+
+  DartType subst(Link<DartType> arguments, Link<DartType> parameters) {
+    if (parameters.isEmpty) {
+      assert(arguments.isEmpty);
+      // Return fast on empty substitutions.
+      return this;
+    }
+    var newReturnType = returnType.subst(arguments, parameters);
+    bool changed = !identical(newReturnType, returnType);
+    var newParameterTypes =
+        Types.substTypes(parameterTypes, arguments, parameters);
+    var newOptionalParameterTypes =
+        Types.substTypes(optionalParameterTypes, arguments, parameters);
+    var newNamedParameterTypes =
+        Types.substTypes(namedParameterTypes, arguments, parameters);
+    if (!changed &&
+        (!identical(parameterTypes, newParameterTypes) ||
+         !identical(optionalParameterTypes, newOptionalParameterTypes) ||
+         !identical(namedParameterTypes, newNamedParameterTypes))) {
+      changed = true;
+    }
+    if (changed) {
+      // Create a new type only if necessary.
+      return new FunctionType(element,
+                              newReturnType,
+                              newParameterTypes,
+                              newOptionalParameterTypes,
+                              namedParameters,
+                              newNamedParameterTypes);
+    }
+    return this;
+  }
+
+  bool forEachMalformedType(bool f(MalformedType type)) {
+    if (!returnType.forEachMalformedType(f)) {
+      return false;
+    }
+    for (DartType parameterType in parameterTypes) {
+      if (!parameterType.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    for (DartType parameterType in optionalParameterTypes) {
+      if (!parameterType.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    for (DartType parameterType in namedParameterTypes) {
+      if (!parameterType.forEachMalformedType(f)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  DartType unalias(Compiler compiler) => this;
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.add('(');
+    parameterTypes.printOn(sb, ', ');
+    bool first = parameterTypes.isEmpty;
+    if (!optionalParameterTypes.isEmpty) {
+      if (!first) {
+        sb.add(', ');
+      }
+      sb.add('[');
+      optionalParameterTypes.printOn(sb, ', ');
+      sb.add(']');
+      first = false;
+    }
+    if (!namedParameterTypes.isEmpty) {
+      if (!first) {
+        sb.add(', ');
+      }
+      sb.add('{');
+      Link<SourceString> namedParameter = namedParameters;
+      Link<DartType> namedParameterType = namedParameterTypes;
+      first = true;
+      while (!namedParameter.isEmpty && !namedParameterType.isEmpty) {
+        if (!first) {
+          sb.add(', ');
+        }
+        sb.add(namedParameterType.head);
+        sb.add(' ');
+          sb.add(namedParameter.head.slowToString());
+        namedParameter = namedParameter.tail;
+        namedParameterType = namedParameterType.tail;
+        first = false;
+      }
+      sb.add('}');
+    }
+    sb.add(') -> ${returnType}');
+    return sb.toString();
+  }
+
+  SourceString get name => const SourceString('Function');
+
+  int computeArity() {
+    int arity = 0;
+    parameterTypes.forEach((_) { arity++; });
+    return arity;
+  }
+
+  int get hashCode {
+    int hash = 17 * element.hashCode + 3 * returnType.hashCode;
+    for (DartType parameter  in parameterTypes) {
+      hash = 17 * hash + 3 * parameter.hashCode;
+    }
+    for (DartType parameter  in optionalParameterTypes) {
+      hash = 17 * hash + 3 * parameter.hashCode;
+    }
+    for (SourceString name  in namedParameters) {
+      hash = 17 * hash + 3 * name.hashCode;
+    }
+    for (DartType parameter  in namedParameterTypes) {
+      hash = 17 * hash + 3 * parameter.hashCode;
+    }
+    return hash;
+  }
+
+  bool operator ==(other) {
+    if (other is !FunctionType) return false;
+    return returnType == other.returnType
+           && parameterTypes == other.parameterTypes
+           && optionalParameterTypes == other.optionalParameterTypes
+           && namedParameters == other.namedParameters
+           && namedParameterTypes == other.namedParameterTypes;
+  }
+}
+
+class TypedefType extends GenericType {
+  final TypedefElement element;
+
+  TypedefType(this.element,
+              [Link<DartType> typeArguments = const Link<DartType>()])
+      : super(typeArguments, hasMalformed(typeArguments));
+
+  TypedefType _createType(Link<DartType> newTypeArguments) {
+    return new TypedefType(element, newTypeArguments);
+  }
+
+  TypeKind get kind => TypeKind.TYPEDEF;
+
+  SourceString get name => element.name;
+
+  DartType unalias(Compiler compiler) {
+    // TODO(ahe): This should be [ensureResolved].
+    compiler.resolveTypedef(element);
+    DartType definition = element.alias.unalias(compiler);
+    TypedefType declaration = element.computeType(compiler);
+    return definition.subst(typeArguments, declaration.typeArguments);
+  }
+
+  bool operator ==(other) {
+    if (other is !TypedefType) return false;
+    return super == other;
+  }
+
+  TypedefType asRaw() => super.asRaw();
+}
+
+/**
+ * Special type to hold the [dynamic] type. Used for correctly returning
+ * 'dynamic' on [toString].
+ */
+class DynamicType extends InterfaceType {
+  DynamicType(ClassElement element) : super(element);
+
+  SourceString get name => const SourceString('dynamic');
+}
+
+class Types {
+  final Compiler compiler;
+  // TODO(karlklose): should we have a class Void?
+  final VoidType voidType;
+  final DynamicType dynamicType;
+
+  factory Types(Compiler compiler, ClassElement dynamicElement) {
+    LibraryElement library = new LibraryElementX(new Script(null, null));
+    VoidType voidType = new VoidType(new VoidElementX(library));
+    DynamicType dynamicType = new DynamicType(dynamicElement);
+    dynamicElement.rawType = dynamicElement.thisType = dynamicType;
+    return new Types.internal(compiler, voidType, dynamicType);
+  }
+
+  Types.internal(this.compiler, this.voidType, this.dynamicType);
+
+  /** Returns true if t is a subtype of s */
+  bool isSubtype(DartType t, DartType s) {
+    if (identical(t, s) ||
+        identical(t, dynamicType) ||
+        identical(s, dynamicType) ||
+        t.isMalformed ||
+        s.isMalformed ||
+        identical(s.element, compiler.objectClass) ||
+        identical(t.element, compiler.nullClass)) {
+      return true;
+    }
+    t = t.unalias(compiler);
+    s = s.unalias(compiler);
+
+    if (t is VoidType) {
+      return false;
+    } else if (t is InterfaceType) {
+      if (s is !InterfaceType) return false;
+      ClassElement tc = t.element;
+      if (identical(tc, s.element)) return true;
+      for (Link<DartType> supertypes = tc.allSupertypes;
+           supertypes != null && !supertypes.isEmpty;
+           supertypes = supertypes.tail) {
+        DartType supertype = supertypes.head;
+        if (identical(supertype.element, s.element)) return true;
+      }
+      return false;
+    } else if (t is FunctionType) {
+      if (identical(s.element, compiler.functionClass)) return true;
+      if (s is !FunctionType) return false;
+      FunctionType tf = t;
+      FunctionType sf = s;
+      Link<DartType> tps = tf.parameterTypes;
+      Link<DartType> sps = sf.parameterTypes;
+      while (!tps.isEmpty && !sps.isEmpty) {
+        if (!isAssignable(tps.head, sps.head)) return false;
+        tps = tps.tail;
+        sps = sps.tail;
+      }
+      if (!tps.isEmpty || !sps.isEmpty) return false;
+      if (!isAssignable(sf.returnType, tf.returnType)) return false;
+      if (!sf.namedParameters.isEmpty) {
+        // Since named parameters are globally ordered we can determine the
+        // subset relation with a linear search for [:sf.NamedParameters:]
+        // within [:tf.NamedParameters:].
+        Link<SourceString> tNames = tf.namedParameters;
+        Link<DartType> tTypes = tf.namedParameterTypes;
+        Link<SourceString> sNames = sf.namedParameters;
+        Link<DartType> sTypes = sf.namedParameterTypes;
+        while (!tNames.isEmpty && !sNames.isEmpty) {
+          if (sNames.head == tNames.head) {
+            if (!isAssignable(tTypes.head, sTypes.head)) return false;
+
+            sNames = sNames.tail;
+            sTypes = sTypes.tail;
+          }
+          tNames = tNames.tail;
+          tTypes = tTypes.tail;
+        }
+        if (!sNames.isEmpty) {
+          // We didn't find all names.
+          return false;
+        }
+      }
+      if (!sf.optionalParameterTypes.isEmpty) {
+        Link<DartType> tOptionalParameterType = tf.optionalParameterTypes;
+        Link<DartType> sOptionalParameterType = sf.optionalParameterTypes;
+        while (!tOptionalParameterType.isEmpty &&
+               !sOptionalParameterType.isEmpty) {
+          if (!isAssignable(tOptionalParameterType.head,
+                            sOptionalParameterType.head)) {
+            return false;
+          }
+          sOptionalParameterType = sOptionalParameterType.tail;
+          tOptionalParameterType = tOptionalParameterType.tail;
+        }
+        if (!sOptionalParameterType.isEmpty) {
+          // We didn't find enough optional parameters.
+          return false;
+        }
+      }
+      return true;
+    } else if (t is TypeVariableType) {
+      if (s is !TypeVariableType) return false;
+      return (identical(t.element, s.element));
+    } else {
+      throw 'internal error: unknown type kind';
+    }
+  }
+
+  bool isAssignable(DartType r, DartType s) {
+    return isSubtype(r, s) || isSubtype(s, r);
+  }
+
+
+  /**
+   * Helper method for performing substitution of a linked list of types.
+   *
+   * If no types are changed by the substitution, the [types] is returned
+   * instead of a newly created linked list.
+   */
+  static Link<DartType> substTypes(Link<DartType> types,
+                                   Link<DartType> arguments,
+                                   Link<DartType> parameters) {
+    bool changed = false;
+    var builder = new LinkBuilder<DartType>();
+    Link<DartType> typeLink = types;
+    while (!typeLink.isEmpty) {
+      var argument = typeLink.head.subst(arguments, parameters);
+      if (!changed && !identical(argument, typeLink.head)) {
+        changed = true;
+      }
+      builder.addLast(argument);
+      typeLink = typeLink.tail;
+    }
+    if (changed) {
+      // Create a new link only if necessary.
+      return builder.toLink();
+    }
+    return types;
+  }
+
+  /**
+   * Combine error messages in a malformed type to a single message string.
+   */
+  static String fetchReasonsFromMalformedType(DartType type) {
+    // TODO(johnniwinther): Figure out how to produce good error message in face
+    // of multiple errors, and how to ensure non-localized error messages.
+    var reasons = new List<String>();
+    type.forEachMalformedType((MalformedType malformedType) {
+      ErroneousElement error = malformedType.element;
+      Message message = error.messageKind.message(error.messageArguments);
+      reasons.add(message.toString());
+      return true;
+    });
+    return Strings.join(reasons, ', ');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart b/pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart
new file mode 100644
index 0000000..1d0c2b9
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart
@@ -0,0 +1,28 @@
+// 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.
+
+part of dart2js;
+
+abstract class DiagnosticListener {
+  // TODO(karlklose): replace cancel with better error reporting mechanism.
+  void cancel(String reason, {node, token, instruction, element});
+  // TODO(karlklose): rename log to something like reportInfo.
+  void log(message);
+  // TODO(karlklose): add reportWarning and reportError to this interface.
+
+  void internalErrorOnElement(Element element, String message);
+  void internalError(String message,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element});
+
+  SourceSpan spanFromSpannable(Spannable node, [Uri uri]);
+
+  void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind);
+
+  // TODO(ahe): Rename to reportError when that method has been removed.
+  void reportErrorCode(Spannable node, MessageKind errorCode, [Map arguments]);
+
+  /// Returns true if a diagnostic was emitted.
+  bool onDeprecatedFeature(Spannable span, String feature);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart b/pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart
new file mode 100644
index 0000000..2798f95
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart
@@ -0,0 +1,792 @@
+// 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.
+
+library elements;
+
+import 'dart:uri';
+
+import 'modelx.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../resolution/resolution.dart';
+
+import '../dart2jslib.dart' show InterfaceType,
+                                 DartType,
+                                 TypeVariableType,
+                                 TypedefType,
+                                 MessageKind,
+                                 DiagnosticListener,
+                                 Script,
+                                 FunctionType,
+                                 SourceString,
+                                 Selector,
+                                 Constant,
+                                 Compiler;
+
+import '../dart_types.dart';
+
+import '../scanner/scannerlib.dart' show Token,
+                                         isUserDefinableOperator,
+                                         isMinusOperator;
+
+const int STATE_NOT_STARTED = 0;
+const int STATE_STARTED = 1;
+const int STATE_DONE = 2;
+
+class ElementCategory {
+  /**
+   * Represents things that we don't expect to find when looking in a
+   * scope.
+   */
+  static const int NONE = 0;
+
+  /** Field, parameter, or variable. */
+  static const int VARIABLE = 1;
+
+  /** Function, method, or foreign function. */
+  static const int FUNCTION = 2;
+
+  static const int CLASS = 4;
+
+  static const int PREFIX = 8;
+
+  /** Constructor or factory. */
+  static const int FACTORY = 16;
+
+  static const int ALIAS = 32;
+
+  static const int SUPER = 64;
+
+  /** Type variable */
+  static const int TYPE_VARIABLE = 128;
+
+  static const int IMPLIES_TYPE = CLASS | ALIAS | TYPE_VARIABLE;
+}
+
+class ElementKind {
+  final String id;
+  final int category;
+
+  const ElementKind(String this.id, this.category);
+
+  static const ElementKind VARIABLE =
+      const ElementKind('variable', ElementCategory.VARIABLE);
+  static const ElementKind PARAMETER =
+      const ElementKind('parameter', ElementCategory.VARIABLE);
+  // Parameters in constructors that directly initialize fields. For example:
+  // [:A(this.field):].
+  static const ElementKind FIELD_PARAMETER =
+      const ElementKind('field_parameter', ElementCategory.VARIABLE);
+  static const ElementKind FUNCTION =
+      const ElementKind('function', ElementCategory.FUNCTION);
+  static const ElementKind CLASS =
+      const ElementKind('class', ElementCategory.CLASS);
+  static const ElementKind GENERATIVE_CONSTRUCTOR =
+      const ElementKind('generative_constructor', ElementCategory.FACTORY);
+  static const ElementKind FIELD =
+      const ElementKind('field', ElementCategory.VARIABLE);
+  static const ElementKind VARIABLE_LIST =
+      const ElementKind('variable_list', ElementCategory.NONE);
+  static const ElementKind FIELD_LIST =
+      const ElementKind('field_list', ElementCategory.NONE);
+  static const ElementKind GENERATIVE_CONSTRUCTOR_BODY =
+      const ElementKind('generative_constructor_body', ElementCategory.NONE);
+  static const ElementKind COMPILATION_UNIT =
+      const ElementKind('compilation_unit', ElementCategory.NONE);
+  static const ElementKind GETTER =
+      const ElementKind('getter', ElementCategory.NONE);
+  static const ElementKind SETTER =
+      const ElementKind('setter', ElementCategory.NONE);
+  static const ElementKind TYPE_VARIABLE =
+      const ElementKind('type_variable', ElementCategory.TYPE_VARIABLE);
+  static const ElementKind ABSTRACT_FIELD =
+      const ElementKind('abstract_field', ElementCategory.VARIABLE);
+  static const ElementKind LIBRARY =
+      const ElementKind('library', ElementCategory.NONE);
+  static const ElementKind PREFIX =
+      const ElementKind('prefix', ElementCategory.PREFIX);
+  static const ElementKind TYPEDEF =
+      const ElementKind('typedef', ElementCategory.ALIAS);
+
+  static const ElementKind STATEMENT =
+      const ElementKind('statement', ElementCategory.NONE);
+  static const ElementKind LABEL =
+      const ElementKind('label', ElementCategory.NONE);
+  static const ElementKind VOID =
+      const ElementKind('void', ElementCategory.NONE);
+
+  static const ElementKind AMBIGUOUS =
+      const ElementKind('ambiguous', ElementCategory.NONE);
+  static const ElementKind ERROR =
+      const ElementKind('error', ElementCategory.NONE);
+  static const ElementKind MALFORMED_TYPE =
+      const ElementKind('malformed', ElementCategory.NONE);
+
+  toString() => id;
+}
+
+abstract class Element implements Spannable {
+  SourceString get name;
+  ElementKind get kind;
+  Modifiers get modifiers;
+  Element get enclosingElement;
+  Link<MetadataAnnotation> get metadata;
+
+  Node parseNode(DiagnosticListener listener);
+  DartType computeType(Compiler compiler);
+
+  bool isFunction();
+  bool isConstructor();
+  bool isClosure();
+  bool isMember();
+  bool isInstanceMember();
+  bool isInStaticMember();
+
+  bool isFactoryConstructor();
+  bool isGenerativeConstructor();
+  bool isGenerativeConstructorBody();
+  bool isCompilationUnit();
+  bool isClass();
+  bool isPrefix();
+  bool isVariable();
+  bool isParameter();
+  bool isStatement();
+  bool isTypedef();
+  bool isTypeVariable();
+  bool isField();
+  bool isAbstractField();
+  bool isGetter();
+  bool isSetter();
+  bool isAccessor();
+  bool isLibrary();
+  bool isErroneous();
+  bool isAmbiguous();
+
+  bool isTopLevel();
+  bool isAssignable();
+  bool isNative();
+
+  bool impliesType();
+
+  Token position();
+
+  CompilationUnitElement getCompilationUnit();
+  LibraryElement getLibrary();
+  LibraryElement getImplementationLibrary();
+  ClassElement getEnclosingClass();
+  Element getEnclosingClassOrCompilationUnit();
+  Element getEnclosingMember();
+  Element getOutermostEnclosingMemberOrTopLevel();
+
+  FunctionElement asFunctionElement();
+
+  bool get isPatched;
+  bool get isPatch;
+  bool get isImplementation;
+  bool get isDeclaration;
+  bool get isSynthesized;
+
+  Element get implementation;
+  Element get declaration;
+  Element get patch;
+  Element get origin;
+
+  bool hasFixedBackendName();
+  String fixedBackendName();
+
+  bool isAbstract(Compiler compiler);
+  bool isForeign(Compiler compiler);
+
+  void addMetadata(MetadataAnnotation annotation);
+  void setNative(String name);
+  void setFixedBackendName(String name);
+
+  Scope buildScope();
+}
+
+class Elements {
+  static bool isUnresolved(Element e) {
+    return e == null || e.isErroneous();
+  }
+  static bool isErroneousElement(Element e) => e != null && e.isErroneous();
+
+  static bool isClass(Element e) => e != null && e.kind == ElementKind.CLASS;
+  static bool isTypedef(Element e) {
+    return e != null && e.kind == ElementKind.TYPEDEF;
+  }
+
+  static bool isLocal(Element element) {
+    return !Elements.isUnresolved(element)
+            && !element.isInstanceMember()
+            && !isStaticOrTopLevelField(element)
+            && !isStaticOrTopLevelFunction(element)
+            && (identical(element.kind, ElementKind.VARIABLE) ||
+                identical(element.kind, ElementKind.PARAMETER) ||
+                identical(element.kind, ElementKind.FUNCTION));
+  }
+
+  static bool isInstanceField(Element element) {
+    return !Elements.isUnresolved(element)
+           && element.isInstanceMember()
+           && (identical(element.kind, ElementKind.FIELD)
+               || identical(element.kind, ElementKind.GETTER)
+               || identical(element.kind, ElementKind.SETTER));
+  }
+
+  static bool isStaticOrTopLevel(Element element) {
+    // TODO(ager): This should not be necessary when patch support has
+    // been reworked.
+    if (!Elements.isUnresolved(element)
+        && element.modifiers.isStatic()) {
+      return true;
+    }
+    return !Elements.isUnresolved(element)
+           && !element.isInstanceMember()
+           && !element.isPrefix()
+           && element.enclosingElement != null
+           && (element.enclosingElement.kind == ElementKind.CLASS ||
+               element.enclosingElement.kind == ElementKind.COMPILATION_UNIT ||
+               element.enclosingElement.kind == ElementKind.LIBRARY);
+  }
+
+  static bool isStaticOrTopLevelField(Element element) {
+    return isStaticOrTopLevel(element)
+           && (identical(element.kind, ElementKind.FIELD)
+               || identical(element.kind, ElementKind.GETTER)
+               || identical(element.kind, ElementKind.SETTER));
+  }
+
+  static bool isStaticOrTopLevelFunction(Element element) {
+    return isStaticOrTopLevel(element)
+           && (identical(element.kind, ElementKind.FUNCTION));
+  }
+
+  static bool isInstanceMethod(Element element) {
+    return !Elements.isUnresolved(element)
+           && element.isInstanceMember()
+           && (identical(element.kind, ElementKind.FUNCTION));
+  }
+
+  static bool isInstanceSend(Send send, TreeElements elements) {
+    Element element = elements[send];
+    if (element == null) return !isClosureSend(send, element);
+    return isInstanceMethod(element) || isInstanceField(element);
+  }
+
+  static bool isClosureSend(Send send, Element element) {
+    if (send.isPropertyAccess) return false;
+    if (send.receiver != null) return false;
+    // (o)() or foo()().
+    if (element == null && send.selector.asIdentifier() == null) return true;
+    if (element == null) return false;
+    // foo() with foo a local or a parameter.
+    return isLocal(element);
+  }
+
+  static SourceString constructConstructorName(SourceString receiver,
+                                               SourceString selector) {
+    String r = receiver.slowToString();
+    String s = selector.slowToString();
+    return new SourceString('$r\$$s');
+  }
+
+  static SourceString deconstructConstructorName(SourceString name,
+                                                 ClassElement holder) {
+    String r = '${holder.name.slowToString()}\$';
+    String s = name.slowToString();
+    if (s.startsWith(r)) {
+      return new SourceString(s.substring(r.length));
+    }
+    return null;
+  }
+
+  /**
+   * Map an operator-name to a valid Dart identifier.
+   *
+   * For non-operator names, this metod just returns its input.
+   *
+   * The results returned from this method are guaranteed to be valid
+   * JavaScript identifers, except it may include reserved words for
+   * non-operator names.
+   */
+  static SourceString operatorNameToIdentifier(SourceString name) {
+    if (name == null) return null;
+    String value = name.stringValue;
+    if (value == null) {
+      return name;
+    } else if (identical(value, '==')) {
+      return const SourceString(r'operator$eq');
+    } else if (identical(value, '~')) {
+      return const SourceString(r'operator$not');
+    } else if (identical(value, '[]')) {
+      return const SourceString(r'operator$index');
+    } else if (identical(value, '[]=')) {
+      return const SourceString(r'operator$indexSet');
+    } else if (identical(value, '*')) {
+      return const SourceString(r'operator$mul');
+    } else if (identical(value, '/')) {
+      return const SourceString(r'operator$div');
+    } else if (identical(value, '%')) {
+      return const SourceString(r'operator$mod');
+    } else if (identical(value, '~/')) {
+      return const SourceString(r'operator$tdiv');
+    } else if (identical(value, '+')) {
+      return const SourceString(r'operator$add');
+    } else if (identical(value, '<<')) {
+      return const SourceString(r'operator$shl');
+    } else if (identical(value, '>>')) {
+      return const SourceString(r'operator$shr');
+    } else if (identical(value, '>=')) {
+      return const SourceString(r'operator$ge');
+    } else if (identical(value, '>')) {
+      return const SourceString(r'operator$gt');
+    } else if (identical(value, '<=')) {
+      return const SourceString(r'operator$le');
+    } else if (identical(value, '<')) {
+      return const SourceString(r'operator$lt');
+    } else if (identical(value, '&')) {
+      return const SourceString(r'operator$and');
+    } else if (identical(value, '^')) {
+      return const SourceString(r'operator$xor');
+    } else if (identical(value, '|')) {
+      return const SourceString(r'operator$or');
+    } else if (identical(value, '-')) {
+      return const SourceString(r'operator$sub');
+    } else if (identical(value, 'unary-')) {
+      return const SourceString(r'operator$negate');
+    } else {
+      return name;
+    }
+  }
+
+  static SourceString constructOperatorNameOrNull(SourceString op,
+                                                  bool isUnary) {
+    String value = op.stringValue;
+    if (isMinusOperator(value)) {
+      return isUnary ? const SourceString('unary-') : op;
+    } else if (isUserDefinableOperator(value)) {
+      return op;
+    } else {
+      return null;
+    }
+  }
+
+  static SourceString constructOperatorName(SourceString op, bool isUnary) {
+    SourceString operatorName = constructOperatorNameOrNull(op, isUnary);
+    if (operatorName == null) throw 'Unhandled operator: ${op.slowToString()}';
+    else return operatorName;
+  }
+
+  static SourceString mapToUserOperatorOrNull(SourceString op) {
+    String value = op.stringValue;
+
+    if (identical(value, '!=')) return const SourceString('==');
+    if (identical(value, '*=')) return const SourceString('*');
+    if (identical(value, '/=')) return const SourceString('/');
+    if (identical(value, '%=')) return const SourceString('%');
+    if (identical(value, '~/=')) return const SourceString('~/');
+    if (identical(value, '+=')) return const SourceString('+');
+    if (identical(value, '-=')) return const SourceString('-');
+    if (identical(value, '<<=')) return const SourceString('<<');
+    if (identical(value, '>>=')) return const SourceString('>>');
+    if (identical(value, '&=')) return const SourceString('&');
+    if (identical(value, '^=')) return const SourceString('^');
+    if (identical(value, '|=')) return const SourceString('|');
+
+    return null;
+  }
+
+  static SourceString mapToUserOperator(SourceString op) {
+    SourceString userOperator = mapToUserOperatorOrNull(op);
+    if (userOperator == null) throw 'Unhandled operator: ${op.slowToString()}';
+    else return userOperator;
+  }
+
+  static bool isNumberOrStringSupertype(Element element, Compiler compiler) {
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    return (element == coreLibrary.find(const SourceString('Comparable')));
+  }
+
+  static bool isStringOnlySupertype(Element element, Compiler compiler) {
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    return element == coreLibrary.find(const SourceString('Pattern'));
+  }
+
+  static bool isListSupertype(Element element, Compiler compiler) {
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    return (element == coreLibrary.find(const SourceString('Collection')))
+        || (element == coreLibrary.find(const SourceString('Iterable')));
+  }
+
+  /// A `compareTo` function that places [Element]s in a consistent order based
+  /// on the source code order.
+  static int compareByPosition(Element a, Element b) {
+    CompilationUnitElement unitA = a.getCompilationUnit();
+    CompilationUnitElement unitB = b.getCompilationUnit();
+    if (!identical(unitA, unitB)) {
+      int r = unitA.script.uri.path.compareTo(unitB.script.uri.path);
+      if (r != 0) return r;
+    }
+    Token positionA = a.position();
+    Token positionB = b.position();
+    int r = positionA.charOffset.compareTo(positionB.charOffset);
+    if (r != 0) return r;
+    r = a.name.slowToString().compareTo(b.name.slowToString());
+    if (r != 0) return r;
+    // Same file, position and name.  If this happens, we should find out why
+    // and make the order total and independent of hashCode.
+    return a.hashCode.compareTo(b.hashCode);
+  }
+
+  static List<Element> sortedByPosition(Iterable<Element> elements) {
+    return elements.toList()..sort(compareByPosition);
+  }
+}
+
+abstract class ErroneousElement extends Element implements FunctionElement {
+  MessageKind get messageKind;
+  Map get messageArguments;
+}
+
+abstract class AmbiguousElement extends Element {
+  MessageKind get messageKind;
+  Map get messageArguments;
+  Element get existingElement;
+  Element get newElement;
+}
+
+// TODO(kasperl): This probably shouldn't be called an element. It's
+// just an interface shared by classes and libraries.
+abstract class ScopeContainerElement {
+  Element localLookup(SourceString elementName);
+}
+
+abstract class CompilationUnitElement extends Element {
+  Script get script;
+  PartOf get partTag;
+
+  void addMember(Element element, DiagnosticListener listener);
+  void setPartOf(PartOf tag, DiagnosticListener listener);
+  bool get hasMembers;
+}
+
+abstract class LibraryElement extends Element implements ScopeContainerElement {
+  /**
+   * The canonical uri for this library.
+   *
+   * For user libraries the canonical uri is the script uri. For platform
+   * libraries the canonical uri is of the form [:dart:x:].
+   */
+  Uri get canonicalUri;
+  CompilationUnitElement get entryCompilationUnit;
+  Link<CompilationUnitElement> get compilationUnits;
+  Link<LibraryTag> get tags;
+  LibraryName get libraryTag;
+  Link<Element> get exports;
+
+  /**
+   * [:true:] if this library is part of the platform, that is its canonical
+   * uri has the scheme 'dart'.
+   */
+  bool get isPlatformLibrary;
+
+  /**
+   * [:true:] if this library is a platform library whose path starts with
+   * an underscore.
+   */
+  bool get isInternalLibrary;
+  bool get canUseNative;
+  bool get exportsHandled;
+
+  // TODO(kasperl): We should try to get rid of these.
+  void set canUseNative(bool value);
+  void set libraryTag(LibraryName value);
+
+  LibraryElement get implementation;
+
+  void addCompilationUnit(CompilationUnitElement element);
+  void addTag(LibraryTag tag, DiagnosticListener listener);
+  void addImport(Element element, DiagnosticListener listener);
+
+  void addMember(Element element, DiagnosticListener listener);
+  void addToScope(Element element, DiagnosticListener listener);
+
+  // TODO(kasperl): Get rid of this method.
+  Iterable<Element> getNonPrivateElementsInScope();
+
+  void setExports(Iterable<Element> exportedElements);
+
+  Element find(SourceString elementName);
+  Element findLocal(SourceString elementName);
+  void forEachExport(f(Element element));
+
+  void forEachLocalMember(f(Element element));
+
+  bool hasLibraryName();
+  String getLibraryOrScriptName();
+}
+
+abstract class PrefixElement extends Element {
+  Map<SourceString, Element> get imported;
+  Element lookupLocalMember(SourceString memberName);
+}
+
+abstract class TypedefElement extends Element
+    implements TypeDeclarationElement {
+  TypedefType get rawType;
+  DartType get alias;
+  FunctionSignature get functionSignature;
+  Link<DartType> get typeVariables;
+
+  bool get isResolved;
+  bool get isBeingResolved;
+
+  // TODO(kasperl): Try to get rid of these setters.
+  void set alias(DartType value);
+  void set isResolved(bool value);
+  void set isBeingResolved(bool value);
+  void set functionSignature(FunctionSignature value);
+}
+
+abstract class VariableElement extends Element {
+  VariableListElement get variables;
+
+  // TODO(kasperl): Try to get rid of this.
+  Expression get cachedNode;
+}
+
+abstract class FieldParameterElement extends VariableElement {
+  VariableElement get fieldElement;
+}
+
+abstract class VariableListElement extends Element {
+  DartType get type;
+  FunctionSignature get functionSignature;
+
+  // TODO(kasperl): Try to get rid of this.
+  void set type(DartType value);
+}
+
+abstract class AbstractFieldElement extends Element {
+  FunctionElement get getter;
+  FunctionElement get setter;
+}
+
+abstract class FunctionSignature {
+  DartType get returnType;
+  Link<Element> get requiredParameters;
+  Link<Element> get optionalParameters;
+
+  int get requiredParameterCount;
+  int get optionalParameterCount;
+  bool get optionalParametersAreNamed;
+
+  int get parameterCount;
+  List<Element> get orderedOptionalParameters;
+
+  void forEachParameter(void function(Element parameter));
+  void forEachRequiredParameter(void function(Element parameter));
+  void forEachOptionalParameter(void function(Element parameter));
+
+  void orderedForEachParameter(void function(Element parameter));
+}
+
+abstract class FunctionElement extends Element {
+  FunctionExpression get cachedNode;
+  DartType get type;
+  FunctionSignature get functionSignature;
+  FunctionElement get redirectionTarget;
+  FunctionElement get defaultImplementation;
+
+  FunctionElement get patch;
+  FunctionElement get origin;
+
+  // TODO(kasperl): These are bit fishy. Do we really need them?
+  void set patch(FunctionElement value);
+  void set origin(FunctionElement value);
+  void set defaultImplementation(FunctionElement value);
+
+  void setPatch(FunctionElement patchElement);
+  FunctionSignature computeSignature(Compiler compiler);
+  int requiredParameterCount(Compiler compiler);
+  int optionalParameterCount(Compiler compiler);
+  int parameterCount(Compiler compiler);
+
+  FunctionExpression parseNode(DiagnosticListener listener);
+}
+
+abstract class ConstructorBodyElement extends FunctionElement {
+  FunctionElement get constructor;
+}
+
+/**
+ * [TypeDeclarationElement] defines the common interface for class/interface
+ * declarations and typedefs.
+ */
+abstract class TypeDeclarationElement extends Element {
+  GenericType get rawType;
+
+  /**
+   * The type variables declared on this declaration. The type variables are not
+   * available until the type of the element has been computed through
+   * [computeType].
+   */
+  Link<DartType> get typeVariables;
+}
+
+abstract class ClassElement extends TypeDeclarationElement
+    implements ScopeContainerElement {
+  int get id;
+
+  InterfaceType get rawType;
+  InterfaceType get thisType;
+
+  ClassElement get superclass;
+
+  DartType get supertype;
+  Link<DartType> get allSupertypes;
+  Link<DartType> get interfaces;
+
+  bool get hasConstructor;
+  Link<Element> get constructors;
+
+  ClassElement get patch;
+  ClassElement get origin;
+  ClassElement get declaration;
+  ClassElement get implementation;
+
+  int get supertypeLoadState;
+  int get resolutionState;
+  SourceString get nativeTagInfo;
+
+  bool get isMixinApplication;
+  bool get hasBackendMembers;
+  bool get hasLocalScopeMembers;
+
+  // TODO(kasperl): These are bit fishy. Do we really need them?
+  void set rawType(InterfaceType value);
+  void set thisType(InterfaceType value);
+  void set supertype(DartType value);
+  void set allSupertypes(Link<DartType> value);
+  void set interfaces(Link<DartType> value);
+  void set patch(ClassElement value);
+  void set origin(ClassElement value);
+  void set supertypeLoadState(int value);
+  void set resolutionState(int value);
+  void set nativeTagInfo(SourceString value);
+
+  // TODO(kasperl): These seem outdated.
+  bool isInterface();
+  DartType get defaultClass;
+  void set defaultClass(DartType value);
+
+  bool isObject(Compiler compiler);
+  bool isSubclassOf(ClassElement cls);
+  bool implementsInterface(ClassElement intrface);
+  bool isShadowedByField(Element fieldMember);
+
+  ClassElement ensureResolved(Compiler compiler);
+
+  void addMember(Element element, DiagnosticListener listener);
+  void addToScope(Element element, DiagnosticListener listener);
+
+  /**
+   * Add a synthetic nullary constructor if there are no other
+   * constructors.
+   */
+  void addDefaultConstructorIfNeeded(Compiler compiler);
+
+  void addBackendMember(Element element);
+  void reverseBackendMembers();
+
+  Element lookupMember(SourceString memberName);
+  Element lookupSelector(Selector selector);
+
+  Element lookupLocalMember(SourceString memberName);
+  Element lookupBackendMember(SourceString memberName);
+  Element lookupSuperMember(SourceString memberName);
+
+  Element lookupSuperMemberInLibrary(SourceString memberName,
+                                     LibraryElement library);
+
+  Element lookupSuperInterfaceMember(SourceString memberName,
+                                     LibraryElement fromLibrary);
+
+  Element validateConstructorLookupResults(Selector selector,
+                                           Element result,
+                                           Element noMatch(Element));
+
+  Element lookupConstructor(Selector selector, [Element noMatch(Element)]);
+  Element lookupFactoryConstructor(Selector selector,
+                                   [Element noMatch(Element)]);
+
+  void forEachMember(void f(ClassElement enclosingClass, Element member),
+                     {includeBackendMembers: false,
+                      includeSuperMembers: false});
+
+  void forEachInstanceField(void f(ClassElement enclosingClass, Element field),
+                            {includeBackendMembers: false,
+                             includeSuperMembers: false});
+
+  void forEachLocalMember(void f(Element member));
+  void forEachBackendMember(void f(Element member));
+}
+
+abstract class MixinApplicationElement extends ClassElement {
+  ClassElement get mixin;
+  void set mixin(ClassElement value);
+}
+
+abstract class LabelElement extends Element {
+  Label get label;
+  String get labelName;
+  TargetElement get target;
+
+  bool get isTarget;
+  bool get isBreakTarget;
+  bool get isContinueTarget;
+
+  void setBreakTarget();
+  void setContinueTarget();
+}
+
+abstract class TargetElement extends Element {
+  Node get statement;
+  int get nestingLevel;
+  Link<LabelElement> get labels;
+
+  bool get isTarget;
+  bool get isBreakTarget;
+  bool get isContinueTarget;
+  bool get isSwitch;
+
+  // TODO(kasperl): Try to get rid of these.
+  void set isBreakTarget(bool value);
+  void set isContinueTarget(bool value);
+
+  LabelElement addLabel(Label label, String labelName);
+}
+
+abstract class TypeVariableElement extends Element {
+  TypeVariableType get type;
+  DartType get bound;
+
+  // TODO(kasperl): Try to get rid of these.
+  void set type(TypeVariableType value);
+  void set bound(DartType value);
+}
+
+abstract class MetadataAnnotation implements Spannable {
+  Constant get value;
+  Element get annotatedElement;
+  int get resolutionState;
+  Token get beginToken;
+  Token get endToken;
+
+  // TODO(kasperl): Try to get rid of these.
+  void set annotatedElement(Element value);
+  void set resolutionState(int value);
+
+  MetadataAnnotation ensureResolved(Compiler compiler);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart b/pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart
new file mode 100644
index 0000000..f007946
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart
@@ -0,0 +1,1981 @@
+// Copyright (c) 2013, 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.
+
+library elements.modelx;
+
+import 'dart:uri';
+
+import 'elements.dart';
+import '../../compiler.dart' as api;
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../resolution/resolution.dart';
+
+import '../dart2jslib.dart' show invariant,
+                                 InterfaceType,
+                                 DartType,
+                                 TypeVariableType,
+                                 TypedefType,
+                                 MessageKind,
+                                 DiagnosticListener,
+                                 Script,
+                                 FunctionType,
+                                 SourceString,
+                                 Selector,
+                                 Constant,
+                                 Compiler;
+
+import '../dart_types.dart';
+
+import '../scanner/scannerlib.dart' show Token, EOF_TOKEN;
+
+
+class ElementX implements Element {
+  static int elementHashCode = 0;
+
+  final SourceString name;
+  final ElementKind kind;
+  final Element enclosingElement;
+  final int hashCode = ++elementHashCode;
+  Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>();
+
+  ElementX(this.name, this.kind, this.enclosingElement) {
+    assert(isErroneous() || getImplementationLibrary() != null);
+  }
+
+  Modifiers get modifiers => Modifiers.EMPTY;
+
+  Node parseNode(DiagnosticListener listener) {
+    listener.internalErrorOnElement(this, 'not implemented');
+  }
+
+  DartType computeType(Compiler compiler) {
+    compiler.internalError("$this.computeType.", token: position());
+  }
+
+  void addMetadata(MetadataAnnotation annotation) {
+    assert(annotation.annotatedElement == null);
+    annotation.annotatedElement = this;
+    metadata = metadata.prepend(annotation);
+  }
+
+  bool isFunction() => identical(kind, ElementKind.FUNCTION);
+  bool isConstructor() => isFactoryConstructor() || isGenerativeConstructor();
+  bool isClosure() => false;
+  bool isMember() {
+    // Check that this element is defined in the scope of a Class.
+    return enclosingElement != null && enclosingElement.isClass();
+  }
+  bool isInstanceMember() => false;
+
+  /**
+   * Returns [:true:] if this element is enclosed in a static member or is
+   * itself a static member.
+   */
+  bool isInStaticMember() {
+    Element member = getEnclosingMember();
+    return member != null && member.modifiers.isStatic();
+  }
+
+  bool isFactoryConstructor() => modifiers.isFactory();
+  bool isGenerativeConstructor() =>
+      identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR);
+  bool isGenerativeConstructorBody() =>
+      identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY);
+  bool isCompilationUnit() => identical(kind, ElementKind.COMPILATION_UNIT);
+  bool isClass() => identical(kind, ElementKind.CLASS);
+  bool isPrefix() => identical(kind, ElementKind.PREFIX);
+  bool isVariable() => identical(kind, ElementKind.VARIABLE);
+  bool isParameter() => identical(kind, ElementKind.PARAMETER);
+  bool isStatement() => identical(kind, ElementKind.STATEMENT);
+  bool isTypedef() => identical(kind, ElementKind.TYPEDEF);
+  bool isTypeVariable() => identical(kind, ElementKind.TYPE_VARIABLE);
+  bool isField() => identical(kind, ElementKind.FIELD);
+  bool isAbstractField() => identical(kind, ElementKind.ABSTRACT_FIELD);
+  bool isGetter() => identical(kind, ElementKind.GETTER);
+  bool isSetter() => identical(kind, ElementKind.SETTER);
+  bool isAccessor() => isGetter() || isSetter();
+  bool isLibrary() => identical(kind, ElementKind.LIBRARY);
+  bool impliesType() => (kind.category & ElementCategory.IMPLIES_TYPE) != 0;
+
+  /** See [ErroneousElement] for documentation. */
+  bool isErroneous() => false;
+
+  /** See [AmbiguousElement] for documentation. */
+  bool isAmbiguous() => false;
+
+  /**
+   * Is [:true:] if this element has a corresponding patch.
+   *
+   * If [:true:] this element has a non-null [patch] field.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isPatched => false;
+
+  /**
+   * Is [:true:] if this element is a patch.
+   *
+   * If [:true:] this element has a non-null [origin] field.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isPatch => false;
+
+  /**
+   * Is [:true:] if this element defines the implementation for the entity of
+   * this element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isImplementation => !isPatched;
+
+  /**
+   * Is [:true:] if this element introduces the entity of this element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  bool get isDeclaration => !isPatch;
+
+  bool get isSynthesized => false;
+
+  /**
+   * Returns the element which defines the implementation for the entity of this
+   * element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  Element get implementation => isPatched ? patch : this;
+
+  /**
+   * Returns the element which introduces the entity of this element.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  Element get declaration => isPatch ? origin : this;
+
+  Element get patch {
+    throw new UnsupportedError('patch is not supported on $this');
+  }
+
+  Element get origin {
+    throw new UnsupportedError('origin is not supported on $this');
+  }
+
+  // TODO(johnniwinther): This breaks for libraries (for which enclosing
+  // elements are null) and is invalid for top level variable declarations for
+  // which the enclosing element is a VariableDeclarations and not a compilation
+  // unit.
+  bool isTopLevel() {
+    return enclosingElement != null && enclosingElement.isCompilationUnit();
+  }
+
+  bool isAssignable() {
+    if (modifiers.isFinalOrConst()) return false;
+    if (isFunction() || isGenerativeConstructor()) return false;
+    return true;
+  }
+
+  Token position() => null;
+
+  Token findMyName(Token token) {
+    for (Token t = token; !identical(t.kind, EOF_TOKEN); t = t.next) {
+      if (t.value == name) return t;
+    }
+    return token;
+  }
+
+  CompilationUnitElement getCompilationUnit() {
+    Element element = this;
+    while (!element.isCompilationUnit()) {
+      element = element.enclosingElement;
+    }
+    return element;
+  }
+
+  LibraryElement getLibrary() => enclosingElement.getLibrary();
+
+  LibraryElement getImplementationLibrary() {
+    Element element = this;
+    while (!identical(element.kind, ElementKind.LIBRARY)) {
+      element = element.enclosingElement;
+    }
+    return element;
+  }
+
+  ClassElement getEnclosingClass() {
+    for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isClass()) return e;
+    }
+    return null;
+  }
+
+  Element getEnclosingClassOrCompilationUnit() {
+   for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isClass() || e.isCompilationUnit()) return e;
+    }
+    return null;
+  }
+
+  /**
+   * Returns the member enclosing this element or the element itself if it is a
+   * member. If no enclosing element is found, [:null:] is returned.
+   */
+  Element getEnclosingMember() {
+    for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isMember()) return e;
+    }
+    return null;
+  }
+
+  Element getOutermostEnclosingMemberOrTopLevel() {
+    // TODO(lrn): Why is this called "Outermost"?
+    for (Element e = this; e != null; e = e.enclosingElement) {
+      if (e.isMember() || e.isTopLevel()) {
+        return e;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Creates the scope for this element.
+   */
+  Scope buildScope() => enclosingElement.buildScope();
+
+  String toString() {
+    // TODO(johnniwinther): Test for nullness of name, or make non-nullness an
+    // invariant for all element types?
+    var nameText = name != null ? name.slowToString() : '?';
+    if (enclosingElement != null && !isTopLevel()) {
+      String holderName = enclosingElement.name != null
+          ? enclosingElement.name.slowToString()
+          : '${enclosingElement.kind}?';
+      return '$kind($holderName#${nameText})';
+    } else {
+      return '$kind(${nameText})';
+    }
+  }
+
+  String _fixedBackendName = null;
+  bool _isNative = false;
+  bool isNative() => _isNative;
+  bool hasFixedBackendName() => _fixedBackendName != null;
+  String fixedBackendName() => _fixedBackendName;
+  // Marks this element as a native element.
+  void setNative(String name) {
+    _isNative = true;
+    _fixedBackendName = name;
+  }
+  void setFixedBackendName(String name) {
+    _fixedBackendName = name;
+  }
+
+  FunctionElement asFunctionElement() => null;
+
+  static bool isInvalid(Element e) => e == null || e.isErroneous();
+
+  bool isAbstract(Compiler compiler) => modifiers.isAbstract();
+  bool isForeign(Compiler compiler) => getLibrary() == compiler.foreignLibrary;
+}
+
+/**
+ * Represents an unresolvable or duplicated element.
+ *
+ * An [ErroneousElement] is used instead of [null] to provide additional
+ * information about the error that caused the element to be unresolvable
+ * or otherwise invalid.
+ *
+ * Accessing any field or calling any method defined on [ErroneousElement]
+ * except [isErroneous] will currently throw an exception. (This might
+ * change when we actually want more information on the erroneous element,
+ * e.g., the name of the element we were trying to resolve.)
+ *
+ * Code that cannot not handle an [ErroneousElement] should use
+ *   [: Element.isInvalid(element) :]
+ * to check for unresolvable elements instead of
+ *   [: element == null :].
+ */
+class ErroneousElementX extends ElementX implements ErroneousElement {
+  final MessageKind messageKind;
+  final Map messageArguments;
+
+  ErroneousElementX(this.messageKind, this.messageArguments,
+                    SourceString name, Element enclosing)
+      : super(name, ElementKind.ERROR, enclosing);
+
+  isErroneous() => true;
+
+  unsupported() {
+    throw 'unsupported operation on erroneous element';
+  }
+
+  Link<MetadataAnnotation> get metadata => unsupported();
+  get type => unsupported();
+  get cachedNode => unsupported();
+  get functionSignature => unsupported();
+  get patch => unsupported();
+  get origin => unsupported();
+  get defaultImplementation => unsupported();
+
+  bool get isPatched => unsupported();
+  bool get isPatch => unsupported();
+
+  setPatch(patch) => unsupported();
+  computeSignature(compiler) => unsupported();
+  requiredParameterCount(compiler) => unsupported();
+  optionalParameterCount(compiler) => unsupported();
+  parameterCount(compiler) => unsupported();
+
+  // TODO(kasperl): These seem unnecessary.
+  set patch(value) => unsupported();
+  set origin(value) => unsupported();
+  set defaultImplementation(value) => unsupported();
+
+  get redirectionTarget => this;
+
+  getLibrary() => enclosingElement.getLibrary();
+
+  String toString() {
+    String n = name.slowToString();
+    return '<$n: ${messageKind.message(messageArguments)}>';
+  }
+}
+
+/**
+ * An ambiguous element represents multiple elements accessible by the same name.
+ *
+ * Ambiguous elements are created during handling of import/export scopes. If an
+ * ambiguous element is encountered during resolution a warning/error should be
+ * reported.
+ */
+class AmbiguousElementX extends ElementX implements AmbiguousElement {
+  /**
+   * The message to report on resolving this element.
+   */
+  final MessageKind messageKind;
+
+  /**
+   * The message arguments to report on resolving this element.
+   */
+  final Map messageArguments;
+
+  /**
+   * The first element that this ambiguous element might refer to.
+   */
+  final Element existingElement;
+
+  /**
+   * The second element that this ambiguous element might refer to.
+   */
+  final Element newElement;
+
+  AmbiguousElementX(this.messageKind, this.messageArguments,
+      Element enclosingElement, Element existingElement, Element newElement)
+      : this.existingElement = existingElement,
+        this.newElement = newElement,
+        super(existingElement.name, ElementKind.AMBIGUOUS, enclosingElement);
+
+  bool isAmbiguous() => true;
+}
+
+class ScopeX {
+  final Map<SourceString, Element> contents = new Map<SourceString, Element>();
+
+  bool get isEmpty => contents.isEmpty;
+  Iterable<Element> get values => contents.values;
+
+  Element lookup(SourceString name) {
+    return contents[name];
+  }
+
+  void add(Element element, DiagnosticListener listener) {
+    if (element.isAccessor()) {
+      addAccessor(element, contents[element.name], listener);
+    } else {
+      Element existing = contents.putIfAbsent(element.name, () => element);
+      if (!identical(existing, element)) {
+        // TODO(ahe): Do something similar to Resolver.reportErrorWithContext.
+        listener.cancel('duplicate definition', token: element.position());
+        listener.cancel('existing definition', token: existing.position());
+      }
+    }
+  }
+
+  /**
+   * Adds a definition for an [accessor] (getter or setter) to a scope.
+   * The definition binds to an abstract field that can hold both a getter
+   * and a setter.
+   *
+   * The abstract field is added once, for the first getter or setter, and
+   * reused if the other one is also added.
+   * The abstract field should not be treated as a proper member of the
+   * container, it's simply a way to return two results for one lookup.
+   * That is, the getter or setter does not have the abstract field as enclosing
+   * element, they are enclosed by the class or compilation unit, as is the
+   * abstract field.
+   */
+  void addAccessor(Element accessor,
+                   Element existing,
+                   DiagnosticListener listener) {
+    void reportError(Element other) {
+      // TODO(ahe): Do something similar to Resolver.reportErrorWithContext.
+      listener.cancel('duplicate definition of ${accessor.name.slowToString()}',
+                      element: accessor);
+      listener.cancel('existing definition', element: other);
+    }
+
+    if (existing != null) {
+      if (!identical(existing.kind, ElementKind.ABSTRACT_FIELD)) {
+        reportError(existing);
+      } else {
+        AbstractFieldElementX field = existing;
+        if (accessor.isGetter()) {
+          if (field.getter != null && field.getter != accessor) {
+            reportError(field.getter);
+          }
+          field.getter = accessor;
+        } else {
+          assert(accessor.isSetter());
+          if (field.setter != null && field.setter != accessor) {
+            reportError(field.setter);
+          }
+          field.setter = accessor;
+        }
+      }
+    } else {
+      Element container = accessor.getEnclosingClassOrCompilationUnit();
+      AbstractFieldElementX field =
+          new AbstractFieldElementX(accessor.name, container);
+      if (accessor.isGetter()) {
+        field.getter = accessor;
+      } else {
+        field.setter = accessor;
+      }
+      add(field, listener);
+    }
+  }
+}
+
+class CompilationUnitElementX extends ElementX
+    implements CompilationUnitElement {
+  final Script script;
+  PartOf partTag;
+  Link<Element> localMembers = const Link<Element>();
+
+  CompilationUnitElementX(Script script, LibraryElement library)
+    : this.script = script,
+      super(new SourceString(script.name),
+            ElementKind.COMPILATION_UNIT,
+            library) {
+    library.addCompilationUnit(this);
+  }
+
+  void addMember(Element element, DiagnosticListener listener) {
+    // Keep a list of top level members.
+    localMembers = localMembers.prepend(element);
+    // Provide the member to the library to build scope.
+    if (enclosingElement.isPatch) {
+      getImplementationLibrary().addMember(element, listener);
+    } else {
+      getLibrary().addMember(element, listener);
+    }
+  }
+
+  void setPartOf(PartOf tag, DiagnosticListener listener) {
+    LibraryElementX library = enclosingElement;
+    if (library.entryCompilationUnit == this) {
+      listener.reportMessage(
+          listener.spanFromSpannable(tag),
+          MessageKind.ILLEGAL_DIRECTIVE.error(),
+          api.Diagnostic.WARNING);
+      return;
+    }
+    if (!localMembers.isEmpty) {
+      listener.reportErrorCode(tag, MessageKind.BEFORE_TOP_LEVEL);
+      return;
+    }
+    if (partTag != null) {
+      listener.reportMessage(
+          listener.spanFromSpannable(tag),
+          MessageKind.DUPLICATED_PART_OF.error(),
+          api.Diagnostic.WARNING);
+      return;
+    }
+    partTag = tag;
+    LibraryName libraryTag = getLibrary().libraryTag;
+    if (libraryTag != null) {
+      String actualName = tag.name.toString();
+      String expectedName = libraryTag.name.toString();
+      if (expectedName != actualName) {
+        listener.reportMessage(
+            listener.spanFromSpannable(tag.name),
+            MessageKind.LIBRARY_NAME_MISMATCH.error(
+                {'libraryName': expectedName}),
+            api.Diagnostic.WARNING);
+      }
+    }
+  }
+
+  bool get hasMembers => !localMembers.isEmpty;
+}
+
+class LibraryElementX extends ElementX implements LibraryElement {
+  final Uri canonicalUri;
+  CompilationUnitElement entryCompilationUnit;
+  Link<CompilationUnitElement> compilationUnits =
+      const Link<CompilationUnitElement>();
+  Link<LibraryTag> tags = const Link<LibraryTag>();
+  LibraryName libraryTag;
+  bool canUseNative = false;
+  Link<Element> localMembers = const Link<Element>();
+  final ScopeX localScope = new ScopeX();
+
+  /**
+   * If this library is patched, [patch] points to the patch library.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  LibraryElementX patch = null;
+
+  /**
+   * If this is a patch library, [origin] points to the origin library.
+   *
+   * See [:patch_parser.dart:] for a description of the terminology.
+   */
+  final LibraryElementX origin;
+
+  /**
+   * Map for elements imported through import declarations.
+   *
+   * Addition to the map is performed by [addImport]. Lookup is done trough
+   * [find].
+   */
+  final Map<SourceString, Element> importScope;
+
+  /**
+   * Link for elements exported either through export declarations or through
+   * declaration. This field should not be accessed directly but instead through
+   * the [exports] getter.
+   *
+   * [LibraryDependencyHandler] sets this field through [setExports] when the
+   * library is loaded.
+   */
+  Link<Element> slotForExports;
+
+  LibraryElementX(Script script, [Uri canonicalUri, LibraryElement this.origin])
+    : this.canonicalUri = ((canonicalUri == null) ? script.uri : canonicalUri),
+      importScope = new Map<SourceString, Element>(),
+      super(new SourceString(script.name), ElementKind.LIBRARY, null) {
+    entryCompilationUnit = new CompilationUnitElementX(script, this);
+    if (isPatch) {
+      origin.patch = this;
+    }
+  }
+
+  bool get isPatched => patch != null;
+  bool get isPatch => origin != null;
+
+  LibraryElement get declaration => super.declaration;
+  LibraryElement get implementation => super.implementation;
+
+  CompilationUnitElement getCompilationUnit() => entryCompilationUnit;
+
+  void addCompilationUnit(CompilationUnitElement element) {
+    compilationUnits = compilationUnits.prepend(element);
+  }
+
+  void addTag(LibraryTag tag, DiagnosticListener listener) {
+    tags = tags.prepend(tag);
+  }
+
+  /**
+   * Adds [element] to the import scope of this library.
+   *
+   * If an element by the same name is already in the imported scope, an
+   * [ErroneousElement] will be put in the imported scope, allowing for the
+   * detection of ambiguous uses of imported names.
+   */
+  void addImport(Element element, DiagnosticListener listener) {
+    Element existing = importScope[element.name];
+    if (existing != null) {
+      // TODO(johnniwinther): Provide access to the import tags from which
+      // the elements came.
+      importScope[element.name] = new AmbiguousElementX(
+          MessageKind.DUPLICATE_IMPORT, {'name': element.name},
+          this, existing, element);
+    } else {
+      importScope[element.name] = element;
+    }
+  }
+
+  void addMember(Element element, DiagnosticListener listener) {
+    localMembers = localMembers.prepend(element);
+    addToScope(element, listener);
+  }
+
+  void addToScope(Element element, DiagnosticListener listener) {
+    localScope.add(element, listener);
+  }
+
+  Element localLookup(SourceString elementName) {
+    Element result = localScope.lookup(elementName);
+    if (result == null && isPatch) {
+      result = origin.localLookup(elementName);
+    }
+    return result;
+  }
+
+  /**
+   * Returns [:true:] if the export scope has already been computed for this
+   * library.
+   */
+  bool get exportsHandled => slotForExports != null;
+
+  Link<Element> get exports {
+    assert(invariant(this, exportsHandled,
+                     message: 'Exports not handled on $this'));
+    return slotForExports;
+  }
+
+  /**
+   * Sets the export scope of this library. This method can only be called once.
+   */
+  void setExports(Iterable<Element> exportedElements) {
+    assert(invariant(this, !exportsHandled,
+        message: 'Exports already set to $slotForExports on $this'));
+    assert(invariant(this, exportedElements != null));
+    var builder = new LinkBuilder<Element>();
+    for (Element export in exportedElements) {
+      builder.addLast(export);
+    }
+    slotForExports = builder.toLink();
+  }
+
+  LibraryElement getLibrary() => isPatch ? origin : this;
+
+  /**
+   * Look up a top-level element in this library. The element could
+   * potentially have been imported from another library. Returns
+   * null if no such element exist and an [ErroneousElement] if multiple
+   * elements have been imported.
+   */
+  Element find(SourceString elementName) {
+    Element result = localScope.lookup(elementName);
+    if (result != null) return result;
+    if (origin != null) {
+      result = origin.localScope.lookup(elementName);
+      if (result != null) return result;
+    }
+    result = importScope[elementName];
+    if (result != null) return result;
+    if (origin != null) {
+      result = origin.importScope[elementName];
+      if (result != null) return result;
+    }
+    return null;
+  }
+
+  /** Look up a top-level element in this library, but only look for
+    * non-imported elements. Returns null if no such element exist. */
+  Element findLocal(SourceString elementName) {
+    // TODO(johnniwinther): How to handle injected elements in the patch
+    // library?
+    Element result = localScope.lookup(elementName);
+    if (result == null || result.getLibrary() != this) return null;
+    return result;
+  }
+
+  void forEachExport(f(Element element)) {
+    exports.forEach((Element e) => f(e));
+  }
+
+  void forEachLocalMember(f(Element element)) {
+    if (isPatch) {
+      // Patch libraries traverse both origin and injected members.
+      origin.localMembers.forEach(f);
+
+      void filterPatch(Element element) {
+        if (!element.isPatch) {
+          // Do not traverse the patch members.
+          f(element);
+        }
+      }
+      localMembers.forEach(filterPatch);
+    } else {
+      localMembers.forEach(f);
+    }
+  }
+
+  Iterable<Element> getNonPrivateElementsInScope() {
+    return localScope.values.where((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();
+    });
+  }
+
+  bool hasLibraryName() => libraryTag != null;
+
+  /**
+   * Returns the library name (as defined by the #library tag) or for script
+   * (which have no #library tag) the script file name. The latter case is used
+   * to private 'library name' for scripts to use for instance in dartdoc.
+   */
+  String getLibraryOrScriptName() {
+    if (libraryTag != null) {
+      return libraryTag.name.toString();
+    } else {
+      // Use the file name as script name.
+      String path = canonicalUri.path;
+      return path.substring(path.lastIndexOf('/') + 1);
+    }
+  }
+
+  Scope buildScope() => new LibraryScope(this);
+
+  bool get isPlatformLibrary => canonicalUri.scheme == "dart";
+
+  bool get isInternalLibrary =>
+      isPlatformLibrary && canonicalUri.path.startsWith('_');
+
+  String toString() {
+    if (origin != null) {
+      return 'patch library(${getLibraryOrScriptName()})';
+    } else if (patch != null) {
+      return 'origin library(${getLibraryOrScriptName()})';
+    } else {
+      return 'library(${getLibraryOrScriptName()})';
+    }
+  }
+}
+
+class PrefixElementX extends ElementX implements PrefixElement {
+  Map<SourceString, Element> imported;
+  Token firstPosition;
+
+  PrefixElementX(SourceString prefix, Element enclosing, this.firstPosition)
+      : imported = new Map<SourceString, Element>(),
+        super(prefix, ElementKind.PREFIX, enclosing);
+
+  Element lookupLocalMember(SourceString memberName) => imported[memberName];
+
+  DartType computeType(Compiler compiler) => compiler.types.dynamicType;
+
+  Token position() => firstPosition;
+}
+
+class TypedefElementX extends ElementX implements TypedefElement {
+  Typedef cachedNode;
+  TypedefType cachedType;
+
+  /**
+   * Canonicalize raw version of [cachedType].
+   *
+   * See [ClassElement.rawType] for motivation.
+   *
+   * The [rawType] is computed together with [cachedType] in [computeType].
+   */
+  TypedefType rawType;
+
+  /**
+   * The type annotation which defines this typedef.
+   */
+  DartType alias;
+
+  bool isResolved = false;
+  bool isBeingResolved = false;
+
+  TypedefElementX(SourceString name, Element enclosing)
+      : super(name, ElementKind.TYPEDEF, enclosing);
+
+  /**
+   * Function signature for a typedef of a function type. The signature is
+   * kept to provide full information about parameter names through the mirror
+   * system.
+   *
+   * The [functionSignature] is not available until the typedef element has been
+   * resolved.
+   */
+  FunctionSignature functionSignature;
+
+  TypedefType computeType(Compiler compiler) {
+    if (cachedType != null) return cachedType;
+    Typedef node = parseNode(compiler);
+    Link<DartType> parameters =
+        TypeDeclarationElementX.createTypeVariables(this, node.typeParameters);
+    cachedType = new TypedefType(this, parameters);
+    if (parameters.isEmpty) {
+      rawType = cachedType;
+    } else {
+      var dynamicParameters = const Link<DartType>();
+      parameters.forEach((_) {
+        dynamicParameters =
+            dynamicParameters.prepend(compiler.types.dynamicType);
+      });
+      rawType = new TypedefType(this, dynamicParameters);
+    }
+    compiler.resolveTypedef(this);
+    return cachedType;
+  }
+
+  Link<DartType> get typeVariables => cachedType.typeArguments;
+
+  Scope buildScope() {
+    return new TypeDeclarationScope(enclosingElement.buildScope(), this);
+  }
+}
+
+class VariableElementX extends ElementX implements VariableElement {
+  final VariableListElement variables;
+  Expression cachedNode; // The send or the identifier in the variables list.
+
+  Modifiers get modifiers => variables.modifiers;
+
+  VariableElementX(SourceString name,
+                   VariableListElement variables,
+                   ElementKind kind,
+                   this.cachedNode)
+    : this.variables = variables,
+      super(name, kind, variables.enclosingElement);
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    VariableDefinitions definitions = variables.parseNode(listener);
+    for (Link<Node> link = definitions.definitions.nodes;
+         !link.isEmpty; link = link.tail) {
+      Expression initializedIdentifier = link.head;
+      Identifier identifier = initializedIdentifier.asIdentifier();
+      if (identifier == null) {
+        identifier = initializedIdentifier.asSendSet().selector.asIdentifier();
+      }
+      if (identical(name, identifier.source)) {
+        cachedNode = initializedIdentifier;
+        return cachedNode;
+      }
+    }
+    listener.cancel('internal error: could not find $name', node: variables);
+  }
+
+  DartType computeType(Compiler compiler) {
+    return variables.computeType(compiler);
+  }
+
+  DartType get type => variables.type;
+
+  bool isInstanceMember() => variables.isInstanceMember();
+
+  // Note: cachedNode.getBeginToken() will not be correct in all
+  // cases, for example, for function typed parameters.
+  Token position() => findMyName(variables.position());
+}
+
+/**
+ * Parameters in constructors that directly initialize fields. For example:
+ * [:A(this.field):].
+ */
+class FieldParameterElementX extends VariableElementX
+    implements FieldParameterElement {
+  VariableElement fieldElement;
+
+  FieldParameterElementX(SourceString name,
+                         this.fieldElement,
+                         VariableListElement variables,
+                         Node node)
+      : super(name, variables, ElementKind.FIELD_PARAMETER, node);
+}
+
+// This element represents a list of variable or field declaration.
+// It contains the node, and the type. A [VariableElement] always
+// references its [VariableListElement]. It forwards its
+// [computeType] and [parseNode] methods to this element.
+class VariableListElementX extends ElementX implements VariableListElement {
+  VariableDefinitions cachedNode;
+  DartType type;
+  final Modifiers modifiers;
+
+  /**
+   * Function signature for a variable with a function type. The signature is
+   * kept to provide full information about parameter names through the mirror
+   * system.
+   */
+  FunctionSignature functionSignature;
+
+  VariableListElementX(ElementKind kind,
+                       Modifiers this.modifiers,
+                       Element enclosing)
+    : super(null, kind, enclosing);
+
+  VariableListElementX.node(VariableDefinitions node,
+                            ElementKind kind,
+                            Element enclosing)
+      : super(null, kind, enclosing),
+        this.cachedNode = node,
+        this.modifiers = node.modifiers {
+    assert(modifiers != null);
+  }
+
+  VariableDefinitions parseNode(DiagnosticListener listener) {
+    return cachedNode;
+  }
+
+  DartType computeType(Compiler compiler) {
+    if (type != null) return type;
+    compiler.withCurrentElement(this, () {
+      VariableDefinitions node = parseNode(compiler);
+      if (node.type != null) {
+        type = compiler.resolveTypeAnnotation(this, node.type);
+      } else {
+        // Is node.definitions exactly one FunctionExpression?
+        Link<Node> link = node.definitions.nodes;
+        if (!link.isEmpty &&
+            link.head.asFunctionExpression() != null &&
+            link.tail.isEmpty) {
+          FunctionExpression functionExpression = link.head;
+          // We found exactly one FunctionExpression
+          functionSignature =
+              compiler.resolveFunctionExpression(this, functionExpression);
+          type = compiler.computeFunctionType(compiler.functionClass,
+                                              functionSignature);
+        } else {
+          type = compiler.types.dynamicType;
+        }
+      }
+    });
+    assert(type != null);
+    return type;
+  }
+
+  Token position() => cachedNode.getBeginToken();
+
+  bool isInstanceMember() {
+    return isMember() && !modifiers.isStatic();
+  }
+}
+
+class AbstractFieldElementX extends ElementX implements AbstractFieldElement {
+  FunctionElement getter;
+  FunctionElement setter;
+
+  AbstractFieldElementX(SourceString name, Element enclosing)
+      : super(name, ElementKind.ABSTRACT_FIELD, enclosing);
+
+  DartType computeType(Compiler compiler) {
+    throw "internal error: AbstractFieldElement has no type";
+  }
+
+  Node parseNode(DiagnosticListener listener) {
+    throw "internal error: AbstractFieldElement has no node";
+  }
+
+  position() {
+    // The getter and setter may be defined in two different
+    // compilation units.  However, we know that one of them is
+    // non-null and defined in the same compilation unit as the
+    // abstract element.
+    // TODO(lrn): No we don't know that if the element from the same
+    // compilation unit is patched.
+    //
+    // We need to make sure that the position returned is relative to
+    // the compilation unit of the abstract element.
+    if (getter != null
+        && identical(getter.getCompilationUnit(), getCompilationUnit())) {
+      return getter.position();
+    } else {
+      return setter.position();
+    }
+  }
+
+  Modifiers get modifiers {
+    // The resolver ensures that the flags match (ignoring abstract).
+    if (getter != null) {
+      return new Modifiers.withFlags(
+          getter.modifiers.nodes,
+          getter.modifiers.flags | Modifiers.FLAG_ABSTRACT);
+    } else {
+      return new Modifiers.withFlags(
+          setter.modifiers.nodes,
+          setter.modifiers.flags | Modifiers.FLAG_ABSTRACT);
+    }
+  }
+}
+
+// TODO(johnniwinther): [FunctionSignature] should be merged with
+// [FunctionType].
+class FunctionSignatureX implements FunctionSignature {
+  final Link<Element> requiredParameters;
+  final Link<Element> optionalParameters;
+  final DartType returnType;
+  final int requiredParameterCount;
+  final int optionalParameterCount;
+  final bool optionalParametersAreNamed;
+
+  List<Element> _orderedOptionalParameters;
+
+  FunctionSignatureX(this.requiredParameters,
+                     this.optionalParameters,
+                     this.requiredParameterCount,
+                     this.optionalParameterCount,
+                     this.optionalParametersAreNamed,
+                     this.returnType);
+
+  void forEachRequiredParameter(void function(Element parameter)) {
+    for (Link<Element> link = requiredParameters;
+         !link.isEmpty;
+         link = link.tail) {
+      function(link.head);
+    }
+  }
+
+  void forEachOptionalParameter(void function(Element parameter)) {
+    for (Link<Element> link = optionalParameters;
+         !link.isEmpty;
+         link = link.tail) {
+      function(link.head);
+    }
+  }
+
+  List<Element> get orderedOptionalParameters {
+    if (_orderedOptionalParameters != null) return _orderedOptionalParameters;
+    List<Element> list = new List<Element>.from(optionalParameters);
+    if (optionalParametersAreNamed) {
+      list.sort((Element a, Element b) {
+        return a.name.slowToString().compareTo(b.name.slowToString());
+      });
+    }
+    _orderedOptionalParameters = list;
+    return list;
+  }
+
+  void forEachParameter(void function(Element parameter)) {
+    forEachRequiredParameter(function);
+    forEachOptionalParameter(function);
+  }
+
+  void orderedForEachParameter(void function(Element parameter)) {
+    forEachRequiredParameter(function);
+    orderedOptionalParameters.forEach(function);
+  }
+
+  int get parameterCount => requiredParameterCount + optionalParameterCount;
+}
+
+class FunctionElementX extends ElementX implements FunctionElement {
+  FunctionExpression cachedNode;
+  DartType type;
+  final Modifiers modifiers;
+
+  FunctionSignature functionSignature;
+
+  /**
+   * A function declaration that should be parsed instead of the current one.
+   * The patch should be parsed as if it was in the current scope. Its
+   * signature must match this function's signature.
+   */
+  // TODO(lrn): Consider using [defaultImplementation] to store the patch.
+  FunctionElement patch = null;
+  FunctionElement origin = null;
+
+  /**
+   * If this is a redirecting factory, [defaultImplementation] will be
+   * changed by the resolver to point to the redirection target.  If
+   * this is an interface constructor, [defaultImplementation] will be
+   * changed by the resolver to point to the default implementation.
+   * Otherwise, [:identical(defaultImplementation, this):].
+   */
+  // TODO(ahe): Rename this field to redirectionTarget and remove
+  // mention of interface constructors above.
+  FunctionElement defaultImplementation;
+
+  FunctionElementX(SourceString name,
+                   ElementKind kind,
+                   Modifiers modifiers,
+                   Element enclosing)
+      : this.tooMuchOverloading(name, null, kind, modifiers, enclosing, null);
+
+  FunctionElementX.node(SourceString name,
+                        FunctionExpression node,
+                        ElementKind kind,
+                        Modifiers modifiers,
+                        Element enclosing)
+      : this.tooMuchOverloading(name, node, kind, modifiers, enclosing, null);
+
+  FunctionElementX.from(SourceString name,
+                        FunctionElement other,
+                        Element enclosing)
+      : this.tooMuchOverloading(name, other.cachedNode, other.kind,
+                                other.modifiers, enclosing,
+                                other.functionSignature);
+
+  FunctionElementX.tooMuchOverloading(SourceString name,
+                                      FunctionExpression this.cachedNode,
+                                      ElementKind kind,
+                                      Modifiers this.modifiers,
+                                      Element enclosing,
+                                      FunctionSignature this.functionSignature)
+      : super(name, kind, enclosing) {
+    assert(modifiers != null);
+    defaultImplementation = this;
+  }
+
+  bool get isPatched => patch != null;
+  bool get isPatch => origin != null;
+
+  FunctionElement get redirectionTarget {
+    if (this == defaultImplementation) return this;
+    var target = defaultImplementation;
+    Set<Element> seen = new Set<Element>();
+    seen.add(target);
+    while (!target.isErroneous() && target != target.defaultImplementation) {
+      target = target.defaultImplementation;
+      if (seen.contains(target)) {
+        // TODO(ahe): This is expedient for now, but it should be
+        // checked by the resolver.  Keeping http://dartbug.com/3970
+        // open to track this.
+        throw new SpannableAssertionFailure(
+            target, 'redirecting factory leads to cycle');
+      }
+    }
+    return target;
+  }
+
+  /**
+   * Applies a patch function to this function. The patch function's body
+   * is used as replacement when parsing this function's body.
+   * This method must not be called after the function has been parsed,
+   * and it must be called at most once.
+   */
+  void setPatch(FunctionElement patchElement) {
+    // Sanity checks. The caller must check these things before calling.
+    assert(patch == null);
+    this.patch = patchElement;
+  }
+
+  bool isInstanceMember() {
+    return isMember()
+           && !isConstructor()
+           && !modifiers.isStatic();
+  }
+
+  FunctionSignature computeSignature(Compiler compiler) {
+    if (functionSignature != null) return functionSignature;
+    compiler.withCurrentElement(this, () {
+      functionSignature = compiler.resolveSignature(this);
+    });
+    return functionSignature;
+  }
+
+  int requiredParameterCount(Compiler compiler) {
+    return computeSignature(compiler).requiredParameterCount;
+  }
+
+  int optionalParameterCount(Compiler compiler) {
+    return computeSignature(compiler).optionalParameterCount;
+  }
+
+  int parameterCount(Compiler compiler) {
+    return computeSignature(compiler).parameterCount;
+  }
+
+  FunctionType computeType(Compiler compiler) {
+    if (type != null) return type;
+    type = compiler.computeFunctionType(declaration,
+                                        computeSignature(compiler));
+    return type;
+  }
+
+  FunctionExpression parseNode(DiagnosticListener listener) {
+    if (patch == null) {
+      if (modifiers.isExternal()) {
+        listener.cancel("Compiling external function with no implementation.",
+                        element: this);
+      }
+    }
+    return cachedNode;
+  }
+
+  Token position() => cachedNode.getBeginToken();
+
+  FunctionElement asFunctionElement() => this;
+
+  String toString() {
+    if (isPatch) {
+      return 'patch ${super.toString()}';
+    } else if (isPatched) {
+      return 'origin ${super.toString()}';
+    } else {
+      return super.toString();
+    }
+  }
+
+  bool isAbstract(Compiler compiler) {
+    if (super.isAbstract(compiler)) return true;
+    if (modifiers.isExternal()) return false;
+    if (isFunction() || isAccessor()) {
+      return !parseNode(compiler).hasBody();
+    }
+    return false;
+  }
+}
+
+class ConstructorBodyElementX extends FunctionElementX
+    implements ConstructorBodyElement {
+  FunctionElement constructor;
+
+  ConstructorBodyElementX(FunctionElement constructor)
+      : this.constructor = constructor,
+        super(constructor.name,
+              ElementKind.GENERATIVE_CONSTRUCTOR_BODY,
+              Modifiers.EMPTY,
+              constructor.enclosingElement) {
+    functionSignature = constructor.functionSignature;
+  }
+
+  bool isInstanceMember() => true;
+
+  FunctionType computeType(Compiler compiler) {
+    compiler.reportFatalError('Internal error: $this.computeType', this);
+  }
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = constructor.parseNode(listener);
+    assert(cachedNode != null);
+    return cachedNode;
+  }
+
+  Token position() => constructor.position();
+}
+
+class SynthesizedConstructorElementX extends FunctionElementX {
+  SynthesizedConstructorElementX(Element enclosing)
+    : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR,
+            Modifiers.EMPTY, enclosing);
+
+  SynthesizedConstructorElementX.forDefault(Element enclosing,
+                                            Compiler compiler)
+    : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR,
+            Modifiers.EMPTY, enclosing) {
+    type = new FunctionType(this,
+        compiler.types.voidType,
+        const Link<DartType>(),
+        const Link<DartType>(),
+        const Link<SourceString>(),
+        const Link<DartType>());
+    cachedNode = new FunctionExpression(
+        new Identifier(enclosing.position()),
+        new NodeList.empty(),
+        new Block(new NodeList.empty()),
+        null, Modifiers.EMPTY, null, null);
+  }
+
+  bool get isSynthesized => true;
+
+  Token position() => enclosingElement.position();
+}
+
+class VoidElementX extends ElementX {
+  VoidElementX(Element enclosing)
+      : super(const SourceString('void'), ElementKind.VOID, enclosing);
+  DartType computeType(compiler) => compiler.types.voidType;
+  Node parseNode(_) {
+    throw 'internal error: parseNode on void';
+  }
+  bool impliesType() => true;
+}
+
+class TypeDeclarationElementX {
+  /**
+   * Creates the type variables, their type and corresponding element, for the
+   * type variables declared in [parameter] on [element]. The bounds of the type
+   * variables are not set until [element] has been resolved.
+   */
+  static Link<DartType> createTypeVariables(TypeDeclarationElement element,
+                                            NodeList parameters) {
+    if (parameters == null) return const Link<DartType>();
+
+    // Create types and elements for type variable.
+    var arguments = new LinkBuilder<DartType>();
+    for (Link link = parameters.nodes; !link.isEmpty; link = link.tail) {
+      TypeVariable node = link.head;
+      SourceString variableName = node.name.source;
+      TypeVariableElement variableElement =
+          new TypeVariableElementX(variableName, element, node);
+      TypeVariableType variableType = new TypeVariableType(variableElement);
+      variableElement.type = variableType;
+      arguments.addLast(variableType);
+    }
+    return arguments.toLink();
+  }
+}
+
+abstract class BaseClassElementX extends ElementX implements ClassElement {
+  final int id;
+
+  /**
+   * The type of [:this:] for this class declaration.
+   *
+   * The type of [:this:] is the interface type based on this element in which
+   * the type arguments are the declared type variables. For instance,
+   * [:List<E>:] for [:List:] and [:Map<K,V>:] for [:Map:].
+   *
+   * This type is computed in [computeType].
+   */
+  InterfaceType thisType;
+
+  /**
+   * The raw type for this class declaration.
+   *
+   * The raw type is the interface type base on this element in which the type
+   * arguments are all [dynamic]. For instance [:List<dynamic>:] for [:List:]
+   * and [:Map<dynamic,dynamic>:] for [:Map:]. For non-generic classes [rawType]
+   * is the same as [thisType].
+   *
+   * The [rawType] field is a canonicalization of the raw type and should be
+   * used to distinguish explicit and implicit uses of the [dynamic]
+   * type arguments. For instance should [:List:] be the [rawType] of the
+   * [:List:] class element whereas [:List<dynamic>:] should be its own
+   * instantiation of [InterfaceType] with [:dynamic:] as type argument. Using
+   * this distinction, we can print the raw type with type arguments only when
+   * the input source has used explicit type arguments.
+   *
+   * This type is computed together with [thisType] in [computeType].
+   */
+  InterfaceType rawType;
+  DartType supertype;
+  DartType defaultClass;
+  Link<DartType> interfaces;
+  SourceString nativeTagInfo;
+  int supertypeLoadState;
+  int resolutionState;
+
+  // backendMembers are members that have been added by the backend to simplify
+  // compilation. They don't have any user-side counter-part.
+  Link<Element> backendMembers = const Link<Element>();
+
+  Link<DartType> allSupertypes;
+
+  BaseClassElementX(SourceString name,
+                    Element enclosing,
+                    this.id,
+                    int initialState)
+      : supertypeLoadState = initialState,
+        resolutionState = initialState,
+        super(name, ElementKind.CLASS, enclosing);
+
+  int get hashCode => id;
+  ClassElement get patch => super.patch;
+  ClassElement get origin => super.origin;
+  ClassElement get declaration => super.declaration;
+  ClassElement get implementation => super.implementation;
+
+  bool get hasBackendMembers => !backendMembers.isEmpty;
+
+  InterfaceType computeType(Compiler compiler) {
+    if (thisType == null) {
+      if (origin == null) {
+        Link<DartType> parameters = computeTypeParameters(compiler);
+        thisType = new InterfaceType(this, parameters);
+        if (parameters.isEmpty) {
+          rawType = thisType;
+        } else {
+          var dynamicParameters = const Link<DartType>();
+          parameters.forEach((_) {
+            dynamicParameters =
+                dynamicParameters.prepend(compiler.types.dynamicType);
+          });
+          rawType = new InterfaceType(this, dynamicParameters);
+        }
+      } else {
+        thisType = origin.computeType(compiler);
+        rawType = origin.rawType;
+      }
+    }
+    return thisType;
+  }
+
+  Link<DartType> computeTypeParameters(Compiler compiler);
+
+  /**
+   * Return [:true:] if this element is the [:Object:] class for the [compiler].
+   */
+  bool isObject(Compiler compiler) =>
+      identical(declaration, compiler.objectClass);
+
+  Link<DartType> get typeVariables => thisType.typeArguments;
+
+  ClassElement ensureResolved(Compiler compiler) {
+    if (resolutionState == STATE_NOT_STARTED) {
+      compiler.resolver.resolveClass(this);
+    }
+    return this;
+  }
+
+  void addDefaultConstructorIfNeeded(Compiler compiler) {
+    if (hasConstructor) return;
+    FunctionElement constructor =
+        new SynthesizedConstructorElementX.forDefault(this, compiler);
+    setDefaultConstructor(constructor, compiler);
+  }
+
+  void setDefaultConstructor(FunctionElement constructor, Compiler compiler);
+
+  void addBackendMember(Element member) {
+    backendMembers = backendMembers.prepend(member);
+  }
+
+  void reverseBackendMembers() {
+    backendMembers = backendMembers.reverse();
+  }
+
+  /**
+   * Lookup local members in the class. This will ignore constructors.
+   */
+  Element lookupLocalMember(SourceString memberName) {
+    var result = localLookup(memberName);
+    if (result != null && result.isConstructor()) return null;
+    return result;
+  }
+
+  /// Lookup a synthetic element created by the backend.
+  Element lookupBackendMember(SourceString memberName) {
+    for (Element element in backendMembers) {
+      if (element.name == memberName) {
+        return element;
+      }
+    }
+  }
+  /**
+   * Lookup super members for the class. This will ignore constructors.
+   */
+  Element lookupSuperMember(SourceString memberName) {
+    return lookupSuperMemberInLibrary(memberName, getLibrary());
+  }
+
+  /**
+   * Lookup super members for the class that is accessible in [library].
+   * This will ignore constructors.
+   */
+  Element lookupSuperMemberInLibrary(SourceString memberName,
+                                     LibraryElement library) {
+    bool includeInjectedMembers = isPatch;
+    bool isPrivate = memberName.isPrivate();
+    for (ClassElement s = superclass; s != null; s = s.superclass) {
+      // Private members from a different library are not visible.
+      if (isPrivate && !identical(library, s.getLibrary())) continue;
+      s = includeInjectedMembers ? s.implementation : s;
+      Element e = s.lookupLocalMember(memberName);
+      if (e == null) continue;
+      // Static members are not inherited.
+      if (e.modifiers.isStatic()) continue;
+      return e;
+    }
+    if (isInterface()) {
+      return lookupSuperInterfaceMember(memberName, getLibrary());
+    }
+    return null;
+  }
+
+  Element lookupSuperInterfaceMember(SourceString memberName,
+                                     LibraryElement fromLibrary) {
+    bool includeInjectedMembers = isPatch;
+    bool isPrivate = memberName.isPrivate();
+    for (InterfaceType t in interfaces) {
+      ClassElement cls = t.element;
+      cls = includeInjectedMembers ? cls.implementation : cls;
+      Element e = cls.lookupLocalMember(memberName);
+      if (e == null) continue;
+      // Private members from a different library are not visible.
+      if (isPrivate && !identical(fromLibrary, e.getLibrary())) continue;
+      // Static members are not inherited.
+      if (e.modifiers.isStatic()) continue;
+      return e;
+    }
+    return null;
+  }
+
+  /**
+   * Find the first member in the class chain with the given [selector].
+   *
+   * This method is NOT to be used for resolving
+   * unqualified sends because it does not implement the scoping
+   * rules, where library scope comes before superclass scope.
+   *
+   * When called on the implementation element both members declared in the
+   * origin and the patch class are returned.
+   */
+  Element lookupSelector(Selector selector) {
+    SourceString memberName = selector.name;
+    LibraryElement library = selector.library;
+    Element localMember = lookupLocalMember(memberName);
+    if (localMember != null &&
+        (!memberName.isPrivate() || getLibrary() == library)) {
+      return localMember;
+    }
+    return lookupSuperMemberInLibrary(memberName, library);
+  }
+
+  /**
+   * Find the first member in the class chain with the given
+   * [memberName]. This method is NOT to be used for resolving
+   * unqualified sends because it does not implement the scoping
+   * rules, where library scope comes before superclass scope.
+   */
+  Element lookupMember(SourceString memberName) {
+    Element localMember = lookupLocalMember(memberName);
+    return localMember == null ? lookupSuperMember(memberName) : localMember;
+  }
+
+  /**
+   * Returns true if the [fieldMember] is shadowed by another field. The given
+   * [fieldMember] must be a member of this class.
+   *
+   * This method also works if the [fieldMember] is private.
+   */
+  bool isShadowedByField(Element fieldMember) {
+    assert(fieldMember.isField());
+    // Note that we cannot use [lookupMember] or [lookupSuperMember] since it
+    // will not do the right thing for private elements.
+    ClassElement lookupClass = this;
+    LibraryElement memberLibrary = fieldMember.getLibrary();
+    if (fieldMember.name.isPrivate()) {
+      // We find a super class in the same library as the field. This way the
+      // lookupMember will work.
+      while (lookupClass.getLibrary() != memberLibrary) {
+        lookupClass = lookupClass.superclass;
+      }
+    }
+    SourceString fieldName = fieldMember.name;
+    while (true) {
+      Element foundMember = lookupClass.lookupMember(fieldName);
+      if (foundMember == fieldMember) return false;
+      if (foundMember.isField()) return true;
+      lookupClass = foundMember.getEnclosingClass().superclass;
+    }
+  }
+
+  Element validateConstructorLookupResults(Selector selector,
+                                           Element result,
+                                           Element noMatch(Element)) {
+    if (result == null
+        || !result.isConstructor()
+        || (selector.name.isPrivate()
+            && result.getLibrary() != selector.library)) {
+      result = noMatch != null ? noMatch(result) : null;
+    }
+    return result;
+  }
+
+  // TODO(aprelev@gmail.com): Peter believes that it would be great to
+  // make noMatch a required argument. Peter's suspicion is that most
+  // callers of this method would benefit from using the noMatch method.
+  Element lookupConstructor(Selector selector, [Element noMatch(Element)]) {
+    SourceString normalizedName;
+    SourceString className = this.name;
+    SourceString constructorName = selector.name;
+    if (constructorName != const SourceString('')) {
+      normalizedName = Elements.constructConstructorName(className,
+                                                         constructorName);
+    } else {
+      normalizedName = className;
+    }
+    Element result = localLookup(normalizedName);
+    return validateConstructorLookupResults(selector, result, noMatch);
+  }
+
+  Element lookupFactoryConstructor(Selector selector,
+                                   [Element noMatch(Element)]) {
+    SourceString constructorName = selector.name;
+    Element result = localLookup(constructorName);
+    return validateConstructorLookupResults(selector, result, noMatch);
+  }
+
+  Link<Element> get constructors {
+    // TODO(ajohnsen): See if we can avoid this method at some point.
+    Link<Element> result = const Link<Element>();
+    // TODO(johnniwinther): Should we include injected constructors?
+    forEachMember((_, Element member) {
+      if (member.isConstructor()) result = result.prepend(member);
+    });
+    return result;
+  }
+
+  /**
+   * Returns the super class, if any.
+   *
+   * The returned element may not be resolved yet.
+   */
+  ClassElement get superclass {
+    assert(supertypeLoadState == STATE_DONE);
+    return supertype == null ? null : supertype.element;
+  }
+
+  /**
+   * Runs through all members of this class.
+   *
+   * The enclosing class is passed to the callback. This is useful when
+   * [includeSuperMembers] is [:true:].
+   *
+   * When called on an implementation element both the members in the origin
+   * and patch class are included.
+   */
+  // TODO(johnniwinther): Clean up lookup to get rid of the include predicates.
+  void forEachMember(void f(ClassElement enclosingClass, Element member),
+                     {includeBackendMembers: false,
+                      includeSuperMembers: false}) {
+    bool includeInjectedMembers = isPatch;
+    Set<ClassElement> seen = new Set<ClassElement>();
+    ClassElement classElement = declaration;
+    do {
+      if (seen.contains(classElement)) return;
+      seen.add(classElement);
+
+      // Iterate through the members in textual order, which requires
+      // to reverse the data structure [localMembers] we created.
+      // Textual order may be important for certain operations, for
+      // example when emitting the initializers of fields.
+      classElement.forEachLocalMember((e) => f(classElement, e));
+      if (includeBackendMembers) {
+        classElement.forEachBackendMember((e) => f(classElement, e));
+      }
+      if (includeInjectedMembers) {
+        if (classElement.patch != null) {
+          classElement.patch.forEachLocalMember((e) {
+            if (!e.isPatch) f(classElement, e);
+          });
+        }
+      }
+      classElement = includeSuperMembers ? classElement.superclass : null;
+    } while(classElement != null);
+  }
+
+  /**
+   * Runs through all instance-field members of this class.
+   *
+   * The enclosing class is passed to the callback. This is useful when
+   * [includeSuperMembers] is [:true:].
+   *
+   * When [includeBackendMembers] and [includeSuperMembers] are both [:true:]
+   * then the fields are visited in the same order as they need to be given
+   * to the JavaScript constructor.
+   *
+   * When called on the implementation element both the fields declared in the
+   * origin and in the patch are included.
+   */
+  void forEachInstanceField(void f(ClassElement enclosingClass, Element field),
+                            {includeBackendMembers: false,
+                             includeSuperMembers: false}) {
+    // Filters so that [f] is only invoked with instance fields.
+    void fieldFilter(ClassElement enclosingClass, Element member) {
+      if (member.isInstanceMember() && member.kind == ElementKind.FIELD) {
+        f(enclosingClass, member);
+      }
+    }
+
+    forEachMember(fieldFilter,
+                  includeBackendMembers: includeBackendMembers,
+                  includeSuperMembers: includeSuperMembers);
+  }
+
+  void forEachBackendMember(void f(Element member)) {
+    backendMembers.forEach(f);
+  }
+
+  bool implementsInterface(ClassElement intrface) {
+    for (DartType implementedInterfaceType in allSupertypes) {
+      ClassElement implementedInterface = implementedInterfaceType.element;
+      if (identical(implementedInterface, intrface)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if [this] is a subclass of [cls].
+   *
+   * This method is not to be used for checking type hierarchy and
+   * assignments, because it does not take parameterized types into
+   * account.
+   */
+  bool isSubclassOf(ClassElement cls) {
+    // Use [declaration] for both [this] and [cls], because
+    // declaration classes hold the superclass hierarchy.
+    cls = cls.declaration;
+    for (ClassElement s = declaration; s != null; s = s.superclass) {
+      if (identical(s, cls)) return true;
+    }
+    return false;
+  }
+
+  bool isInterface() => false;
+  bool isNative() => nativeTagInfo != null;
+  void setNative(String name) {
+    nativeTagInfo = new SourceString(name);
+  }
+}
+
+abstract class ClassElementX extends BaseClassElementX {
+  // Lazily applied patch of class members.
+  ClassElement patch = null;
+  ClassElement origin = null;
+
+  Link<Element> localMembers = const Link<Element>();
+  final ScopeX localScope = new ScopeX();
+
+  ClassElementX(SourceString name, Element enclosing, int id, int initialState)
+      : super(name, enclosing, id, initialState);
+
+  ClassNode parseNode(Compiler compiler);
+
+  bool get isMixinApplication => false;
+  bool get isPatched => patch != null;
+  bool get isPatch => origin != null;
+  bool get hasLocalScopeMembers => !localScope.isEmpty;
+
+  void addMember(Element element, DiagnosticListener listener) {
+    localMembers = localMembers.prepend(element);
+    addToScope(element, listener);
+  }
+
+  void addToScope(Element element, DiagnosticListener listener) {
+    localScope.add(element, listener);
+  }
+
+  Element localLookup(SourceString elementName) {
+    Element result = localScope.lookup(elementName);
+    if (result == null && isPatch) {
+      result = origin.localLookup(elementName);
+    }
+    return result;
+  }
+
+  void forEachLocalMember(void f(Element member)) {
+    localMembers.reverse().forEach(f);
+  }
+
+  bool get hasConstructor {
+    // Search in scope to be sure we search patched constructors.
+    for (var element in localScope.values) {
+      if (element.isConstructor()) return true;
+    }
+    return false;
+  }
+
+  void setDefaultConstructor(FunctionElement constructor, Compiler compiler) {
+    addToScope(constructor, compiler);
+  }
+
+  Link<DartType> computeTypeParameters(Compiler compiler) {
+    ClassNode node = parseNode(compiler);
+    return TypeDeclarationElementX.createTypeVariables(
+        this, node.typeParameters);
+  }
+
+  Scope buildScope() => new ClassScope(enclosingElement.buildScope(), this);
+
+  String toString() {
+    if (origin != null) {
+      return 'patch ${super.toString()}';
+    } else if (patch != null) {
+      return 'origin ${super.toString()}';
+    } else {
+      return super.toString();
+    }
+  }
+}
+
+class MixinApplicationElementX extends BaseClassElementX
+    implements MixinApplicationElement {
+  final Node node;
+  final Modifiers modifiers;
+
+  FunctionElement constructor;
+  ClassElement mixin;
+
+  // TODO(kasperl): The analyzer complains when I don't have these two
+  // fields. This is pretty weird. I cannot replace them with getters.
+  final ClassElement patch = null;
+  final ClassElement origin = null;
+
+  MixinApplicationElementX(SourceString name, Element enclosing, int id,
+                           this.node, this.modifiers)
+      : super(name, enclosing, id, STATE_NOT_STARTED);
+
+  bool get isMixinApplication => true;
+  bool get hasConstructor => constructor != null;
+  bool get hasLocalScopeMembers => false;
+
+  Token position() => node.getBeginToken();
+
+  Node parseNode(DiagnosticListener listener) => node;
+
+  Element localLookup(SourceString name) {
+    if (this.name == name) return constructor;
+    if (mixin == null) return null;
+    Element mixedInElement = mixin.localLookup(name);
+    if (mixedInElement == null) return null;
+    return mixedInElement.isInstanceMember() ? mixedInElement : null;
+  }
+
+  void forEachLocalMember(void f(Element member)) {
+    if (mixin != null) mixin.forEachLocalMember((Element mixedInElement) {
+      if (mixedInElement.isInstanceMember()) f(mixedInElement);
+    });
+  }
+
+  void addMember(Element element, DiagnosticListener listener) {
+    throw new UnsupportedError("cannot add member to $this");
+  }
+
+  void addToScope(Element element, DiagnosticListener listener) {
+    throw new UnsupportedError("cannot add to scope of $this");
+  }
+
+  void setDefaultConstructor(FunctionElement constructor, Compiler compiler) {
+    assert(!hasConstructor);
+    this.constructor = constructor;
+  }
+
+  Link<DartType> computeTypeParameters(Compiler compiler) {
+    NamedMixinApplication named = node.asNamedMixinApplication();
+    if (named == null) return const Link<DartType>();
+    return TypeDeclarationElementX.createTypeVariables(
+        this, named.typeParameters);
+  }
+}
+
+class LabelElementX extends ElementX implements LabelElement {
+
+  // We store the original label here so it can be returned by [parseNode].
+  final Label label;
+  final String labelName;
+  final TargetElement target;
+  bool isBreakTarget = false;
+  bool isContinueTarget = false;
+  LabelElementX(Label label, String labelName, this.target,
+                Element enclosingElement)
+      : this.label = label,
+        this.labelName = labelName,
+        // In case of a synthetic label, just use [labelName] for
+        // identifying the element.
+        super(label == null
+                  ? new SourceString(labelName)
+                  : label.identifier.source,
+              ElementKind.LABEL,
+              enclosingElement);
+
+  void setBreakTarget() {
+    isBreakTarget = true;
+    target.isBreakTarget = true;
+  }
+  void setContinueTarget() {
+    isContinueTarget = true;
+    target.isContinueTarget = true;
+  }
+
+  bool get isTarget => isBreakTarget || isContinueTarget;
+  Node parseNode(DiagnosticListener l) => label;
+
+  Token position() => label.getBeginToken();
+  String toString() => "${labelName}:";
+}
+
+// Represents a reference to a statement or switch-case, either by label or the
+// default target of a break or continue.
+class TargetElementX extends ElementX implements TargetElement {
+  final Node statement;
+  final int nestingLevel;
+  Link<LabelElement> labels = const Link<LabelElement>();
+  bool isBreakTarget = false;
+  bool isContinueTarget = false;
+
+  TargetElementX(this.statement, this.nestingLevel, Element enclosingElement)
+      : super(const SourceString(""), ElementKind.STATEMENT, enclosingElement);
+  bool get isTarget => isBreakTarget || isContinueTarget;
+
+  LabelElement addLabel(Label label, String labelName) {
+    LabelElement result = new LabelElementX(label, labelName, this,
+                                            enclosingElement);
+    labels = labels.prepend(result);
+    return result;
+  }
+
+  Node parseNode(DiagnosticListener l) => statement;
+
+  bool get isSwitch => statement is SwitchStatement;
+
+  Token position() => statement.getBeginToken();
+  String toString() => statement.toString();
+}
+
+class TypeVariableElementX extends ElementX implements TypeVariableElement {
+  final Node cachedNode;
+  TypeVariableType type;
+  DartType bound;
+
+  TypeVariableElementX(name, Element enclosing, this.cachedNode,
+                       [this.type, this.bound])
+    : super(name, ElementKind.TYPE_VARIABLE, enclosing);
+
+  TypeVariableType computeType(compiler) => type;
+
+  Node parseNode(compiler) => cachedNode;
+
+  String toString() => "${enclosingElement.toString()}.${name.slowToString()}";
+
+  Token position() => cachedNode.getBeginToken();
+}
+
+/**
+ * A single metadata annotation.
+ *
+ * For example, consider:
+ *
+ * [:
+ * class Data {
+ *   const Data();
+ * }
+ *
+ * const data = const Data();
+ *
+ * @data
+ * class Foo {}
+ *
+ * @data @data
+ * class Bar {}
+ * :]
+ *
+ * In this example, there are three instances of [MetadataAnnotation]
+ * and they correspond each to a location in the source code where
+ * there is an at-sign, '@'. The [value] of each of these instances
+ * are the same compile-time constant, [: const Data() :].
+ *
+ * The mirror system does not have a concept matching this class.
+ */
+abstract class MetadataAnnotationX implements MetadataAnnotation {
+  /**
+   * The compile-time constant which this annotation resolves to.
+   * In the mirror system, this would be an object mirror.
+   */
+  Constant get value;
+  Element annotatedElement;
+  int resolutionState;
+
+  /**
+   * The beginning token of this annotation, or [:null:] if it is synthetic.
+   */
+  Token get beginToken;
+
+  MetadataAnnotationX([this.resolutionState = STATE_NOT_STARTED]);
+
+  MetadataAnnotation ensureResolved(Compiler compiler) {
+    if (resolutionState == STATE_NOT_STARTED) {
+      compiler.resolver.resolveMetadataAnnotation(this);
+    }
+    return this;
+  }
+
+  String toString() => 'MetadataAnnotation($value, $resolutionState)';
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart b/pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart
new file mode 100644
index 0000000..4a4fea8
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart
@@ -0,0 +1,552 @@
+// 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.
+
+part of dart2js;
+
+class EnqueueTask extends CompilerTask {
+  final ResolutionEnqueuer resolution;
+  final CodegenEnqueuer codegen;
+
+  String get name => 'Enqueue';
+
+  EnqueueTask(Compiler compiler)
+    : resolution = new ResolutionEnqueuer(
+          compiler, compiler.backend.createItemCompilationContext),
+      codegen = new CodegenEnqueuer(
+          compiler, compiler.backend.createItemCompilationContext),
+      super(compiler) {
+    codegen.task = this;
+    resolution.task = this;
+
+    codegen.nativeEnqueuer = compiler.backend.nativeCodegenEnqueuer(codegen);
+    resolution.nativeEnqueuer =
+        compiler.backend.nativeResolutionEnqueuer(resolution);
+  }
+}
+
+abstract class Enqueuer {
+  final String name;
+  final Compiler compiler; // TODO(ahe): Remove this dependency.
+  final Function itemCompilationContextCreator;
+  final Map<String, Link<Element>> instanceMembersByName;
+  final Set<ClassElement> seenClasses;
+  final Universe universe;
+
+  bool queueIsClosed = false;
+  EnqueueTask task;
+  native.NativeEnqueuer nativeEnqueuer;  // Set by EnqueueTask
+
+  Enqueuer(this.name, this.compiler,
+           ItemCompilationContext itemCompilationContextCreator())
+    : this.itemCompilationContextCreator = itemCompilationContextCreator,
+      instanceMembersByName = new Map<String, Link<Element>>(),
+      universe = new Universe(),
+      seenClasses = new Set<ClassElement>();
+
+  /// Returns [:true:] if this enqueuer is the resolution enqueuer.
+  bool get isResolutionQueue => false;
+
+  /// Returns [:true:] if [member] has been processed by this enqueuer.
+  bool isProcessed(Element member);
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void addToWorkList(Element element, [TreeElements elements]) {
+    assert(invariant(element, element.isDeclaration));
+    if (element.isForeign(compiler)) return;
+
+    if (!addElementToWorkList(element, elements)) return;
+
+    // Enable runtime type support if we discover a getter called runtimeType.
+    // We have to enable runtime type before hitting the codegen, so
+    // that constructors know whether they need to generate code for
+    // runtime type.
+    if (element.isGetter() && element.name == Compiler.RUNTIME_TYPE) {
+      compiler.enabledRuntimeType = true;
+    } else if (element == compiler.functionApplyMethod) {
+      compiler.enabledFunctionApply = true;
+    } else if (element == compiler.invokeOnMethod) {
+      compiler.enabledInvokeOn = true;
+    }
+
+    nativeEnqueuer.registerElement(element);
+  }
+
+  /**
+   * Adds [element] to the work list if it has not already been processed.
+   *
+   * Returns [:true:] if the [element] should be processed.
+   */
+  // TODO(johnniwinther): Change to 'Returns true if the element was added to
+  // the work list'?
+  bool addElementToWorkList(Element element, [TreeElements elements]);
+
+  void registerInstantiatedClass(ClassElement cls) {
+    if (universe.instantiatedClasses.contains(cls)) return;
+    if (!cls.isAbstract(compiler)) {
+      universe.instantiatedClasses.add(cls);
+      onRegisterInstantiatedClass(cls);
+    }
+    compiler.backend.registerInstantiatedClass(cls, this);
+  }
+
+  bool checkNoEnqueuedInvokedInstanceMethods() {
+    task.measure(() {
+      // Run through the classes and see if we need to compile methods.
+      for (ClassElement classElement in universe.instantiatedClasses) {
+        for (ClassElement currentClass = classElement;
+             currentClass != null;
+             currentClass = currentClass.superclass) {
+          processInstantiatedClass(currentClass);
+        }
+      }
+    });
+    return true;
+  }
+
+  void processInstantiatedClass(ClassElement cls) {
+    cls.implementation.forEachMember(processInstantiatedClassMember);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   */
+  void processInstantiatedClassMember(ClassElement cls, Element member) {
+    assert(invariant(member, member.isDeclaration));
+    if (isProcessed(member)) return;
+    if (!member.isInstanceMember()) return;
+    if (member.isField()) {
+      // Native fields need to go into instanceMembersByName as they are virtual
+      // instantiation points and escape points.
+      // Test the enclosing class, since the metadata has not been parsed yet.
+      if (!member.enclosingElement.isNative()) return;
+    }
+
+    String memberName = member.name.slowToString();
+    Link<Element> members = instanceMembersByName.putIfAbsent(
+        memberName, () => const Link<Element>());
+    instanceMembersByName[memberName] = members.prepend(member);
+
+    if (member.kind == ElementKind.FUNCTION) {
+      if (member.name == Compiler.NO_SUCH_METHOD) {
+        enableNoSuchMethod(member);
+      }
+      if (universe.hasInvocation(member, compiler)) {
+        return addToWorkList(member);
+      }
+      // If there is a property access with the same name as a method we
+      // need to emit the method.
+      if (universe.hasInvokedGetter(member, compiler)) {
+        // We will emit a closure, so make sure the closure class is
+        // generated.
+        compiler.closureClass.ensureResolved(compiler);
+        registerInstantiatedClass(compiler.closureClass);
+        return addToWorkList(member);
+      }
+    } else if (member.kind == ElementKind.GETTER) {
+      if (universe.hasInvokedGetter(member, compiler)) {
+        return addToWorkList(member);
+      }
+      // We don't know what selectors the returned closure accepts. If
+      // the set contains any selector we have to assume that it matches.
+      if (universe.hasInvocation(member, compiler)) {
+        return addToWorkList(member);
+      }
+    } else if (member.kind == ElementKind.SETTER) {
+      if (universe.hasInvokedSetter(member, compiler)) {
+        return addToWorkList(member);
+      }
+    } else if (member.kind == ElementKind.FIELD &&
+               member.enclosingElement.isNative()) {
+      nativeEnqueuer.handleFieldAnnotations(member);
+      if (universe.hasInvokedGetter(member, compiler) ||
+          universe.hasInvocation(member, compiler)) {
+        nativeEnqueuer.registerFieldLoad(member);
+        // In handleUnseenSelector we can't tell if the field is loaded or
+        // stored.  We need the basic algorithm to be Church-Rosser, since the
+        // resolution 'reduction' order is different to the codegen order. So
+        // register that the field is also stored.  In other words: if we don't
+        // register the store here during resolution, the store could be
+        // registered during codegen on the handleUnseenSelector path, and cause
+        // the set of codegen elements to include unresolved elements.
+        nativeEnqueuer.registerFieldStore(member);
+      }
+      if (universe.hasInvokedSetter(member, compiler)) {
+        nativeEnqueuer.registerFieldStore(member);
+        // See comment after registerFieldLoad above.
+        nativeEnqueuer.registerFieldLoad(member);
+      }
+    }
+  }
+
+  void enableNoSuchMethod(Element element) {}
+
+  void onRegisterInstantiatedClass(ClassElement cls) {
+    task.measure(() {
+      // The class must be resolved to compute the set of all
+      // supertypes.
+      cls.ensureResolved(compiler);
+
+      void processClass(ClassElement cls) {
+        if (seenClasses.contains(cls)) return;
+
+        seenClasses.add(cls);
+        cls.ensureResolved(compiler);
+        cls.implementation.forEachMember(processInstantiatedClassMember);
+        if (isResolutionQueue) {
+          compiler.resolver.checkClass(cls);
+        }
+
+        if (compiler.enableTypeAssertions) {
+          // We need to register is checks and helpers for checking
+          // assignments to fields.
+          // TODO(ngeoffray): This should really move to the backend.
+          cls.forEachLocalMember((Element member) {
+            if (!member.isInstanceMember() || !member.isField()) return;
+            DartType type = member.computeType(compiler);
+            registerIsCheck(type);
+            SourceString helper = compiler.backend.getCheckedModeHelper(type);
+            if (helper != null) {
+              Element helperElement = compiler.findHelper(helper);
+              registerStaticUse(helperElement);
+            }
+          });
+        }
+      }
+      processClass(cls);
+      for (Link<DartType> supertypes = cls.allSupertypes;
+           !supertypes.isEmpty; supertypes = supertypes.tail) {
+        processClass(supertypes.head.element);
+      }
+    });
+  }
+
+  void registerNewSelector(SourceString name,
+                           Selector selector,
+                           Map<SourceString, Set<Selector>> selectorsMap) {
+    if (name != selector.name) {
+      String message = "$name != ${selector.name} (${selector.kind})";
+      compiler.internalError("Wrong selector name: $message.");
+    }
+    Set<Selector> selectors =
+        selectorsMap.putIfAbsent(name, () => new Set<Selector>());
+    if (!selectors.contains(selector)) {
+      selectors.add(selector);
+      handleUnseenSelector(name, selector);
+    }
+  }
+
+  void registerInvocation(SourceString methodName, Selector selector) {
+    task.measure(() {
+      registerNewSelector(methodName, selector, universe.invokedNames);
+    });
+  }
+
+  void registerInvokedGetter(SourceString getterName, Selector selector) {
+    task.measure(() {
+      registerNewSelector(getterName, selector, universe.invokedGetters);
+    });
+  }
+
+  void registerInvokedSetter(SourceString setterName, Selector selector) {
+    task.measure(() {
+      registerNewSelector(setterName, selector, universe.invokedSetters);
+    });
+  }
+
+  processInstanceMembers(SourceString n, bool f(Element e)) {
+    String memberName = n.slowToString();
+    Link<Element> members = instanceMembersByName[memberName];
+    if (members != null) {
+      LinkBuilder<Element> remaining = new LinkBuilder<Element>();
+      for (; !members.isEmpty; members = members.tail) {
+        if (!f(members.head)) remaining.addLast(members.head);
+      }
+      instanceMembersByName[memberName] = remaining.toLink();
+    }
+  }
+
+  void handleUnseenSelector(SourceString methodName, Selector selector) {
+    processInstanceMembers(methodName, (Element member) {
+      if (selector.appliesUnnamed(member, compiler)) {
+        if (member.isField() && member.enclosingElement.isNative()) {
+          if (selector.isGetter() || selector.isCall()) {
+            nativeEnqueuer.registerFieldLoad(member);
+            // We have to also handle storing to the field because we only get
+            // one look at each member and there might be a store we have not
+            // seen yet.
+            // TODO(sra): Process fields for storing separately.
+            nativeEnqueuer.registerFieldStore(member);
+          } else {
+            nativeEnqueuer.registerFieldStore(member);
+            // We have to also handle loading from the field because we only get
+            // one look at each member and there might be a load we have not
+            // seen yet.
+            // TODO(sra): Process fields for storing separately.
+            nativeEnqueuer.registerFieldLoad(member);
+          }
+        } else {
+          addToWorkList(member);
+        }
+        return true;
+      }
+      return false;
+    });
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void registerStaticUse(Element element) {
+    if (element == null) return;
+    assert(invariant(element, element.isDeclaration));
+    addToWorkList(element);
+  }
+
+  void registerGetOfStaticFunction(FunctionElement element) {
+    registerStaticUse(element);
+    universe.staticFunctionsNeedingGetter.add(element);
+  }
+
+  void registerDynamicInvocation(SourceString methodName, Selector selector) {
+    assert(selector != null);
+    registerInvocation(methodName, selector);
+  }
+
+  void registerDynamicInvocationOf(Element element, Selector selector) {
+    assert(selector.isCall()
+           || selector.isOperator()
+           || selector.isIndex()
+           || selector.isIndexSet());
+    if (element.isFunction()) {
+      addToWorkList(element);
+    } else if (element.isAbstractField()) {
+      AbstractFieldElement field = element;
+      // Since the invocation is a dynamic call on a getter, we only
+      // need to schedule the getter on the work list.
+      addToWorkList(field.getter);
+    } else {
+      assert(element.isField());
+    }
+    // We also need to add the selector to the invoked names map,
+    // because the emitter uses that map to generate parameter stubs.
+    Set<Selector> selectors = universe.invokedNames.putIfAbsent(
+        element.name, () => new Set<Selector>());
+    selectors.add(selector);
+  }
+
+  void registerDynamicGetter(SourceString methodName, Selector selector) {
+    registerInvokedGetter(methodName, selector);
+  }
+
+  void registerDynamicSetter(SourceString methodName, Selector selector) {
+    registerInvokedSetter(methodName, selector);
+  }
+
+  void registerFieldGetter(SourceString getterName,
+                           LibraryElement library,
+                           DartType type) {
+    task.measure(() {
+      Selector getter = new Selector.getter(getterName, library);
+      registerNewSelector(getterName,
+                          new TypedSelector(type, getter),
+                          universe.fieldGetters);
+    });
+  }
+
+  void registerFieldSetter(SourceString setterName,
+                           LibraryElement library,
+                           DartType type) {
+    task.measure(() {
+      Selector setter = new Selector.setter(setterName, library);
+      registerNewSelector(setterName,
+                          new TypedSelector(type, setter),
+                          universe.fieldSetters);
+    });
+  }
+
+  void registerIsCheck(DartType type) {
+    universe.isChecks.add(type);
+  }
+
+  void forEach(f(WorkItem work));
+
+  void logSummary(log(message)) {
+    _logSpecificSummary(log);
+    nativeEnqueuer.logSummary(log);
+  }
+
+  /// Log summary specific to the concrete enqueuer.
+  void _logSpecificSummary(log(message));
+
+  String toString() => 'Enqueuer($name)';
+}
+
+/// [Enqueuer] which is specific to resolution.
+class ResolutionEnqueuer extends Enqueuer {
+  /**
+   * Map from declaration elements to the [TreeElements] object holding the
+   * resolution mapping for the element implementation.
+   *
+   * Invariant: Key elements are declaration elements.
+   */
+  final Map<Element, TreeElements> resolvedElements;
+
+  final Queue<ResolutionWorkItem> queue;
+
+  ResolutionEnqueuer(Compiler compiler,
+                     ItemCompilationContext itemCompilationContextCreator())
+      : super('resolution enqueuer', compiler, itemCompilationContextCreator),
+        resolvedElements = new Map<Element, TreeElements>(),
+        queue = new Queue<ResolutionWorkItem>();
+
+  bool get isResolutionQueue => true;
+
+  bool isProcessed(Element member) => resolvedElements.containsKey(member);
+
+  TreeElements getCachedElements(Element element) {
+    // TODO(ngeoffray): Get rid of this check.
+    if (element.enclosingElement.isClosure()) {
+      closureMapping.ClosureClassElement cls = element.enclosingElement;
+      element = cls.methodElement;
+    }
+    Element owner = element.getOutermostEnclosingMemberOrTopLevel();
+    return resolvedElements[owner.declaration];
+  }
+
+  /**
+   * Sets the resolved elements of [element] to [elements], or if [elements] is
+   * [:null:], to the elements found through [getCachedElements].
+   *
+   * Returns the resolved elements.
+   */
+  TreeElements ensureCachedElements(Element element, TreeElements elements) {
+    if (elements == null) {
+      elements = getCachedElements(element);
+    }
+    resolvedElements[element] = elements;
+    return elements;
+  }
+
+  bool addElementToWorkList(Element element, [TreeElements elements]) {
+    if (queueIsClosed) {
+      if (getCachedElements(element) != null) return false;
+      throw new SpannableAssertionFailure(element,
+                                          "Resolution work list is closed.");
+    }
+    if (elements == null) {
+      elements = getCachedElements(element);
+    }
+    compiler.world.registerUsedElement(element);
+
+    if (elements == null) {
+      queue.add(
+          new ResolutionWorkItem(element, itemCompilationContextCreator()));
+    }
+
+    // Enable isolate support if we start using something from the
+    // isolate library, or timers for the async library.
+    LibraryElement library = element.getLibrary();
+    if (!compiler.hasIsolateSupport()) {
+      String uri = library.canonicalUri.toString();
+      if (uri == 'dart:isolate') {
+        enableIsolateSupport(library);
+      } else if (uri == 'dart:async') {
+        ClassElement cls = element.getEnclosingClass();
+        if (cls != null && cls.name == const SourceString('Timer')) {
+          // The [:Timer:] class uses the event queue of the isolate
+          // library, so we make sure that event queue is generated.
+          enableIsolateSupport(library);
+        }
+      }
+    }
+
+    return true;
+  }
+
+  void enableIsolateSupport(LibraryElement element) {
+    compiler.isolateLibrary = element.patch;
+    addToWorkList(
+        compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE));
+    addToWorkList(compiler.isolateHelperLibrary.find(
+        const SourceString('_currentIsolate')));
+    addToWorkList(compiler.isolateHelperLibrary.find(
+        const SourceString('_callInIsolate')));
+  }
+
+  void enableNoSuchMethod(Element element) {
+    if (compiler.enabledNoSuchMethod) return;
+    Selector selector = new Selector.noSuchMethod();
+    if (identical(element.getEnclosingClass(), compiler.objectClass)) {
+      registerDynamicInvocationOf(element, selector);
+      return;
+    }
+    compiler.enabledNoSuchMethod = true;
+    registerInvocation(Compiler.NO_SUCH_METHOD, selector);
+
+    compiler.createInvocationMirrorElement =
+        compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR);
+    addToWorkList(compiler.createInvocationMirrorElement);
+  }
+
+  void forEach(f(WorkItem work)) {
+    while (!queue.isEmpty) {
+      // TODO(johnniwinther): Find an optimal process order for resolution.
+      f(queue.removeLast());
+    }
+  }
+
+  void registerJsCall(Send node, ResolverVisitor resolver) {
+    nativeEnqueuer.registerJsCall(node, resolver);
+  }
+
+  void _logSpecificSummary(log(message)) {
+    log('Resolved ${resolvedElements.length} elements.');
+  }
+}
+
+/// [Enqueuer] which is specific to code generation.
+class CodegenEnqueuer extends Enqueuer {
+  final Queue<CodegenWorkItem> queue;
+  final Map<Element, js.Expression> generatedCode =
+      new Map<Element, js.Expression>();
+
+  CodegenEnqueuer(Compiler compiler,
+                  ItemCompilationContext itemCompilationContextCreator())
+      : super('codegen enqueuer', compiler, itemCompilationContextCreator),
+        queue = new Queue<CodegenWorkItem>();
+
+  bool isProcessed(Element member) => generatedCode.containsKey(member);
+
+  bool addElementToWorkList(Element element, [TreeElements elements]) {
+    if (queueIsClosed) {
+      throw new SpannableAssertionFailure(element,
+                                          "Codegen work list is closed.");
+    }
+    elements =
+        compiler.enqueuer.resolution.ensureCachedElements(element, elements);
+
+    CodegenWorkItem workItem = new CodegenWorkItem(
+        element, elements, itemCompilationContextCreator());
+    queue.add(workItem);
+
+    return true;
+  }
+
+  void forEach(f(WorkItem work)) {
+    while(!queue.isEmpty) {
+      // TODO(johnniwinther): Find an optimal process order for codegen.
+      f(queue.removeLast());
+    }
+  }
+
+  void _logSpecificSummary(log(message)) {
+    log('Compiled ${generatedCode.length} methods.');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart b/pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart
new file mode 100644
index 0000000..198a28f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart
@@ -0,0 +1,29 @@
+// 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.
+
+library filenames;
+
+import 'dart:io';
+import 'dart:uri';
+
+// TODO(ahe): This library should be replaced by a general
+// path-munging library.
+//
+// See also:
+// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx
+
+String nativeToUriPath(String filename) {
+  return new Path(filename).toString();
+}
+
+String uriPathToNative(String path) {
+  return new Path(path).toNativePath();
+}
+
+Uri getCurrentDirectory() {
+  final String dir = nativeToUriPath(new File('.').fullPathSync());
+  return new Uri.fromComponents(scheme: 'file', path: appendSlash(dir));
+}
+
+String appendSlash(String path) => path.endsWith('/') ? path : '$path/';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart
new file mode 100644
index 0000000..b7df642
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart
@@ -0,0 +1,15 @@
+// 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.
+
+library js;
+
+import 'precedence.dart';
+import '../util/characters.dart' as charCodes;
+
+// TODO(floitsch): remove this dependency (currently necessary for the
+// CodeBuffer).
+import '../dart2jslib.dart' as leg;
+
+part 'nodes.dart';
+part 'printer.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart
new file mode 100644
index 0000000..da8cd44
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart
@@ -0,0 +1,906 @@
+// 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.
+
+part of js;
+
+abstract class NodeVisitor<T> {
+  T visitProgram(Program node);
+
+  T visitBlock(Block node);
+  T visitExpressionStatement(ExpressionStatement node);
+  T visitEmptyStatement(EmptyStatement node);
+  T visitIf(If node);
+  T visitFor(For node);
+  T visitForIn(ForIn node);
+  T visitWhile(While node);
+  T visitDo(Do node);
+  T visitContinue(Continue node);
+  T visitBreak(Break node);
+  T visitReturn(Return node);
+  T visitThrow(Throw node);
+  T visitTry(Try node);
+  T visitCatch(Catch node);
+  T visitSwitch(Switch node);
+  T visitCase(Case node);
+  T visitDefault(Default node);
+  T visitFunctionDeclaration(FunctionDeclaration node);
+  T visitLabeledStatement(LabeledStatement node);
+  T visitLiteralStatement(LiteralStatement node);
+
+  T visitLiteralExpression(LiteralExpression node);
+  T visitVariableDeclarationList(VariableDeclarationList node);
+  T visitSequence(Sequence node);
+  T visitAssignment(Assignment node);
+  T visitVariableInitialization(VariableInitialization node);
+  T visitConditional(Conditional cond);
+  T visitNew(New node);
+  T visitCall(Call node);
+  T visitBinary(Binary node);
+  T visitPrefix(Prefix node);
+  T visitPostfix(Postfix node);
+
+  T visitVariableUse(VariableUse node);
+  T visitThis(This node);
+  T visitVariableDeclaration(VariableDeclaration node);
+  T visitParameter(Parameter node);
+  T visitAccess(PropertyAccess node);
+
+  T visitNamedFunction(NamedFunction node);
+  T visitFun(Fun node);
+
+  T visitLiteralBool(LiteralBool node);
+  T visitLiteralString(LiteralString node);
+  T visitLiteralNumber(LiteralNumber node);
+  T visitLiteralNull(LiteralNull node);
+
+  T visitArrayInitializer(ArrayInitializer node);
+  T visitArrayElement(ArrayElement node);
+  T visitObjectInitializer(ObjectInitializer node);
+  T visitProperty(Property node);
+  T visitRegExpLiteral(RegExpLiteral node);
+}
+
+class BaseVisitor<T> implements NodeVisitor<T> {
+  T visitNode(Node node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  T visitProgram(Program node) => visitNode(node);
+
+  T visitStatement(Statement node) => visitNode(node);
+  T visitLoop(Loop node) => visitStatement(node);
+  T visitJump(Statement node) => visitStatement(node);
+
+  T visitBlock(Block node) => visitStatement(node);
+  T visitExpressionStatement(ExpressionStatement node)
+      => visitStatement(node);
+  T visitEmptyStatement(EmptyStatement node) => visitStatement(node);
+  T visitIf(If node) => visitStatement(node);
+  T visitFor(For node) => visitLoop(node);
+  T visitForIn(ForIn node) => visitLoop(node);
+  T visitWhile(While node) => visitLoop(node);
+  T visitDo(Do node) => visitLoop(node);
+  T visitContinue(Continue node) => visitJump(node);
+  T visitBreak(Break node) => visitJump(node);
+  T visitReturn(Return node) => visitJump(node);
+  T visitThrow(Throw node) => visitJump(node);
+  T visitTry(Try node) => visitStatement(node);
+  T visitSwitch(Switch node) => visitStatement(node);
+  T visitFunctionDeclaration(FunctionDeclaration node)
+      => visitStatement(node);
+  T visitLabeledStatement(LabeledStatement node) => visitStatement(node);
+  T visitLiteralStatement(LiteralStatement node) => visitStatement(node);
+
+  T visitCatch(Catch node) => visitNode(node);
+  T visitCase(Case node) => visitNode(node);
+  T visitDefault(Default node) => visitNode(node);
+
+  T visitExpression(Expression node) => visitNode(node);
+  T visitVariableReference(VariableReference node) => visitExpression(node);
+
+  T visitLiteralExpression(LiteralExpression node) => visitExpression(node);
+  T visitVariableDeclarationList(VariableDeclarationList node)
+      => visitExpression(node);
+  T visitSequence(Sequence node) => visitExpression(node);
+  T visitAssignment(Assignment node) => visitExpression(node);
+  T visitVariableInitialization(VariableInitialization node) {
+    if (node.value != null) {
+      visitAssignment(node);
+    } else {
+      visitExpression(node);
+    }
+  }
+  T visitConditional(Conditional node) => visitExpression(node);
+  T visitNew(New node) => visitExpression(node);
+  T visitCall(Call node) => visitExpression(node);
+  T visitBinary(Binary node) => visitCall(node);
+  T visitPrefix(Prefix node) => visitCall(node);
+  T visitPostfix(Postfix node) => visitCall(node);
+  T visitAccess(PropertyAccess node) => visitExpression(node);
+
+  T visitVariableUse(VariableUse node) => visitVariableReference(node);
+  T visitVariableDeclaration(VariableDeclaration node)
+      => visitVariableReference(node);
+  T visitParameter(Parameter node) => visitVariableDeclaration(node);
+  T visitThis(This node) => visitParameter(node);
+
+  T visitNamedFunction(NamedFunction node) => visitExpression(node);
+  T visitFun(Fun node) => visitExpression(node);
+
+  T visitLiteral(Literal node) => visitExpression(node);
+
+  T visitLiteralBool(LiteralBool node) => visitLiteral(node);
+  T visitLiteralString(LiteralString node) => visitLiteral(node);
+  T visitLiteralNumber(LiteralNumber node) => visitLiteral(node);
+  T visitLiteralNull(LiteralNull node) => visitLiteral(node);
+
+  T visitArrayInitializer(ArrayInitializer node) => visitExpression(node);
+  T visitArrayElement(ArrayElement node) => visitNode(node);
+  T visitObjectInitializer(ObjectInitializer node) => visitExpression(node);
+  T visitProperty(Property node) => visitNode(node);
+  T visitRegExpLiteral(RegExpLiteral node) => visitExpression(node);
+}
+
+abstract class Node {
+  var sourcePosition;
+  var endSourcePosition;
+
+  accept(NodeVisitor visitor);
+  void visitChildren(NodeVisitor visitor);
+
+  VariableUse asVariableUse() => null;
+}
+
+class Program extends Node {
+  final List<Statement> body;
+  Program(this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitProgram(this);
+  void visitChildren(NodeVisitor visitor) {
+    for (Statement statement in body) statement.accept(visitor);
+  }
+}
+
+abstract class Statement extends Node {
+}
+
+class Block extends Statement {
+  final List<Statement> statements;
+  Block(this.statements);
+  Block.empty() : this.statements = <Statement>[];
+
+  accept(NodeVisitor visitor) => visitor.visitBlock(this);
+  void visitChildren(NodeVisitor visitor) {
+    for (Statement statement in statements) statement.accept(visitor);
+  }
+}
+
+class ExpressionStatement extends Statement {
+  final Expression expression;
+  ExpressionStatement(this.expression);
+
+  accept(NodeVisitor visitor) => visitor.visitExpressionStatement(this);
+  void visitChildren(NodeVisitor visitor) { expression.accept(visitor); }
+}
+
+class EmptyStatement extends Statement {
+  EmptyStatement();
+
+  accept(NodeVisitor visitor) => visitor.visitEmptyStatement(this);
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class If extends Statement {
+  final Expression condition;
+  final Node then;
+  final Node otherwise;
+
+  If(this.condition, this.then, this.otherwise);
+  If.noElse(this.condition, this.then) : this.otherwise = new EmptyStatement();
+
+  bool get hasElse => otherwise is !EmptyStatement;
+
+  accept(NodeVisitor visitor) => visitor.visitIf(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    condition.accept(visitor);
+    then.accept(visitor);
+    otherwise.accept(visitor);
+  }
+}
+
+abstract class Loop extends Statement {
+  final Statement body;
+  Loop(this.body);
+}
+
+class For extends Loop {
+  final Expression init;
+  final Expression condition;
+  final Expression update;
+
+  For(this.init, this.condition, this.update, Statement body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitFor(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    if (init != null) init.accept(visitor);
+    if (condition != null) condition.accept(visitor);
+    if (update != null) update.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class ForIn extends Loop {
+  // Note that [VariableDeclarationList] is a subclass of [Expression].
+  // Therefore we can type the leftHandSide as [Expression].
+  final Expression leftHandSide;
+  final Expression object;
+
+  ForIn(this.leftHandSide, this.object, Statement body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitForIn(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    leftHandSide.accept(visitor);
+    object.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class While extends Loop {
+  final Node condition;
+
+  While(this.condition, Statement body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitWhile(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    condition.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class Do extends Loop {
+  final Expression condition;
+
+  Do(Statement body, this.condition) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitDo(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+    condition.accept(visitor);
+  }
+}
+
+class Continue extends Statement {
+  final String targetLabel;  // Can be null.
+
+  Continue(this.targetLabel);
+
+  accept(NodeVisitor visitor) => visitor.visitContinue(this);
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class Break extends Statement {
+  final String targetLabel;  // Can be null.
+
+  Break(this.targetLabel);
+
+  accept(NodeVisitor visitor) => visitor.visitBreak(this);
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class Return extends Statement {
+  final Expression value;  // Can be null.
+
+  Return([this.value = null]);
+
+  accept(NodeVisitor visitor) => visitor.visitReturn(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    if (value != null) value.accept(visitor);
+  }
+}
+
+class Throw extends Statement {
+  final Expression expression;
+
+  Throw(this.expression);
+
+  accept(NodeVisitor visitor) => visitor.visitThrow(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    expression.accept(visitor);
+  }
+}
+
+class Try extends Statement {
+  final Block body;
+  final Catch catchPart;  // Can be null if [finallyPart] is non-null.
+  final Block finallyPart;  // Can be null if [catchPart] is non-null.
+
+  Try(this.body, this.catchPart, this.finallyPart) {
+    assert(catchPart != null || finallyPart != null);
+  }
+
+  accept(NodeVisitor visitor) => visitor.visitTry(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+    if (catchPart != null) catchPart.accept(visitor);
+    if (finallyPart != null) finallyPart.accept(visitor);
+  }
+}
+
+class Catch extends Node {
+  final VariableDeclaration declaration;
+  final Block body;
+
+  Catch(this.declaration, this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitCatch(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    declaration.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class Switch extends Statement {
+  final Expression key;
+  final List<SwitchClause> cases;
+
+  Switch(this.key, this.cases);
+
+  accept(NodeVisitor visitor) => visitor.visitSwitch(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    key.accept(visitor);
+    for (SwitchClause clause in cases) clause.accept(visitor);
+  }
+}
+
+abstract class SwitchClause extends Node {
+  final Block body;
+
+  SwitchClause(this.body);
+}
+
+class Case extends SwitchClause {
+  final Expression expression;
+
+  Case(this.expression, Block body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitCase(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    expression.accept(visitor);
+    body.accept(visitor);
+  }
+}
+
+class Default extends SwitchClause {
+  Default(Block body) : super(body);
+
+  accept(NodeVisitor visitor) => visitor.visitDefault(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+  }
+}
+
+class FunctionDeclaration extends Statement {
+  final VariableDeclaration name;
+  final Fun function;
+
+  FunctionDeclaration(this.name, this.function);
+
+  accept(NodeVisitor visitor) => visitor.visitFunctionDeclaration(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    name.accept(visitor);
+    function.accept(visitor);
+  }
+}
+
+class LabeledStatement extends Statement {
+  final String label;
+  final Statement body;
+
+  LabeledStatement(this.label, this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitLabeledStatement(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    body.accept(visitor);
+  }
+}
+
+class LiteralStatement extends Statement {
+  final String code;
+
+  LiteralStatement(this.code);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralStatement(this);
+  void visitChildren(NodeVisitor visitor) { }
+}
+
+abstract class Expression extends Node {
+  int get precedenceLevel;
+
+  PropertyAccess dot(String name) => new PropertyAccess.field(this, name);
+  Call callWith(List<Expression> arguments) => new Call(this, arguments);
+}
+
+class LiteralExpression extends Expression {
+  final String template;
+  final List<Expression> inputs;
+
+  LiteralExpression(this.template) : inputs = const [];
+  LiteralExpression.withData(this.template, this.inputs);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralExpression(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Expression expr in inputs) expr.accept(visitor);
+  }
+
+  // Code that uses JS must take care of operator precedences, and
+  // put parenthesis if needed.
+  int get precedenceLevel => PRIMARY;
+}
+
+/**
+ * [VariableDeclarationList] is a subclass of [Expression] to simplify the
+ * AST.
+ */
+class VariableDeclarationList extends Expression {
+  final List<VariableInitialization> declarations;
+
+  VariableDeclarationList(this.declarations);
+
+  accept(NodeVisitor visitor) => visitor.visitVariableDeclarationList(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (VariableInitialization declaration in declarations) {
+      declaration.accept(visitor);
+    }
+  }
+
+  int get precedenceLevel => EXPRESSION;
+}
+
+class Sequence extends Expression {
+  final List<Expression> expressions;
+
+  Sequence(this.expressions);
+
+  accept(NodeVisitor visitor) => visitor.visitSequence(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Expression expr in expressions) expr.accept(visitor);
+  }
+
+  int get precedenceLevel => EXPRESSION;
+}
+
+class Assignment extends Expression {
+  final Expression leftHandSide;
+  // Null, if the assignment is not compound.
+  final VariableReference compoundTarget;
+  final Expression value;  // May be null, for [VariableInitialization]s.
+
+  Assignment(this.leftHandSide, this.value) : compoundTarget = null;
+  Assignment.compound(this.leftHandSide, String op, this.value)
+      : compoundTarget = new VariableUse(op);
+
+  int get precedenceLevel => ASSIGNMENT;
+
+  bool get isCompound => compoundTarget != null;
+  String get op => compoundTarget == null ? null : compoundTarget.name;
+
+  accept(NodeVisitor visitor) => visitor.visitAssignment(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    leftHandSide.accept(visitor);
+    if (compoundTarget != null) compoundTarget.accept(visitor);
+    if (value != null) value.accept(visitor);
+  }
+}
+
+class VariableInitialization extends Assignment {
+  /** [value] may be null. */
+  VariableInitialization(VariableDeclaration declaration, Expression value)
+      : super(declaration, value);
+
+  VariableDeclaration get declaration => leftHandSide;
+
+  accept(NodeVisitor visitor) => visitor.visitVariableInitialization(this);
+}
+
+class Conditional extends Expression {
+  final Expression condition;
+  final Expression then;
+  final Expression otherwise;
+
+  Conditional(this.condition, this.then, this.otherwise);
+
+  accept(NodeVisitor visitor) => visitor.visitConditional(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    condition.accept(visitor);
+    then.accept(visitor);
+    otherwise.accept(visitor);
+  }
+
+  int get precedenceLevel => ASSIGNMENT;
+}
+
+class Call extends Expression {
+  Expression target;
+  List<Expression> arguments;
+
+  Call(this.target, this.arguments);
+
+  accept(NodeVisitor visitor) => visitor.visitCall(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    target.accept(visitor);
+    for (Expression arg in arguments) arg.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+class New extends Call {
+  New(Expression cls, List<Expression> arguments) : super(cls, arguments);
+
+  accept(NodeVisitor visitor) => visitor.visitNew(this);
+}
+
+class Binary extends Call {
+  Binary(String op, Expression left, Expression right)
+      : super(new VariableUse(op), <Expression>[left, right]);
+
+  String get op {
+    VariableUse use = target;
+    return use.name;
+  }
+
+  Expression get left => arguments[0];
+  Expression get right => arguments[1];
+
+  accept(NodeVisitor visitor) => visitor.visitBinary(this);
+
+  int get precedenceLevel {
+    // TODO(floitsch): switch to constant map.
+    switch (op) {
+      case "*":
+      case "/":
+      case "%":
+        return MULTIPLICATIVE;
+      case "+":
+      case "-":
+        return ADDITIVE;
+      case "<<":
+      case ">>":
+      case ">>>":
+        return SHIFT;
+      case "<":
+      case ">":
+      case "<=":
+      case ">=":
+      case "instanceof":
+      case "in":
+        return RELATIONAL;
+      case "==":
+      case "===":
+      case "!=":
+      case "!==":
+        return EQUALITY;
+      case "&":
+        return BIT_AND;
+      case "^":
+        return BIT_XOR;
+      case "|":
+        return BIT_OR;
+      case "&&":
+        return LOGICAL_AND;
+      case "||":
+        return LOGICAL_OR;
+      default:
+        throw new leg.CompilerCancelledException(
+            "Internal Error: Unhandled binary operator: $op");
+    }
+  }
+}
+
+class Prefix extends Call {
+  Prefix(String op, Expression arg)
+      : super(new VariableUse(op), <Expression>[arg]);
+
+  String get op => (target as VariableUse).name;
+  Expression get argument => arguments[0];
+
+  accept(NodeVisitor visitor) => visitor.visitPrefix(this);
+
+  int get precedenceLevel => UNARY;
+}
+
+class Postfix extends Call {
+  Postfix(String op, Expression arg)
+      : super(new VariableUse(op), <Expression>[arg]);
+
+  String get op => (target as VariableUse).name;
+  Expression get argument => arguments[0];
+
+  accept(NodeVisitor visitor) => visitor.visitPostfix(this);
+
+  int get precedenceLevel => UNARY;
+}
+
+abstract class VariableReference extends Expression {
+  final String name;
+
+  // We treat operators as if they were special functions. They can thus be
+  // referenced like other variables.
+  VariableReference(this.name);
+
+  accept(NodeVisitor visitor);
+  int get precedenceLevel => PRIMARY;
+  void visitChildren(NodeVisitor visitor) {}
+}
+
+class VariableUse extends VariableReference {
+  VariableUse(String name) : super(name);
+
+  accept(NodeVisitor visitor) => visitor.visitVariableUse(this);
+
+  VariableUse asVariableUse() => this;
+}
+
+class VariableDeclaration extends VariableReference {
+  VariableDeclaration(String name) : super(name);
+
+  accept(NodeVisitor visitor) => visitor.visitVariableDeclaration(this);
+}
+
+class Parameter extends VariableDeclaration {
+  Parameter(String id) : super(id);
+
+  accept(NodeVisitor visitor) => visitor.visitParameter(this);
+}
+
+class This extends Parameter {
+  This() : super("this");
+
+  accept(NodeVisitor visitor) => visitor.visitThis(this);
+}
+
+class NamedFunction extends Expression {
+  final VariableDeclaration name;
+  final Fun function;
+
+  NamedFunction(this.name, this.function);
+
+  accept(NodeVisitor visitor) => visitor.visitNamedFunction(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    name.accept(visitor);
+    function.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+class Fun extends Expression {
+  final List<Parameter> params;
+  final Block body;
+
+  Fun(this.params, this.body);
+
+  accept(NodeVisitor visitor) => visitor.visitFun(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Parameter param in params) param.accept(visitor);
+    body.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+class PropertyAccess extends Expression {
+  final Expression receiver;
+  final Expression selector;
+
+  PropertyAccess(this.receiver, this.selector);
+  PropertyAccess.field(this.receiver, String fieldName)
+      : selector = new LiteralString("'$fieldName'");
+  PropertyAccess.indexed(this.receiver, int index)
+      : selector = new LiteralNumber('$index');
+
+  accept(NodeVisitor visitor) => visitor.visitAccess(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    receiver.accept(visitor);
+    selector.accept(visitor);
+  }
+
+  int get precedenceLevel => CALL;
+}
+
+abstract class Literal extends Expression {
+  void visitChildren(NodeVisitor visitor) {}
+
+  int get precedenceLevel => PRIMARY;
+}
+
+class LiteralBool extends Literal {
+  final bool value;
+
+  LiteralBool(this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralBool(this);
+  // [visitChildren] inherited from [Literal].
+}
+
+class LiteralNull extends Literal {
+  LiteralNull();
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralNull(this);
+}
+
+class LiteralString extends Literal {
+  final String value;
+
+  LiteralString(this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralString(this);
+}
+
+class LiteralNumber extends Literal {
+  final String value;
+
+  LiteralNumber(this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitLiteralNumber(this);
+}
+
+class ArrayInitializer extends Expression {
+  final int length;
+  // We represent the array as sparse list of elements. Each element knows its
+  // position in the array.
+  final List<ArrayElement> elements;
+
+  ArrayInitializer(this.length, this.elements);
+
+  factory ArrayInitializer.from(Iterable<Expression> expressions) =>
+      new ArrayInitializer(expressions.length, _convert(expressions));
+
+  accept(NodeVisitor visitor) => visitor.visitArrayInitializer(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (ArrayElement element in elements) element.accept(visitor);
+  }
+
+  int get precedenceLevel => PRIMARY;
+
+  static List<ArrayElement> _convert(Iterable<Expression> expressions) {
+    int index = 0;
+    return expressions.map(
+        (expression) => new ArrayElement(index++, expression))
+        .toList();
+  }
+}
+
+/**
+ * An expression inside an [ArrayInitialization]. An [ArrayElement] knows
+ * its position in the containing [ArrayInitialization].
+ */
+class ArrayElement extends Node {
+  int index;
+  Expression value;
+
+  ArrayElement(this.index, this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitArrayElement(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    value.accept(visitor);
+  }
+}
+
+class ObjectInitializer extends Expression {
+  List<Property> properties;
+
+  ObjectInitializer(this.properties);
+
+  accept(NodeVisitor visitor) => visitor.visitObjectInitializer(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    for (Property init in properties) init.accept(visitor);
+  }
+
+  int get precedenceLevel => PRIMARY;
+}
+
+class Property extends Node {
+  Literal name;
+  Expression value;
+
+  Property(this.name, this.value);
+
+  accept(NodeVisitor visitor) => visitor.visitProperty(this);
+
+  void visitChildren(NodeVisitor visitor) {
+    name.accept(visitor);
+    value.accept(visitor);
+  }
+}
+
+/**
+ * [RegExpLiteral]s, despite being called "Literal", are not inheriting from
+ * [Literal]. Indeed, regular expressions in JavaScript have a side-effect and
+ * are thus not in the same category as numbers or strings.
+ */
+class RegExpLiteral extends Expression {
+  /** Contains the pattern and the flags.*/
+  String pattern;
+
+  RegExpLiteral(this.pattern);
+
+  accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this);
+  void visitChildren(NodeVisitor visitor) {}
+
+  int get precedenceLevel => PRIMARY;
+}
+
+Prefix typeOf(Expression argument) => new Prefix('typeof', argument);
+
+Binary equals(Expression left, Expression right) {
+  return new Binary('==', left, right);
+}
+
+Binary strictEquals(Expression left, Expression right) {
+  return new Binary('===', left, right);
+}
+
+LiteralString string(String value) => new LiteralString('"$value"');
+
+If if_(Expression condition, Node then, [Node otherwise]) {
+  return (otherwise == null)
+      ? new If.noElse(condition, then)
+      : new If(condition, then, otherwise);
+}
+
+Return return_([Expression value]) => new Return(value);
+
+VariableUse use(String name) => new VariableUse(name);
+
+PropertyAccess fieldAccess(Expression receiver, String fieldName) {
+  return new PropertyAccess.field(receiver, fieldName);
+}
+
+Block emptyBlock() => new Block.empty();
+
+Block block1(Statement statement) => new Block(<Statement>[statement]);
+
+Block block2(Statement s1, Statement s2) => new Block(<Statement>[s1, s2]);
+
+Call call(Expression target, List<Expression> arguments) {
+  return new Call(target, arguments);
+}
+
+Fun fun(List<String> parameterNames, Block body) {
+  return new Fun(parameterNames.map((n) => new Parameter(n)).toList(), body);
+}
+
+Assignment assign(Expression leftHandSide, Expression value) {
+  return new Assignment(leftHandSide, value);
+}
+
+Expression undefined() => new Prefix('void', new LiteralNumber('0'));
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart
new file mode 100644
index 0000000..6d66f1f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart
@@ -0,0 +1,25 @@
+// 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.
+
+library precedence;
+
+const EXPRESSION = 0;
+const ASSIGNMENT = EXPRESSION + 1;
+const LOGICAL_OR = ASSIGNMENT + 1;
+const LOGICAL_AND = LOGICAL_OR + 1;
+const BIT_OR = LOGICAL_AND + 1;
+const BIT_XOR = BIT_OR + 1;
+const BIT_AND = BIT_XOR + 1;
+const EQUALITY = BIT_AND + 1;
+const RELATIONAL = EQUALITY + 1;
+const SHIFT = RELATIONAL + 1;
+const ADDITIVE = SHIFT + 1;
+const MULTIPLICATIVE = ADDITIVE + 1;
+const UNARY = MULTIPLICATIVE + 1;
+const LEFT_HAND_SIDE = UNARY + 1;
+// We merge new, call and member expressions.
+// This means that we have to emit parenthesis for 'new's. For example `new X;`
+// should be printed as `new X();`. This simplifies the requirements.
+const CALL = LEFT_HAND_SIDE;
+const PRIMARY = CALL + 1;
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart
new file mode 100644
index 0000000..504933d
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart
@@ -0,0 +1,1111 @@
+// 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.
+
+part of js;
+
+class Printer implements NodeVisitor {
+  final bool shouldCompressOutput;
+  leg.Compiler compiler;
+  leg.CodeBuffer outBuffer;
+  int indentLevel = 0;
+  bool inForInit = false;
+  bool atStatementBegin = false;
+  final DanglingElseVisitor danglingElseVisitor;
+  final LocalNamer localNamer;
+  bool pendingSemicolon = false;
+  bool pendingSpace = false;
+  static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]');
+  static final expressionContinuationRegExp = new RegExp(r'^[-+([]');
+
+  Printer(leg.Compiler compiler, { allowVariableMinification: true })
+      : shouldCompressOutput = compiler.enableMinification,
+        this.compiler = compiler,
+        outBuffer = new leg.CodeBuffer(),
+        danglingElseVisitor = new DanglingElseVisitor(compiler),
+        localNamer = determineRenamer(compiler.enableMinification,
+                                      allowVariableMinification);
+
+  static LocalNamer determineRenamer(bool shouldCompressOutput,
+                                     bool allowVariableMinification) {
+    return (shouldCompressOutput && allowVariableMinification)
+        ? new MinifyRenamer() : new IdentityNamer();
+  }
+
+  /// Always emit a newline, even under `enableMinification`.
+  void forceLine() {
+    out("\n");
+  }
+  /// Emits a newline for readability.
+  void lineOut() {
+    if (!shouldCompressOutput) forceLine();
+  }
+  void spaceOut() {
+    if (!shouldCompressOutput) out(" ");
+  }
+
+  String lastAddedString = null;
+  int get lastCharCode {
+    if (lastAddedString == null) return 0;
+    assert(lastAddedString.length != "");
+    return lastAddedString.charCodeAt(lastAddedString.length - 1);
+  }
+
+  void out(String str) {
+    if (str != "") {
+      if (pendingSemicolon) {
+        if (!shouldCompressOutput) {
+          outBuffer.add(";");
+        } else if (str != "}") {
+          // We want to output newline instead of semicolon because it makes
+          // the raw stack traces much easier to read and it also makes line-
+          // based tools like diff work much better.  JavaScript will
+          // automatically insert the semicolon at the newline if it means a
+          // parsing error is avoided, so we can only do this trick if the
+          // next line is not something that can be glued onto a valid
+          // expression to make a new valid expression.
+          if (expressionContinuationRegExp.hasMatch(str)) {
+            outBuffer.add(";");
+          } else {
+            outBuffer.add("\n");
+          }
+        }
+      }
+      if (pendingSpace &&
+          (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) {
+        outBuffer.add(" ");
+      }
+      pendingSpace = false;
+      pendingSemicolon = false;
+      outBuffer.add(str);
+      lastAddedString = str;
+    }
+  }
+
+  void outLn(String str) {
+    out(str);
+    lineOut();
+  }
+
+  void outSemicolonLn() {
+    if (shouldCompressOutput) {
+      pendingSemicolon = true;
+    } else {
+      out(";");
+      forceLine();
+    }
+  }
+
+  void outIndent(String str) { indent(); out(str); }
+  void outIndentLn(String str) { indent(); outLn(str); }
+  void indent() {
+    if (!shouldCompressOutput) {
+      for (int i = 0; i < indentLevel; i++) out("  ");
+    }
+  }
+
+  void recordSourcePosition(var position) {
+    if (position != null) {
+      outBuffer.setSourceLocation(position);
+    }
+  }
+
+  visit(Node node) {
+    if (node.sourcePosition != null) outBuffer.beginMappedRange();
+    recordSourcePosition(node.sourcePosition);
+    node.accept(this);
+    recordSourcePosition(node.endSourcePosition);
+    if (node.sourcePosition != null) outBuffer.endMappedRange();
+  }
+
+  visitCommaSeparated(List<Node> nodes, int hasRequiredType,
+                      {bool newInForInit, bool newAtStatementBegin}) {
+    for (int i = 0; i < nodes.length; i++) {
+      if (i != 0) {
+        atStatementBegin = false;
+        out(",");
+        spaceOut();
+      }
+      visitNestedExpression(nodes[i], hasRequiredType,
+                            newInForInit: newInForInit,
+                            newAtStatementBegin: newAtStatementBegin);
+    }
+  }
+
+  visitAll(List<Node> nodes) {
+    nodes.forEach(visit);
+  }
+
+  visitProgram(Program program) {
+    visitAll(program.body);
+  }
+
+  bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) {
+    if (body is Block) {
+      spaceOut();
+      blockOut(body, false, needsNewline);
+      return true;
+    }
+    if (shouldCompressOutput && needsSeparation) {
+      // If [shouldCompressOutput] is false, then the 'lineOut' will insert
+      // the separation.
+      out(" ");
+    } else {
+      lineOut();
+    }
+    indentLevel++;
+    visit(body);
+    indentLevel--;
+    return false;
+  }
+
+  void blockOutWithoutBraces(Node node) {
+    if (node is Block) {
+      node.statements.forEach(blockOutWithoutBraces);
+    } else {
+      visit(node);
+    }
+  }
+
+  void blockOut(Block node, bool shouldIndent, bool needsNewline) {
+    if (shouldIndent) indent();
+    out("{");
+    lineOut();
+    indentLevel++;
+    node.statements.forEach(blockOutWithoutBraces);
+    indentLevel--;
+    indent();
+    out("}");
+    if (needsNewline) lineOut();
+  }
+
+  visitBlock(Block block) {
+    blockOut(block, true, true);
+  }
+
+  visitExpressionStatement(ExpressionStatement expressionStatement) {
+    indent();
+    visitNestedExpression(expressionStatement.expression, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: true);
+    outSemicolonLn();
+  }
+
+  visitEmptyStatement(EmptyStatement nop) {
+    outIndentLn(";");
+  }
+
+  void ifOut(If node, bool shouldIndent) {
+    Node then = node.then;
+    Node elsePart = node.otherwise;
+    bool hasElse = node.hasElse;
+
+    // Handle dangling elses.
+    if (hasElse) {
+      bool needsBraces = node.then.accept(danglingElseVisitor);
+      if (needsBraces) {
+        then = new Block(<Statement>[then]);
+      }
+    }
+    if (shouldIndent) indent();
+    out("if");
+    spaceOut();
+    out("(");
+    visitNestedExpression(node.condition, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    bool thenWasBlock =
+        blockBody(then, needsSeparation: false, needsNewline: !hasElse);
+    if (hasElse) {
+      if (thenWasBlock) {
+        spaceOut();
+      } else {
+        indent();
+      }
+      out("else");
+      if (elsePart is If) {
+        pendingSpace = true;
+        ifOut(elsePart, false);
+      } else {
+        blockBody(elsePart, needsSeparation: true, needsNewline: true);
+      }
+    }
+  }
+
+  visitIf(If node) {
+    ifOut(node, true);
+  }
+
+  visitFor(For loop) {
+    outIndent("for");
+    spaceOut();
+    out("(");
+    if (loop.init != null) {
+      visitNestedExpression(loop.init, EXPRESSION,
+                            newInForInit: true, newAtStatementBegin: false);
+    }
+    out(";");
+    if (loop.condition != null) {
+      spaceOut();
+      visitNestedExpression(loop.condition, EXPRESSION,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    out(";");
+    if (loop.update != null) {
+      spaceOut();
+      visitNestedExpression(loop.update, EXPRESSION,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    out(")");
+    blockBody(loop.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitForIn(ForIn loop) {
+    outIndent("for");
+    spaceOut();
+    out("(");
+    visitNestedExpression(loop.leftHandSide, EXPRESSION,
+                          newInForInit: true, newAtStatementBegin: false);
+    out(" in");
+    pendingSpace = true;
+    visitNestedExpression(loop.object, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    blockBody(loop.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitWhile(While loop) {
+    outIndent("while");
+    spaceOut();
+    out("(");
+    visitNestedExpression(loop.condition, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    blockBody(loop.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitDo(Do loop) {
+    outIndent("do");
+    if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) {
+      spaceOut();
+    } else {
+      indent();
+    }
+    out("while");
+    spaceOut();
+    out("(");
+    visitNestedExpression(loop.condition, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    outSemicolonLn();
+  }
+
+  visitContinue(Continue node) {
+    if (node.targetLabel == null) {
+      outIndent("continue");
+    } else {
+      outIndent("continue ${node.targetLabel}");
+    }
+    outSemicolonLn();
+  }
+
+  visitBreak(Break node) {
+    if (node.targetLabel == null) {
+      outIndent("break");
+    } else {
+      outIndent("break ${node.targetLabel}");
+    }
+    outSemicolonLn();
+  }
+
+  visitReturn(Return node) {
+    if (node.value == null) {
+      outIndent("return");
+    } else {
+      outIndent("return");
+      pendingSpace = true;
+      visitNestedExpression(node.value, EXPRESSION,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    outSemicolonLn();
+  }
+
+  visitThrow(Throw node) {
+    outIndent("throw");
+    pendingSpace = true;
+    visitNestedExpression(node.expression, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    outSemicolonLn();
+  }
+
+  visitTry(Try node) {
+    outIndent("try");
+    blockBody(node.body, needsSeparation: true, needsNewline: false);
+    if (node.catchPart != null) {
+      visit(node.catchPart);
+    }
+    if (node.finallyPart != null) {
+      spaceOut();
+      out("finally");
+      blockBody(node.finallyPart, needsSeparation: true, needsNewline: true);
+    } else {
+      lineOut();
+    }
+  }
+
+  visitCatch(Catch node) {
+    spaceOut();
+    out("catch");
+    spaceOut();
+    out("(");
+    visitNestedExpression(node.declaration, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    blockBody(node.body, needsSeparation: false, needsNewline: true);
+  }
+
+  visitSwitch(Switch node) {
+    outIndent("switch");
+    spaceOut();
+    out("(");
+    visitNestedExpression(node.key, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out(")");
+    spaceOut();
+    outLn("{");
+    indentLevel++;
+    visitAll(node.cases);
+    indentLevel--;
+    outIndentLn("}");
+  }
+
+  visitCase(Case node) {
+    outIndent("case");
+    pendingSpace = true;
+    visitNestedExpression(node.expression, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    outLn(":");
+    if (!node.body.statements.isEmpty) {
+      indentLevel++;
+      blockOutWithoutBraces(node.body);
+      indentLevel--;
+    }
+  }
+
+  visitDefault(Default node) {
+    outIndentLn("default:");
+    if (!node.body.statements.isEmpty) {
+      indentLevel++;
+      blockOutWithoutBraces(node.body);
+      indentLevel--;
+    }
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    outIndent("${node.label}:");
+    blockBody(node.body, needsSeparation: false, needsNewline: true);
+  }
+
+  void functionOut(Fun fun, Node name, VarCollector vars) {
+    out("function");
+    if (name != null) {
+      out(" ");
+      // Name must be a [Decl]. Therefore only test for primary expressions.
+      visitNestedExpression(name, PRIMARY,
+                            newInForInit: false, newAtStatementBegin: false);
+    }
+    localNamer.enterScope(vars);
+    out("(");
+    if (fun.params != null) {
+      visitCommaSeparated(fun.params, PRIMARY,
+                          newInForInit: false, newAtStatementBegin: false);
+    }
+    out(")");
+    blockBody(fun.body, needsSeparation: false, needsNewline: false);
+    localNamer.leaveScope();
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration declaration) {
+    VarCollector vars = new VarCollector();
+    vars.visitFunctionDeclaration(declaration);
+    indent();
+    functionOut(declaration.function, declaration.name, vars);
+    lineOut();
+  }
+
+  visitNestedExpression(Expression node, int requiredPrecedence,
+                        {bool newInForInit, bool newAtStatementBegin}) {
+    bool needsParentheses =
+        // a - (b + c).
+        (requiredPrecedence != EXPRESSION &&
+         node.precedenceLevel < requiredPrecedence) ||
+        // for (a = (x in o); ... ; ... ) { ... }
+        (newInForInit && node is Binary && (node as Binary).op == "in") ||
+        // (function() { ... })().
+        // ({a: 2, b: 3}.toString()).
+        (newAtStatementBegin && (node is NamedFunction ||
+                                 node is Fun ||
+                                 node is ObjectInitializer));
+    if (needsParentheses) {
+      inForInit = false;
+      atStatementBegin = false;
+      out("(");
+      visit(node);
+      out(")");
+    } else {
+      inForInit = newInForInit;
+      atStatementBegin = newAtStatementBegin;
+      visit(node);
+    }
+  }
+
+  visitVariableDeclarationList(VariableDeclarationList list) {
+    out("var ");
+    visitCommaSeparated(list.declarations, ASSIGNMENT,
+                        newInForInit: inForInit, newAtStatementBegin: false);
+  }
+
+  visitSequence(Sequence sequence) {
+    // Note that we only require that the entries are expressions and not
+    // assignments. This means that nested sequences are not put into
+    // parenthesis.
+    visitCommaSeparated(sequence.expressions, EXPRESSION,
+                        newInForInit: false,
+                        newAtStatementBegin: atStatementBegin);
+  }
+
+  visitAssignment(Assignment assignment) {
+    visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    if (assignment.value != null) {
+      spaceOut();
+      String op = assignment.op;
+      if (op != null) out(op);
+      out("=");
+      spaceOut();
+      visitNestedExpression(assignment.value, ASSIGNMENT,
+                            newInForInit: inForInit,
+                            newAtStatementBegin: false);
+    }
+  }
+
+  visitVariableInitialization(VariableInitialization initialization) {
+    visitAssignment(initialization);
+  }
+
+  visitConditional(Conditional cond) {
+    visitNestedExpression(cond.condition, LOGICAL_OR,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    spaceOut();
+    out("?");
+    spaceOut();
+    // The then part is allowed to have an 'in'.
+    visitNestedExpression(cond.then, ASSIGNMENT,
+                          newInForInit: false, newAtStatementBegin: false);
+    spaceOut();
+    out(":");
+    spaceOut();
+    visitNestedExpression(cond.otherwise, ASSIGNMENT,
+                          newInForInit: inForInit, newAtStatementBegin: false);
+  }
+
+  visitNew(New node) {
+    out("new ");
+    visitNestedExpression(node.target, CALL,
+                          newInForInit: inForInit, newAtStatementBegin: false);
+    out("(");
+    visitCommaSeparated(node.arguments, ASSIGNMENT,
+                        newInForInit: false, newAtStatementBegin: false);
+    out(")");
+  }
+
+  visitCall(Call call) {
+    visitNestedExpression(call.target, LEFT_HAND_SIDE,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    out("(");
+    visitCommaSeparated(call.arguments, ASSIGNMENT,
+                        newInForInit: false, newAtStatementBegin: false);
+    out(")");
+  }
+
+  visitBinary(Binary binary) {
+    Expression left = binary.left;
+    Expression right = binary.right;
+    String op = binary.op;
+    int leftPrecedenceRequirement;
+    int rightPrecedenceRequirement;
+    switch (op) {
+      case "||":
+        leftPrecedenceRequirement = LOGICAL_OR;
+        // x || (y || z) <=> (x || y) || z.
+        rightPrecedenceRequirement = LOGICAL_OR;
+        break;
+      case "&&":
+        leftPrecedenceRequirement = LOGICAL_AND;
+        // x && (y && z) <=> (x && y) && z.
+        rightPrecedenceRequirement = LOGICAL_AND;
+        break;
+      case "|":
+        leftPrecedenceRequirement = BIT_OR;
+        // x | (y | z) <=> (x | y) | z.
+        rightPrecedenceRequirement = BIT_OR;
+        break;
+      case "^":
+        leftPrecedenceRequirement = BIT_XOR;
+        // x ^ (y ^ z) <=> (x ^ y) ^ z.
+        rightPrecedenceRequirement = BIT_XOR;
+        break;
+      case "&":
+        leftPrecedenceRequirement = BIT_AND;
+        // x & (y & z) <=> (x & y) & z.
+        rightPrecedenceRequirement = BIT_AND;
+        break;
+      case "==":
+      case "!=":
+      case "===":
+      case "!==":
+        leftPrecedenceRequirement = EQUALITY;
+        rightPrecedenceRequirement = RELATIONAL;
+        break;
+      case "<":
+      case ">":
+      case "<=":
+      case ">=":
+      case "instanceof":
+      case "in":
+        leftPrecedenceRequirement = RELATIONAL;
+        rightPrecedenceRequirement = SHIFT;
+        break;
+      case ">>":
+      case "<<":
+      case ">>>":
+        leftPrecedenceRequirement = SHIFT;
+        rightPrecedenceRequirement = ADDITIVE;
+        break;
+      case "+":
+      case "-":
+        leftPrecedenceRequirement = ADDITIVE;
+        // We cannot remove parenthesis for "+" because
+        //   x + (y + z) <!=> (x + y) + z:
+        // Example:
+        //   "a" + (1 + 2) => "a3";
+        //   ("a" + 1) + 2 => "a12";
+        rightPrecedenceRequirement = MULTIPLICATIVE;
+        break;
+      case "*":
+      case "/":
+      case "%":
+        leftPrecedenceRequirement = MULTIPLICATIVE;
+        // We cannot remove parenthesis for "*" because of precision issues.
+        rightPrecedenceRequirement = UNARY;
+        break;
+      default:
+        compiler.internalError("Forgot operator: $op");
+    }
+
+    visitNestedExpression(left, leftPrecedenceRequirement,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+
+    if (op == "in" || op == "instanceof") {
+      // There are cases where the space is not required but without further
+      // analysis we cannot know.
+      out(" ");
+      out(op);
+      out(" ");
+    } else {
+      spaceOut();
+      out(op);
+      spaceOut();
+    }
+    visitNestedExpression(right, rightPrecedenceRequirement,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: false);
+  }
+
+  visitPrefix(Prefix unary) {
+    String op = unary.op;
+    switch (op) {
+      case "delete":
+      case "void":
+      case "typeof":
+        // There are cases where the space is not required but without further
+        // analysis we cannot know.
+        out(op);
+        out(" ");
+        break;
+      case "+":
+      case "++":
+        if (lastCharCode == charCodes.$PLUS) out(" ");
+        out(op);
+        break;
+      case "-":
+      case "--":
+        if (lastCharCode == charCodes.$MINUS) out(" ");
+        out(op);
+        break;
+      default:
+        out(op);
+    }
+    visitNestedExpression(unary.argument, UNARY,
+                          newInForInit: inForInit, newAtStatementBegin: false);
+  }
+
+  visitPostfix(Postfix postfix) {
+    visitNestedExpression(postfix.argument, LEFT_HAND_SIDE,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    out(postfix.op);
+  }
+
+  visitVariableUse(VariableUse ref) {
+    out(localNamer.getName(ref.name));
+  }
+
+  visitThis(This node) {
+    out("this");
+  }
+
+  visitVariableDeclaration(VariableDeclaration decl) {
+    out(localNamer.getName(decl.name));
+  }
+
+  visitParameter(Parameter param) {
+    out(localNamer.getName(param.name));
+  }
+
+  bool isDigit(int charCode) {
+    return charCodes.$0 <= charCode && charCode <= charCodes.$9;
+  }
+
+  bool isValidJavaScriptId(String field) {
+    if (field.length < 3) return false;
+    // Ignore the leading and trailing string-delimiter.
+    for (int i = 1; i < field.length - 1; i++) {
+      // TODO(floitsch): allow more characters.
+      int charCode = field.charCodeAt(i);
+      if (!(charCodes.$a <= charCode && charCode <= charCodes.$z ||
+            charCodes.$A <= charCode && charCode <= charCodes.$Z ||
+            charCode == charCodes.$$ ||
+            charCode == charCodes.$_ ||
+            i != 1 && isDigit(charCode))) {
+        return false;
+      }
+    }
+    // TODO(floitsch): normally we should also check that the field is not a
+    // reserved word.  We don't generate fields with reserved word names except
+    // for 'super'.
+    if (field == '"super"') return false;
+    return true;
+  }
+
+  visitAccess(PropertyAccess access) {
+    visitNestedExpression(access.receiver, CALL,
+                          newInForInit: inForInit,
+                          newAtStatementBegin: atStatementBegin);
+    Node selector = access.selector;
+    if (selector is LiteralString) {
+      LiteralString selectorString = selector;
+      String fieldWithQuotes = selectorString.value;
+      if (isValidJavaScriptId(fieldWithQuotes)) {
+        if (access.receiver is LiteralNumber) out(" ");
+        out(".");
+        out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1));
+        return;
+      }
+    }
+    out("[");
+    visitNestedExpression(selector, EXPRESSION,
+                          newInForInit: false, newAtStatementBegin: false);
+    out("]");
+  }
+
+  visitNamedFunction(NamedFunction namedFunction) {
+    VarCollector vars = new VarCollector();
+    vars.visitNamedFunction(namedFunction);
+    functionOut(namedFunction.function, namedFunction.name, vars);
+  }
+
+  visitFun(Fun fun) {
+    VarCollector vars = new VarCollector();
+    vars.visitFun(fun);
+    functionOut(fun, null, vars);
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    out(node.value ? "true" : "false");
+  }
+
+  visitLiteralString(LiteralString node) {
+    out(node.value);
+  }
+
+  visitLiteralNumber(LiteralNumber node) {
+    int charCode = node.value.charCodeAt(0);
+    if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) {
+      out(" ");
+    }
+    out(node.value);
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    out("null");
+  }
+
+  visitArrayInitializer(ArrayInitializer node) {
+    out("[");
+    List<ArrayElement> elements = node.elements;
+    int elementIndex = 0;
+    for (int i = 0; i < node.length; i++) {
+      if (elementIndex < elements.length &&
+          elements[elementIndex].index == i) {
+        visitNestedExpression(elements[elementIndex].value, ASSIGNMENT,
+                              newInForInit: false, newAtStatementBegin: false);
+        elementIndex++;
+        // We can avoid a trailing "," if there was an element just before. So
+        // `[1]` and `[1,]` are the same, but `[,]` and `[]` are not.
+        if (i != node.length - 1) {
+          out(",");
+          spaceOut();
+        }
+      } else {
+        out(",");
+      }
+    }
+    out("]");
+  }
+
+  visitArrayElement(ArrayElement node) {
+    throw "Unreachable";
+  }
+
+  visitObjectInitializer(ObjectInitializer node) {
+    // Print all the properties on one line until we see a function-valued
+    // property.  Ideally, we would use a proper pretty-printer to make the
+    // decision based on layout.
+    bool onePerLine = false;
+    List<Property> properties = node.properties;
+    out("{");
+    ++indentLevel;
+    for (int i = 0; i < properties.length; i++) {
+      Expression value = properties[i].value;
+      if (value is Fun || value is NamedFunction) onePerLine = true;
+      if (i != 0) {
+        out(",");
+        if (!onePerLine) spaceOut();
+      }
+      if (onePerLine) {
+        forceLine();
+        indent();
+      }
+      visitProperty(properties[i]);
+    }
+    --indentLevel;
+    if (onePerLine) lineOut();
+    out("}");
+  }
+
+  visitProperty(Property node) {
+    if (node.name is LiteralString) {
+      LiteralString nameString = node.name;
+      String name = nameString.value;
+      if (isValidJavaScriptId(name)) {
+        out(name.substring(1, name.length - 1));
+      } else {
+        out(name);
+      }
+    } else {
+      assert(node.name is LiteralNumber);
+      LiteralNumber nameNumber = node.name;
+      out(nameNumber.value);
+    }
+    out(":");
+    spaceOut();
+    visitNestedExpression(node.value, ASSIGNMENT,
+                          newInForInit: false, newAtStatementBegin: false);
+  }
+
+  visitRegExpLiteral(RegExpLiteral node) {
+    out(node.pattern);
+  }
+
+  visitLiteralExpression(LiteralExpression node) {
+    String template = node.template;
+    List<Expression> inputs = node.inputs;
+
+    List<String> parts = template.split('#');
+    if (parts.length != inputs.length + 1) {
+      compiler.internalError('Wrong number of arguments for JS: $template');
+    }
+    // Code that uses JS must take care of operator precedences, and
+    // put parenthesis if needed.
+    out(parts[0]);
+    for (int i = 0; i < inputs.length; i++) {
+      visit(inputs[i]);
+      out(parts[i + 1]);
+    }
+  }
+
+  visitLiteralStatement(LiteralStatement node) {
+    outLn(node.code);
+  }
+}
+
+
+class OrderedSet<T> {
+  final Set<T> set;
+  final List<T> list;
+
+  OrderedSet() : set = new Set<T>(), list = <T>[];
+
+  void add(T x) {
+    if (!set.contains(x)) {
+      set.add(x);
+      list.add(x);
+    }
+  }
+
+  void forEach(void fun(T x)) {
+    list.forEach(fun);
+  }
+}
+
+// Collects all the var declarations in the function.  We need to do this in a
+// separate pass because JS vars are lifted to the top of the function.
+class VarCollector extends BaseVisitor {
+  bool nested;
+  final OrderedSet<String> vars;
+  final OrderedSet<String> params;
+
+  VarCollector() : nested = false,
+                   vars = new OrderedSet<String>(),
+                   params = new OrderedSet<String>();
+
+  void forEachVar(void fn(String v)) => vars.forEach(fn);
+  void forEachParam(void fn(String p)) => params.forEach(fn);
+
+  void collectVarsInFunction(Fun fun) {
+    if (!nested) {
+      nested = true;
+      if (fun.params != null) {
+        for (int i = 0; i < fun.params.length; i++) {
+          params.add(fun.params[i].name);
+        }
+      }
+      visitBlock(fun.body);
+      nested = false;
+    }
+  }
+
+  void visitFunctionDeclaration(FunctionDeclaration declaration) {
+    // Note that we don't bother collecting the name of the function.
+    collectVarsInFunction(declaration.function);
+  }
+
+  void visitNamedFunction(NamedFunction namedFunction) {
+    // Note that we don't bother collecting the name of the function.
+    collectVarsInFunction(namedFunction.function);
+  }
+
+  void visitFun(Fun fun) {
+    collectVarsInFunction(fun);
+  }
+
+  void visitThis(This node) {}
+
+  void visitVariableDeclaration(VariableDeclaration decl) {
+    vars.add(decl.name);
+  }
+}
+
+
+/**
+ * Returns true, if the given node must be wrapped into braces when used
+ * as then-statement in an [If] that has an else branch.
+ */
+class DanglingElseVisitor extends BaseVisitor<bool> {
+  leg.Compiler compiler;
+
+  DanglingElseVisitor(this.compiler);
+
+  bool visitProgram(Program node) => false;
+
+  bool visitNode(Statement node) {
+    compiler.internalError("Forgot node: $node");
+  }
+
+  bool visitBlock(Block node) => false;
+  bool visitExpressionStatement(ExpressionStatement node) => false;
+  bool visitEmptyStatement(EmptyStatement node) => false;
+  bool visitIf(If node) {
+    if (!node.hasElse) return true;
+    return node.otherwise.accept(this);
+  }
+  bool visitFor(For node) => node.body.accept(this);
+  bool visitForIn(ForIn node) => node.body.accept(this);
+  bool visitWhile(While node) => node.body.accept(this);
+  bool visitDo(Do node) => false;
+  bool visitContinue(Continue node) => false;
+  bool visitBreak(Break node) => false;
+  bool visitReturn(Return node) => false;
+  bool visitThrow(Throw node) => false;
+  bool visitTry(Try node) {
+    if (node.finallyPart != null) {
+      return node.finallyPart.accept(this);
+    } else {
+      return node.catchPart.accept(this);
+    }
+  }
+  bool visitCatch(Catch node) => node.body.accept(this);
+  bool visitSwitch(Switch node) => false;
+  bool visitCase(Case node) => false;
+  bool visitDefault(Default node) => false;
+  bool visitFunctionDeclaration(FunctionDeclaration node) => false;
+  bool visitLabeledStatement(LabeledStatement node)
+      => node.body.accept(this);
+  bool visitLiteralStatement(LiteralStatement node) => true;
+
+  bool visitExpression(Expression node) => false;
+}
+
+
+leg.CodeBuffer prettyPrint(Node node, leg.Compiler compiler,
+                           { allowVariableMinification: true }) {
+  Printer printer =
+      new Printer(compiler,
+                  allowVariableMinification: allowVariableMinification);
+  printer.visit(node);
+  return printer.outBuffer;
+}
+
+
+abstract class LocalNamer {
+  String getName(String oldName);
+  String declareVariable(String oldName);
+  String declareParameter(String oldName);
+  void enterScope(VarCollector vars);
+  void leaveScope();
+}
+
+
+class IdentityNamer implements LocalNamer {
+  String getName(String oldName) => oldName;
+  String declareVariable(String oldName) => oldName;
+  String declareParameter(String oldName) => oldName;
+  void enterScope(VarCollector vars) {}
+  void leaveScope() {}
+}
+
+
+class MinifyRenamer implements LocalNamer {
+  final List<Map<String, String>> maps = [];
+  final List<int> parameterNumberStack = [];
+  final List<int> variableNumberStack = [];
+  int parameterNumber = 0;
+  int variableNumber = 0;
+
+  MinifyRenamer();
+
+  void enterScope(VarCollector vars) {
+    maps.add(new Map<String, String>());
+    variableNumberStack.add(variableNumber);
+    parameterNumberStack.add(parameterNumber);
+    vars.forEachVar(declareVariable);
+    vars.forEachParam(declareParameter);
+  }
+
+  void leaveScope() {
+    maps.removeLast();
+    variableNumber = variableNumberStack.removeLast();
+    parameterNumber = parameterNumberStack.removeLast();
+  }
+
+  String getName(String oldName) {
+    // Go from inner scope to outer looking for mapping of name.
+    for (int i = maps.length - 1; i >= 0; i--) {
+      var map = maps[i];
+      var replacement = map[oldName];
+      if (replacement != null) return replacement;
+    }
+    return oldName;
+  }
+
+  static const LOWER_CASE_LETTERS = 26;
+  static const LETTERS = 52;
+  static const DIGITS = 10;
+
+  static int nthLetter(int n) {
+    return (n < LOWER_CASE_LETTERS) ?
+           charCodes.$a + n :
+           charCodes.$A + n - LOWER_CASE_LETTERS;
+  }
+
+  // Parameters go from a to z and variables go from z to a.  This makes each
+  // argument list and each top-of-function var declaration look similar and
+  // helps gzip compress the file.  If we have more than 26 arguments and
+  // variables then we meet somewhere in the middle of the alphabet.  After
+  // that we give up trying to be nice to the compression algorithm and just
+  // use the same namespace for arguments and variables, starting with A, and
+  // moving on to a0, a1, etc.
+  String declareVariable(String oldName) {
+    var newName;
+    if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) {
+      // Variables start from z and go backwards, for better gzipability.
+      newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber);
+    } else {
+      // After 26 variables and parameters we allocate them in the same order.
+      newName = getNameNumber(oldName, variableNumber + parameterNumber);
+    }
+    variableNumber++;
+    return newName;
+  }
+
+  String declareParameter(String oldName) {
+    var newName;
+    if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) {
+      newName = getNameNumber(oldName, parameterNumber);
+    } else {
+      newName = getNameNumber(oldName, variableNumber + parameterNumber);
+    }
+    parameterNumber++;
+    return newName;
+  }
+
+  String getNameNumber(String oldName, int n) {
+    if (maps.isEmpty) return oldName;
+
+    String newName;
+    if (n < LETTERS) {
+      // Start naming variables a, b, c, ..., z, A, B, C, ..., Z.
+      newName = new String.fromCharCodes([nthLetter(n)]);
+    } else {
+      // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ...
+      // For all functions with fewer than 500 locals this is just as compact
+      // as using aa, ab, etc. but avoids clashes with keywords.
+      n -= LETTERS;
+      int digit = n % DIGITS;
+      n ~/= DIGITS;
+      int alphaChars = 1;
+      int nameSpaceSize = LETTERS;
+      // Find out whether we should use the 1-character namespace (size 52), the
+      // 2-character namespace (size 52*52), etc.
+      while (n >= nameSpaceSize) {
+        n -= nameSpaceSize;
+        alphaChars++;
+        nameSpaceSize *= LETTERS;
+      }
+      var codes = <int>[];
+      for (var i = 0; i < alphaChars; i++) {
+        nameSpaceSize ~/= LETTERS;
+        codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS));
+      }
+      codes.add(charCodes.$0 + digit);
+      newName = new String.fromCharCodes(codes);
+    }
+    assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName));
+    maps.last[oldName] = newName;
+    return newName;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart
new file mode 100644
index 0000000..2a681d8
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart
@@ -0,0 +1,1262 @@
+// 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.
+
+part of js_backend;
+
+typedef void Recompile(Element element);
+
+class ReturnInfo {
+  HType returnType;
+  List<Element> compiledFunctions;
+
+  ReturnInfo(HType this.returnType)
+      : compiledFunctions = new List<Element>();
+
+  ReturnInfo.unknownType() : this(null);
+
+  void update(HType type, Recompile recompile, Compiler compiler) {
+    HType newType =
+        returnType != null ? returnType.union(type, compiler) : type;
+    if (newType != returnType) {
+      if (returnType == null && identical(newType, HType.UNKNOWN)) {
+        // If the first actual piece of information is not providing any type
+        // information there is no need to recompile callers.
+        compiledFunctions.clear();
+      }
+      returnType = newType;
+      if (recompile != null) {
+        compiledFunctions.forEach(recompile);
+      }
+      compiledFunctions.clear();
+    }
+  }
+
+  // Note that lazy initializers are treated like functions (but are not
+  // of type [FunctionElement].
+  addCompiledFunction(Element function) => compiledFunctions.add(function);
+}
+
+class OptionalParameterTypes {
+  final List<SourceString> names;
+  final List<HType> types;
+
+  OptionalParameterTypes(int optionalArgumentsCount)
+      : names = new List<SourceString>.fixedLength(optionalArgumentsCount),
+        types = new List<HType>.fixedLength(optionalArgumentsCount);
+
+  int get length => names.length;
+  SourceString name(int index) => names[index];
+  HType type(int index) => types[index];
+  int indexOf(SourceString name) => names.indexOf(name);
+
+  HType typeFor(SourceString name) {
+    int index = indexOf(name);
+    if (index == -1) return null;
+    return type(index);
+  }
+
+  void update(int index, SourceString name, HType type) {
+    names[index] = name;
+    types[index] = type;
+  }
+
+  String toString() => "OptionalParameterTypes($names, $types)";
+}
+
+class HTypeList {
+  final List<HType> types;
+  final List<SourceString> namedArguments;
+
+  HTypeList(int length)
+      : types = new List<HType>.fixedLength(length),
+        namedArguments = null;
+  HTypeList.withNamedArguments(int length, this.namedArguments)
+      : types = new List<HType>.fixedLength(length);
+  const HTypeList.withAllUnknown()
+      : types = null,
+        namedArguments = null;
+
+  factory HTypeList.fromStaticInvocation(HInvokeStatic node, HTypeMap types) {
+    bool allUnknown = true;
+    for (int i = 1; i < node.inputs.length; i++) {
+      if (types[node.inputs[i]] != HType.UNKNOWN) {
+        allUnknown = false;
+        break;
+      }
+    }
+    if (allUnknown) return HTypeList.ALL_UNKNOWN;
+
+    HTypeList result = new HTypeList(node.inputs.length - 1);
+    for (int i = 0; i < result.types.length; i++) {
+      result.types[i] = types[node.inputs[i + 1]];
+    }
+    return result;
+  }
+
+  factory HTypeList.fromDynamicInvocation(HInvokeDynamic node,
+                                          Selector selector,
+                                          HTypeMap types) {
+    HTypeList result;
+    int argumentsCount = node.inputs.length - 1;
+    int startInvokeIndex = HInvoke.ARGUMENTS_OFFSET;
+
+    if (node.isInterceptorCall) {
+      argumentsCount--;
+      startInvokeIndex++;
+    }
+
+    if (selector.namedArgumentCount > 0) {
+      result =
+          new HTypeList.withNamedArguments(
+              argumentsCount, selector.namedArguments);
+    } else {
+      result = new HTypeList(argumentsCount);
+    }
+
+    for (int i = 0; i < result.types.length; i++) {
+      result.types[i] = types[node.inputs[i + startInvokeIndex]];
+    }
+    return result;
+  }
+
+  static const HTypeList ALL_UNKNOWN = const HTypeList.withAllUnknown();
+
+  bool get allUnknown => types == null;
+  bool get hasNamedArguments => namedArguments != null;
+  int get length => types.length;
+  HType operator[](int index) => types[index];
+  void operator[]=(int index, HType type) { types[index] = type; }
+
+  HTypeList union(HTypeList other, Compiler compiler) {
+    if (allUnknown) return this;
+    if (other.allUnknown) return other;
+    if (length != other.length) return HTypeList.ALL_UNKNOWN;
+    bool onlyUnknown = true;
+    HTypeList result = this;
+    for (int i = 0; i < length; i++) {
+      HType newType = this[i].union(other[i], compiler);
+      if (result == this && newType != this[i]) {
+        // Create a new argument types object with the matching types copied.
+        result = new HTypeList(length);
+        result.types.setRange(0, i, this.types);
+      }
+      if (result != this) {
+        result.types[i] = newType;
+      }
+      if (result[i] != HType.UNKNOWN) onlyUnknown = false;
+    }
+    return onlyUnknown ? HTypeList.ALL_UNKNOWN : result;
+  }
+
+  HTypeList unionWithOptionalParameters(
+      Selector selector,
+      FunctionSignature signature,
+      OptionalParameterTypes defaultValueTypes) {
+    assert(allUnknown || selector.argumentCount == this.length);
+    // Create a new HTypeList for holding types for all parameters.
+    HTypeList result = new HTypeList(signature.parameterCount);
+
+    // First fill in the type of the positional arguments.
+    int nextTypeIndex = -1;
+    if (allUnknown) {
+      for (int i = 0; i < selector.positionalArgumentCount; i++) {
+        result.types[i] = HType.UNKNOWN;
+      }
+    } else {
+      result.types.setRange(0, selector.positionalArgumentCount, this.types);
+      nextTypeIndex = selector.positionalArgumentCount;
+    }
+
+    // Next fill the type of the optional arguments.
+    // As the selector can pass optional arguments positionally some of the
+    // optional arguments might already have a type set. We only need to look
+    // at the optional arguments not passed positionally.
+    // The variable 'index' is counting the signatures optional arguments, the
+    // variable 'next' is set to the next optional arguments to look at and
+    // is used to skip some optional arguments.
+    int next = selector.positionalArgumentCount;
+    int index = signature.requiredParameterCount;
+    signature.forEachOptionalParameter((Element element) {
+      // If some optional parameters were passed positionally these have
+      // already been filled.
+      if (index == next) {
+        assert(result.types[index] == null);
+        HType type = null;
+        if (hasNamedArguments &&
+            selector.namedArguments.indexOf(element.name) >= 0) {
+          type = types[nextTypeIndex++];
+        } else {
+          type = defaultValueTypes.typeFor(element.name);
+        }
+        result.types[index] = type;
+        next++;
+      }
+      index++;
+    });
+    return result;
+  }
+
+  String toString() =>
+      allUnknown ? "HTypeList.ALL_UNKNOWN" : "HTypeList $types";
+}
+
+class FieldTypesRegistry {
+  final JavaScriptBackend backend;
+
+  /**
+   * For each class, [constructors] holds the set of constructors. If there is
+   * more than one constructor for a class it is currently not possible to
+   * infer the field types from construction, as the information collected does
+   * not correlate the generative constructors and generative constructor
+   * body/bodies.
+   */
+  final Map<ClassElement, Set<Element>> constructors;
+
+  /**
+   * The collected type information is stored in three maps. One for types
+   * assigned in the initializer list(s) [fieldInitializerTypeMap], one for
+   * types assigned in the constructor(s) [fieldConstructorTypeMap], and one
+   * for types assigned in the rest of the code, where the field can be
+   * resolved [fieldTypeMap].
+   *
+   * If a field has a type both from constructors and from the initializer
+   * list(s), then the type from the constructor(s) will owerride the one from
+   * the initializer list(s).
+   *
+   * Because the order in which generative constructors, generative constructor
+   * bodies and normal method/function bodies are compiled is undefined, and
+   * because they can all be recompiled, it is not possible to combine this
+   * information into one map at the moment.
+   */
+  final Map<Element, HType> fieldInitializerTypeMap;
+  final Map<Element, HType> fieldConstructorTypeMap;
+  final Map<Element, HType> fieldTypeMap;
+
+  /**
+   * The set of current names setter selectors used. If a named selector is
+   * used it is currently not possible to infer the type of the field.
+   */
+  final Set<SourceString> setterSelectorsUsed;
+
+  final Map<Element, Set<Element>> optimizedStaticFunctions;
+  final Map<Element, FunctionSet> optimizedFunctions;
+
+  FieldTypesRegistry(JavaScriptBackend backend)
+      : constructors =  new Map<ClassElement, Set<Element>>(),
+        fieldInitializerTypeMap = new Map<Element, HType>(),
+        fieldConstructorTypeMap = new Map<Element, HType>(),
+        fieldTypeMap = new Map<Element, HType>(),
+        setterSelectorsUsed = new Set<SourceString>(),
+        optimizedStaticFunctions = new Map<Element, Set<Element>>(),
+        optimizedFunctions = new Map<Element, FunctionSet>(),
+        this.backend = backend;
+
+  Compiler get compiler => backend.compiler;
+
+  void scheduleRecompilation(Element field) {
+    Set optimizedStatics = optimizedStaticFunctions[field];
+    if (optimizedStatics != null) {
+      optimizedStatics.forEach(backend.scheduleForRecompilation);
+      optimizedStaticFunctions.remove(field);
+    }
+    FunctionSet optimized = optimizedFunctions[field];
+    if (optimized != null) {
+      optimized.forEach(backend.scheduleForRecompilation);
+      optimizedFunctions.remove(field);
+    }
+  }
+
+  int constructorCount(Element element) {
+    assert(element.isClass());
+    Set<Element> ctors = constructors[element];
+    return ctors == null ? 0 : ctors.length;
+  }
+
+  void registerFieldType(Map<Element, HType> typeMap,
+                         Element field,
+                         HType type) {
+    assert(field.isField());
+    HType before = optimisticFieldType(field);
+
+    HType oldType = typeMap[field];
+    HType newType;
+
+    if (oldType != null) {
+      newType = oldType.union(type, compiler);
+    } else {
+      newType = type;
+    }
+    typeMap[field] = newType;
+    if (oldType != newType) {
+      scheduleRecompilation(field);
+    }
+  }
+
+  void registerConstructor(Element element) {
+    assert(element.isGenerativeConstructor());
+    Element cls = element.getEnclosingClass();
+    constructors.putIfAbsent(cls, () => new Set<Element>());
+    Set<Element> ctors = constructors[cls];
+    if (ctors.contains(element)) return;
+    ctors.add(element);
+    // We cannot infer field types for classes with more than one constructor.
+    // When the second constructor is seen, recompile all functions relying on
+    // optimistic field types for that class.
+    // TODO(sgjesse): Handle field types for classes with more than one
+    // constructor.
+    if (ctors.length == 2) {
+      optimizedFunctions.forEach((Element field, _) {
+        if (identical(field.enclosingElement, cls)) {
+          scheduleRecompilation(field);
+        }
+      });
+    }
+  }
+
+  void registerFieldInitializer(Element field, HType type) {
+    registerFieldType(fieldInitializerTypeMap, field, type);
+  }
+
+  void registerFieldConstructor(Element field, HType type) {
+    registerFieldType(fieldConstructorTypeMap, field, type);
+  }
+
+  void registerFieldSetter(FunctionElement element, Element field, HType type) {
+    HType initializerType = fieldInitializerTypeMap[field];
+    HType constructorType = fieldConstructorTypeMap[field];
+    HType setterType = fieldTypeMap[field];
+    if (type == HType.UNKNOWN
+        && initializerType == null
+        && constructorType == null
+        && setterType == null) {
+      // Don't register UNKONWN if there is currently no type information
+      // present for the field. Instead register the function holding the
+      // setter for recompilation if better type information for the field
+      // becomes available.
+      registerOptimizedFunction(element, field, type);
+      return;
+    }
+    registerFieldType(fieldTypeMap, field, type);
+  }
+
+  void addedDynamicSetter(Selector setter, HType type) {
+    // Field type optimizations are disabled for all fields matching a
+    // setter selector.
+    assert(setter.isSetter());
+    // TODO(sgjesse): Take the type of the setter into account.
+    if (setterSelectorsUsed.contains(setter.name)) return;
+    setterSelectorsUsed.add(setter.name);
+    optimizedStaticFunctions.forEach((Element field, _) {
+      if (field.name == setter.name) {
+        scheduleRecompilation(field);
+      }
+    });
+    optimizedFunctions.forEach((Element field, _) {
+      if (field.name == setter.name) {
+        scheduleRecompilation(field);
+      }
+    });
+  }
+
+  HType optimisticFieldType(Element field) {
+    assert(field.isField());
+    if (constructorCount(field.getEnclosingClass()) > 1) {
+      return HType.UNKNOWN;
+    }
+    if (setterSelectorsUsed.contains(field.name)) {
+      return HType.UNKNOWN;
+    }
+    HType initializerType = fieldInitializerTypeMap[field];
+    HType constructorType = fieldConstructorTypeMap[field];
+    if (initializerType == null && constructorType == null) {
+      // If there are no constructor type information return UNKNOWN. This
+      // ensures that the function will be recompiled if useful constructor
+      // type information becomes available.
+      return HType.UNKNOWN;
+    }
+    // A type set through the constructor overrides the type from the
+    // initializer list.
+    HType result = constructorType != null ? constructorType : initializerType;
+    HType type = fieldTypeMap[field];
+    if (type != null) result = result.union(type, compiler);
+    return result;
+  }
+
+  void registerOptimizedFunction(FunctionElement element,
+                                 Element field,
+                                 HType type) {
+    assert(field.isField());
+    if (Elements.isStaticOrTopLevel(element)) {
+      optimizedStaticFunctions.putIfAbsent(
+          field, () => new Set<Element>());
+      optimizedStaticFunctions[field].add(element);
+    } else {
+      optimizedFunctions.putIfAbsent(
+          field, () => new FunctionSet(backend.compiler));
+      optimizedFunctions[field].add(element);
+    }
+  }
+
+  void dump() {
+    Set<Element> allFields = new Set<Element>();
+    fieldInitializerTypeMap.keys.forEach(allFields.add);
+    fieldConstructorTypeMap.keys.forEach(allFields.add);
+    fieldTypeMap.keys.forEach(allFields.add);
+    allFields.forEach((Element field) {
+      print("Inferred $field has type ${optimisticFieldType(field)}");
+    });
+  }
+}
+
+class ArgumentTypesRegistry {
+  final JavaScriptBackend backend;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Keys must be declaration elements.
+   */
+  final Map<Element, HTypeList> staticTypeMap;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements must be declaration elements.
+   */
+  final Set<Element> optimizedStaticFunctions;
+  final SelectorMap<HTypeList> selectorTypeMap;
+  final FunctionSet optimizedFunctions;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Keys must be declaration elements.
+   */
+  final Map<Element, HTypeList> optimizedTypes;
+  final Map<Element, OptionalParameterTypes> optimizedDefaultValueTypes;
+
+  ArgumentTypesRegistry(JavaScriptBackend backend)
+      : staticTypeMap = new Map<Element, HTypeList>(),
+        optimizedStaticFunctions = new Set<Element>(),
+        selectorTypeMap = new SelectorMap<HTypeList>(backend.compiler),
+        optimizedFunctions = new FunctionSet(backend.compiler),
+        optimizedTypes = new Map<Element, HTypeList>(),
+        optimizedDefaultValueTypes =
+            new Map<Element, OptionalParameterTypes>(),
+        this.backend = backend;
+
+  Compiler get compiler => backend.compiler;
+
+  bool updateTypes(HTypeList oldTypes, HTypeList newTypes, var key, var map) {
+    if (oldTypes.allUnknown) return false;
+    newTypes = oldTypes.union(newTypes, backend.compiler);
+    if (identical(newTypes, oldTypes)) return false;
+    map[key] = newTypes;
+    return true;
+  }
+
+  void registerStaticInvocation(HInvokeStatic node, HTypeMap types) {
+    Element element = node.element;
+    assert(invariant(node, element.isDeclaration));
+    HTypeList oldTypes = staticTypeMap[element];
+    HTypeList newTypes = new HTypeList.fromStaticInvocation(node, types);
+    if (oldTypes == null) {
+      staticTypeMap[element] = newTypes;
+    } else if (updateTypes(oldTypes, newTypes, element, staticTypeMap)) {
+      if (optimizedStaticFunctions.contains(element)) {
+        backend.scheduleForRecompilation(element);
+      }
+    }
+  }
+
+  void registerNonCallStaticUse(HStatic node) {
+    // When a static is used for anything else than a call target we cannot
+    // infer anything about its parameter types.
+    Element element = node.element;
+    assert(invariant(node, element.isDeclaration));
+    if (optimizedStaticFunctions.contains(element)) {
+      backend.scheduleForRecompilation(element);
+    }
+    staticTypeMap[element] = HTypeList.ALL_UNKNOWN;
+  }
+
+  void registerDynamicInvocation(HTypeList providedTypes, Selector selector) {
+    if (selector.isClosureCall()) {
+      // We cannot use the current framework to do optimizations based
+      // on the 'call' selector because we are also generating closure
+      // calls during the emitter phase, which at this point, does not
+      // track parameter types, nor invalidates optimized methods.
+      return;
+    }
+    if (!selectorTypeMap.containsKey(selector)) {
+      selectorTypeMap[selector] = providedTypes;
+    } else {
+      HTypeList oldTypes = selectorTypeMap[selector];
+      updateTypes(oldTypes, providedTypes, selector, selectorTypeMap);
+    }
+
+    // If we're not compiling, we don't have to do anything.
+    if (compiler.phase != Compiler.PHASE_COMPILING) return;
+
+    // Run through all optimized functions and figure out if they need
+    // to be recompiled because of this new invocation.
+    optimizedFunctions.filterBySelector(selector).forEach((Element element) {
+      // TODO(kasperl): Maybe check if the element is already marked for
+      // recompilation? Could be pretty cheap compared to computing
+      // union types.
+      HTypeList newTypes =
+          parameterTypes(element, optimizedDefaultValueTypes[element]);
+      bool recompile = false;
+      if (newTypes.allUnknown) {
+        recompile = true;
+      } else {
+        HTypeList oldTypes = optimizedTypes[element];
+        assert(newTypes.length == oldTypes.length);
+        for (int i = 0; i < oldTypes.length; i++) {
+          if (newTypes[i] != oldTypes[i]) {
+            recompile = true;
+            break;
+          }
+        }
+      }
+      if (recompile) backend.scheduleForRecompilation(element);
+    });
+  }
+
+  HTypeList parameterTypes(FunctionElement element,
+                           OptionalParameterTypes defaultValueTypes) {
+    assert(invariant(element, element.isDeclaration));
+    // Handle static functions separately.
+    if (Elements.isStaticOrTopLevelFunction(element) ||
+        element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
+      HTypeList types = staticTypeMap[element];
+      if (types != null) {
+        if (!optimizedStaticFunctions.contains(element)) {
+          optimizedStaticFunctions.add(element);
+        }
+        return types;
+      } else {
+        return HTypeList.ALL_UNKNOWN;
+      }
+    }
+
+    // Getters have no parameters.
+    if (element.isGetter()) return HTypeList.ALL_UNKNOWN;
+
+    // TODO(kasperl): What kind of non-members do we get here?
+    if (!element.isMember()) return HTypeList.ALL_UNKNOWN;
+
+    // If there are any getters for this method we cannot know anything about
+    // the types of the provided parameters. Use resolverWorld for now as that
+    // information does not change during compilation.
+    // TODO(ngeoffray): These checks should use the codegenWorld and keep track
+    // of changes to this information.
+    if (compiler.resolverWorld.hasInvokedGetter(element, compiler)) {
+      return HTypeList.ALL_UNKNOWN;
+    }
+
+    FunctionSignature signature = element.computeSignature(compiler);
+    HTypeList found = null;
+    selectorTypeMap.visitMatching(element,
+        (Selector selector, HTypeList types) {
+      if (selector.argumentCount != signature.parameterCount ||
+          selector.namedArgumentCount > 0) {
+        types = types.unionWithOptionalParameters(selector,
+                                                  signature,
+                                                  defaultValueTypes);
+      }
+      assert(types.allUnknown || types.length == signature.parameterCount);
+      found = (found == null) ? types : found.union(types, compiler);
+      return !found.allUnknown;
+    });
+    return found != null ? found : HTypeList.ALL_UNKNOWN;
+  }
+
+  void registerOptimizedFunction(Element element,
+                                 HTypeList parameterTypes,
+                                 OptionalParameterTypes defaultValueTypes) {
+    if (Elements.isStaticOrTopLevelFunction(element)) {
+      if (parameterTypes.allUnknown) {
+        optimizedStaticFunctions.remove(element);
+      } else {
+        optimizedStaticFunctions.add(element);
+      }
+    }
+
+    // TODO(kasperl): What kind of non-members do we get here?
+    if (!element.isMember()) return;
+
+    if (parameterTypes.allUnknown) {
+      optimizedFunctions.remove(element);
+      optimizedTypes.remove(element);
+      optimizedDefaultValueTypes.remove(element);
+    } else {
+      optimizedFunctions.add(element);
+      optimizedTypes[element] = parameterTypes;
+      optimizedDefaultValueTypes[element] = defaultValueTypes;
+    }
+  }
+
+  void dump() {
+    optimizedFunctions.forEach((Element element) {
+      HTypeList types = optimizedTypes[element];
+      print("Inferred $element has argument types ${types.types}");
+    });
+  }
+}
+
+class JavaScriptItemCompilationContext extends ItemCompilationContext {
+  final HTypeMap types;
+  final Set<HInstruction> boundsChecked;
+
+  JavaScriptItemCompilationContext()
+      : types = new HTypeMap(),
+        boundsChecked = new Set<HInstruction>();
+}
+
+class JavaScriptBackend extends Backend {
+  SsaBuilderTask builder;
+  SsaOptimizerTask optimizer;
+  SsaCodeGeneratorTask generator;
+  CodeEmitterTask emitter;
+
+  /**
+   * The generated code as a js AST for compiled methods. 
+   */
+  Map<Element, js.Expression> get generatedCode {
+    return compiler.enqueuer.codegen.generatedCode;
+  }
+
+  /**
+   * The generated code as a js AST for compiled bailout methods. 
+   */
+  final Map<Element, js.Expression> generatedBailoutCode =
+      new Map<Element, js.Expression>();
+
+  ClassElement jsStringClass;
+  ClassElement jsArrayClass;
+  ClassElement jsNumberClass;
+  ClassElement jsIntClass;
+  ClassElement jsDoubleClass;
+  ClassElement jsFunctionClass;
+  ClassElement jsNullClass;
+  ClassElement jsBoolClass;
+  ClassElement objectInterceptorClass;
+  Element jsArrayLength;
+  Element jsStringLength;
+  Element jsArrayRemoveLast;
+  Element jsArrayAdd;
+  Element jsStringSplit;
+  Element jsStringConcat;
+  Element jsStringToString;
+  Element getInterceptorMethod;
+  Element fixedLengthListConstructor;
+  bool seenAnyClass = false;
+
+  final Namer namer;
+
+  /**
+   * Interface used to determine if an object has the JavaScript
+   * indexing behavior. The interface is only visible to specific
+   * libraries.
+   */
+  ClassElement jsIndexingBehaviorInterface;
+
+  final Map<Element, ReturnInfo> returnInfo;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements must be declaration elements.
+   */
+  final List<Element> invalidateAfterCodegen;
+  ArgumentTypesRegistry argumentTypes;
+  FieldTypesRegistry fieldTypes;
+
+  /**
+   * A collection of selectors of intercepted method calls. The
+   * emitter uses this set to generate the [:ObjectInterceptor:] class
+   * whose members just forward the call to the intercepted receiver.
+   */
+  final Set<Selector> usedInterceptors;
+
+  /**
+   * A collection of selectors that must have a one shot interceptor
+   * generated.
+   */
+  final Set<Selector> oneShotInterceptors;
+
+  /**
+   * The members of instantiated interceptor classes: maps a member
+   * name to the list of members that have that name. This map is used
+   * by the codegen to know whether a send must be intercepted or not.
+   */
+  final Map<SourceString, Set<Element>> interceptedElements;
+
+  /**
+   * A map of specialized versions of the [getInterceptorMethod].
+   * Since [getInterceptorMethod] is a hot method at runtime, we're
+   * always specializing it based on the incoming type. The keys in
+   * the map are the names of these specialized versions. Note that
+   * the generic version that contains all possible type checks is
+   * also stored in this map.
+   */
+  final Map<String, Collection<ClassElement>> specializedGetInterceptors;
+
+  /**
+   * Set of classes whose instances are intercepted. Implemented as a
+   * [LinkedHashMap] to preserve the insertion order.
+   * TODO(ngeoffray): No need to preserve order anymore.
+   */
+  final Map<ClassElement, ClassElement> interceptedClasses;
+
+  List<CompilerTask> get tasks {
+    return <CompilerTask>[builder, optimizer, generator, emitter];
+  }
+
+  final RuntimeTypeInformation rti;
+
+  JavaScriptBackend(Compiler compiler, bool generateSourceMap, bool disableEval)
+      : namer = determineNamer(compiler),
+        returnInfo = new Map<Element, ReturnInfo>(),
+        invalidateAfterCodegen = new List<Element>(),
+        usedInterceptors = new Set<Selector>(),
+        oneShotInterceptors = new Set<Selector>(),
+        interceptedElements = new Map<SourceString, Set<Element>>(),
+        rti = new RuntimeTypeInformation(compiler),
+        specializedGetInterceptors =
+            new Map<String, Collection<ClassElement>>(),
+        interceptedClasses = new LinkedHashMap<ClassElement, ClassElement>(),
+        super(compiler, JAVA_SCRIPT_CONSTANT_SYSTEM) {
+    emitter = disableEval
+        ? new CodeEmitterNoEvalTask(compiler, namer, generateSourceMap)
+        : new CodeEmitterTask(compiler, namer, generateSourceMap);
+    builder = new SsaBuilderTask(this);
+    optimizer = new SsaOptimizerTask(this);
+    generator = new SsaCodeGeneratorTask(this);
+    argumentTypes = new ArgumentTypesRegistry(this);
+    fieldTypes = new FieldTypesRegistry(this);
+  }
+
+  static Namer determineNamer(Compiler compiler) {
+    return compiler.enableMinification ?
+        new MinifyNamer(compiler) :
+        new Namer(compiler);
+  }
+
+  bool isInterceptorClass(Element element) {
+    if (element == null) return false;
+    return interceptedClasses.containsKey(element);
+  }
+
+  void addInterceptedSelector(Selector selector) {
+    usedInterceptors.add(selector);
+  }
+
+  void addOneShotInterceptor(Selector selector) {
+    oneShotInterceptors.add(selector);
+  }
+
+  /**
+   * Returns a set of interceptor classes that contain a member whose
+   * signature matches the given [selector]. Returns [:null:] if there
+   * is no class.
+   */
+  Set<ClassElement> getInterceptedClassesOn(Selector selector) {
+    Set<Element> intercepted = interceptedElements[selector.name];
+    if (intercepted == null) return null;
+    Set<ClassElement> result = new Set<ClassElement>();
+    for (Element element in intercepted) {
+      if (selector.applies(element, compiler)) {
+        result.add(element.getEnclosingClass());
+      }
+    }
+    if (result.isEmpty) return null;
+    return result;
+  }
+
+  List<ClassElement> getListOfInterceptedClasses() {
+      return <ClassElement>[jsStringClass, jsArrayClass, jsIntClass,
+                            jsDoubleClass, jsNumberClass, jsNullClass,
+                            jsFunctionClass, jsBoolClass];
+  }
+
+  void initializeInterceptorElements() {
+    objectInterceptorClass =
+        compiler.findInterceptor(const SourceString('ObjectInterceptor'));
+    getInterceptorMethod =
+        compiler.findInterceptor(const SourceString('getInterceptor'));
+    List<ClassElement> classes = [
+      jsStringClass = compiler.findInterceptor(const SourceString('JSString')),
+      jsArrayClass = compiler.findInterceptor(const SourceString('JSArray')),
+      // The int class must be before the double class, because the
+      // emitter relies on this list for the order of type checks.
+      jsIntClass = compiler.findInterceptor(const SourceString('JSInt')),
+      jsDoubleClass = compiler.findInterceptor(const SourceString('JSDouble')),
+      jsNumberClass = compiler.findInterceptor(const SourceString('JSNumber')),
+      jsNullClass = compiler.findInterceptor(const SourceString('JSNull')),
+      jsFunctionClass =
+          compiler.findInterceptor(const SourceString('JSFunction')),
+      jsBoolClass = compiler.findInterceptor(const SourceString('JSBool'))];
+
+    jsArrayClass.ensureResolved(compiler);
+    jsArrayLength = compiler.lookupElementIn(
+        jsArrayClass, const SourceString('length'));
+    jsArrayRemoveLast = compiler.lookupElementIn(
+        jsArrayClass, const SourceString('removeLast'));
+    jsArrayAdd = compiler.lookupElementIn(
+        jsArrayClass, const SourceString('add'));
+
+    jsStringClass.ensureResolved(compiler);
+    jsStringLength = compiler.lookupElementIn(
+        jsStringClass, const SourceString('length'));
+    jsStringSplit = compiler.lookupElementIn(
+        jsStringClass, const SourceString('split'));
+    jsStringConcat = compiler.lookupElementIn(
+        jsStringClass, const SourceString('concat'));
+    jsStringToString = compiler.lookupElementIn(
+        jsStringClass, const SourceString('toString'));
+
+    for (ClassElement cls in classes) {
+      if (cls != null) interceptedClasses[cls] = null;
+    }
+  }
+
+  void addInterceptors(ClassElement cls, Enqueuer enqueuer) {
+    if (enqueuer.isResolutionQueue) {
+      cls.ensureResolved(compiler);
+      cls.forEachMember((ClassElement classElement, Element member) {
+          Set<Element> set = interceptedElements.putIfAbsent(
+              member.name, () => new Set<Element>());
+          set.add(member);
+        },
+        includeSuperMembers: true);
+    }
+    enqueuer.registerInstantiatedClass(cls);
+  }
+
+  void registerSpecializedGetInterceptor(Set<ClassElement> classes) {
+    compiler.enqueuer.codegen.registerInstantiatedClass(objectInterceptorClass);
+    String name = namer.getInterceptorName(getInterceptorMethod, classes);
+    if (classes.contains(compiler.objectClass)) {
+      // We can't use a specialized [getInterceptorMethod], so we make
+      // sure we emit the one with all checks.
+      specializedGetInterceptors.putIfAbsent(name, () {
+        // It is important to take the order provided by the map,
+        // because we want the int type check to happen before the
+        // double type check: the double type check covers the int
+        // type check. Also we don't need to do a number type check
+        // because that is covered by the double type check.
+        List<ClassElement> keys = <ClassElement>[];
+        interceptedClasses.forEach((ClassElement cls, _) {
+          if (cls != jsNumberClass) keys.add(cls);
+        });
+        return keys;
+      });
+    } else {
+      specializedGetInterceptors[name] = classes;
+    }
+  }
+
+  void initializeNoSuchMethod() {
+    // In case the emitter generates noSuchMethod calls, we need to
+    // make sure all [noSuchMethod] methods know they might take a
+    // [JsInvocationMirror] as parameter.
+    HTypeList types = new HTypeList(1);
+    types[0] = new HType.fromBoundedType(
+        compiler.jsInvocationMirrorClass.computeType(compiler),
+        compiler,
+        false);
+    argumentTypes.registerDynamicInvocation(types, new Selector.noSuchMethod());
+  }
+
+  void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {
+    if (!seenAnyClass) {
+      initializeInterceptorElements();
+      initializeNoSuchMethod();
+      seenAnyClass = true;
+    }
+    ClassElement result = null;
+    if (cls == compiler.stringClass) {
+      addInterceptors(jsStringClass, enqueuer);
+    } else if (cls == compiler.listClass) {
+      addInterceptors(jsArrayClass, enqueuer);
+      // The backend will try to optimize array access and use the
+      // `ioore` and `iae` helpers directly.
+      if (enqueuer.isResolutionQueue) {
+        enqueuer.registerStaticUse(
+            compiler.findHelper(const SourceString('ioore')));
+        enqueuer.registerStaticUse(
+            compiler.findHelper(const SourceString('iae')));
+      }
+    } else if (cls == compiler.intClass) {
+      addInterceptors(jsIntClass, enqueuer);
+      addInterceptors(jsNumberClass, enqueuer);
+    } else if (cls == compiler.doubleClass) {
+      addInterceptors(jsDoubleClass, enqueuer);
+      addInterceptors(jsNumberClass, enqueuer);
+    } else if (cls == compiler.functionClass) {
+      addInterceptors(jsFunctionClass, enqueuer);
+    } else if (cls == compiler.boolClass) {
+      addInterceptors(jsBoolClass, enqueuer);
+    } else if (cls == compiler.nullClass) {
+      addInterceptors(jsNullClass, enqueuer);
+    } else if (cls == compiler.numClass) {
+      addInterceptors(jsIntClass, enqueuer);
+      addInterceptors(jsDoubleClass, enqueuer);
+      addInterceptors(jsNumberClass, enqueuer);
+    } else if (cls == compiler.mapClass) {
+      // The backend will use a literal list to initialize the entries
+      // of the map.
+      if (enqueuer.isResolutionQueue) {
+        enqueuer.registerInstantiatedClass(compiler.listClass); 
+      }
+    }
+  }
+
+  Element get cyclicThrowHelper {
+    return compiler.findHelper(const SourceString("throwCyclicInit"));
+  }
+
+  JavaScriptItemCompilationContext createItemCompilationContext() {
+    return new JavaScriptItemCompilationContext();
+  }
+
+  void enqueueHelpers(ResolutionEnqueuer world) {
+    enqueueAllTopLevelFunctions(compiler.jsHelperLibrary, world);
+
+    jsIndexingBehaviorInterface =
+        compiler.findHelper(const SourceString('JavaScriptIndexingBehavior'));
+    if (jsIndexingBehaviorInterface != null) {
+      world.registerIsCheck(jsIndexingBehaviorInterface.computeType(compiler));
+    }
+
+    for (var helper in [const SourceString('Closure'),
+                        const SourceString('ConstantMap'),
+                        const SourceString('ConstantProtoMap')]) {
+      var e = compiler.findHelper(helper);
+      if (e != null) world.registerInstantiatedClass(e);
+    }
+  }
+
+  void codegen(CodegenWorkItem work) {
+    Element element = work.element;
+    if (element.kind.category == ElementCategory.VARIABLE) {
+      Constant initialValue = compiler.constantHandler.compileWorkItem(work);
+      if (initialValue != null) {
+        return;
+      } else {
+        // If the constant-handler was not able to produce a result we have to
+        // go through the builder (below) to generate the lazy initializer for
+        // the static variable.
+        // We also need to register the use of the cyclic-error helper.
+        compiler.enqueuer.codegen.registerStaticUse(cyclicThrowHelper);
+      }
+    }
+
+    HGraph graph = builder.build(work);
+    optimizer.optimize(work, graph, false);
+    if (work.allowSpeculativeOptimization
+        && optimizer.trySpeculativeOptimizations(work, graph)) {
+      js.Expression code = generator.generateBailoutMethod(work, graph);
+      generatedBailoutCode[element] = code;
+      optimizer.prepareForSpeculativeOptimizations(work, graph);
+      optimizer.optimize(work, graph, true);
+    }
+    js.Expression code = generator.generateCode(work, graph);
+    generatedCode[element] = code;
+    invalidateAfterCodegen.forEach(eagerRecompile);
+    invalidateAfterCodegen.clear();
+  }
+
+  native.NativeEnqueuer nativeResolutionEnqueuer(Enqueuer world) {
+    return new native.NativeResolutionEnqueuer(world, compiler);
+  }
+
+  native.NativeEnqueuer nativeCodegenEnqueuer(Enqueuer world) {
+    return new native.NativeCodegenEnqueuer(world, compiler, emitter);
+  }
+
+  /**
+   * Unit test hook that returns code of an element as a String.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  String assembleCode(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    return js.prettyPrint(generatedCode[element], compiler).getText();
+  }
+
+  void assembleProgram() {
+    emitter.assembleProgram();
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void scheduleForRecompilation(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    if (compiler.phase == Compiler.PHASE_COMPILING) {
+      invalidateAfterCodegen.add(element);
+    }
+  }
+
+  /**
+   *  Register a dynamic invocation and collect the provided types for the
+   *  named selector.
+   */
+  void registerDynamicInvocation(HInvokeDynamic node,
+                                 Selector selector,
+                                 HTypeMap types) {
+    HTypeList providedTypes =
+        new HTypeList.fromDynamicInvocation(node, selector, types);
+    argumentTypes.registerDynamicInvocation(providedTypes, selector);
+  }
+
+  /**
+   *  Register a static invocation and collect the provided types for the
+   *  named selector.
+   */
+  void registerStaticInvocation(HInvokeStatic node, HTypeMap types) {
+    argumentTypes.registerStaticInvocation(node, types);
+  }
+
+  /**
+   *  Register that a static is used for something else than a direct call
+   *  target.
+   */
+  void registerNonCallStaticUse(HStatic node) {
+    argumentTypes.registerNonCallStaticUse(node);
+  }
+
+  /**
+   * Retrieve the types of the parameters used for calling the [element]
+   * function. The types are optimistic in the sense as they are based on the
+   * possible invocations of the function seen so far.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  HTypeList optimisticParameterTypes(
+      FunctionElement element,
+      OptionalParameterTypes defaultValueTypes) {
+    assert(invariant(element, element.isDeclaration));
+    if (element.parameterCount(compiler) == 0) return HTypeList.ALL_UNKNOWN;
+    return argumentTypes.parameterTypes(element, defaultValueTypes);
+  }
+
+  /**
+   * Register that the function [element] has been optimized under the
+   * assumptions that the types [parameterType] will be used for calling it.
+   * The passed [defaultValueTypes] holds the types of default values for
+   * the optional parameters. If this assumption fail the function will be
+   * scheduled for recompilation.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  registerParameterTypesOptimization(
+      FunctionElement element,
+      HTypeList parameterTypes,
+      OptionalParameterTypes defaultValueTypes) {
+    assert(invariant(element, element.isDeclaration));
+    if (element.parameterCount(compiler) == 0) return;
+    argumentTypes.registerOptimizedFunction(
+        element, parameterTypes, defaultValueTypes);
+  }
+
+  registerFieldTypesOptimization(FunctionElement element,
+                                 Element field,
+                                 HType type) {
+    fieldTypes.registerOptimizedFunction(element, field, type);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void registerReturnType(FunctionElement element, HType returnType) {
+    assert(invariant(element, element.isDeclaration));
+    ReturnInfo info = returnInfo[element];
+    if (info != null) {
+      info.update(returnType, scheduleForRecompilation, compiler);
+    } else {
+      returnInfo[element] = new ReturnInfo(returnType);
+    }
+  }
+
+  /**
+   * Retrieve the return type of the function [callee]. The type is optimistic
+   * in the sense that is is based on the compilation of [callee]. If [callee]
+   * is recompiled the return type might change to someting broader. For that
+   * reason [caller] is registered for recompilation if this happens. If the
+   * function [callee] has not yet been compiled the returned type is [null].
+   *
+   * Invariant: Both [caller] and [callee] must be declaration elements.
+   */
+  HType optimisticReturnTypesWithRecompilationOnTypeChange(
+      Element caller, FunctionElement callee) {
+    assert(invariant(callee, callee.isDeclaration));
+    returnInfo.putIfAbsent(callee, () => new ReturnInfo.unknownType());
+    ReturnInfo info = returnInfo[callee];
+    HType returnType = info.returnType;
+    if (returnType != HType.UNKNOWN && returnType != null && caller != null) {
+      assert(invariant(caller, caller.isDeclaration));
+      info.addCompiledFunction(caller);
+    }
+    return info.returnType;
+  }
+
+  void dumpReturnTypes() {
+    returnInfo.forEach((Element element, ReturnInfo info) {
+      if (info.returnType != HType.UNKNOWN) {
+        print("Inferred $element has return type ${info.returnType}");
+      }
+    });
+  }
+
+  void registerConstructor(Element element) {
+    fieldTypes.registerConstructor(element);
+  }
+
+  void registerFieldInitializer(Element field, HType type) {
+    fieldTypes.registerFieldInitializer(field, type);
+  }
+
+  void registerFieldConstructor(Element field, HType type) {
+    fieldTypes.registerFieldConstructor(field, type);
+  }
+
+  void registerFieldSetter(FunctionElement element, Element field, HType type) {
+    fieldTypes.registerFieldSetter(element, field, type);
+  }
+
+  void addedDynamicSetter(Selector setter, HType type) {
+    fieldTypes.addedDynamicSetter(setter, type);
+  }
+
+  HType optimisticFieldType(Element element) {
+    return fieldTypes.optimisticFieldType(element);
+  }
+
+  /**
+   * Return the checked mode helper name that will be needed to do a
+   * type check on [type] at runtime. Note that this method is being
+   * called both by the resolver with interface types (int, String,
+   * ...), and by the SSA backend with implementation types (JSInt,
+   * JSString, ...).
+   */
+  SourceString getCheckedModeHelper(DartType type) {
+    Element element = type.element;
+    bool nativeCheck =
+          emitter.nativeEmitter.requiresNativeIsCheck(element);
+    if (type.isMalformed) {
+      // Check for malformed types first, because the type may be a list type
+      // with a malformed argument type.
+      return const SourceString('malformedTypeCheck');
+    } else if (type == compiler.types.voidType) {
+      return const SourceString('voidTypeCheck');
+    } else if (element == jsStringClass || element == compiler.stringClass) {
+      return const SourceString('stringTypeCheck');
+    } else if (element == jsDoubleClass || element == compiler.doubleClass) {
+      return const SourceString('doubleTypeCheck');
+    } else if (element == jsNumberClass || element == compiler.numClass) {
+      return const SourceString('numTypeCheck');
+    } else if (element == jsBoolClass || element == compiler.boolClass) {
+      return const SourceString('boolTypeCheck');
+    } else if (element == jsFunctionClass
+               || element == compiler.functionClass) {
+      return const SourceString('functionTypeCheck');
+    } else if (element == jsIntClass || element == compiler.intClass) {
+      return const SourceString('intTypeCheck');
+    } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
+      return nativeCheck
+          ? const SourceString('numberOrStringSuperNativeTypeCheck')
+          : const SourceString('numberOrStringSuperTypeCheck');
+    } else if (Elements.isStringOnlySupertype(element, compiler)) {
+      return nativeCheck
+          ? const SourceString('stringSuperNativeTypeCheck')
+          : const SourceString('stringSuperTypeCheck');
+    } else if (element == compiler.listClass || element == jsArrayClass) {
+      return const SourceString('listTypeCheck');
+    } else {
+      if (Elements.isListSupertype(element, compiler)) {
+        return nativeCheck
+            ? const SourceString('listSuperNativeTypeCheck')
+            : const SourceString('listSuperTypeCheck');
+      } else {
+        return nativeCheck
+            ? const SourceString('callTypeCheck')
+            : const SourceString('propertyTypeCheck');
+      }
+    }
+  }
+
+  void dumpInferredTypes() {
+    print("Inferred argument types:");
+    print("------------------------");
+    argumentTypes.dump();
+    print("");
+    print("Inferred return types:");
+    print("----------------------");
+    dumpReturnTypes();
+    print("");
+    print("Inferred field types:");
+    print("------------------------");
+    fieldTypes.dump();
+    print("");
+  }
+
+  Element getExceptionUnwrapper() {
+    return compiler.findHelper(const SourceString('unwrapException'));
+  }
+
+  Element getThrowRuntimeError() {
+    return compiler.findHelper(const SourceString('throwRuntimeError'));
+  }
+
+  Element getThrowMalformedSubtypeError() {
+    return compiler.findHelper(
+        const SourceString('throwMalformedSubtypeError'));
+  }
+
+  Element getThrowAbstractClassInstantiationError() {
+    return compiler.findHelper(
+        const SourceString('throwAbstractClassInstantiationError'));
+  }
+
+  Element getClosureConverter() {
+    return compiler.findHelper(const SourceString('convertDartClosureToJS'));
+  }
+
+  Element getTraceFromException() {
+    return compiler.findHelper(const SourceString('getTraceFromException'));
+  }
+
+  Element getMapMaker() {
+    return compiler.findHelper(const SourceString('makeLiteralMap'));
+  }
+
+  Element getSetRuntimeTypeInfo() {
+    return compiler.findHelper(const SourceString('setRuntimeTypeInfo'));
+  }
+
+  Element getGetRuntimeTypeInfo() {
+    return compiler.findHelper(const SourceString('getRuntimeTypeInfo'));
+  }
+
+  /**
+   * Remove [element] from the set of generated code, and put it back
+   * into the worklist.
+   *
+   * Invariant: [element] must be a declaration element.
+   */
+  void eagerRecompile(Element element) {
+    assert(invariant(element, element.isDeclaration));
+    generatedCode.remove(element);
+    generatedBailoutCode.remove(element);
+    compiler.enqueuer.codegen.addToWorkList(element);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart
new file mode 100644
index 0000000..86717ae
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart
@@ -0,0 +1,325 @@
+// 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.
+
+part of js_backend;
+
+class ConstantEmitter  {
+  ConstantReferenceEmitter _referenceEmitter;
+  ConstantInitializerEmitter _initializerEmitter;
+
+  ConstantEmitter(Compiler compiler, Namer namer) {
+    _referenceEmitter = new ConstantReferenceEmitter(compiler, namer);
+    _initializerEmitter = new ConstantInitializerEmitter(
+        compiler, namer, _referenceEmitter);
+  }
+
+  /**
+   * Constructs an expression that is a reference to the constant.  Uses a
+   * canonical name unless the constant can be emitted multiple times (as for
+   * numbers and strings).
+   */
+  js.Expression reference(Constant constant) {
+    return _referenceEmitter.generate(constant);
+  }
+
+  /**
+   * Constructs an expression like [reference], but the expression is valid
+   * during isolate initialization.
+   */
+  js.Expression referenceInInitializationContext(Constant constant) {
+    return _referenceEmitter.generateInInitializationContext(constant);
+  }
+
+  /**
+   * Constructs an expression used to initialize a canonicalized constant.
+   */
+  js.Expression initializationExpression(Constant constant) {
+    return _initializerEmitter.generate(constant);
+  }
+}
+
+/**
+ * Visitor for generating JavaScript expressions to refer to [Constant]s.
+ * Do not use directly, use methods from [ConstantEmitter].
+ */
+class ConstantReferenceEmitter implements ConstantVisitor<js.Expression> {
+  final Compiler compiler;
+  final Namer namer;
+  bool inIsolateInitializationContext = false;
+
+  ConstantReferenceEmitter(this.compiler, this.namer);
+
+  js.Expression generate(Constant constant) {
+    inIsolateInitializationContext = false;
+    return _visit(constant);
+  }
+
+  js.Expression generateInInitializationContext(Constant constant) {
+    inIsolateInitializationContext = true;
+    return _visit(constant);
+  }
+
+  js.Expression _visit(Constant constant) {
+    return constant.accept(this);
+  }
+
+  js.Expression visitSentinel(SentinelConstant constant) {
+    return new js.VariableUse(namer.CURRENT_ISOLATE);
+  }
+
+  js.Expression visitFunction(FunctionConstant constant) {
+    return inIsolateInitializationContext
+        ? new js.VariableUse(namer.isolatePropertiesAccess(constant.element))
+        : new js.VariableUse(namer.isolateAccess(constant.element));
+  }
+
+  js.Expression visitNull(NullConstant constant) {
+    return new js.LiteralNull();
+  }
+
+  js.Expression visitInt(IntConstant constant) {
+    return new js.LiteralNumber('${constant.value}');
+  }
+
+  js.Expression visitDouble(DoubleConstant constant) {
+    double value = constant.value;
+    if (value.isNaN) {
+      return new js.LiteralNumber("(0/0)");
+    } else if (value == double.INFINITY) {
+      return new js.LiteralNumber("(1/0)");
+    } else if (value == -double.INFINITY) {
+      return new js.LiteralNumber("(-1/0)");
+    } else {
+      return new js.LiteralNumber("$value");
+    }
+  }
+
+  js.Expression visitTrue(TrueConstant constant) {
+    if (compiler.enableMinification) {
+      // Use !0 for true.
+      return new js.Prefix("!", new js.LiteralNumber("0"));
+    } else {
+      return new js.LiteralBool(true);
+    }
+
+  }
+
+  js.Expression visitFalse(FalseConstant constant) {
+    if (compiler.enableMinification) {
+      // Use !1 for false.
+      return new js.Prefix("!", new js.LiteralNumber("1"));
+    } else {
+      return new js.LiteralBool(false);
+    }
+  }
+
+  /**
+   * Write the contents of the quoted string to a [CodeBuffer] in
+   * a form that is valid as JavaScript string literal content.
+   * The string is assumed quoted by double quote characters.
+   */
+  js.Expression visitString(StringConstant constant) {
+    // TODO(sra): If the string is long *and repeated* (and not on a hot path)
+    // then it should be assigned to a name.  We don't have reference counts (or
+    // profile information) here, so this is the wrong place.
+    StringBuffer sb = new StringBuffer();
+    writeJsonEscapedCharsOn(constant.value.slowToString(), sb);
+    return new js.LiteralString('"$sb"');
+  }
+
+  js.Expression emitCanonicalVersion(Constant constant) {
+    String name = namer.constantName(constant);
+    if (inIsolateInitializationContext) {
+      //  $isolateName.$isolatePropertiesName.$name
+      return new js.PropertyAccess.field(
+          new js.PropertyAccess.field(
+              new js.VariableUse(namer.isolateName),
+              namer.isolatePropertiesName),
+          name);
+    } else {
+      return new js.PropertyAccess.field(
+          new js.VariableUse(namer.CURRENT_ISOLATE),
+          name);
+    }
+  }
+
+  js.Expression visitList(ListConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+
+  js.Expression visitMap(MapConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+
+  js.Expression visitType(TypeConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+
+  js.Expression visitConstructed(ConstructedConstant constant) {
+    return emitCanonicalVersion(constant);
+  }
+}
+
+/**
+ * Visitor for generating JavaScript expressions to initialize [Constant]s.
+ * Do not use directly; use methods from [ConstantEmitter].
+ */
+class ConstantInitializerEmitter implements ConstantVisitor<js.Expression> {
+  final Compiler compiler;
+  final Namer namer;
+  final ConstantReferenceEmitter referenceEmitter;
+
+  ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter);
+
+  js.Expression generate(Constant constant) {
+    return _visit(constant);
+  }
+
+  js.Expression _visit(Constant constant) {
+    return constant.accept(this);
+  }
+
+  js.Expression _reference(Constant constant) {
+    return referenceEmitter.generateInInitializationContext(constant);
+  }
+
+  js.Expression visitSentinel(SentinelConstant constant) {
+    compiler.internalError(
+        "The parameter sentinel constant does not need specific JS code");
+  }
+
+  js.Expression visitFunction(FunctionConstant constant) {
+    compiler.internalError(
+        "The function constant does not need specific JS code");
+  }
+
+  js.Expression visitNull(NullConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitInt(IntConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitDouble(DoubleConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitTrue(TrueConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitFalse(FalseConstant constant) {
+    return _reference(constant);
+  }
+
+  js.Expression visitString(StringConstant constant) {
+    // TODO(sra): Some larger strings are worth sharing.
+    return _reference(constant);
+  }
+
+  js.Expression visitList(ListConstant constant) {
+    return new js.Call(
+        new js.PropertyAccess.field(
+            new js.VariableUse(namer.isolateName),
+            'makeConstantList'),
+        [new js.ArrayInitializer.from(_array(constant.entries))]);
+  }
+
+  String getJsConstructor(ClassElement element) {
+    return namer.isolatePropertiesAccess(element);
+  }
+
+  js.Expression visitMap(MapConstant constant) {
+    js.Expression jsMap() {
+      List<js.Property> properties = <js.Property>[];
+      int valueIndex = 0;
+      for (int i = 0; i < constant.keys.entries.length; i++) {
+        StringConstant key = constant.keys.entries[i];
+        if (key.value == MapConstant.PROTO_PROPERTY) continue;
+
+        // Keys in literal maps must be emitted in place.
+        js.Literal keyExpression = _visit(key);
+        js.Expression valueExpression =
+            _reference(constant.values[valueIndex++]);
+        properties.add(new js.Property(keyExpression, valueExpression));
+      }
+      if (valueIndex != constant.values.length) {
+        compiler.internalError("Bad value count.");
+      }
+      return new js.ObjectInitializer(properties);
+    }
+
+    void badFieldCountError() {
+      compiler.internalError(
+          "Compiler and ConstantMap disagree on number of fields.");
+    }
+
+    ClassElement classElement = constant.type.element;
+
+    List<js.Expression> arguments = <js.Expression>[];
+
+    // The arguments of the JavaScript constructor for any given Dart class
+    // are in the same order as the members of the class element.
+    int emittedArgumentCount = 0;
+    classElement.implementation.forEachInstanceField(
+        (ClassElement enclosing, Element field) {
+          if (field.name == MapConstant.LENGTH_NAME) {
+            arguments.add(
+                new js.LiteralNumber('${constant.keys.entries.length}'));
+          } else if (field.name == MapConstant.JS_OBJECT_NAME) {
+            arguments.add(jsMap());
+          } else if (field.name == MapConstant.KEYS_NAME) {
+            arguments.add(_reference(constant.keys));
+          } else if (field.name == MapConstant.PROTO_VALUE) {
+            assert(constant.protoValue != null);
+            arguments.add(_reference(constant.protoValue));
+          } else {
+            badFieldCountError();
+          }
+          emittedArgumentCount++;
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: true);
+
+    if ((constant.protoValue == null && emittedArgumentCount != 3) ||
+        (constant.protoValue != null && emittedArgumentCount != 4)) {
+      badFieldCountError();
+    }
+
+    return new js.New(
+        new js.VariableUse(getJsConstructor(classElement)),
+        arguments);
+  }
+
+  js.Expression visitType(TypeConstant constant) {
+    SourceString helperSourceName = const SourceString('createRuntimeType');
+    Element helper = compiler.findHelper(helperSourceName);
+    JavaScriptBackend backend = compiler.backend;
+    String helperName = backend.namer.getName(helper);
+    DartType type = constant.representedType;
+    Element element = type.element;
+    String name = backend.rti.getRawTypeRepresentation(type);
+    js.Expression typeName = new js.LiteralString("'$name'");
+    return new js.Call(
+        new js.PropertyAccess.field(
+            new js.VariableUse(namer.CURRENT_ISOLATE),
+            helperName),
+        [typeName]);
+  }
+
+  js.Expression visitConstructed(ConstructedConstant constant) {
+    return new js.New(
+        new js.VariableUse(getJsConstructor(constant.type.element)),
+        _array(constant.fields));
+  }
+
+  List<js.Expression> _array(List<Constant> values) {
+    List<js.Expression> valueList = <js.Expression>[];
+    for (int i = 0; i < values.length; i++) {
+      valueList.add(_reference(values[i]));
+    }
+    return valueList;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart
new file mode 100644
index 0000000..30c8bc5
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart
@@ -0,0 +1,240 @@
+// 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.
+
+part of js_backend;
+
+const JAVA_SCRIPT_CONSTANT_SYSTEM = const JavaScriptConstantSystem();
+
+class JavaScriptBitNotOperation extends BitNotOperation {
+  const JavaScriptBitNotOperation();
+
+  Constant fold(Constant constant) {
+    if (JAVA_SCRIPT_CONSTANT_SYSTEM.isInt(constant)) {
+      // In JavaScript we don't check for -0 and treat it as if it was zero.
+      if (constant.isMinusZero()) constant = DART_CONSTANT_SYSTEM.createInt(0);
+      IntConstant intConstant = constant;
+      // We convert the result of bit-operations to 32 bit unsigned integers.
+      return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(~intConstant.value);
+    }
+    return null;
+  }
+}
+
+/**
+ * In JavaScript we truncate the result to an unsigned 32 bit integer. Also, -0
+ * is treated as if it was the integer 0.
+ */
+class JavaScriptBinaryBitOperation implements BinaryOperation {
+  final BinaryBitOperation dartBitOperation;
+
+  const JavaScriptBinaryBitOperation(this.dartBitOperation);
+
+  bool isUserDefinable() => dartBitOperation.isUserDefinable();
+  SourceString get name => dartBitOperation.name;
+
+  Constant fold(Constant left, Constant right) {
+    // In JavaScript we don't check for -0 and treat it as if it was zero.
+    if (left.isMinusZero()) left = DART_CONSTANT_SYSTEM.createInt(0);
+    if (right.isMinusZero()) right = DART_CONSTANT_SYSTEM.createInt(0);
+    IntConstant result = dartBitOperation.fold(left, right);
+    if (result != null) {
+      // We convert the result of bit-operations to 32 bit unsigned integers.
+      return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(result.value);
+    }
+    return result;
+  }
+
+  apply(left, right) => dartBitOperation.apply(left, right);
+}
+
+class JavaScriptShiftRightOperation extends JavaScriptBinaryBitOperation {
+  const JavaScriptShiftRightOperation() : super(const ShiftRightOperation());
+
+  Constant fold(Constant left, Constant right) {
+    // Truncate the input value to 32 bits if necessary.
+    if (left.isInt()) {
+      IntConstant intConstant = left;
+      int value = intConstant.value;
+      int truncatedValue = value & JAVA_SCRIPT_CONSTANT_SYSTEM.BITS32;
+      // TODO(floitsch): we should treat the input to right shifts as unsigned.
+
+      // Sign-extend. A 32 bit complement-two value x can be computed by:
+      //    x_u - 2^32 (where x_u is its unsigned representation).
+      // Example: 0xFFFFFFFF - 0x100000000 => -1.
+      // We simply and with the sign-bit and multiply by two. If the sign-bit
+      // was set, then the result is 0. Otherwise it will become 2^32.
+      final int SIGN_BIT = 0x80000000;
+      truncatedValue -= 2 * (truncatedValue & SIGN_BIT);
+      if (value != truncatedValue) {
+        left = DART_CONSTANT_SYSTEM.createInt(truncatedValue);
+      }
+    }
+    return super.fold(left, right);
+  }
+}
+
+class JavaScriptNegateOperation implements UnaryOperation {
+  final NegateOperation dartNegateOperation = const NegateOperation();
+
+  const JavaScriptNegateOperation();
+
+  bool isUserDefinable() => dartNegateOperation.isUserDefinable();
+  SourceString get name => dartNegateOperation.name;
+
+  Constant fold(Constant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      if (intConstant.value == 0) {
+        return JAVA_SCRIPT_CONSTANT_SYSTEM.createDouble(-0.0);
+      }
+    }
+    return dartNegateOperation.fold(constant);
+  }
+  apply(value) => -value;
+}
+
+class JavaScriptBinaryArithmeticOperation implements BinaryOperation {
+  final BinaryOperation dartArithmeticOperation;
+
+  const JavaScriptBinaryArithmeticOperation(this.dartArithmeticOperation);
+
+  bool isUserDefinable() => dartArithmeticOperation.isUserDefinable();
+  SourceString get name => dartArithmeticOperation.name;
+
+  Constant fold(Constant left, Constant right) {
+    Constant result = dartArithmeticOperation.fold(left, right);
+    if (result == null) return result;
+    return JAVA_SCRIPT_CONSTANT_SYSTEM.convertToJavaScriptConstant(result);
+  }
+
+  apply(left, right) => dartArithmeticOperation.apply(left, right);
+}
+
+class JavaScriptIdentityOperation implements BinaryOperation {
+  final IdentityOperation dartIdentityOperation = const IdentityOperation();
+
+  const JavaScriptIdentityOperation();
+
+  bool isUserDefinable() => dartIdentityOperation.isUserDefinable();
+  SourceString get name => dartIdentityOperation.name;
+
+  BoolConstant fold(Constant left, Constant right) {
+    BoolConstant result = dartIdentityOperation.fold(left, right);
+    if (result == null || result.value) return result;
+    // In JavaScript -0.0 === 0 and all doubles are equal to their integer
+    // values. Furthermore NaN !== NaN.
+    if (left.isNum() && right.isNum()) {
+      NumConstant leftNum = left;
+      NumConstant rightNum = right;
+      double leftDouble = leftNum.value.toDouble();
+      double rightDouble = rightNum.value.toDouble();
+      return new BoolConstant(leftDouble == rightDouble);
+    }
+    return result;
+  }
+
+  apply(left, right) => identical(left, right);
+}
+
+/**
+ * Constant system following the semantics for Dart code that has been
+ * compiled to JavaScript.
+ */
+class JavaScriptConstantSystem extends ConstantSystem {
+  const int BITS31 = 0x8FFFFFFF;
+  const int BITS32 = 0xFFFFFFFF;
+  // The maximum integer value a double can represent without losing
+  // precision.
+  const int BITS53 = 0x1FFFFFFFFFFFFF;
+
+  final add = const JavaScriptBinaryArithmeticOperation(const AddOperation());
+  final bitAnd = const JavaScriptBinaryBitOperation(const BitAndOperation());
+  final bitNot = const JavaScriptBitNotOperation();
+  final bitOr = const JavaScriptBinaryBitOperation(const BitOrOperation());
+  final bitXor = const JavaScriptBinaryBitOperation(const BitXorOperation());
+  final booleanAnd = const BooleanAndOperation();
+  final booleanOr = const BooleanOrOperation();
+  final divide =
+      const JavaScriptBinaryArithmeticOperation(const DivideOperation());
+  final equal = const EqualsOperation();
+  final greaterEqual = const GreaterEqualOperation();
+  final greater = const GreaterOperation();
+  final identity = const JavaScriptIdentityOperation();
+  final lessEqual = const LessEqualOperation();
+  final less = const LessOperation();
+  final modulo =
+      const JavaScriptBinaryArithmeticOperation(const ModuloOperation());
+  final multiply =
+      const JavaScriptBinaryArithmeticOperation(const MultiplyOperation());
+  final negate = const JavaScriptNegateOperation();
+  final not = const NotOperation();
+  final shiftLeft =
+      const JavaScriptBinaryBitOperation(const ShiftLeftOperation());
+  final shiftRight = const JavaScriptShiftRightOperation();
+  final subtract =
+      const JavaScriptBinaryArithmeticOperation(const SubtractOperation());
+  final truncatingDivide = const JavaScriptBinaryArithmeticOperation(
+      const TruncatingDivideOperation());
+
+  const JavaScriptConstantSystem();
+
+  /**
+   * Returns true if the given [value] fits into a double without losing
+   * precision.
+   */
+  bool integerFitsIntoDouble(int value) {
+    int absValue = value.abs();
+    return (absValue & BITS53) == absValue;
+  }
+
+  NumConstant convertToJavaScriptConstant(NumConstant constant) {
+    if (constant.isInt()) {
+      IntConstant intConstant = constant;
+      int intValue = intConstant.value;
+      if (!integerFitsIntoDouble(intValue)) {
+        return new DoubleConstant(intValue.toDouble());
+      }
+    } else if (constant.isDouble()) {
+      DoubleConstant doubleResult = constant;
+      double doubleValue = doubleResult.value;
+      if (!doubleValue.isInfinite && !doubleValue.isNaN &&
+          !constant.isMinusZero()) {
+        int intValue = doubleValue.toInt();
+        if (intValue == doubleValue && integerFitsIntoDouble(intValue)) {
+          return new IntConstant(intValue);
+        }
+      }
+    }
+    return constant;
+  }
+
+  NumConstant createInt(int i)
+      => convertToJavaScriptConstant(new IntConstant(i));
+  NumConstant createInt32(int i) => new IntConstant(i & BITS32);
+  NumConstant createDouble(double d)
+      => convertToJavaScriptConstant(new DoubleConstant(d));
+  StringConstant createString(DartString string, Node diagnosticNode)
+      => new StringConstant(string, diagnosticNode);
+  BoolConstant createBool(bool value) => new BoolConstant(value);
+  NullConstant createNull() => new NullConstant();
+
+  // Integer checks don't verify that the number is not -0.0.
+  bool isInt(Constant constant) => constant.isInt() || constant.isMinusZero();
+  bool isDouble(Constant constant)
+      => constant.isDouble() && !constant.isMinusZero();
+  bool isString(Constant constant) => constant.isString();
+  bool isBool(Constant constant) => constant.isBool();
+  bool isNull(Constant constant) => constant.isNull();
+
+  bool isSubtype(Compiler compiler, DartType s, DartType t) {
+    // At runtime, an integer is both an integer and a double: the
+    // integer type check is Math.floor, which will return true only
+    // for real integers, and our double type check is 'typeof number'
+    // which will return true for both integers and doubles.
+    if (s.element == compiler.intClass && t.element == compiler.doubleClass) {
+      return true;
+    }
+    return compiler.types.isSubtype(s, t);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart
new file mode 100644
index 0000000..f401235
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart
@@ -0,0 +1,2359 @@
+// 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.
+
+part of js_backend;
+
+/**
+ * A function element that represents a closure call. The signature is copied
+ * from the given element.
+ */
+class ClosureInvocationElement extends FunctionElementX {
+  ClosureInvocationElement(SourceString name,
+                           FunctionElement other)
+      : super.from(name, other, other.enclosingElement),
+        methodElement = other;
+
+  isInstanceMember() => true;
+
+  Element getOutermostEnclosingMemberOrTopLevel() => methodElement;
+
+  /**
+   * The [member] this invocation refers to.
+   */
+  Element methodElement;
+}
+
+/**
+ * A convenient type alias for some functions that emit keyed values.
+ */
+typedef void DefineStubFunction(String invocationName, js.Expression value);
+
+/**
+ * A data structure for collecting fragments of a class definition.
+ */
+class ClassBuilder {
+  final List<js.Property> properties = <js.Property>[];
+
+  // Has the same signature as [DefineStubFunction].
+  void addProperty(String name, js.Expression value) {
+    properties.add(new js.Property(js.string(name), value));
+  }
+
+  js.Expression toObjectInitializer() => new js.ObjectInitializer(properties);
+}
+
+/**
+ * Generates the code for all used classes in the program. Static fields (even
+ * in classes) are ignored, since they can be treated as non-class elements.
+ *
+ * The code for the containing (used) methods must exist in the [:universe:].
+ */
+class CodeEmitterTask extends CompilerTask {
+  bool needsInheritFunction = false;
+  bool needsDefineClass = false;
+  bool needsClosureClass = false;
+  bool needsLazyInitializer = false;
+  final Namer namer;
+  ConstantEmitter constantEmitter;
+  NativeEmitter nativeEmitter;
+  CodeBuffer boundClosureBuffer;
+  CodeBuffer mainBuffer;
+  /** Shorter access to [isolatePropertiesName]. Both here in the code, as
+      well as in the generated code. */
+  String isolateProperties;
+  String classesCollector;
+  Set<ClassElement> neededClasses;
+  // TODO(ngeoffray): remove this field.
+  Set<ClassElement> instantiatedClasses;
+
+  String get _ => compiler.enableMinification ? "" : " ";
+  String get n => compiler.enableMinification ? "" : "\n";
+  String get N => compiler.enableMinification ? "\n" : ";\n";
+
+  /**
+   * A cache of closures that are used to closurize instance methods.
+   * A closure is dynamically bound to the instance used when
+   * closurized.
+   */
+  final Map<int, String> boundClosureCache;
+
+  /**
+   * A cache of closures that are used to closurize instance methods
+   * of interceptors. These closures are dynamically bound to the
+   * interceptor instance, and the actual receiver of the method.
+   */
+  final Map<int, String> interceptorClosureCache;
+
+  /**
+   * Raw ClassElement symbols occuring in is-checks and type assertions.  If the
+   * program contains parameterized checks `x is Set<int>` and
+   * `x is Set<String>` then the ClassElement `Set` will occur once in
+   * [checkedClasses].
+   */
+  Set<ClassElement> checkedClasses;
+
+  /**
+   * Raw Typedef symbols occuring in is-checks and type assertions.  If the
+   * program contains `x is F<int>` and `x is F<bool>` then the TypedefElement
+   * `F` will occur once in [checkedTypedefs].
+   */
+  Set<TypedefElement> checkedTypedefs;
+
+  final bool generateSourceMap;
+
+  CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap)
+      : boundClosureBuffer = new CodeBuffer(),
+        mainBuffer = new CodeBuffer(),
+        this.namer = namer,
+        boundClosureCache = new Map<int, String>(),
+        interceptorClosureCache = new Map<int, String>(),
+        constantEmitter = new ConstantEmitter(compiler, namer),
+        super(compiler) {
+    nativeEmitter = new NativeEmitter(this);
+  }
+
+  void computeRequiredTypeChecks() {
+    assert(checkedClasses == null);
+    checkedClasses = new Set<ClassElement>();
+    checkedTypedefs = new Set<TypedefElement>();
+    compiler.codegenWorld.isChecks.forEach((DartType t) {
+      if (t is InterfaceType) {
+        checkedClasses.add(t.element);
+      } else if (t is TypedefType) {
+        checkedTypedefs.add(t.element);
+      }
+    });
+  }
+
+  js.Expression constantReference(Constant value) {
+    return constantEmitter.reference(value);
+  }
+
+  js.Expression constantInitializerExpression(Constant value) {
+    return constantEmitter.initializationExpression(value);
+  }
+
+  String get name => 'CodeEmitter';
+
+  String get defineClassName
+      => '${namer.isolateName}.\$defineClass';
+  String get currentGenerateAccessorName
+      => '${namer.CURRENT_ISOLATE}.\$generateAccessor';
+  String get generateAccessorHolder
+      => '$isolatePropertiesName.\$generateAccessor';
+  String get finishClassesName
+      => '${namer.isolateName}.\$finishClasses';
+  String get finishIsolateConstructorName
+      => '${namer.isolateName}.\$finishIsolateConstructor';
+  String get pendingClassesName
+      => '${namer.isolateName}.\$pendingClasses';
+  String get isolatePropertiesName
+      => '${namer.isolateName}.${namer.isolatePropertiesName}';
+  String get supportsProtoName
+      => 'supportsProto';
+  String get lazyInitializerName
+      => '${namer.isolateName}.\$lazy';
+
+  // Property name suffixes.  If the accessors are renaming then the format
+  // is <accessorName>:<fieldName><suffix>.  We use the suffix to know whether
+  // to look for the ':' separator in order to avoid doing the indexOf operation
+  // on every single property (they are quite rare).  None of these characters
+  // are legal in an identifier and they are related by bit patterns.
+  // setter          <          0x3c
+  // both            =          0x3d
+  // getter          >          0x3e
+  // renaming setter |          0x7c
+  // renaming both   }          0x7d
+  // renaming getter ~          0x7e
+  const SUFFIX_MASK = 0x3f;
+  const FIRST_SUFFIX_CODE = 0x3c;
+  const SETTER_CODE = 0x3c;
+  const GETTER_SETTER_CODE = 0x3d;
+  const GETTER_CODE = 0x3e;
+  const RENAMING_FLAG = 0x40;
+  String needsGetterCode(String variable) => '($variable & 3) > 0';
+  String needsSetterCode(String variable) => '($variable & 2) == 0';
+  String isRenaming(String variable) => '($variable & $RENAMING_FLAG) != 0';
+
+  String get generateAccessorFunction {
+    return """
+function generateAccessor(field, prototype) {
+  var len = field.length;
+  var lastCharCode = field.charCodeAt(len - 1);
+  var needsAccessor = (lastCharCode & $SUFFIX_MASK) >= $FIRST_SUFFIX_CODE;
+  if (needsAccessor) {
+    var needsGetter = ${needsGetterCode('lastCharCode')};
+    var needsSetter = ${needsSetterCode('lastCharCode')};
+    var renaming = ${isRenaming('lastCharCode')};
+    var accessorName = field = field.substring(0, len - 1);
+    if (renaming) {
+      var divider = field.indexOf(":");
+      accessorName = field.substring(0, divider);
+      field = field.substring(divider + 1);
+    }
+    if (needsGetter) {
+      var getterString = "return this." + field + ";";
+      prototype["get\$" + accessorName] = new Function(getterString);
+    }
+    if (needsSetter) {
+      var setterString = "this." + field + " = v;";
+      prototype["set\$" + accessorName] = new Function("v", setterString);
+    }
+  }
+  return field;
+}""";
+  }
+
+  String get defineClassFunction {
+    // First the class name, then the field names in an array and the members
+    // (inside an Object literal).
+    // The caller can also pass in the constructor as a function if needed.
+    //
+    // Example:
+    // defineClass("A", ["x", "y"], {
+    //  foo$1: function(y) {
+    //   print(this.x + y);
+    //  },
+    //  bar$2: function(t, v) {
+    //   this.x = t - v;
+    //  },
+    // });
+    return """
+function(cls, fields, prototype) {
+  var constructor;
+  if (typeof fields == 'function') {
+    constructor = fields;
+  } else {
+    var str = "function " + cls + "(";
+    var body = "";
+    for (var i = 0; i < fields.length; i++) {
+      if (i != 0) str += ", ";
+      var field = fields[i];
+      field = generateAccessor(field, prototype);
+      str += field;
+      body += "this." + field + " = " + field + ";\\n";
+    }
+    str += ") {" + body + "}\\n";
+    str += "return " + cls + ";";
+    constructor = new Function(str)();
+  }
+  constructor.prototype = prototype;
+  constructor.builtin\$cls = cls;
+  return constructor;
+}""";
+  }
+
+  /** Needs defineClass to be defined. */
+  String get protoSupportCheck {
+    // On Firefox and Webkit browsers we can manipulate the __proto__
+    // directly. Opera claims to have __proto__ support, but it is buggy.
+    // So we have to do more checks.
+    // Opera bug was filed as DSK-370158, and fixed as CORE-47615
+    // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes).
+    // If the browser does not support __proto__ we need to instantiate an
+    // object with the correct (internal) prototype set up correctly, and then
+    // copy the members.
+
+    return '''
+var $supportsProtoName = false;
+var tmp = $defineClassName('c', ['f?'], {}).prototype;
+if (tmp.__proto__) {
+  tmp.__proto__ = {};
+  if (typeof tmp.get\$f !== 'undefined') $supportsProtoName = true;
+}
+''';
+  }
+
+  String get finishClassesFunction {
+    // 'defineClass' does not require the classes to be constructed in order.
+    // Classes are initially just stored in the 'pendingClasses' field.
+    // 'finishClasses' takes all pending classes and sets up the prototype.
+    // Once set up, the constructors prototype field satisfy:
+    //  - it contains all (local) members.
+    //  - its internal prototype (__proto__) points to the superclass'
+    //    prototype field.
+    //  - the prototype's constructor field points to the JavaScript
+    //    constructor.
+    // For engines where we have access to the '__proto__' we can manipulate
+    // the object literal directly. For other engines we have to create a new
+    // object and copy over the members.
+    return '''
+function(collectedClasses) {
+  var hasOwnProperty = Object.prototype.hasOwnProperty;
+  for (var cls in collectedClasses) {
+    if (hasOwnProperty.call(collectedClasses, cls)) {
+      var desc = collectedClasses[cls];
+'''/* The 'fields' are either a constructor function or a string encoding
+      fields, constructor and superclass.  Get the superclass and the fields
+      in the format Super;field1,field2 from the null-string property on the
+      descriptor. */'''
+      var fields = desc[''], supr;
+      if (typeof fields == 'string') {
+        var s = fields.split(';'); supr = s[0];
+        fields = s[1] == '' ? [] : s[1].split(',');
+      } else {
+        supr = desc['super'];
+      }
+      $isolatePropertiesName[cls] = $defineClassName(cls, fields, desc);
+      if (supr) $pendingClassesName[cls] = supr;
+    }
+  }
+  var pendingClasses = $pendingClassesName;
+'''/* FinishClasses can be called multiple times. This means that we need to
+      clear the pendingClasses property. */'''
+  $pendingClassesName = {};
+  var finishedClasses = {};
+  function finishClass(cls) {
+'''/* Opera does not support 'getOwnPropertyNames'. Therefore we use
+      hasOwnProperty instead. */'''
+    var hasOwnProperty = Object.prototype.hasOwnProperty;
+    if (hasOwnProperty.call(finishedClasses, cls)) return;
+    finishedClasses[cls] = true;
+    var superclass = pendingClasses[cls];
+'''/* The superclass is only false (empty string) for Dart's Object class. */'''
+    if (!superclass) return;
+    finishClass(superclass);
+    var constructor = $isolatePropertiesName[cls];
+    var superConstructor = $isolatePropertiesName[superclass];
+    var prototype = constructor.prototype;
+    if ($supportsProtoName) {
+      prototype.__proto__ = superConstructor.prototype;
+      prototype.constructor = constructor;
+    } else {
+      function tmp() {};
+      tmp.prototype = superConstructor.prototype;
+      var newPrototype = new tmp();
+      constructor.prototype = newPrototype;
+      newPrototype.constructor = constructor;
+      for (var member in prototype) {
+        if (!member) continue;  '''/* Short version of: if (member == '') */'''
+        if (hasOwnProperty.call(prototype, member)) {
+          newPrototype[member] = prototype[member];
+        }
+      }
+    }
+  }
+  for (var cls in pendingClasses) finishClass(cls);
+}''';
+  }
+
+  String get finishIsolateConstructorFunction {
+    String isolate = namer.isolateName;
+    // We replace the old Isolate function with a new one that initializes
+    // all its field with the initial (and often final) value of all globals.
+    // This has two advantages:
+    //   1. the properties are in the object itself (thus avoiding to go through
+    //      the prototype when looking up globals.
+    //   2. a new isolate goes through a (usually well optimized) constructor
+    //      function of the form: "function() { this.x = ...; this.y = ...; }".
+    //
+    // Example: If [isolateProperties] is an object containing: x = 3 and
+    // A = function A() { /* constructor of class A. */ }, then we generate:
+    // str = "{
+    //   var isolateProperties = Isolate.$isolateProperties;
+    //   this.x = isolateProperties.x;
+    //   this.A = isolateProperties.A;
+    // }";
+    // which is then dynamically evaluated:
+    //   var newIsolate = new Function(str);
+    //
+    // We also copy over old values like the prototype, and the
+    // isolateProperties themselves.
+    return """function(oldIsolate) {
+  var isolateProperties = oldIsolate.${namer.isolatePropertiesName};
+  var isolatePrototype = oldIsolate.prototype;
+  var str = "{\\n";
+  str += "var properties = $isolate.${namer.isolatePropertiesName};\\n";
+  for (var staticName in isolateProperties) {
+    if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) {
+      str += "this." + staticName + "= properties." + staticName + ";\\n";
+    }
+  }
+  str += "}\\n";
+  var newIsolate = new Function(str);
+  newIsolate.prototype = isolatePrototype;
+  isolatePrototype.constructor = newIsolate;
+  newIsolate.${namer.isolatePropertiesName} = isolateProperties;
+  return newIsolate;
+}""";
+  }
+
+  String get lazyInitializerFunction {
+    String isolate = namer.CURRENT_ISOLATE;
+    return """
+function(prototype, staticName, fieldName, getterName, lazyValue) {
+  var getter = new Function("{ return $isolate." + fieldName + ";}");
+$lazyInitializerLogic
+}""";
+  }
+
+  String get lazyInitializerLogic {
+    String isolate = namer.CURRENT_ISOLATE;
+    JavaScriptBackend backend = compiler.backend;
+    String cyclicThrow = namer.isolateAccess(backend.cyclicThrowHelper);
+    return """
+  var sentinelUndefined = {};
+  var sentinelInProgress = {};
+  prototype[fieldName] = sentinelUndefined;
+  prototype[getterName] = function() {
+    var result = $isolate[fieldName];
+    try {
+      if (result === sentinelUndefined) {
+        $isolate[fieldName] = sentinelInProgress;
+        try {
+          result = $isolate[fieldName] = lazyValue();
+        } finally {
+""" // Use try-finally, not try-catch/throw as it destroys the stack trace.
+"""
+          if (result === sentinelUndefined) {
+            if ($isolate[fieldName] === sentinelInProgress) {
+              $isolate[fieldName] = null;
+            }
+          }
+        }
+      } else if (result === sentinelInProgress) {
+        $cyclicThrow(staticName);
+      }
+      return result;
+    } finally {
+      $isolate[getterName] = getter;
+    }
+  };""";
+  }
+
+  void addDefineClassAndFinishClassFunctionsIfNecessary(CodeBuffer buffer) {
+    if (needsDefineClass) {
+      // Declare function called generateAccessor.  This is used in
+      // defineClassFunction (it's a local declaration in init()).
+      buffer.add("$generateAccessorFunction$N");
+      buffer.add("$generateAccessorHolder = generateAccessor$N");
+      buffer.add("$defineClassName = $defineClassFunction$N");
+      buffer.add(protoSupportCheck);
+      buffer.add("$pendingClassesName = {}$N");
+      buffer.add("$finishClassesName = $finishClassesFunction$N");
+    }
+  }
+
+  void addLazyInitializerFunctionIfNecessary(CodeBuffer buffer) {
+    if (needsLazyInitializer) {
+      buffer.add("$lazyInitializerName = $lazyInitializerFunction$N");
+    }
+  }
+
+  void emitFinishIsolateConstructor(CodeBuffer buffer) {
+    String name = finishIsolateConstructorName;
+    String value = finishIsolateConstructorFunction;
+    buffer.add("$name = $value$N");
+  }
+
+  void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) {
+    String isolate = namer.isolateName;
+    buffer.add("$isolate = $finishIsolateConstructorName($isolate)$N");
+  }
+
+  /**
+   * Generate stubs to handle invocation of methods with optional
+   * arguments.
+   *
+   * A method like [: foo([x]) :] may be invoked by the following
+   * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this
+   * function for detailed examples.
+   */
+  void addParameterStub(FunctionElement member,
+                        Selector selector,
+                        DefineStubFunction defineStub,
+                        Set<String> alreadyGenerated) {
+    FunctionSignature parameters = member.computeSignature(compiler);
+    int positionalArgumentCount = selector.positionalArgumentCount;
+    if (positionalArgumentCount == parameters.parameterCount) {
+      assert(selector.namedArgumentCount == 0);
+      return;
+    }
+    if (parameters.optionalParametersAreNamed
+        && selector.namedArgumentCount == parameters.optionalParameterCount) {
+      // If the selector has the same number of named arguments as
+      // the element, we don't need to add a stub. The call site will
+      // hit the method directly.
+      return;
+    }
+    ConstantHandler handler = compiler.constantHandler;
+    List<SourceString> names = selector.getOrderedNamedArguments();
+
+    String invocationName = namer.invocationName(selector);
+    if (alreadyGenerated.contains(invocationName)) return;
+    alreadyGenerated.add(invocationName);
+
+    JavaScriptBackend backend = compiler.backend;
+    bool isInterceptorClass =
+        backend.isInterceptorClass(member.getEnclosingClass());
+
+    // If the method is in an interceptor class, we need to also pass
+    // the actual receiver.
+    int extraArgumentCount = isInterceptorClass ? 1 : 0;
+    // Use '$receiver' to avoid clashes with other parameter names. Using
+    // '$receiver' works because [:namer.safeName:] used for getting parameter
+    // names never returns a name beginning with a single '$'.
+    String receiverArgumentName = r'$receiver';
+
+    // The parameters that this stub takes.
+    List<js.Parameter> parametersBuffer =
+        new List<js.Parameter>.fixedLength(
+            selector.argumentCount + extraArgumentCount);
+    // The arguments that will be passed to the real method.
+    List<js.Expression> argumentsBuffer =
+        new List<js.Expression>.fixedLength(
+            parameters.parameterCount + extraArgumentCount);
+
+    int count = 0;
+    if (isInterceptorClass) {
+      count++;
+      parametersBuffer[0] = new js.Parameter(receiverArgumentName);
+      argumentsBuffer[0] = new js.VariableUse(receiverArgumentName);
+    }
+
+    int indexOfLastOptionalArgumentInParameters = positionalArgumentCount - 1;
+    TreeElements elements =
+        compiler.enqueuer.resolution.getCachedElements(member);
+
+    parameters.orderedForEachParameter((Element element) {
+      String jsName = backend.namer.safeName(element.name.slowToString());
+      assert(jsName != receiverArgumentName);
+      int optionalParameterStart = positionalArgumentCount + extraArgumentCount;
+      if (count < optionalParameterStart) {
+        parametersBuffer[count] = new js.Parameter(jsName);
+        argumentsBuffer[count] = new js.VariableUse(jsName);
+      } else {
+        int index = names.indexOf(element.name);
+        if (index != -1) {
+          indexOfLastOptionalArgumentInParameters = count;
+          // The order of the named arguments is not the same as the
+          // one in the real method (which is in Dart source order).
+          argumentsBuffer[count] = new js.VariableUse(jsName);
+          parametersBuffer[optionalParameterStart + index] =
+              new js.Parameter(jsName);
+        // Note that [elements] may be null for a synthesized [member].
+        } else if (elements != null && elements.isParameterChecked(element)) {
+          argumentsBuffer[count] = constantReference(SentinelConstant.SENTINEL);
+        } else {
+          Constant value = handler.initialVariableValues[element];
+          if (value == null) {
+            argumentsBuffer[count] = constantReference(new NullConstant());
+          } else {
+            if (!value.isNull()) {
+              // If the value is the null constant, we should not pass it
+              // down to the native method.
+              indexOfLastOptionalArgumentInParameters = count;
+            }
+            argumentsBuffer[count] = constantReference(value);
+          }
+        }
+      }
+      count++;
+    });
+
+    List<js.Statement> body;
+    if (member.hasFixedBackendName()) {
+      body = nativeEmitter.generateParameterStubStatements(
+          member, invocationName, parametersBuffer, argumentsBuffer,
+          indexOfLastOptionalArgumentInParameters);
+    } else {
+      body = <js.Statement>[
+          new js.Return(
+              new js.VariableUse('this')
+                  .dot(namer.getName(member))
+                  .callWith(argumentsBuffer))];
+    }
+
+    js.Fun function = new js.Fun(parametersBuffer, new js.Block(body));
+
+    defineStub(invocationName, function);
+  }
+
+  void addParameterStubs(FunctionElement member,
+                         DefineStubFunction defineStub) {
+    // We fill the lists depending on the selector. For example,
+    // take method foo:
+    //    foo(a, b, {c, d});
+    //
+    // We may have multiple ways of calling foo:
+    // (1) foo(1, 2);
+    // (2) foo(1, 2, c: 3);
+    // (3) foo(1, 2, d: 4);
+    // (4) foo(1, 2, c: 3, d: 4);
+    // (5) foo(1, 2, d: 4, c: 3);
+    //
+    // What we generate at the call sites are:
+    // (1) foo$2(1, 2);
+    // (2) foo$3$c(1, 2, 3);
+    // (3) foo$3$d(1, 2, 4);
+    // (4) foo$4$c$d(1, 2, 3, 4);
+    // (5) foo$4$c$d(1, 2, 3, 4);
+    //
+    // The stubs we generate are (expressed in Dart):
+    // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null)
+    // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null);
+    // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d);
+    // (4) No stub generated, call is direct.
+    // (5) No stub generated, call is direct.
+
+    // Keep a cache of which stubs have already been generated, to
+    // avoid duplicates. Note that even if selectors are
+    // canonicalized, we would still need this cache: a typed selector
+    // on A and a typed selector on B could yield the same stub.
+    Set<String> generatedStubNames = new Set<String>();
+    if (compiler.enabledFunctionApply
+        && member.name == namer.closureInvocationSelectorName) {
+      // If [Function.apply] is called, we pessimistically compile all
+      // possible stubs for this closure.
+      FunctionSignature signature = member.computeSignature(compiler);
+      Set<Selector> selectors = signature.optionalParametersAreNamed
+          ? computeNamedSelectors(signature, member)
+          : computeOptionalSelectors(signature, member);
+      for (Selector selector in selectors) {
+        addParameterStub(member, selector, defineStub, generatedStubNames);
+      }
+    } else {
+      Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
+      if (selectors == null) return;
+      for (Selector selector in selectors) {
+        if (!selector.applies(member, compiler)) continue;
+        addParameterStub(member, selector, defineStub, generatedStubNames);
+      }
+    }
+  }
+
+  /**
+   * Compute the set of possible selectors in the presence of named
+   * parameters.
+   */
+  Set<Selector> computeNamedSelectors(FunctionSignature signature,
+                                      FunctionElement element) {
+    Set<Selector> selectors = new Set<Selector>();
+    // Add the selector that does not have any optional argument.
+    selectors.add(new Selector(SelectorKind.CALL,
+                               element.name,
+                               element.getLibrary(),
+                               signature.requiredParameterCount,
+                               <SourceString>[]));
+
+    // For each optional parameter, we iterator over the set of
+    // already computed selectors and create new selectors with that
+    // parameter now being passed.
+    signature.forEachOptionalParameter((Element element) {
+      Set<Selector> newSet = new Set<Selector>();
+      selectors.forEach((Selector other) {
+        List<SourceString> namedArguments = [element.name];
+        namedArguments.addAll(other.namedArguments);
+        newSet.add(new Selector(other.kind,
+                                other.name,
+                                other.library,
+                                other.argumentCount + 1,
+                                namedArguments));
+      });
+      selectors.addAll(newSet);
+    });
+    return selectors;
+  }
+
+  /**
+   * Compute the set of possible selectors in the presence of optional
+   * non-named parameters.
+   */
+  Set<Selector> computeOptionalSelectors(FunctionSignature signature,
+                                         FunctionElement element) {
+    Set<Selector> selectors = new Set<Selector>();
+    // Add the selector that does not have any optional argument.
+    selectors.add(new Selector(SelectorKind.CALL,
+                               element.name,
+                               element.getLibrary(),
+                               signature.requiredParameterCount,
+                               <SourceString>[]));
+
+    // For each optional parameter, we increment the number of passed
+    // argument.
+    for (int i = 1; i <= signature.optionalParameterCount; i++) {
+      selectors.add(new Selector(SelectorKind.CALL,
+                                 element.name,
+                                 element.getLibrary(),
+                                 signature.requiredParameterCount + i,
+                                 <SourceString>[]));
+    }
+    return selectors;
+  }
+
+  bool instanceFieldNeedsGetter(Element member) {
+    assert(member.isField());
+    return compiler.codegenWorld.hasInvokedGetter(member, compiler);
+  }
+
+  bool instanceFieldNeedsSetter(Element member) {
+    assert(member.isField());
+    return (!member.modifiers.isFinalOrConst())
+        && compiler.codegenWorld.hasInvokedSetter(member, compiler);
+  }
+
+  String compiledFieldName(Element member) {
+    assert(member.isField());
+    return member.hasFixedBackendName()
+        ? member.fixedBackendName()
+        : namer.getName(member);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void addInstanceMember(Element member, ClassBuilder builder) {
+    assert(invariant(member, member.isDeclaration));
+    // TODO(floitsch): we don't need to deal with members of
+    // uninstantiated classes, that have been overwritten by subclasses.
+
+    if (member.isFunction()
+        || member.isGenerativeConstructorBody()
+        || member.isAccessor()) {
+      if (member.isAbstract(compiler)) return;
+      JavaScriptBackend backend = compiler.backend;
+      js.Expression code = backend.generatedCode[member];
+      if (code == null) return;
+      builder.addProperty(namer.getName(member), code);
+      code = backend.generatedBailoutCode[member];
+      if (code != null) {
+        builder.addProperty(namer.getBailoutName(member), code);
+      }
+      FunctionElement function = member;
+      FunctionSignature parameters = function.computeSignature(compiler);
+      if (!parameters.optionalParameters.isEmpty) {
+        addParameterStubs(member, builder.addProperty);
+      }
+    } else if (!member.isField()) {
+      compiler.internalError('unexpected kind: "${member.kind}"',
+                             element: member);
+    }
+    emitExtraAccessors(member, builder);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [classElement] must be a declaration element.
+   */
+  void emitInstanceMembers(ClassElement classElement,
+                           ClassBuilder builder) {
+    assert(invariant(classElement, classElement.isDeclaration));
+    JavaScriptBackend backend = compiler.backend;
+    if (classElement == backend.objectInterceptorClass) {
+      emitInterceptorMethods(builder);
+      // The ObjectInterceptor does not have any instance methods.
+      return;
+    }
+
+    void visitMember(ClassElement enclosing, Element member) {
+      assert(invariant(classElement, member.isDeclaration));
+      if (member.isInstanceMember()) {
+        addInstanceMember(member, builder);
+      }
+    }
+
+    // TODO(kasperl): We should make sure to only emit one version of
+    // overridden methods. Right now, we rely on the ordering so the
+    // methods pulled in from mixins are replaced with the members
+    // from the class definition.
+
+    // If the class is a native class, we have to add the instance
+    // members defined in the non-native mixin applications used by
+    // the class.
+    visitNativeMixins(classElement, (MixinApplicationElement mixin) {
+      mixin.forEachMember(
+          visitMember,
+          includeBackendMembers: true,
+          includeSuperMembers: false);
+    });
+
+    classElement.implementation.forEachMember(
+        visitMember,
+        includeBackendMembers: true,
+        includeSuperMembers: false);
+
+    generateIsTestsOn(classElement, (Element other) {
+      js.Expression code;
+      if (compiler.objectClass == other) return;
+      if (nativeEmitter.requiresNativeIsCheck(other)) {
+        code = js.fun([], js.block1(js.return_(new js.LiteralBool(true))));
+      } else {
+        code = new js.LiteralBool(true);
+      }
+      builder.addProperty(namer.operatorIs(other), code);
+    });
+
+    if (identical(classElement, compiler.objectClass)
+        && compiler.enabledNoSuchMethod) {
+      // Emit the noSuchMethod handlers on the Object prototype now,
+      // so that the code in the dynamicFunction helper can find
+      // them. Note that this helper is invoked before analyzing the
+      // full JS script.
+      if (!nativeEmitter.handleNoSuchMethod) {
+        emitNoSuchMethodHandlers(builder.addProperty);
+      }
+    }
+
+    if (backend.isInterceptorClass(classElement)) {
+      // The operator== method in [:Object:] does not take the same
+      // number of arguments as an intercepted method, therefore we
+      // explicitely add one to all interceptor classes. Note that we
+      // would not have do do that if all intercepted methods had
+      // a calling convention where the receiver is the first
+      // parameter.
+      String name = backend.namer.publicInstanceMethodNameByArity(
+          const SourceString('=='), 1);
+      Function kind = (classElement == backend.jsNullClass)
+          ? js.equals
+          : js.strictEquals;
+      builder.addProperty(name, js.fun(['receiver', 'a'],
+          js.block1(js.return_(kind(js.use('receiver'), js.use('a'))))));
+    }
+  }
+
+  void emitRuntimeClassesAndTests(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    RuntimeTypeInformation rti = backend.rti;
+
+    TypeChecks typeChecks = rti.computeRequiredChecks();
+
+    bool needsHolder(ClassElement cls) {
+      return !neededClasses.contains(cls) || cls.isNative() ||
+          rti.isJsNative(cls);
+    }
+
+    void maybeGenerateHolder(ClassElement cls) {
+      if (!needsHolder(cls)) return;
+
+      String holder = namer.isolateAccess(cls);
+      String name = namer.getName(cls);
+      buffer.add("$holder$_=$_{builtin\$cls:$_'$name'");
+      for (ClassElement check in typeChecks[cls]) {
+        buffer.add(',$_${namer.operatorIs(check)}:${_}true');
+      };
+      buffer.add('}$N');
+    }
+
+    // Create representation objects for classes that we do not have a class
+    // definition for (because they are uninstantiated or native).
+    for (ClassElement cls in rti.allArguments) {
+      maybeGenerateHolder(cls);
+    }
+
+    // Add checks to the constructors of instantiated classes.
+    for (ClassElement cls in typeChecks) {
+      if (needsHolder(cls)) {
+        // We already emitted the is-checks in the object definition for this
+        // class.
+        continue;
+      }
+      String holder = namer.isolateAccess(cls);
+      for (ClassElement check in typeChecks[cls]) {
+        buffer.add('$holder.${namer.operatorIs(check)}$_=${_}true$N');
+      };
+    }
+  }
+
+  void visitNativeMixins(ClassElement classElement,
+                         void visit(MixinApplicationElement mixinApplication)) {
+    if (!classElement.isNative()) return;
+    // Use recursion to make sure to visit the superclasses before the
+    // subclasses. Once we start keeping track of the emitted fields
+    // and members, we're going to want to visit these in the other
+    // order so we get the most specialized definition first.
+    void recurse(ClassElement cls) {
+      if (cls == null || !cls.isMixinApplication) return;
+      recurse(cls.superclass);
+      assert(!cls.isNative());
+      visit(cls);
+    }
+    recurse(classElement.superclass);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [classElement] must be a declaration element.
+   */
+  void visitClassFields(ClassElement classElement,
+                        void addField(Element member,
+                                      String name,
+                                      String accessorName,
+                                      bool needsGetter,
+                                      bool needsSetter,
+                                      bool needsCheckedSetter)) {
+    assert(invariant(classElement, classElement.isDeclaration));
+    // If the class is never instantiated we still need to set it up for
+    // inheritance purposes, but we can simplify its JavaScript constructor.
+    bool isInstantiated =
+        compiler.codegenWorld.instantiatedClasses.contains(classElement);
+
+    void visitField(ClassElement enclosingClass, Element member) {
+      assert(invariant(classElement, member.isDeclaration));
+      LibraryElement library = member.getLibrary();
+      SourceString name = member.name;
+      bool isPrivate = name.isPrivate();
+
+      // Keep track of whether or not we're dealing with a field mixin
+      // into a native class.
+      bool isMixinNativeField =
+          classElement.isNative() && enclosingClass.isMixinApplication;
+
+      // See if we can dynamically create getters and setters.
+      // We can only generate getters and setters for [classElement] since
+      // the fields of super classes could be overwritten with getters or
+      // setters.
+      bool needsGetter = false;
+      bool needsSetter = false;
+      // We need to name shadowed fields differently, so they don't clash with
+      // the non-shadowed field.
+      bool isShadowed = false;
+      if (isMixinNativeField || identical(enclosingClass, classElement)) {
+        needsGetter = instanceFieldNeedsGetter(member);
+        needsSetter = instanceFieldNeedsSetter(member);
+      } else {
+        isShadowed = classElement.isShadowedByField(member);
+      }
+
+      if ((isInstantiated && !enclosingClass.isNative())
+          || needsGetter
+          || needsSetter) {
+        String accessorName = isShadowed
+            ? namer.shadowedFieldName(member)
+            : namer.getName(member);
+        String fieldName = member.hasFixedBackendName()
+            ? member.fixedBackendName()
+            : (isMixinNativeField ? member.name.slowToString() : accessorName);
+        bool needsCheckedSetter = false;
+        if (needsSetter && compiler.enableTypeAssertions
+            && canGenerateCheckedSetter(member)) {
+          needsCheckedSetter = true;
+          needsSetter = false;
+        }
+        // Getters and setters with suffixes will be generated dynamically.
+        addField(member,
+                 fieldName,
+                 accessorName,
+                 needsGetter,
+                 needsSetter,
+                 needsCheckedSetter);
+      }
+    }
+
+    // TODO(kasperl): We should make sure to only emit one version of
+    // overridden fields. Right now, we rely on the ordering so the
+    // fields pulled in from mixins are replaced with the fields from
+    // the class definition.
+
+    // If the class is a native class, we have to add the fields
+    // defined in the non-native mixin applications used by the class.
+    visitNativeMixins(classElement, (MixinApplicationElement mixin) {
+      mixin.forEachInstanceField(
+          visitField,
+          includeBackendMembers: true,
+          includeSuperMembers: false);
+    });
+
+    // If a class is not instantiated then we add the field just so we can
+    // generate the field getter/setter dynamically. Since this is only
+    // allowed on fields that are in [classElement] we don't need to visit
+    // superclasses for non-instantiated classes.
+    classElement.implementation.forEachInstanceField(
+        visitField,
+        includeBackendMembers: true,
+        includeSuperMembers: isInstantiated && !classElement.isNative());
+  }
+
+  void generateGetter(Element member, String fieldName, String accessorName,
+                      ClassBuilder builder) {
+    String getterName = namer.getterNameFromAccessorName(accessorName);
+    builder.addProperty(getterName,
+        js.fun([], js.block1(js.return_(js.use('this').dot(fieldName)))));
+  }
+
+  void generateSetter(Element member, String fieldName, String accessorName,
+                      ClassBuilder builder) {
+    String setterName = namer.setterNameFromAccessorName(accessorName);
+    builder.addProperty(setterName,
+        js.fun(['v'],
+            js.block1(
+                new js.ExpressionStatement(
+                    js.assign(js.use('this').dot(fieldName), js.use('v'))))));
+  }
+
+  bool canGenerateCheckedSetter(Element member) {
+    DartType type = member.computeType(compiler);
+    if (type.element.isTypeVariable()
+        || type.element == compiler.dynamicClass
+        || type.element == compiler.objectClass) {
+      // TODO(ngeoffray): Support type checks on type parameters.
+      return false;
+    }
+    return true;
+  }
+
+  void generateCheckedSetter(Element member,
+                             String fieldName,
+                             String accessorName,
+                             ClassBuilder builder) {
+    assert(canGenerateCheckedSetter(member));
+    DartType type = member.computeType(compiler);
+    // TODO(ahe): Generate a dynamic type error here.
+    if (type.element.isErroneous()) return;
+    SourceString helper = compiler.backend.getCheckedModeHelper(type);
+    FunctionElement helperElement = compiler.findHelper(helper);
+    String helperName = namer.isolateAccess(helperElement);
+    List<js.Expression> arguments = <js.Expression>[js.use('v')];
+    if (helperElement.computeSignature(compiler).parameterCount != 1) {
+      arguments.add(js.string(namer.operatorIs(type.element)));
+    }
+
+    String setterName = namer.setterNameFromAccessorName(accessorName);
+    builder.addProperty(setterName,
+        js.fun(['v'],
+            js.block1(
+                new js.ExpressionStatement(
+                    js.assign(
+                        js.use('this').dot(fieldName),
+                        js.call(js.use(helperName), arguments))))));
+  }
+
+  void emitClassConstructor(ClassElement classElement, ClassBuilder builder) {
+    /* Do nothing. */
+  }
+
+  void emitSuper(String superName, ClassBuilder builder) {
+    /* Do nothing. */
+  }
+
+  void emitClassFields(ClassElement classElement,
+                       ClassBuilder builder,
+                       { String superClass: "",
+                         bool classIsNative: false}) {
+    bool isFirstField = true;
+    StringBuffer buffer = new StringBuffer();
+    if (!classIsNative) {
+      buffer.add('$superClass;');
+    }
+    visitClassFields(classElement, (Element member,
+                                    String name,
+                                    String accessorName,
+                                    bool needsGetter,
+                                    bool needsSetter,
+                                    bool needsCheckedSetter) {
+      // Ignore needsCheckedSetter - that is handled below.
+      bool needsAccessor = (needsGetter || needsSetter);
+      // We need to output the fields for non-native classes so we can auto-
+      // generate the constructor.  For native classes there are no
+      // constructors, so we don't need the fields unless we are generating
+      // accessors at runtime.
+      if (!classIsNative || needsAccessor) {
+        // Emit correct commas.
+        if (isFirstField) {
+          isFirstField = false;
+        } else {
+          buffer.add(',');
+        }
+        int flag = 0;
+        if (!needsAccessor) {
+          // Emit field for constructor generation.
+          assert(!classIsNative);
+          buffer.add(name);
+        } else {
+          // Emit (possibly renaming) field name so we can add accessors at
+          // runtime.
+          buffer.add(accessorName);
+          if (name != accessorName) {
+            buffer.add(':$name');
+            // Only the native classes can have renaming accessors.
+            assert(classIsNative);
+            flag = RENAMING_FLAG;
+          }
+        }
+        if (needsGetter && needsSetter) {
+          buffer.addCharCode(GETTER_SETTER_CODE + flag);
+        } else if (needsGetter) {
+          buffer.addCharCode(GETTER_CODE + flag);
+        } else if (needsSetter) {
+          buffer.addCharCode(SETTER_CODE + flag);
+        }
+      }
+    });
+
+    String compactClassData = buffer.toString();
+    if (compactClassData.length > 0) {
+      builder.addProperty('', js.string(compactClassData));
+    }
+  }
+
+  void emitClassGettersSetters(ClassElement classElement,
+                               ClassBuilder builder) {
+
+    visitClassFields(classElement, (Element member,
+                                    String name,
+                                    String accessorName,
+                                    bool needsGetter,
+                                    bool needsSetter,
+                                    bool needsCheckedSetter) {
+      compiler.withCurrentElement(member, () {
+        if (needsCheckedSetter) {
+          assert(!needsSetter);
+          generateCheckedSetter(member, name, accessorName, builder);
+        }
+        if (!getterAndSetterCanBeImplementedByFieldSpec) {
+          if (needsGetter) {
+            generateGetter(member, name, accessorName, builder);
+          }
+          if (needsSetter) {
+            generateSetter(member, name, accessorName, builder);
+          }
+        }
+      });
+    });
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [classElement] must be a declaration element.
+   */
+  void generateClass(ClassElement classElement, CodeBuffer buffer) {
+    assert(invariant(classElement, classElement.isDeclaration));
+    if (classElement.isNative()) {
+      nativeEmitter.generateNativeClass(classElement);
+      return;
+    }
+
+    needsDefineClass = true;
+    String className = namer.getName(classElement);
+
+    // Find the first non-native superclass.
+    ClassElement superclass = classElement.superclass;
+    while (superclass != null && superclass.isNative()) {
+      superclass = superclass.superclass;
+    }
+
+    String superName = "";
+    if (superclass != null) {
+      superName = namer.getName(superclass);
+    }
+
+    ClassBuilder builder = new ClassBuilder();
+
+    emitClassConstructor(classElement, builder);
+    emitSuper(superName, builder);
+    emitClassFields(classElement, builder,
+                    superClass: superName, classIsNative: false);
+    emitClassGettersSetters(classElement, builder);
+    emitInstanceMembers(classElement, builder);
+
+    js.Expression init =
+        js.assign(
+            js.use(classesCollector).dot(className),
+            builder.toObjectInitializer());
+    buffer.add(js.prettyPrint(init, compiler));
+    buffer.add('$N$n');
+  }
+
+  bool get getterAndSetterCanBeImplementedByFieldSpec => true;
+
+  int _selectorRank(Selector selector) {
+    int arity = selector.argumentCount * 3;
+    if (selector.isGetter()) return arity + 2;
+    if (selector.isSetter()) return arity + 1;
+    return arity;
+  }
+
+  int _compareSelectorNames(Selector selector1, Selector selector2) {
+    String name1 = selector1.name.toString();
+    String name2 = selector2.name.toString();
+    if (name1 != name2) return Comparable.compare(name1, name2);
+    return _selectorRank(selector1) - _selectorRank(selector2);
+  }
+
+  void emitInterceptorMethods(ClassBuilder builder) {
+    JavaScriptBackend backend = compiler.backend;
+    // Emit forwarders for the ObjectInterceptor class. We need to
+    // emit all possible sends on intercepted methods.
+    for (Selector selector in
+         backend.usedInterceptors.toList()..sort(_compareSelectorNames)) {
+      List<js.Parameter> parameters = <js.Parameter>[];
+      List<js.Expression> arguments = <js.Expression>[];
+      parameters.add(new js.Parameter('receiver'));
+
+      String name = backend.namer.invocationName(selector);
+      if (selector.isSetter()) {
+        parameters.add(new js.Parameter('value'));
+        arguments.add(new js.VariableUse('value'));
+      } else {
+        for (int i = 0; i < selector.argumentCount; i++) {
+          String argName = 'a$i';
+          parameters.add(new js.Parameter(argName));
+          arguments.add(new js.VariableUse(argName));
+        }
+      }
+      js.Fun function =
+          new js.Fun(parameters,
+              new js.Block(
+                  <js.Statement>[
+                      new js.Return(
+                          new js.VariableUse('receiver')
+                              .dot(name)
+                              .callWith(arguments))]));
+      builder.addProperty(name, function);
+    }
+  }
+
+  Iterable<Element> getTypedefChecksOn(DartType type) {
+    bool isSubtype(TypedefElement typedef) {
+      FunctionType typedefType =
+          typedef.computeType(compiler).unalias(compiler);
+      return compiler.types.isSubtype(type, typedefType);
+    }
+    return checkedTypedefs.where(isSubtype).toList()
+        ..sort(Elements.compareByPosition);
+  }
+
+  /**
+   * Generate "is tests" for [cls]: itself, and the "is tests" for the
+   * classes it implements. We don't need to add the "is tests" of the
+   * super class because they will be inherited at runtime.
+   */
+  void generateIsTestsOn(ClassElement cls,
+                         void emitIsTest(Element element)) {
+    if (checkedClasses.contains(cls)) {
+      emitIsTest(cls);
+    }
+
+    Set<Element> generated = new Set<Element>();
+    // A class that defines a [:call:] method implicitly implements
+    // [Function] and needs checks for all typedefs that are used in is-checks.
+    if (checkedClasses.contains(compiler.functionClass) ||
+        !checkedTypedefs.isEmpty) {
+      FunctionElement call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME);
+      if (call == null) {
+        // If [cls] is a closure, it has a synthetic call operator method.
+        call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME);
+      }
+      if (call != null) {
+        generateInterfacesIsTests(compiler.functionClass,
+                                  emitIsTest,
+                                  generated);
+        getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest);
+      }
+    }
+
+    for (DartType interfaceType in cls.interfaces) {
+      generateInterfacesIsTests(interfaceType.element, emitIsTest, generated);
+    }
+
+    // For native classes, we also have to run through their mixin
+    // applications and make sure we deal with 'is' tests correctly
+    // for those.
+    visitNativeMixins(cls, (MixinApplicationElement mixin) {
+      for (DartType interfaceType in mixin.interfaces) {
+        ClassElement interfaceElement = interfaceType.element;
+        generateInterfacesIsTests(interfaceType.element, emitIsTest, generated);
+      }
+    });
+  }
+
+  /**
+   * Generate "is tests" where [cls] is being implemented.
+   */
+  void generateInterfacesIsTests(ClassElement cls,
+                                 void emitIsTest(ClassElement element),
+                                 Set<Element> alreadyGenerated) {
+    void tryEmitTest(ClassElement cls) {
+      if (!alreadyGenerated.contains(cls) && checkedClasses.contains(cls)) {
+        alreadyGenerated.add(cls);
+        emitIsTest(cls);
+      }
+    };
+
+    tryEmitTest(cls);
+
+    for (DartType interfaceType in cls.interfaces) {
+      Element element = interfaceType.element;
+      tryEmitTest(element);
+      generateInterfacesIsTests(element, emitIsTest, alreadyGenerated);
+    }
+
+    // We need to also emit "is checks" for the superclass and its supertypes.
+    ClassElement superclass = cls.superclass;
+    if (superclass != null) {
+      tryEmitTest(superclass);
+      generateInterfacesIsTests(superclass, emitIsTest, alreadyGenerated);
+    }
+  }
+
+  /**
+   * Return a function that returns true if its argument is a class
+   * that needs to be emitted.
+   */
+  Function computeClassFilter() {
+    Set<ClassElement> unneededClasses = new Set<ClassElement>();
+    // The [Bool] class is not marked as abstract, but has a factory
+    // constructor that always throws. We never need to emit it.
+    unneededClasses.add(compiler.boolClass);
+
+    JavaScriptBackend backend = compiler.backend;
+
+    // Go over specialized interceptors and then constants to know which
+    // interceptors are needed.
+    Set<ClassElement> needed = new Set<ClassElement>();
+    backend.specializedGetInterceptors.forEach(
+        (_, Collection<ClassElement> elements) {
+          needed.addAll(elements);
+        }
+    );
+
+    ConstantHandler handler = compiler.constantHandler;
+    List<Constant> constants = handler.getConstantsForEmission();
+    for (Constant constant in constants) {
+      if (constant is ConstructedConstant) {
+        Element element = constant.computeType(compiler).element;
+        if (backend.isInterceptorClass(element)) {
+          needed.add(element);
+        }
+      }
+    }
+
+    // Add unneeded interceptors to the [unneededClasses] set.
+    for (ClassElement interceptor in backend.interceptedClasses.keys) {
+      if (!needed.contains(interceptor)) {
+        unneededClasses.add(interceptor);
+      }
+    }
+
+    return (ClassElement cls) => !unneededClasses.contains(cls);
+  }
+
+  void emitClasses(CodeBuffer buffer) {
+    // Compute the required type checks to know which classes need a
+    // 'is$' method.
+    computeRequiredTypeChecks();
+    List<ClassElement> sortedClasses =
+        new List<ClassElement>.from(neededClasses);
+    sortedClasses.sort((ClassElement class1, ClassElement class2) {
+      // We sort by the ids of the classes. There is no guarantee that these
+      // ids are meaningful (or even deterministic), but in the current
+      // implementation they are increasing within a source file.
+      return class1.id - class2.id;
+    });
+
+    // If we need noSuchMethod support, we run through all needed
+    // classes to figure out if we need the support on any native
+    // class. If so, we let the native emitter deal with it.
+    if (compiler.enabledNoSuchMethod) {
+      SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD;
+      Selector noSuchMethodSelector = new Selector.noSuchMethod();
+      for (ClassElement element in sortedClasses) {
+        if (!element.isNative()) continue;
+        Element member = element.lookupLocalMember(noSuchMethodName);
+        if (member == null) continue;
+        if (noSuchMethodSelector.applies(member, compiler)) {
+          nativeEmitter.handleNoSuchMethod = true;
+          break;
+        }
+      }
+    }
+
+    for (ClassElement element in sortedClasses) {
+      generateClass(element, buffer);
+    }
+
+    // The closure class could have become necessary because of the generation
+    // of stubs.
+    ClassElement closureClass = compiler.closureClass;
+    if (needsClosureClass && !instantiatedClasses.contains(closureClass)) {
+      generateClass(closureClass, buffer);
+    }
+  }
+
+  void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) {
+    if (needsDefineClass) {
+      buffer.add("$finishClassesName($classesCollector)$N");
+      // Reset the map.
+      buffer.add("$classesCollector$_=$_{}$N");
+    }
+  }
+
+  void emitStaticFunction(CodeBuffer buffer,
+                          String name,
+                          js.Expression functionExpression) {
+    js.Expression assignment =
+        js.assign(js.use(isolateProperties).dot(name), functionExpression);
+    buffer.add(js.prettyPrint(assignment, compiler));
+    buffer.add('$N$n');
+  }
+
+  void emitStaticFunctions(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    bool isStaticFunction(Element element) =>
+        !element.isInstanceMember() && !element.isField();
+
+    Iterable<Element> elements =
+        backend.generatedCode.keys.where(isStaticFunction);
+    Set<Element> pendingElementsWithBailouts =
+        backend.generatedBailoutCode.keys
+            .where(isStaticFunction)
+            .toSet();
+
+    for (Element element in Elements.sortedByPosition(elements)) {
+      js.Expression code = backend.generatedCode[element];
+      emitStaticFunction(buffer, namer.getName(element), code);
+      js.Expression bailoutCode = backend.generatedBailoutCode[element];
+      if (bailoutCode != null) {
+        pendingElementsWithBailouts.remove(element);
+        emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode);
+      }
+    }
+
+    // Is it possible the primary function was inlined but the bailout was not?
+    for (Element element in
+             Elements.sortedByPosition(pendingElementsWithBailouts)) {
+      js.Expression bailoutCode = backend.generatedBailoutCode[element];
+      emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode);
+    }
+  }
+
+  void emitStaticFunctionGetters(CodeBuffer buffer) {
+    Set<FunctionElement> functionsNeedingGetter =
+        compiler.codegenWorld.staticFunctionsNeedingGetter;
+    for (FunctionElement element in
+             Elements.sortedByPosition(functionsNeedingGetter)) {
+      // The static function does not have the correct name. Since
+      // [addParameterStubs] use the name to create its stubs we simply
+      // create a fake element with the correct name.
+      // Note: the callElement will not have any enclosingElement.
+      FunctionElement callElement =
+          new ClosureInvocationElement(namer.closureInvocationSelectorName,
+                                       element);
+      String staticName = namer.getName(element);
+      String invocationName = namer.instanceMethodName(callElement);
+      String fieldAccess = '$isolateProperties.$staticName';
+      buffer.add("$fieldAccess.$invocationName$_=$_$fieldAccess$N");
+
+      addParameterStubs(callElement, (String name, js.Expression value) {
+        js.Expression assignment =
+            js.assign(
+                js.use(isolateProperties).dot(staticName).dot(name),
+                value);
+        buffer.add(
+            js.prettyPrint(new js.ExpressionStatement(assignment), compiler));
+        buffer.add('$N');
+      });
+
+      // If a static function is used as a closure we need to add its name
+      // in case it is used in spawnFunction.
+      String fieldName = namer.STATIC_CLOSURE_NAME_NAME;
+      buffer.add('$fieldAccess.$fieldName$_=$_"$staticName"$N');
+      getTypedefChecksOn(element.computeType(compiler)).forEach(
+        (Element typedef) {
+          String operator = namer.operatorIs(typedef);
+          buffer.add('$fieldAccess.$operator$_=${_}true$N');
+        }
+      );
+    }
+  }
+
+  void emitBoundClosureClassHeader(String mangledName,
+                                   String superName,
+                                   List<String> fieldNames,
+                                   ClassBuilder builder) {
+    builder.addProperty('',
+        js.string("$superName;${Strings.join(fieldNames,',')}"));
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void emitDynamicFunctionGetter(FunctionElement member,
+                                 DefineStubFunction defineStub) {
+    assert(invariant(member, member.isDeclaration));
+    // For every method that has the same name as a property-get we create a
+    // getter that returns a bound closure. Say we have a class 'A' with method
+    // 'foo' and somewhere in the code there is a dynamic property get of
+    // 'foo'. Then we generate the following code (in pseudo Dart/JavaScript):
+    //
+    // class A {
+    //    foo(x, y, z) { ... } // Original function.
+    //    get foo { return new BoundClosure499(this, "foo"); }
+    // }
+    // class BoundClosure499 extends Closure {
+    //   var self;
+    //   BoundClosure499(this.self, this.name);
+    //   $call3(x, y, z) { return self[name](x, y, z); }
+    // }
+
+    // TODO(floitsch): share the closure classes with other classes
+    // if they share methods with the same signature. Currently we do this only
+    // if there are no optional parameters. Closures with optional parameters
+    // are more difficult to canonicalize because they would need to have the
+    // same default values.
+
+    bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0;
+    int parameterCount = member.parameterCount(compiler);
+
+    Map<int, String> cache;
+    String extraArg = null;
+    // Methods on interceptor classes take an extra parameter, which is the
+    // actual receiver of the call.
+    JavaScriptBackend backend = compiler.backend;
+    bool inInterceptor = backend.isInterceptorClass(member.getEnclosingClass());
+    if (inInterceptor) {
+      cache = interceptorClosureCache;
+      extraArg = 'receiver';
+    } else {
+      cache = boundClosureCache;
+    }
+    List<String> fieldNames = compiler.enableMinification
+        ? inInterceptor ? const ['a', 'b', 'c']
+                        : const ['a', 'b']
+        : inInterceptor ? const ['self', 'target', 'receiver']
+                        : const ['self', 'target'];
+
+    Iterable<Element> typedefChecks =
+        getTypedefChecksOn(member.computeType(compiler));
+    bool hasTypedefChecks = !typedefChecks.isEmpty;
+
+    bool canBeShared = !hasOptionalParameters && !hasTypedefChecks;
+
+    String closureClass = canBeShared ? cache[parameterCount] : null;
+    if (closureClass == null) {
+      // Either the class was not cached yet, or there are optional parameters.
+      // Create a new closure class.
+      String name;
+      if (canBeShared) {
+        if (inInterceptor) {
+          name = 'BoundClosure\$i${parameterCount}';
+        } else {
+          name = 'BoundClosure\$${parameterCount}';
+        }
+      } else {
+        name = 'Bound_${member.name.slowToString()}'
+            '_${member.enclosingElement.name.slowToString()}';
+      }
+
+      ClassElement closureClassElement = new ClosureClassElement(
+          new SourceString(name), compiler, member, member.getCompilationUnit());
+      String mangledName = namer.getName(closureClassElement);
+      String superName = namer.getName(closureClassElement.superclass);
+      needsClosureClass = true;
+
+      // Define the constructor with a name so that Object.toString can
+      // find the class name of the closure class.
+      ClassBuilder boundClosureBuilder = new ClassBuilder();
+      emitBoundClosureClassHeader(
+          mangledName, superName, fieldNames, boundClosureBuilder);
+      // Now add the methods on the closure class. The instance method does not
+      // have the correct name. Since [addParameterStubs] use the name to create
+      // its stubs we simply create a fake element with the correct name.
+      // Note: the callElement will not have any enclosingElement.
+      FunctionElement callElement =
+          new ClosureInvocationElement(namer.closureInvocationSelectorName,
+                                       member);
+
+      String invocationName = namer.instanceMethodName(callElement);
+
+      List<String> parameters = <String>[];
+      List<js.Expression> arguments = <js.Expression>[];
+      if (inInterceptor) {
+        arguments.add(js.use('this').dot(fieldNames[2]));
+      }
+      for (int i = 0; i < parameterCount; i++) {
+        String name = 'p$i';
+        parameters.add(name);
+        arguments.add(js.use(name));
+      }
+
+      js.Expression fun =
+          js.fun(parameters,
+              js.block1(
+                  js.return_(
+                      new js.PropertyAccess(
+                          js.use('this').dot(fieldNames[0]),
+                          js.use('this').dot(fieldNames[1]))
+                      .callWith(arguments))));
+      boundClosureBuilder.addProperty(invocationName, fun);
+
+      addParameterStubs(callElement, boundClosureBuilder.addProperty);
+      typedefChecks.forEach((Element typedef) {
+        String operator = namer.operatorIs(typedef);
+        boundClosureBuilder.addProperty(operator, new js.LiteralBool(true));
+      });
+
+      js.Expression init =
+          js.assign(
+              js.use(classesCollector).dot(mangledName),
+              boundClosureBuilder.toObjectInitializer());
+      boundClosureBuffer.add(js.prettyPrint(init, compiler));
+      boundClosureBuffer.add("$N");
+
+      closureClass = namer.isolateAccess(closureClassElement);
+
+      // Cache it.
+      if (canBeShared) {
+        cache[parameterCount] = closureClass;
+      }
+    }
+
+    // And finally the getter.
+    String getterName = namer.getterName(member);
+    String targetName = namer.instanceMethodName(member);
+
+    List<String> parameters = <String>[];
+    List<js.Expression> arguments = <js.Expression>[];
+    arguments.add(js.use('this'));
+    arguments.add(js.string(targetName));
+    if (inInterceptor) {
+      parameters.add(extraArg);
+      arguments.add(js.use(extraArg));
+    }
+
+    js.Expression getterFunction =
+        js.fun(parameters,
+            js.block1(
+                js.return_(
+                    new js.New(js.use(closureClass), arguments))));
+
+    defineStub(getterName, getterFunction);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void emitCallStubForGetter(Element member,
+                             Set<Selector> selectors,
+                             DefineStubFunction defineStub) {
+    assert(invariant(member, member.isDeclaration));
+    LibraryElement memberLibrary = member.getLibrary();
+    JavaScriptBackend backend = compiler.backend;
+    // If the class is an interceptor class, the stub gets the
+    // receiver explicitely and we need to pass it to the getter call.
+    bool isInterceptorClass =
+        backend.isInterceptorClass(member.getEnclosingClass());
+
+    const String receiverArgumentName = r'$receiver';
+
+    js.Expression buildGetter() {
+      if (member.isGetter()) {
+        String getterName = namer.getterName(member);
+        return new js.VariableUse('this').dot(getterName).callWith(
+            isInterceptorClass
+                ? <js.Expression>[new js.VariableUse(receiverArgumentName)]
+                : <js.Expression>[]);
+      } else {
+        String fieldName = member.hasFixedBackendName()
+            ? member.fixedBackendName()
+            : namer.instanceFieldName(member);
+        return new js.VariableUse('this').dot(fieldName);
+      }
+    }
+
+    // Two selectors may match but differ only in type.  To avoid generating
+    // identical stubs for each we track untyped selectors which already have
+    // stubs.
+    Set<Selector> generatedSelectors = new Set<Selector>();
+
+    for (Selector selector in selectors) {
+      if (selector.applies(member, compiler)) {
+        selector = selector.asUntyped;
+        if (generatedSelectors.contains(selector)) continue;
+        generatedSelectors.add(selector);
+
+        String invocationName = namer.invocationName(selector);
+        Selector callSelector = new Selector.callClosureFrom(selector);
+        String closureCallName = namer.invocationName(callSelector);
+
+        List<js.Parameter> parameters = <js.Parameter>[];
+        List<js.Expression> arguments = <js.Expression>[];
+        if (isInterceptorClass) {
+          parameters.add(new js.Parameter(receiverArgumentName));
+        }
+
+        for (int i = 0; i < selector.argumentCount; i++) {
+          String name = 'arg$i';
+          parameters.add(new js.Parameter(name));
+          arguments.add(new js.VariableUse(name));
+        }
+
+        js.Fun function =
+            new js.Fun(parameters,
+                new js.Block(
+                    <js.Statement>[
+                        new js.Return(
+                            buildGetter().dot(closureCallName)
+                                .callWith(arguments))]));
+
+        defineStub(invocationName, function);
+      }
+    }
+  }
+
+  void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) {
+    ConstantHandler handler = compiler.constantHandler;
+    Iterable<VariableElement> staticNonFinalFields =
+        handler.getStaticNonFinalFieldsForEmission();
+    for (Element element in Elements.sortedByPosition(staticNonFinalFields)) {
+      compiler.withCurrentElement(element, () {
+        Constant initialValue = handler.getInitialValueFor(element);
+        js.Expression init =
+            new js.Assignment(
+                new js.PropertyAccess.field(
+                    new js.VariableUse(isolateProperties),
+                    namer.getName(element)),
+                constantEmitter.referenceInInitializationContext(initialValue));
+        buffer.add(js.prettyPrint(init, compiler));
+        buffer.add('$N');
+      });
+    }
+  }
+
+  void emitLazilyInitializedStaticFields(CodeBuffer buffer) {
+    ConstantHandler handler = compiler.constantHandler;
+    List<VariableElement> lazyFields =
+        handler.getLazilyInitializedFieldsForEmission();
+    JavaScriptBackend backend = compiler.backend;
+    if (!lazyFields.isEmpty) {
+      needsLazyInitializer = true;
+      for (VariableElement element in Elements.sortedByPosition(lazyFields)) {
+        assert(backend.generatedBailoutCode[element] == null);
+        js.Expression code = backend.generatedCode[element];
+        assert(code != null);
+        // The code only computes the initial value. We build the lazy-check
+        // here:
+        //   lazyInitializer(prototype, 'name', fieldName, getterName, initial);
+        // The name is used for error reporting. The 'initial' must be a
+        // closure that constructs the initial value.
+        List<js.Expression> arguments = <js.Expression>[];
+        arguments.add(js.use(isolateProperties));
+        arguments.add(js.string(element.name.slowToString()));
+        arguments.add(js.string(namer.getName(element)));
+        arguments.add(js.string(namer.getLazyInitializerName(element)));
+        arguments.add(code);
+        js.Expression getter = buildLazyInitializedGetter(element);
+        if (getter != null) {
+          arguments.add(getter);
+        }
+        js.Expression init = js.call(js.use(lazyInitializerName), arguments);
+        buffer.add(js.prettyPrint(init, compiler));
+        buffer.add("$N");
+      }
+    }
+  }
+
+  js.Expression buildLazyInitializedGetter(VariableElement element) {
+    // Nothing to do, the 'lazy' function will create the getter.
+    return null;
+  }
+
+  void emitCompileTimeConstants(CodeBuffer buffer) {
+    ConstantHandler handler = compiler.constantHandler;
+    List<Constant> constants = handler.getConstantsForEmission();
+    bool addedMakeConstantList = false;
+    for (Constant constant in constants) {
+      // No need to emit functions. We already did that.
+      if (constant.isFunction()) continue;
+      // Numbers, strings and booleans are currently always inlined.
+      if (constant.isPrimitive()) continue;
+
+      String name = namer.constantName(constant);
+      // The name is null when the constant is already a JS constant.
+      // TODO(floitsch): every constant should be registered, so that we can
+      // share the ones that take up too much space (like some strings).
+      if (name == null) continue;
+      if (!addedMakeConstantList && constant.isList()) {
+        addedMakeConstantList = true;
+        emitMakeConstantList(buffer);
+      }
+      js.Expression init =
+          new js.Assignment(
+              new js.PropertyAccess.field(
+                  new js.VariableUse(isolateProperties),
+                  name),
+              constantInitializerExpression(constant));
+      buffer.add(js.prettyPrint(init, compiler));
+      buffer.add('$N');
+    }
+  }
+
+  void emitMakeConstantList(CodeBuffer buffer) {
+    buffer.add(namer.isolateName);
+    buffer.add(r'''.makeConstantList = function(list) {
+  list.immutable$list = true;
+  list.fixed$length = true;
+  return list;
+};
+''');
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [member] must be a declaration element.
+   */
+  void emitExtraAccessors(Element member, ClassBuilder builder) {
+    assert(invariant(member, member.isDeclaration));
+    if (member.isGetter() || member.isField()) {
+      Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
+      if (selectors != null && !selectors.isEmpty) {
+        emitCallStubForGetter(member, selectors, builder.addProperty);
+      }
+    } else if (member.isFunction()) {
+      if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) {
+        emitDynamicFunctionGetter(member, builder.addProperty);
+      }
+    }
+  }
+
+  void emitNoSuchMethodHandlers(DefineStubFunction defineStub) {
+    // Do not generate no such method handlers if there is no class.
+    if (compiler.codegenWorld.instantiatedClasses.isEmpty) return;
+
+    String noSuchMethodName = namer.publicInstanceMethodNameByArity(
+        Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT);
+
+    Element createInvocationMirrorElement =
+        compiler.findHelper(const SourceString("createInvocationMirror"));
+    String createInvocationMirrorName =
+        namer.getName(createInvocationMirrorElement);
+
+    // Keep track of the JavaScript names we've already added so we
+    // do not introduce duplicates (bad for code size).
+    Set<String> addedJsNames = new Set<String>();
+
+    // Keep track of the noSuchMethod holders for each possible
+    // receiver type.
+    Map<ClassElement, Set<ClassElement>> noSuchMethodHolders =
+        new Map<ClassElement, Set<ClassElement>>();
+    Set<ClassElement> noSuchMethodHoldersFor(DartType type) {
+      ClassElement element = type.element;
+      Set<ClassElement> result = noSuchMethodHolders[element];
+      if (result == null) {
+        // For now, we check the entire world to see if an object of
+        // the given type may have a user-defined noSuchMethod
+        // implementation. We could do better by only looking at
+        // instantiated (or otherwise needed) classes.
+        result = compiler.world.findNoSuchMethodHolders(type);
+        noSuchMethodHolders[element] = result;
+      }
+      return result;
+    }
+
+    js.Expression generateMethod(String jsName, Selector selector) {
+      // Values match JSInvocationMirror in js-helper library.
+      int type = selector.invocationMirrorKind;
+      String methodName = selector.invocationMirrorMemberName;
+      List<js.Parameter> parameters = <js.Parameter>[];
+      CodeBuffer args = new CodeBuffer();
+      for (int i = 0; i < selector.argumentCount; i++) {
+        parameters.add(new js.Parameter('\$$i'));
+      }
+
+      List<js.Expression> argNames =
+          selector.getOrderedNamedArguments().map((SourceString name) =>
+              js.string(name.slowToString())).toList();
+
+      String internalName = namer.invocationMirrorInternalName(selector);
+
+      String createInvocationMirror = namer.getName(
+          compiler.createInvocationMirrorElement);
+
+      js.Expression expression =
+          new js.This()
+          .dot(noSuchMethodName)
+          .callWith(
+              <js.Expression>[
+                  new js.VariableUse(namer.CURRENT_ISOLATE)
+                  .dot(createInvocationMirror)
+                  .callWith(
+                      <js.Expression>[
+                          js.string(methodName),
+                          js.string(internalName),
+                          new js.LiteralNumber('$type'),
+                          new js.ArrayInitializer.from(
+                              parameters.map((param) => js.use(param.name))
+                                        .toList()),
+                          new js.ArrayInitializer.from(argNames)])]);
+      js.Expression function =
+          new js.Fun(parameters,
+              new js.Block(<js.Statement>[new js.Return(expression)]));
+      return function;
+    }
+
+    void addNoSuchMethodHandlers(SourceString ignore, Set<Selector> selectors) {
+      // Cache the object class and type.
+      ClassElement objectClass = compiler.objectClass;
+      DartType objectType = objectClass.computeType(compiler);
+
+      for (Selector selector in selectors) {
+        // Introduce a helper function that determines if the given
+        // class has a member that matches the current name and
+        // selector (grabbed from the scope).
+        bool hasMatchingMember(ClassElement holder) {
+          Element element = holder.lookupMember(selector.name);
+          if (element == null) return false;
+
+          // TODO(kasperl): Consider folding this logic into the
+          // Selector.applies() method.
+          if (element is AbstractFieldElement) {
+            AbstractFieldElement field = element;
+            if (selector.isGetter()) {
+              return field.getter != null;
+            } else if (selector.isSetter()) {
+              return field.setter != null;
+            } else {
+              return false;
+            }
+          } else if (element is VariableElement) {
+            if (selector.isSetter() && element.modifiers.isFinalOrConst()) {
+              return false;
+            }
+          }
+          return selector.applies(element, compiler);
+        }
+
+        // If the selector is typed, we check to see if that type may
+        // have a user-defined noSuchMethod implementation. If not, we
+        // skip the selector altogether.
+        DartType receiverType = objectType;
+        ClassElement receiverClass = objectClass;
+        if (selector is TypedSelector) {
+          TypedSelector typedSelector = selector;
+          receiverType = typedSelector.receiverType;
+          receiverClass = receiverType.element;
+        }
+
+        // If the receiver class is guaranteed to have a member that
+        // matches what we're looking for, there's no need to
+        // introduce a noSuchMethod handler. It will never be called.
+        //
+        // As an example, consider this class hierarchy:
+        //
+        //                   A    <-- noSuchMethod
+        //                  / \
+        //                 C   B  <-- foo
+        //
+        // If we know we're calling foo on an object of type B we
+        // don't have to worry about the noSuchMethod method in A
+        // because objects of type B implement foo. On the other hand,
+        // if we end up calling foo on something of type C we have to
+        // add a handler for it.
+        if (hasMatchingMember(receiverClass)) continue;
+
+        // If the holders of all user-defined noSuchMethod
+        // implementations that might be applicable to the receiver
+        // type have a matching member for the current name and
+        // selector, we avoid introducing a noSuchMethod handler.
+        //
+        // As an example, consider this class hierarchy:
+        //
+        //                       A    <-- foo
+        //                      / \
+        //   noSuchMethod -->  B   C  <-- bar
+        //                     |   |
+        //                     C   D  <-- noSuchMethod
+        //
+        // When calling foo on an object of type A, we know that the
+        // implementations of noSuchMethod are in the classes B and D
+        // that also (indirectly) implement foo, so we do not need a
+        // handler for it.
+        //
+        // If we're calling bar on an object of type D, we don't need
+        // the handler either because all objects of type D implement
+        // bar through inheritance.
+        //
+        // If we're calling bar on an object of type A we do need the
+        // handler because we may have to call B.noSuchMethod since B
+        // does not implement bar.
+        Set<ClassElement> holders = noSuchMethodHoldersFor(receiverType);
+        if (holders.every(hasMatchingMember)) continue;
+        String jsName = namer.invocationMirrorInternalName(selector);
+        if (!addedJsNames.contains(jsName)) {
+          js.Expression method = generateMethod(jsName, selector);
+          defineStub(jsName, method);
+          addedJsNames.add(jsName);
+        }
+      }
+    }
+
+    compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers);
+    compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers);
+    compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers);
+  }
+
+  String buildIsolateSetup(CodeBuffer buffer,
+                           Element appMain,
+                           Element isolateMain) {
+    String mainAccess = "${namer.isolateAccess(appMain)}";
+    String currentIsolate = "${namer.CURRENT_ISOLATE}";
+    // Since we pass the closurized version of the main method to
+    // the isolate method, we must make sure that it exists.
+    if (!compiler.codegenWorld.staticFunctionsNeedingGetter.contains(appMain)) {
+      Selector selector = new Selector.callClosure(0);
+      String invocationName = namer.invocationName(selector);
+      buffer.add("$mainAccess.$invocationName = $mainAccess$N");
+    }
+    return "${namer.isolateAccess(isolateMain)}($mainAccess)";
+  }
+
+  emitMain(CodeBuffer buffer) {
+    if (compiler.isMockCompilation) return;
+    Element main = compiler.mainApp.find(Compiler.MAIN);
+    String mainCall = null;
+    if (compiler.hasIsolateSupport()) {
+      Element isolateMain =
+        compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE);
+      mainCall = buildIsolateSetup(buffer, main, isolateMain);
+    } else {
+      mainCall = '${namer.isolateAccess(main)}()';
+    }
+    if (!compiler.enableMinification) {
+      buffer.add("""
+
+//
+// BEGIN invoke [main].
+//
+""");
+    }
+    buffer.add("""
+if (typeof document !== 'undefined' && document.readyState !== 'complete') {
+  document.addEventListener('readystatechange', function () {
+    if (document.readyState == 'complete') {
+      if (typeof dartMainRunner === 'function') {
+        dartMainRunner(function() { ${mainCall}; });
+      } else {
+        ${mainCall};
+      }
+    }
+  }, false);
+} else {
+  if (typeof dartMainRunner === 'function') {
+    dartMainRunner(function() { ${mainCall}; });
+  } else {
+    ${mainCall};
+  }
+}
+""");
+    if (!compiler.enableMinification) {
+      buffer.add("""
+//
+// END invoke [main].
+//
+
+""");
+    }
+  }
+
+  void emitGetInterceptorMethod(CodeBuffer buffer,
+                                String objectName,
+                                String key,
+                                Collection<ClassElement> classes) {
+    js.Statement buildReturnInterceptor(ClassElement cls) {
+      return js.return_(js.fieldAccess(js.use(namer.isolateAccess(cls)),
+                                       'prototype'));
+    }
+
+    js.VariableUse receiver = js.use('receiver');
+    JavaScriptBackend backend = compiler.backend;
+
+    /**
+     * Build a JavaScrit AST node for doing a type check on
+     * [cls]. [cls] must be an interceptor class.
+     */
+    js.Statement buildInterceptorCheck(ClassElement cls) {
+      js.Expression condition;
+      assert(backend.isInterceptorClass(cls));
+      if (cls == backend.jsBoolClass) {
+        condition = js.equals(js.typeOf(receiver), js.string('boolean'));
+      } else if (cls == backend.jsIntClass ||
+                 cls == backend.jsDoubleClass ||
+                 cls == backend.jsNumberClass) {
+        throw 'internal error';
+      } else if (cls == backend.jsArrayClass) {
+        condition = js.equals(js.fieldAccess(receiver, 'constructor'),
+                              js.use('Array'));
+      } else if (cls == backend.jsStringClass) {
+        condition = js.equals(js.typeOf(receiver), js.string('string'));
+      } else if (cls == backend.jsNullClass) {
+        condition = js.equals(receiver, new js.LiteralNull());
+      } else if (cls == backend.jsFunctionClass) {
+        condition = js.equals(js.typeOf(receiver), js.string('function'));
+      } else {
+        throw 'internal error';
+      }
+      return js.if_(condition, buildReturnInterceptor(cls));
+    }
+
+    bool hasArray = false;
+    bool hasBool = false;
+    bool hasDouble = false;
+    bool hasFunction = false;
+    bool hasInt = false;
+    bool hasNull = false;
+    bool hasNumber = false;
+    bool hasString = false;
+    for (ClassElement cls in classes) {
+      if (cls == backend.jsArrayClass) hasArray = true;
+      else if (cls == backend.jsBoolClass) hasBool = true;
+      else if (cls == backend.jsDoubleClass) hasDouble = true;
+      else if (cls == backend.jsFunctionClass) hasFunction = true;
+      else if (cls == backend.jsIntClass) hasInt = true;
+      else if (cls == backend.jsNullClass) hasNull = true;
+      else if (cls == backend.jsNumberClass) hasNumber = true;
+      else if (cls == backend.jsStringClass) hasString = true;
+      else throw 'Internal error: $cls';
+    }
+    if (hasDouble) {
+      assert(!hasNumber);
+      hasNumber = true;
+    }
+    if (hasInt) hasNumber = true;
+
+    js.Block block = new js.Block.empty();
+
+    if (hasNumber) {
+      js.Statement whenNumber;
+
+      /// Note: there are two number classes in play: Dart's [num],
+      /// and JavaScript's Number (typeof receiver == 'number').  This
+      /// is the fallback used when we have determined that receiver
+      /// is a JavaScript Number.
+      js.Return returnNumberClass = buildReturnInterceptor(
+          hasDouble ? backend.jsDoubleClass : backend.jsNumberClass);
+
+      if (hasInt) {
+        js.Expression isInt =
+            js.equals(js.call(js.fieldAccess(js.use('Math'), 'floor'),
+                              [receiver]),
+                      receiver);
+        (whenNumber = js.emptyBlock()).statements
+          ..add(js.if_(isInt, buildReturnInterceptor(backend.jsIntClass)))
+          ..add(returnNumberClass);
+      } else {
+        whenNumber = returnNumberClass;
+      }
+      block.statements.add(
+          js.if_(js.equals(js.typeOf(receiver), js.string('number')),
+                 whenNumber));
+    }
+
+    if (hasString) {
+      block.statements.add(buildInterceptorCheck(backend.jsStringClass));
+    }
+    if (hasNull) {
+      block.statements.add(buildInterceptorCheck(backend.jsNullClass));
+    } else {
+      // Returning "undefined" here will provoke a JavaScript
+      // TypeError which is later identified as a null-error by
+      // [unwrapException] in js_helper.dart.
+      block.statements.add(js.if_(js.equals(receiver, new js.LiteralNull()),
+                                  js.return_(js.undefined())));
+    }
+    if (hasFunction) {
+      block.statements.add(buildInterceptorCheck(backend.jsFunctionClass));
+    }
+    if (hasBool) {
+      block.statements.add(buildInterceptorCheck(backend.jsBoolClass));
+    }
+    // TODO(ahe): It might be faster to check for Array before
+    // function and bool.
+    if (hasArray) {
+      block.statements.add(buildInterceptorCheck(backend.jsArrayClass));
+    }
+    block.statements.add(js.return_(js.fieldAccess(js.use(objectName),
+                                                   'prototype')));
+
+    js.PropertyAccess name = js.fieldAccess(js.use(isolateProperties), key);
+    buffer.add(js.prettyPrint(js.assign(name, js.fun(['receiver'], block)),
+                              compiler));
+    buffer.add(N);
+  }
+
+  /**
+   * Emit all versions of the [:getInterceptor:] method.
+   */
+  void emitGetInterceptorMethods(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    // If no class needs to be intercepted, just return.
+    if (backend.objectInterceptorClass == null) return;
+    String objectName = namer.isolateAccess(backend.objectInterceptorClass);
+    var specializedGetInterceptors = backend.specializedGetInterceptors;
+    for (String name in specializedGetInterceptors.keys.toList()..sort()) {
+      Collection<ClassElement> classes = specializedGetInterceptors[name];
+      emitGetInterceptorMethod(buffer, objectName, name, classes);
+    }
+  }
+
+  void computeNeededClasses() {
+    instantiatedClasses =
+        compiler.codegenWorld.instantiatedClasses.where(computeClassFilter())
+            .toSet();
+    neededClasses = new Set<ClassElement>.from(instantiatedClasses);
+    for (ClassElement element in instantiatedClasses) {
+      for (ClassElement superclass = element.superclass;
+          superclass != null;
+          superclass = superclass.superclass) {
+        if (neededClasses.contains(superclass)) break;
+        neededClasses.add(superclass);
+      }
+    }
+  }
+
+  int _compareSelectors(Selector selector1, Selector selector2) {
+    int comparison = _compareSelectorNames(selector1, selector2);
+    if (comparison != 0) return comparison;
+
+    JavaScriptBackend backend = compiler.backend;
+    Set<ClassElement> classes1 = backend.getInterceptedClassesOn(selector1);
+    Set<ClassElement> classes2 = backend.getInterceptedClassesOn(selector2);
+    if (classes1.length != classes2.length) {
+      return classes1.length - classes2.length;
+    }
+    String getInterceptor1 =
+        namer.getInterceptorName(backend.getInterceptorMethod, classes1);
+    String getInterceptor2 =
+        namer.getInterceptorName(backend.getInterceptorMethod, classes2);
+    return Comparable.compare(getInterceptor1, getInterceptor2);
+  }
+
+  void emitOneShotInterceptors(CodeBuffer buffer) {
+    JavaScriptBackend backend = compiler.backend;
+    for (Selector selector in
+         backend.oneShotInterceptors.toList()..sort(_compareSelectors)) {
+      Set<ClassElement> classes = backend.getInterceptedClassesOn(selector);
+      String oneShotInterceptorName = namer.oneShotInterceptorName(selector);
+      String getInterceptorName =
+          namer.getInterceptorName(backend.getInterceptorMethod, classes);
+
+      List<js.Parameter> parameters = <js.Parameter>[];
+      List<js.Expression> arguments = <js.Expression>[];
+      parameters.add(new js.Parameter('receiver'));
+      arguments.add(js.use('receiver'));
+
+      if (selector.isSetter()) {
+        parameters.add(new js.Parameter('value'));
+        arguments.add(js.use('value'));
+      } else {
+        for (int i = 0; i < selector.argumentCount; i++) {
+          String argName = 'a$i';
+          parameters.add(new js.Parameter(argName));
+          arguments.add(js.use(argName));
+        }
+      }
+
+      String invocationName = backend.namer.invocationName(selector);
+      js.Fun function =
+          new js.Fun(parameters,
+              js.block1(js.return_(
+                        js.use(isolateProperties)
+                            .dot(getInterceptorName)
+                            .callWith([js.use('receiver')])
+                            .dot(invocationName)
+                            .callWith(arguments))));
+
+      js.PropertyAccess property =
+          js.fieldAccess(js.use(isolateProperties), oneShotInterceptorName);
+
+      buffer.add(js.prettyPrint(js.assign(property, function), compiler));
+      buffer.add(N);
+    }
+  }
+
+  String assembleProgram() {
+    measure(() {
+      computeNeededClasses();
+
+      mainBuffer.add(GENERATED_BY);
+      if (!compiler.enableMinification) mainBuffer.add(HOOKS_API_USAGE);
+      mainBuffer.add('function ${namer.isolateName}()$_{}\n');
+      mainBuffer.add('init()$N$n');
+      // Shorten the code by using "$$" as temporary.
+      classesCollector = r"$$";
+      mainBuffer.add('var $classesCollector$_=$_{}$N');
+      // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary.
+      isolateProperties = namer.CURRENT_ISOLATE;
+      mainBuffer.add(
+          'var $isolateProperties$_=$_$isolatePropertiesName$N');
+      emitClasses(mainBuffer);
+      mainBuffer.add(boundClosureBuffer);
+      // Clear the buffer, so that we can reuse it for the native classes.
+      boundClosureBuffer.clear();
+      emitStaticFunctions(mainBuffer);
+      emitStaticFunctionGetters(mainBuffer);
+      // We need to finish the classes before we construct compile time
+      // constants.
+      emitFinishClassesInvocationIfNecessary(mainBuffer);
+      emitRuntimeClassesAndTests(mainBuffer);
+      emitCompileTimeConstants(mainBuffer);
+      // Static field initializations require the classes and compile-time
+      // constants to be set up.
+      emitStaticNonFinalFieldInitializations(mainBuffer);
+      emitOneShotInterceptors(mainBuffer);
+      emitGetInterceptorMethods(mainBuffer);
+      emitLazilyInitializedStaticFields(mainBuffer);
+
+      isolateProperties = isolatePropertiesName;
+      // The following code should not use the short-hand for the
+      // initialStatics.
+      mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_=${_}null$N');
+      mainBuffer.add(boundClosureBuffer);
+      emitFinishClassesInvocationIfNecessary(mainBuffer);
+      // After this assignment we will produce invalid JavaScript code if we use
+      // the classesCollector variable.
+      classesCollector = 'classesCollector should not be used from now on';
+
+      emitFinishIsolateConstructorInvocation(mainBuffer);
+      mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_='
+                     '${_}new ${namer.isolateName}()$N');
+
+      nativeEmitter.assembleCode(mainBuffer);
+      emitMain(mainBuffer);
+      mainBuffer.add('function init()$_{\n');
+      mainBuffer.add('$isolateProperties$_=$_{}$N');
+      addDefineClassAndFinishClassFunctionsIfNecessary(mainBuffer);
+      addLazyInitializerFunctionIfNecessary(mainBuffer);
+      emitFinishIsolateConstructor(mainBuffer);
+      mainBuffer.add('}\n');
+      compiler.assembledCode = mainBuffer.getText();
+
+      if (generateSourceMap) {
+        SourceFile compiledFile = new SourceFile(null, compiler.assembledCode);
+        String sourceMap = buildSourceMap(mainBuffer, compiledFile);
+        compiler.outputProvider('', 'js.map')
+            ..add(sourceMap)
+            ..close();
+      }
+    });
+    return compiler.assembledCode;
+  }
+
+  String buildSourceMap(CodeBuffer buffer, SourceFile compiledFile) {
+    SourceMapBuilder sourceMapBuilder = new SourceMapBuilder();
+    buffer.forEachSourceLocation(sourceMapBuilder.addMapping);
+    return sourceMapBuilder.build(compiledFile);
+  }
+}
+
+const String GENERATED_BY = """
+// Generated by dart2js, the Dart to JavaScript compiler.
+""";
+const String HOOKS_API_USAGE = """
+// The code supports the following hooks:
+// dartPrint(message)   - if this function is defined it is called
+//                        instead of the Dart [print] method.
+// dartMainRunner(main) - if this function is defined, the Dart [main]
+//                        method will not be invoked directly.
+//                        Instead, a closure that will invoke [main] is
+//                        passed to [dartMainRunner].
+""";
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart
new file mode 100644
index 0000000..06d5d01
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart
@@ -0,0 +1,138 @@
+// 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.
+
+part of js_backend;
+
+class CodeEmitterNoEvalTask extends CodeEmitterTask {
+  CodeEmitterNoEvalTask(Compiler compiler,
+                        Namer namer,
+                        bool generateSourceMap)
+      : super(compiler, namer, generateSourceMap);
+
+  String get generateGetterSetterFunction {
+    return """
+function() {
+  throw 'Internal Error: no dynamic generation of getters and setters allowed';
+}""";
+  }
+
+  String get defineClassFunction {
+    return """
+function(cls, constructor, prototype) {
+  constructor.prototype = prototype;
+  constructor.builtin\$cls = cls;
+  return constructor;
+}""";
+  }
+
+  String get protoSupportCheck {
+    // We don't modify the prototypes in CSP mode. Therefore we can have an
+    // easier prototype-check.
+    return 'var $supportsProtoName = !!{}.__proto__;\n';
+  }
+
+  String get finishIsolateConstructorFunction {
+    // We replace the old Isolate function with a new one that initializes
+    // all its field with the initial (and often final) value of all globals.
+    //
+    // We also copy over old values like the prototype, and the
+    // isolateProperties themselves.
+    return """
+function(oldIsolate) {
+  var isolateProperties = oldIsolate.${namer.isolatePropertiesName};
+  function Isolate() {
+    for (var staticName in isolateProperties) {
+      if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) {
+        this[staticName] = isolateProperties[staticName];
+      }
+    }
+    // Use the newly created object as prototype. In Chrome this creates a
+    // hidden class for the object and makes sure it is fast to access.
+    function ForceEfficientMap() {}
+    ForceEfficientMap.prototype = this;
+    new ForceEfficientMap;
+  }
+  Isolate.prototype = oldIsolate.prototype;
+  Isolate.prototype.constructor = Isolate;
+  Isolate.${namer.isolatePropertiesName} = isolateProperties;
+  return Isolate;
+}""";
+  }
+
+  String get lazyInitializerFunction {
+    return """
+function(prototype, staticName, fieldName, getterName, lazyValue, getter) {
+$lazyInitializerLogic
+}""";
+  }
+
+  js.Expression buildLazyInitializedGetter(VariableElement element) {
+    String isolate = namer.CURRENT_ISOLATE;
+    return js.fun([],
+        js.block1(
+            js.return_(
+                js.fieldAccess(js.use(isolate), namer.getName(element)))));
+  }
+
+  js.Expression buildConstructor(String mangledName, List<String> fieldNames) {
+    return new js.NamedFunction(
+        new js.VariableDeclaration(mangledName),
+        new js.Fun(
+            fieldNames
+                .map((fieldName) => new js.Parameter(fieldName))
+                .toList(),
+            new js.Block(
+                fieldNames.map((fieldName) =>
+                    new js.ExpressionStatement(
+                        new js.Assignment(
+                            new js.This().dot(fieldName),
+                            new js.VariableUse(fieldName))))
+                    .toList())));
+  }
+
+  void emitBoundClosureClassHeader(String mangledName,
+                                   String superName,
+                                   List<String> fieldNames,
+                                   ClassBuilder builder) {
+    builder.addProperty('', buildConstructor(mangledName, fieldNames));
+    builder.addProperty('super', js.string(superName));
+  }
+
+  void emitClassConstructor(ClassElement classElement, ClassBuilder builder) {
+    // Say we have a class A with fields b, c and d, where c needs a getter and
+    // d needs both a getter and a setter. Then we produce:
+    // - a constructor (directly into the given [buffer]):
+    //   function A(b, c, d) { this.b = b, this.c = c, this.d = d; }
+    // - getters and setters (stored in the [explicitGettersSetters] list):
+    //   get$c : function() { return this.c; }
+    //   get$d : function() { return this.d; }
+    //   set$d : function(x) { this.d = x; }
+    List<String> fields = <String>[];
+    visitClassFields(classElement, (Element member,
+                                    String name,
+                                    String accessorName,
+                                    bool needsGetter,
+                                    bool needsSetter,
+                                    bool needsCheckedSetter) {
+      fields.add(name);
+    });
+    String constructorName = namer.safeName(classElement.name.slowToString());
+
+    builder.addProperty('', buildConstructor(constructorName, fields));
+  }
+
+  void emitSuper(String superName, ClassBuilder builder) {
+    if (superName != '') {
+      builder.addProperty('super', js.string(superName));
+    }
+  }
+
+  void emitClassFields(ClassElement classElement,
+                       ClassBuilder builder,
+                       { String superClass: "",
+                         bool classIsNative: false}) {
+  }
+
+  bool get getterAndSetterCanBeImplementedByFieldSpec => false;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart
new file mode 100644
index 0000000..52e6ee0
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart
@@ -0,0 +1,33 @@
+// 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.
+
+library js_backend;
+
+import 'dart:collection' show LinkedHashMap;
+
+import '../closure.dart';
+import '../../compiler.dart' as api;
+import '../elements/elements.dart';
+import '../elements/modelx.dart' show FunctionElementX;
+import '../dart2jslib.dart' hide Selector;
+import '../dart_types.dart';
+import '../js/js.dart' as js;
+import '../native_handler.dart' as native;
+import '../source_file.dart';
+import '../source_map_builder.dart';
+import '../ssa/ssa.dart';
+import '../tree/tree.dart';
+import '../universe/universe.dart';
+import '../util/characters.dart';
+import '../util/util.dart';
+
+part 'backend.dart';
+part 'constant_emitter.dart';
+part 'constant_system_javascript.dart';
+part 'emitter.dart';
+part 'emitter_no_eval.dart';
+part 'minify_namer.dart';
+part 'namer.dart';
+part 'native_emitter.dart';
+part 'runtime_types.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart
new file mode 100644
index 0000000..98344a1
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart
@@ -0,0 +1,200 @@
+// Copyright (c) 2011, 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.
+
+part of js_backend;
+
+/**
+ * Assigns JavaScript identifiers to Dart variables, class-names and members.
+ */
+class MinifyNamer extends Namer {
+  MinifyNamer(Compiler compiler) : super(compiler) {
+    reserveBackendNames();
+  }
+
+  String get isolateName => 'I';
+  String get isolatePropertiesName => 'p';
+  bool get shouldMinify => true;
+
+  const ALPHABET_CHARACTERS = 52;  // a-zA-Z.
+  const ALPHANUMERIC_CHARACTERS = 62;  // a-zA-Z0-9.
+
+  // You can pass an invalid identifier to this and unlike its non-minifying
+  // counterpart it will never return the proposedName as the new fresh name.
+  String getFreshName(String proposedName,
+                      Set<String> usedNames,
+                      Map<String, String> suggestedNames,
+                      {bool ensureSafe: true}) {
+    var freshName;
+    var suggestion = suggestedNames[proposedName];
+    if (suggestion != null && !usedNames.contains(suggestion)) {
+      freshName = suggestion;
+    } else {
+      freshName = _getUnusedName(proposedName, usedNames);
+    }
+    usedNames.add(freshName);
+    return freshName;
+  }
+
+  SourceString getClosureVariableName(SourceString name, int id) {
+    if (id < ALPHABET_CHARACTERS) {
+      return new SourceString(new String.fromCharCodes([_letterNumber(id)]));
+    }
+    return new SourceString("${getMappedInstanceName('closure')}_$id");
+  }
+
+  void reserveBackendNames() {
+    // From issue 7554.  These should not be used on objects (as instance
+    // variables) because they clash with names from the DOM.
+    const reservedNativeProperties = const <String>[
+        'Q', 'a', 'b', 'c', 'd', 'e', 'f', 'r', 'x', 'y', 'z',
+        // 2-letter:
+        'ch', 'cx', 'cy', 'db', 'dx', 'dy', 'fr', 'fx', 'fy', 'go', 'id', 'k1',
+        'k2', 'k3', 'k4', 'r1', 'r2', 'rx', 'ry', 'x1', 'x2', 'y1', 'y2',
+        // 3-letter:
+        'add', 'all', 'alt', 'arc', 'CCW', 'cmp', 'dir', 'end', 'get', 'in1',
+        'in2', 'INT', 'key', 'log', 'low', 'm11', 'm12', 'm13', 'm14', 'm21',
+        'm22', 'm23', 'm24', 'm31', 'm32', 'm33', 'm34', 'm41', 'm42', 'm43',
+        'm44', 'max', 'min', 'now', 'ONE', 'put', 'red', 'rel', 'rev', 'RGB',
+        'sdp', 'set', 'src', 'tag', 'top', 'uid', 'uri', 'url', 'URL',
+        // 4-letter:
+        'abbr', 'atob', 'Attr', 'axes', 'axis', 'back', 'BACK', 'beta', 'bias',
+        'Blob', 'blue', 'blur', 'BLUR', 'body', 'BOOL', 'BOTH', 'btoa', 'BYTE',
+        'cite', 'clip', 'code', 'cols', 'cues', 'data', 'DECR', 'DONE', 'face',
+        'file', 'File', 'fill', 'find', 'font', 'form', 'gain', 'hash', 'head',
+        'high', 'hint', 'host', 'href', 'HRTF', 'IDLE', 'INCR', 'info', 'INIT',
+        'isId', 'item', 'KEEP', 'kind', 'knee', 'lang', 'left', 'LESS', 'line',
+        'link', 'list', 'load', 'loop', 'mode', 'name', 'Node', 'None', 'NONE',
+        'only', 'open', 'OPEN', 'ping', 'play', 'port', 'rect', 'Rect', 'refX',
+        'refY', 'RGBA', 'root', 'rows', 'save', 'seed', 'seek', 'self', 'send',
+        'show', 'SINE', 'size', 'span', 'stat', 'step', 'stop', 'tags', 'text',
+        'Text', 'time', 'type', 'view', 'warn', 'wrap', 'ZERO'];
+    for (var name in reservedNativeProperties) {
+      if (name.length < 2) {
+        instanceNameMap[name] = name;
+      }
+      usedInstanceNames.add(name);
+    }
+
+    // This list of popular instance variable names generated with:
+    // cat out.js |
+    // perl -ne '$_=~s/(?<![^a-z0-9_\$]\$)\.([a-z0-9_\$]+)/print("$1\n")/gei' |
+    // sort | uniq -c | sort -nr | head -40
+    // Removed: html, call*, hasOwnProperty.
+    _populateSuggestedNames(
+        suggestedInstanceNames,
+        usedInstanceNames,
+        const <String>[
+            r'$add', r'add$1', r'box_0', r'charCodeAt$1', r'constructor',
+            r'current', r'$defineNativeClass', r'$eq', r'$ne',
+            r'getPrototypeOf', r'hasOwnProperty', r'$index', r'$indexSet',
+            r'$isJavaScriptIndexingBehavior', r'$isolateProperties',
+            r'iterator', r'length', r'$lt', r'$gt', r'$le', r'$ge',
+            r'moveNext$0', r'node', r'on', r'prototype', r'push', r'self',
+            r'start', r'target', r'this_0', r'value', r'width', r'style']);
+
+    _populateSuggestedNames(
+        suggestedGlobalNames,
+        usedGlobalNames,
+        const <String>[
+            r'Object', r'$throw', r'$eq', r'S', r'ioore', r'UnsupportedError$',
+            r'length', r'$sub', r'getInterceptor$JSStringJSArray', r'$add',
+            r'$gt', r'$ge', r'$lt', r'$le', r'add', r'getInterceptor$JSNumber',
+            r'iterator', r'$index', r'iae', r'getInterceptor$JSArray',
+            r'ArgumentError$', r'BoundClosure', r'StateError$',
+            r'getInterceptor', r'max', r'$mul', r'List_List', r'Map_Map',
+            r'getInterceptor$JSString', r'$div', r'$indexSet',
+            r'List_List$from', r'Set_Set$from', r'toString', r'toInt', r'min',
+            r'StringBuffer_StringBuffer', r'contains1', r'WhereIterable$',
+            r'RangeError$value', r'JSString', r'JSNumber',
+            r'JSArray'
+            ]);
+  }
+
+  void _populateSuggestedNames(Map<String, String> suggestionMap,
+                               Set<String> used,
+                               List<String> suggestions) {
+    int c = $a - 1;
+    String letter;
+    for (String name in suggestions) {
+      do {
+        assert(c != $Z);
+        c = (c == $z) ? $A : c + 1;
+        letter = new String.fromCharCodes([c]);
+      } while (used.contains(letter));
+      assert(suggestionMap[name] == null);
+      suggestionMap[name] = letter;
+    }
+  }
+
+
+  // This gets a minified name based on a hash of the proposed name.  This
+  // is slightly less efficient than just getting the next name in a series,
+  // but it means that small changes in the input program will give smallish
+  // changes in the output, which can be useful for diffing etc.
+  String _getUnusedName(String proposedName, Set<String> usedNames) {
+    int hash = _calculateHash(proposedName);
+    // Avoid very small hashes that won't try many names.
+    hash = hash < 1000 ? hash * 314159 : hash;  // Yes, it's prime.
+
+    // Try other n-character names based on the hash.  We try one to three
+    // character identifiers.  For each length we try around 10 different names
+    // in a predictable order determined by the proposed name.  This is in order
+    // to make the renamer stable: small changes in the input should nornally
+    // result in relatively small changes in the output.
+    for (var n = 2; n <= 3; n++) {
+      int h = hash;
+      while (h > 10) {
+        var codes = <int>[_letterNumber(h)];
+        int h2 = h ~/ ALPHABET_CHARACTERS;
+        for (var i = 1; i < n; i++) {
+          codes.add(_alphaNumericNumber(h2));
+          h2 ~/= ALPHANUMERIC_CHARACTERS;
+        }
+        final candidate = new String.fromCharCodes(codes);
+        if (!usedNames.contains(candidate) && !jsReserved.contains(candidate)) {
+          return candidate;
+        }
+        // Try again with a slightly different hash.  After around 10 turns
+        // around this loop h is zero and we try a longer name.
+        h ~/= 7;
+      }
+    }
+
+    // If we can't find a hash based name in the three-letter space, then base
+    // the name on a letter and a counter.
+    var startLetter = new String.fromCharCodes([_letterNumber(hash)]);
+    var i = 0;
+    while (usedNames.contains("$startLetter$i")) {
+      i++;
+    }
+    return "$startLetter$i";
+  }
+
+  int _calculateHash(String name) {
+    int h = 0;
+    for (int i = 0; i < name.length; i++) {
+      h += name.charCodeAt(i);
+      h &= 0xffffffff;
+      h += h << 10;
+      h &= 0xffffffff;
+      h ^= h >> 6;
+      h &= 0xffffffff;
+    }
+    return h;
+  }
+
+  int _letterNumber(int x) {
+    if (x >= ALPHABET_CHARACTERS) x %= ALPHABET_CHARACTERS;
+    if (x < 26) return $a + x;
+    return $A + x - 26;
+  }
+
+  int _alphaNumericNumber(int x) {
+    if (x >= ALPHANUMERIC_CHARACTERS) x %= ALPHANUMERIC_CHARACTERS;
+    if (x < 26) return $a + x;
+    if (x < 52) return $A + x - 26;
+    return $0 + x - 52;
+  }
+
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart
new file mode 100644
index 0000000..e5bb967
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart
@@ -0,0 +1,754 @@
+// Copyright (c) 2011, 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.
+
+part of js_backend;
+
+/**
+ * Assigns JavaScript identifiers to Dart variables, class-names and members.
+ */
+class Namer implements ClosureNamer {
+
+  static const javaScriptKeywords = const <String>[
+    // These are current keywords.
+    "break", "delete", "function", "return", "typeof", "case", "do", "if",
+    "switch", "var", "catch", "else", "in", "this", "void", "continue",
+    "false", "instanceof", "throw", "while", "debugger", "finally", "new",
+    "true", "with", "default", "for", "null", "try",
+
+    // These are future keywords.
+    "abstract", "double", "goto", "native", "static", "boolean", "enum",
+    "implements", "package", "super", "byte", "export", "import", "private",
+    "synchronized", "char", "extends", "int", "protected", "throws",
+    "class", "final", "interface", "public", "transient", "const", "float",
+    "long", "short", "volatile"
+  ];
+
+  static const reservedPropertySymbols =
+      const <String>["__proto__", "prototype", "constructor", "call"];
+
+  // Symbols that we might be using in our JS snippets.
+  static const reservedGlobalSymbols = const <String>[
+    // Section references are from Ecma-262
+    // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)
+
+    // 15.1.1 Value Properties of the Global Object
+    "NaN", "Infinity", "undefined",
+
+    // 15.1.2 Function Properties of the Global Object
+    "eval", "parseInt", "parseFloat", "isNaN", "isFinite",
+
+    // 15.1.3 URI Handling Function Properties
+    "decodeURI", "decodeURIComponent",
+    "encodeURI",
+    "encodeURIComponent",
+
+    // 15.1.4 Constructor Properties of the Global Object
+    "Object", "Function", "Array", "String", "Boolean", "Number", "Date",
+    "RegExp", "Error", "EvalError", "RangeError", "ReferenceError",
+    "SyntaxError", "TypeError", "URIError",
+
+    // 15.1.5 Other Properties of the Global Object
+    "Math",
+
+    // 10.1.6 Activation Object
+    "arguments",
+
+    // B.2 Additional Properties (non-normative)
+    "escape", "unescape",
+
+    // Window props (https://developer.mozilla.org/en/DOM/window)
+    "applicationCache", "closed", "Components", "content", "controllers",
+    "crypto", "defaultStatus", "dialogArguments", "directories",
+    "document", "frameElement", "frames", "fullScreen", "globalStorage",
+    "history", "innerHeight", "innerWidth", "length",
+    "location", "locationbar", "localStorage", "menubar",
+    "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel",
+    "name", "navigator", "opener", "outerHeight", "outerWidth",
+    "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11",
+    "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY",
+    "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar",
+    "top", "window",
+
+    // Window methods (https://developer.mozilla.org/en/DOM/window)
+    "alert", "addEventListener", "atob", "back", "blur", "btoa",
+    "captureEvents", "clearInterval", "clearTimeout", "close", "confirm",
+    "disableExternalCapture", "dispatchEvent", "dump",
+    "enableExternalCapture", "escape", "find", "focus", "forward",
+    "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount",
+    "getComputedStyle", "getSelection", "home", "maximize", "minimize",
+    "moveBy", "moveTo", "open", "openDialog", "postMessage", "print",
+    "prompt", "QueryInterface", "releaseEvents", "removeEventListener",
+    "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy",
+    "scrollByLines", "scrollByPages", "scrollTo", "setInterval",
+    "setResizeable", "setTimeout", "showModalDialog", "sizeToContent",
+    "stop", "uuescape", "updateCommands", "XPCNativeWrapper",
+    "XPCSafeJSOjbectWrapper",
+
+    // Mozilla Window event handlers, same cite
+    "onabort", "onbeforeunload", "onchange", "onclick", "onclose",
+    "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange",
+    "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown",
+    "onmousemove", "onmouseout", "onmouseover", "onmouseup",
+    "onmozorientation", "onpaint", "onreset", "onresize", "onscroll",
+    "onselect", "onsubmit", "onunload",
+
+    // Safari Web Content Guide
+    // http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/SafariWebContent.pdf
+    // WebKit Window member data, from WebKit DOM Reference
+    // (http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html)
+    "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart",
+    "ongesturestart", "ongesturechange", "ongestureend",
+
+    // extra window methods
+    "uneval",
+
+    // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7,
+    // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1
+    "getPrototypeOf", "let", "yield",
+
+    // "future reserved words"
+    "abstract", "int", "short", "boolean", "interface", "static", "byte",
+    "long", "char", "final", "native", "synchronized", "float", "package",
+    "throws", "goto", "private", "transient", "implements", "protected",
+    "volatile", "double", "public",
+
+    // IE methods
+    // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#)
+    "attachEvent", "clientInformation", "clipboardData", "createPopup",
+    "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth",
+    "onafterprint", "onbeforedeactivate", "onbeforeprint",
+    "oncontrolselect", "ondeactivate", "onhelp", "onresizeend",
+
+    // Common browser-defined identifiers not defined in ECMAScript
+    "event", "external", "Debug", "Enumerator", "Global", "Image",
+    "ActiveXObject", "VBArray", "Components",
+
+    // Functions commonly defined on Object
+    "toString", "getClass", "constructor", "prototype", "valueOf",
+
+    // Client-side JavaScript identifiers
+    "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient",
+    "CanvasPattern", "CanvasRenderingContext2D", "CDATASection",
+    "CharacterData", "Comment", "CSS2Properties", "CSSRule",
+    "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType",
+    "DOMException", "DOMImplementation", "DOMParser", "Element", "Event",
+    "ExternalInterface", "FlashPlayer", "Form", "Frame", "History",
+    "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image",
+    "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType",
+    "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin",
+    "ProcessingInstruction", "Range", "RangeException", "Screen", "Select",
+    "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea",
+    "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer",
+    "XPathException", "XPathResult", "XSLTProcessor",
+
+    // These keywords trigger the loading of the java-plugin. For the
+    // next-generation plugin, this results in starting a new Java process.
+    "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass",
+    "JavaArray", "JavaMember"
+  ];
+
+  Set<String> _jsReserved = null;
+  /// Names that cannot be used by members, top level and static
+  /// methods.
+  Set<String> get jsReserved {
+    if (_jsReserved == null) {
+      _jsReserved = new Set<String>();
+      _jsReserved.addAll(javaScriptKeywords);
+      _jsReserved.addAll(reservedPropertySymbols);
+    }
+    return _jsReserved;
+  }
+
+  Set<String> _jsVariableReserved = null;
+  /// Names that cannot be used by local variables and parameters.
+  Set<String> get jsVariableReserved {
+    if (_jsVariableReserved == null) {
+      _jsVariableReserved = new Set<String>();
+      _jsVariableReserved.addAll(javaScriptKeywords);
+      _jsVariableReserved.addAll(reservedPropertySymbols);
+      _jsVariableReserved.addAll(reservedGlobalSymbols);
+    }
+    return _jsVariableReserved;
+  }
+
+  final String CURRENT_ISOLATE = r'$';
+
+  /**
+   * Map from top-level or static elements to their unique identifiers provided
+   * by [getName].
+   *
+   * Invariant: Keys must be declaration elements.
+   */
+  final Compiler compiler;
+  final Map<Element, String> globals;
+  final Map<Selector, String> oneShotInterceptorNames;
+  final Map<String, LibraryElement> shortPrivateNameOwners;
+
+  final Set<String> usedGlobalNames;
+  final Set<String> usedInstanceNames;
+  final Map<String, String> globalNameMap;
+  final Map<String, String> suggestedGlobalNames;
+  final Map<String, String> instanceNameMap;
+  final Map<String, String> suggestedInstanceNames;
+      
+  final Map<String, String> operatorNameMap;
+  final Map<String, int> popularNameCounters;
+
+  final Map<Element, String> bailoutNames;
+
+  final Map<Constant, String> constantNames;
+
+  Namer(this.compiler)
+      : globals = new Map<Element, String>(),
+        oneShotInterceptorNames = new Map<Selector, String>(),
+        shortPrivateNameOwners = new Map<String, LibraryElement>(),
+        bailoutNames = new Map<Element, String>(),
+        usedGlobalNames = new Set<String>(),
+        usedInstanceNames = new Set<String>(),
+        instanceNameMap = new Map<String, String>(),
+        operatorNameMap = new Map<String, String>(),
+        globalNameMap = new Map<String, String>(),
+        suggestedGlobalNames = new Map<String, String>(),
+        suggestedInstanceNames = new Map<String, String>(),
+        constantNames = new Map<Constant, String>(),
+        popularNameCounters = new Map<String, int>();
+
+  String get isolateName => 'Isolate';
+  String get isolatePropertiesName => r'$isolateProperties';
+  /**
+   * Some closures must contain their name. The name is stored in
+   * [STATIC_CLOSURE_NAME_NAME].
+   */
+  String get STATIC_CLOSURE_NAME_NAME => r'$name';
+  SourceString get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME;
+  bool get shouldMinify => false;
+
+  bool isReserved(String name) => name == isolateName;
+
+  String constantName(Constant constant) {
+    // In the current implementation it doesn't make sense to give names to
+    // function constants since the function-implementation itself serves as
+    // constant and can be accessed directly.
+    assert(!constant.isFunction());
+    String result = constantNames[constant];
+    if (result == null) {
+      String longName;
+      if (shouldMinify) {
+        if (constant.isString()) {
+          StringConstant stringConstant = constant;
+          // The minifier always constructs a new name, using the argument as
+          // input to its hashing algorithm.  The given name does not need to be
+          // valid.
+          longName = stringConstant.value.slowToString();
+        } else {
+          longName = "C";
+        }
+      } else {
+        longName = "CONSTANT";
+      }
+      result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames,
+                            ensureSafe: true);
+      constantNames[constant] = result;
+    }
+    return result;
+  }
+
+  String breakLabelName(LabelElement label) {
+    return '\$${label.labelName}\$${label.target.nestingLevel}';
+  }
+
+  String implicitBreakLabelName(TargetElement target) {
+    return '\$${target.nestingLevel}';
+  }
+
+  // We sometimes handle continue targets differently from break targets,
+  // so we have special continue-only labels.
+  String continueLabelName(LabelElement label) {
+    return 'c\$${label.labelName}\$${label.target.nestingLevel}';
+  }
+
+  String implicitContinueLabelName(TargetElement target) {
+    return 'c\$${target.nestingLevel}';
+  }
+
+  /**
+   * If the [name] is not private returns [:name.slowToString():]. Otherwise
+   * mangles the [name] so that each library has a unique name.
+   */
+  String privateName(LibraryElement library, SourceString name) {
+    // Public names are easy.
+    String nameString = name.slowToString();
+    if (!name.isPrivate()) return nameString;
+
+    // The first library asking for a short private name wins.
+    LibraryElement owner = shouldMinify
+        ? library
+        : shortPrivateNameOwners.putIfAbsent(nameString, () => library);
+
+    // If a private name could clash with a mangled private name we don't
+    // use the short name. For example a private name "_lib3_foo" would
+    // clash with "_foo" from "lib3".
+    if (owner == library &&
+        !nameString.startsWith('_$LIBRARY_PREFIX') &&
+        !shouldMinify) {
+      return nameString;
+    }
+
+    // If a library name does not start with the [LIBRARY_PREFIX] then our
+    // assumptions about clashing with mangled private members do not hold.
+    String libraryName = getName(library);
+    assert(shouldMinify || libraryName.startsWith(LIBRARY_PREFIX));
+    // TODO(erikcorry): Fix this with other manglings to avoid clashes.
+    return '_lib$libraryName\$$nameString';
+  }
+
+  String instanceMethodName(FunctionElement element) {
+    SourceString elementName = element.name;
+    SourceString name = operatorNameToIdentifier(elementName);
+    if (name != elementName) return getMappedOperatorName(name.slowToString());
+
+    LibraryElement library = element.getLibrary();
+    if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY) {
+      ConstructorBodyElement bodyElement = element;
+      name = bodyElement.constructor.name;
+    }
+    FunctionSignature signature = element.computeSignature(compiler);
+    String methodName =
+        '${privateName(library, name)}\$${signature.parameterCount}';
+    if (signature.optionalParametersAreNamed &&
+        !signature.optionalParameters.isEmpty) {
+      StringBuffer buffer = new StringBuffer();
+      signature.orderedOptionalParameters.forEach((Element element) {
+        buffer.add('\$${safeName(element.name.slowToString())}');
+      });
+      methodName = '$methodName$buffer';
+    }
+    if (name == closureInvocationSelectorName) return methodName;
+    return getMappedInstanceName(methodName);
+  }
+
+  String publicInstanceMethodNameByArity(SourceString name, int arity) {
+    SourceString newName = operatorNameToIdentifier(name);
+    if (newName != name) return getMappedOperatorName(newName.slowToString());
+    assert(!name.isPrivate());
+    var base = name.slowToString();
+    // We don't mangle the closure invoking function name because it
+    // is generated by string concatenation in applyFunction from
+    // js_helper.dart.
+    var proposedName = '$base\$$arity';
+    if (name == closureInvocationSelectorName) return proposedName;
+    return getMappedInstanceName(proposedName);
+  }
+
+  String invocationName(Selector selector) {
+    if (selector.isGetter()) {
+      String proposedName = privateName(selector.library, selector.name);
+      return 'get\$${getMappedInstanceName(proposedName)}';
+    } else if (selector.isSetter()) {
+      String proposedName = privateName(selector.library, selector.name);
+      return 'set\$${getMappedInstanceName(proposedName)}';
+    } else {
+      SourceString name = selector.name;
+      if (selector.kind == SelectorKind.OPERATOR
+          || selector.kind == SelectorKind.INDEX) {
+        name = operatorNameToIdentifier(name);
+        assert(name != selector.name);
+        return getMappedOperatorName(name.slowToString());
+      }
+      assert(name == operatorNameToIdentifier(name));
+      StringBuffer buffer = new StringBuffer();
+      for (SourceString argumentName in selector.getOrderedNamedArguments()) {
+        buffer.add(r'$');
+        argumentName.printOn(buffer);
+      }
+      String suffix = '\$${selector.argumentCount}$buffer';
+      // We don't mangle the closure invoking function name because it
+      // is generated by string concatenation in applyFunction from
+      // js_helper.dart.
+      if (selector.isClosureCall()) {
+        return "${name.slowToString()}$suffix";
+      } else {
+        String proposedName = privateName(selector.library, name);
+        return getMappedInstanceName('$proposedName$suffix');
+      }
+    }
+  }
+
+  /**
+   * Returns the internal name used for an invocation mirror of this selector.
+   */
+  String invocationMirrorInternalName(Selector selector)
+      => invocationName(selector);
+
+  String instanceFieldName(Element element) {
+    String proposedName = privateName(element.getLibrary(), element.name);
+    return getMappedInstanceName(proposedName);
+  }
+
+  // Construct a new name for the element based on the library and class it is
+  // in.  The name here is not important, we just need to make sure it is
+  // unique.  If we are minifying, we actually construct the name from the
+  // minified versions of the class and instance names, but the result is
+  // minified once again, so that is not visible in the end result.
+  String shadowedFieldName(Element fieldElement) {
+    // Check for following situation: Native field ${fieldElement.name} has
+    // fixed JSName ${fieldElement.nativeName()}, but a subclass shadows this
+    // name.  We normally handle that by renaming the superclass field, but we
+    // can't do that because native fields have fixed JavaScript names.
+    // In practice this can't happen because we can't inherit from native
+    // classes.
+    assert (!fieldElement.hasFixedBackendName());
+
+    String libraryName = getName(fieldElement.getLibrary());
+    String className = getName(fieldElement.getEnclosingClass());
+    String instanceName = instanceFieldName(fieldElement);
+    return getMappedInstanceName('$libraryName\$$className\$$instanceName');
+  }
+
+  String setterName(Element element) {
+    // We dynamically create setters from the field-name. The setter name must
+    // therefore be derived from the instance field-name.
+    LibraryElement library = element.getLibrary();
+    String name = getMappedInstanceName(privateName(library, element.name));
+    return 'set\$$name';
+  }
+
+  String setterNameFromAccessorName(String name) {
+    // We dynamically create setters from the field-name. The setter name must
+    // therefore be derived from the instance field-name.
+    return 'set\$$name';
+  }
+
+  String publicGetterName(SourceString name) {
+    // We dynamically create getters from the field-name. The getter name must
+    // therefore be derived from the instance field-name.
+    String fieldName = getMappedInstanceName(name.slowToString());
+    return 'get\$$fieldName';
+  }
+
+  String getterNameFromAccessorName(String name) {
+    // We dynamically create getters from the field-name. The getter name must
+    // therefore be derived from the instance field-name.
+    return 'get\$$name';
+  }
+
+  String getterName(Element element) {
+    // We dynamically create getters from the field-name. The getter name must
+    // therefore be derived from the instance field-name.
+    LibraryElement library = element.getLibrary();
+    String name = getMappedInstanceName(privateName(library, element.name));
+    return 'get\$$name';
+  }
+
+  String getMappedGlobalName(String proposedName) {
+    var newName = globalNameMap[proposedName];
+    if (newName == null) {
+      newName = getFreshName(proposedName, usedGlobalNames,
+                             suggestedGlobalNames, ensureSafe: true);
+      globalNameMap[proposedName] = newName;
+    }
+    return newName;
+  }
+
+  String getMappedInstanceName(String proposedName) {
+    var newName = instanceNameMap[proposedName];
+    if (newName == null) {
+      newName = getFreshName(proposedName, usedInstanceNames,
+                             suggestedInstanceNames, ensureSafe: true);
+      instanceNameMap[proposedName] = newName;
+    }
+    return newName;
+  }
+
+  String getMappedOperatorName(String proposedName) {
+    var newName = operatorNameMap[proposedName];
+    if (newName == null) {
+      newName = getFreshName(proposedName, usedInstanceNames,
+                             suggestedInstanceNames, ensureSafe: false);
+      operatorNameMap[proposedName] = newName;
+    }
+    return newName;
+  }
+
+  String getFreshName(String proposedName,
+                      Set<String> usedNames,
+                      Map<String, String> suggestedNames,
+                      {bool ensureSafe: true}) {
+    var candidate;
+    if (ensureSafe) {
+      proposedName = safeName(proposedName);
+    }
+    assert(!jsReserved.contains(proposedName));
+    if (!usedNames.contains(proposedName)) {
+      candidate = proposedName;
+    } else {
+      var counter = popularNameCounters[proposedName];
+      var i = counter == null ? 0 : counter;
+      while (usedNames.contains("$proposedName$i")) {
+        i++;
+      }
+      popularNameCounters[proposedName] = i + 1;
+      candidate = "$proposedName$i";
+    }
+    usedNames.add(candidate);
+    return candidate;
+  }
+
+  SourceString getClosureVariableName(SourceString name, int id) {
+    return new SourceString("${name.slowToString()}_$id");
+  }
+
+  static const String LIBRARY_PREFIX = "lib";
+
+  /**
+   * Returns a preferred JS-id for the given top-level or static element.
+   * The returned id is guaranteed to be a valid JS-id.
+   */
+  String _computeGuess(Element element) {
+    assert(!element.isInstanceMember());
+    String name;
+    if (element.isGenerativeConstructor()) {
+      if (element.name == element.getEnclosingClass().name) {
+        // Keep the class name for the class and not the factory.
+        name = "${element.name.slowToString()}\$";
+      } else {
+        name = element.name.slowToString();
+      }
+    } else if (Elements.isStaticOrTopLevel(element)) {
+      if (element.isMember()) {
+        ClassElement enclosingClass = element.getEnclosingClass();
+        name = "${enclosingClass.name.slowToString()}_"
+               "${element.name.slowToString()}";
+      } else {
+        name = element.name.slowToString();
+      }
+    } else if (element.isLibrary()) {
+      name = LIBRARY_PREFIX;
+    } else {
+      name = element.name.slowToString();
+    }
+    return name;
+  }
+
+  String getInterceptorName(Element element, Collection<ClassElement> classes) {
+    if (classes.contains(compiler.objectClass)) {
+      // If the object class is in the set of intercepted classes, we
+      // need to go through the generic getInterceptorMethod.
+      return getName(element);
+    }
+    // Use the unminified names here to construct the interceptor names.  This
+    // helps ensure that they don't all suddenly change names due to a name
+    // clash in the minifier, which would affect the diff size.
+    StringBuffer buffer = new StringBuffer('${element.name.slowToString()}\$');
+    for (ClassElement cls in classes) {
+      buffer.add(cls.name.slowToString());
+    }
+    return getMappedGlobalName(buffer.toString());
+  }
+
+  String getBailoutName(Element element) {
+    String name = bailoutNames[element];
+    if (name != null) return name;
+    bool global = !element.isInstanceMember();
+    // Despite the name of the variable, this gets the minified name when we
+    // are minifying, but it doesn't really make much difference.  The
+    // important thing is that it is a unique name.  We add $bailout and, if we
+    // are minifying, we minify the minified name and '$bailout'.
+    String unminifiedName = '${getName(element)}\$bailout';
+    if (global) {
+      name = getMappedGlobalName(unminifiedName);
+    } else {
+      // Make sure two bailout methods on the same inheritance chain do not have
+      // the same name to prevent a subclass bailout method being accidentally
+      // called from the superclass main method.  Use the count of the number of
+      // elements with the same name on the superclass chain to disambiguate
+      // based on 'level'.
+      int level = 0;
+      ClassElement classElement = element.getEnclosingClass().superclass;
+      while (classElement != null) {
+        if (classElement.localLookup(element.name) != null) level++;
+        classElement = classElement.superclass;
+      }
+      name = unminifiedName;
+      if (level != 0) {
+        name = '$unminifiedName$level';
+      }
+      name = getMappedInstanceName(name);
+    }
+    bailoutNames[element] = name;
+    return name;
+  }
+
+  /**
+   * Returns a preferred JS-id for the given element. The returned id is
+   * guaranteed to be a valid JS-id. Globals and static fields are furthermore
+   * guaranteed to be unique.
+   *
+   * For accessing statics consider calling
+   * [isolateAccess]/[isolateBailoutAccess] or [isolatePropertyAccess] instead.
+   */
+  String getName(Element element) {
+    if (element.isInstanceMember()) {
+      if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY
+          || element.kind == ElementKind.FUNCTION) {
+        return instanceMethodName(element);
+      } else if (element.kind == ElementKind.GETTER) {
+        return getterName(element);
+      } else if (element.kind == ElementKind.SETTER) {
+        return setterName(element);
+      } else if (element.kind == ElementKind.FIELD) {
+        return instanceFieldName(element);
+      } else {
+        compiler.internalError('getName for bad kind: ${element.kind}',
+                               node: element.parseNode(compiler));
+      }
+    } else {
+      // Use declaration element to ensure invariant on [globals].
+      element = element.declaration;
+      // Dealing with a top-level or static element.
+      String cached = globals[element];
+      if (cached != null) return cached;
+
+      String guess = _computeGuess(element);
+      ElementKind kind = element.kind;
+      if (kind == ElementKind.VARIABLE ||
+          kind == ElementKind.PARAMETER) {
+        // The name is not guaranteed to be unique.
+        return safeName(guess);
+      }
+      if (kind == ElementKind.GENERATIVE_CONSTRUCTOR ||
+          kind == ElementKind.FUNCTION ||
+          kind == ElementKind.CLASS ||
+          kind == ElementKind.FIELD ||
+          kind == ElementKind.GETTER ||
+          kind == ElementKind.SETTER ||
+          kind == ElementKind.TYPEDEF ||
+          kind == ElementKind.LIBRARY ||
+          kind == ElementKind.MALFORMED_TYPE) {
+        bool fixedName = false;
+        if (kind == ElementKind.CLASS) {
+          ClassElement classElement = element;
+        }
+        if (Elements.isInstanceField(element)) {
+          fixedName = element.hasFixedBackendName();
+        }
+        String result = fixedName
+            ? guess
+            : getFreshName(guess, usedGlobalNames, suggestedGlobalNames,
+                           ensureSafe: true);
+        globals[element] = result;
+        return result;
+      }
+      compiler.internalError('getName for unknown kind: ${element.kind}',
+                              node: element.parseNode(compiler));
+    }
+  }
+
+  String getLazyInitializerName(Element element) {
+    assert(Elements.isStaticOrTopLevelField(element));
+    return getMappedGlobalName("get\$${getName(element)}");
+  }
+
+  String isolatePropertiesAccess(Element element) {
+    return "$isolateName.$isolatePropertiesName.${getName(element)}";
+  }
+
+  String isolateAccess(Element element) {
+    return "$CURRENT_ISOLATE.${getName(element)}";
+  }
+
+  String isolateBailoutAccess(Element element) {
+    String newName = getMappedGlobalName('${getName(element)}\$bailout');
+    return '$CURRENT_ISOLATE.$newName';
+  }
+
+  String isolateLazyInitializerAccess(Element element) {
+    return "$CURRENT_ISOLATE.${getLazyInitializerName(element)}";
+  }
+
+  String operatorIsPrefix() => r'$is';
+
+  String operatorIs(Element element) {
+    // TODO(erikcorry): Reduce from $isx to ix when we are minifying.
+    return '${operatorIsPrefix()}${getName(element)}';
+  }
+
+  /*
+   * Returns a name that does not clash with reserved JS keywords,
+   * and also ensures it won't clash with other identifiers.
+   */
+  String _safeName(String name, Set<String> reserved) {
+    if (reserved.contains(name) || name.startsWith(r'$')) {
+      name = '\$$name';
+    }
+    assert(!reserved.contains(name));
+    return name;
+  }
+
+  String safeName(String name) => _safeName(name, jsReserved);
+  String safeVariableName(String name) => _safeName(name, jsVariableReserved);
+
+  String oneShotInterceptorName(Selector selector) {
+    // TODO(ngeoffray): What to do about typed selectors? We could
+    // filter them out, or keep them and hope the generated one shot
+    // interceptor takes advantage of the type.
+    String cached = oneShotInterceptorNames[selector];
+    if (cached != null) return cached;
+    SourceString name = operatorNameToIdentifier(selector.name);
+    String result = getFreshName(name.slowToString(), usedGlobalNames,
+                                 suggestedGlobalNames);
+    oneShotInterceptorNames[selector] = result;
+    return result;
+  }
+
+  SourceString operatorNameToIdentifier(SourceString name) {
+    if (name == null) return null;
+    String value = name.stringValue;
+    if (value == null) {
+      return name;
+    } else if (value == '==') {
+      return const SourceString(r'$eq');
+    } else if (value == '~') {
+      return const SourceString(r'$not');
+    } else if (value == '[]') {
+      return const SourceString(r'$index');
+    } else if (value == '[]=') {
+      return const SourceString(r'$indexSet');
+    } else if (value == '*') {
+      return const SourceString(r'$mul');
+    } else if (value == '/') {
+      return const SourceString(r'$div');
+    } else if (value == '%') {
+      return const SourceString(r'$mod');
+    } else if (value == '~/') {
+      return const SourceString(r'$tdiv');
+    } else if (value == '+') {
+      return const SourceString(r'$add');
+    } else if (value == '<<') {
+      return const SourceString(r'$shl');
+    } else if (value == '>>') {
+      return const SourceString(r'$shr');
+    } else if (value == '>=') {
+      return const SourceString(r'$ge');
+    } else if (value == '>') {
+      return const SourceString(r'$gt');
+    } else if (value == '<=') {
+      return const SourceString(r'$le');
+    } else if (value == '<') {
+      return const SourceString(r'$lt');
+    } else if (value == '&') {
+      return const SourceString(r'$and');
+    } else if (value == '^') {
+      return const SourceString(r'$xor');
+    } else if (value == '|') {
+      return const SourceString(r'$or');
+    } else if (value == '-') {
+      return const SourceString(r'$sub');
+    } else if (value == 'unary-') {
+      return const SourceString(r'$negate');
+    } else {
+      return name;
+    }
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart
new file mode 100644
index 0000000..9400f5f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart
@@ -0,0 +1,546 @@
+// 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.
+
+part of js_backend;
+
+class NativeEmitter {
+
+  CodeEmitterTask emitter;
+  CodeBuffer nativeBuffer;
+
+  // Classes that participate in dynamic dispatch. These are the
+  // classes that contain used members.
+  Set<ClassElement> classesWithDynamicDispatch;
+
+  // Native classes found in the application.
+  Set<ClassElement> nativeClasses;
+
+  // Caches the native subtypes of a native class.
+  Map<ClassElement, List<ClassElement>> subtypes;
+
+  // Caches the direct native subtypes of a native class.
+  Map<ClassElement, List<ClassElement>> directSubtypes;
+
+  // Caches the native methods that are overridden by a native class.
+  // Note that the method that overrides does not have to be native:
+  // it's the overridden method that must make sure it will dispatch
+  // to its subclass if it sees an instance whose class is a subclass.
+  Set<FunctionElement> overriddenMethods;
+
+  // Caches the methods that have a native body.
+  Set<FunctionElement> nativeMethods;
+
+  // Do we need the native emitter to take care of handling
+  // noSuchMethod for us? This flag is set to true in the emitter if
+  // it finds any native class that needs noSuchMethod handling.
+  bool handleNoSuchMethod = false;
+
+  NativeEmitter(this.emitter)
+      : classesWithDynamicDispatch = new Set<ClassElement>(),
+        nativeClasses = new Set<ClassElement>(),
+        subtypes = new Map<ClassElement, List<ClassElement>>(),
+        directSubtypes = new Map<ClassElement, List<ClassElement>>(),
+        overriddenMethods = new Set<FunctionElement>(),
+        nativeMethods = new Set<FunctionElement>(),
+        nativeBuffer = new CodeBuffer();
+
+  Compiler get compiler => emitter.compiler;
+  JavaScriptBackend get backend => compiler.backend;
+
+  String get _ => emitter._;
+  String get n => emitter.n;
+  String get N => emitter.N;
+
+  String get dynamicName {
+    Element element = compiler.findHelper(
+        const SourceString('dynamicFunction'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get dynamicSetMetadataName {
+    Element element = compiler.findHelper(
+        const SourceString('dynamicSetMetadata'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get typeNameOfName {
+    Element element = compiler.findHelper(
+        const SourceString('getTypeNameOf'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get defPropName {
+    Element element = compiler.findHelper(
+        const SourceString('defineProperty'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get toStringHelperName {
+    Element element = compiler.findHelper(
+        const SourceString('toStringForNativeObject'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get hashCodeHelperName {
+    Element element = compiler.findHelper(
+        const SourceString('hashCodeForNativeObject'));
+    return backend.namer.isolateAccess(element);
+  }
+
+  String get defineNativeClassName
+      => '${backend.namer.CURRENT_ISOLATE}.\$defineNativeClass';
+
+  String get defineNativeClassFunction {
+    return """
+function(cls, desc) {
+  var fields = desc[''];
+  var fields_array = fields ? fields.split(',') : [];
+  for (var i = 0; i < fields_array.length; i++) {
+    ${emitter.currentGenerateAccessorName}(fields_array[i], desc);
+  }
+  var hasOwnProperty = Object.prototype.hasOwnProperty;
+  for (var method in desc) {
+    if (method) {
+      if (hasOwnProperty.call(desc, method)) {
+        $dynamicName(method)[cls] = desc[method];
+      }
+    }
+  }
+}""";
+  }
+
+  bool isNativeGlobal(String quotedName) {
+    return identical(quotedName[1], '@');
+  }
+
+  String toNativeTag(ClassElement cls) {
+    String quotedName = cls.nativeTagInfo.slowToString();
+    if (isNativeGlobal(quotedName)) {
+      // Global object, just be like the other types for now.
+      return quotedName.substring(3, quotedName.length - 1);
+    } else {
+      return quotedName.substring(2, quotedName.length - 1);
+    }
+  }
+
+  void generateNativeClass(ClassElement classElement) {
+    assert(!classElement.hasBackendMembers);
+    nativeClasses.add(classElement);
+
+    ClassBuilder builder = new ClassBuilder();
+    emitter.emitClassFields(classElement, builder, classIsNative: true);
+    emitter.emitClassGettersSetters(classElement, builder);
+    emitter.emitInstanceMembers(classElement, builder);
+
+    // An empty native class may be omitted since the superclass methods can be
+    // located via the dispatch metadata.
+    if (builder.properties.isEmpty) return;
+
+    String nativeTag = toNativeTag(classElement);
+    js.Expression definition =
+        js.call(js.use(defineNativeClassName),
+                [js.string(nativeTag), builder.toObjectInitializer()]);
+
+    nativeBuffer.add(js.prettyPrint(definition, compiler));
+    nativeBuffer.add('$N$n');
+
+    classesWithDynamicDispatch.add(classElement);
+  }
+
+  List<ClassElement> getDirectSubclasses(ClassElement cls) {
+    List<ClassElement> result = directSubtypes[cls];
+    return result == null ? const<ClassElement>[] : result;
+  }
+
+  void potentiallyConvertDartClosuresToJs(List<js.Statement> statements,
+                                          FunctionElement member,
+                                          List<js.Parameter> stubParameters) {
+    FunctionSignature parameters = member.computeSignature(compiler);
+    Element converter =
+        compiler.findHelper(const SourceString('convertDartClosureToJS'));
+    String closureConverter = backend.namer.isolateAccess(converter);
+    Set<String> stubParameterNames = new Set<String>.from(
+        stubParameters.map((param) => param.name));
+    parameters.forEachParameter((Element parameter) {
+      String name = parameter.name.slowToString();
+      // If [name] is not in [stubParameters], then the parameter is an optional
+      // parameter that was not provided for this stub.
+      for (js.Parameter stubParameter in stubParameters) {
+        if (stubParameter.name == name) {
+          DartType type = parameter.computeType(compiler).unalias(compiler);
+          if (type is FunctionType) {
+            // The parameter type is a function type either directly or through
+            // typedef(s).
+            int arity = type.computeArity();
+
+            statements.add(
+                new js.ExpressionStatement(
+                    js.assign(
+                        js.use(name),
+                        js.use(closureConverter).callWith(
+                            [js.use(name), new js.LiteralNumber('$arity')]))));
+            break;
+          }
+        }
+      }
+    });
+  }
+
+  List<js.Statement> generateParameterStubStatements(
+      Element member,
+      String invocationName,
+      List<js.Parameter> stubParameters,
+      List<js.Expression> argumentsBuffer,
+      int indexOfLastOptionalArgumentInParameters) {
+    // The target JS function may check arguments.length so we need to
+    // make sure not to pass any unspecified optional arguments to it.
+    // For example, for the following Dart method:
+    //   foo([x, y, z]);
+    // The call:
+    //   foo(y: 1)
+    // must be turned into a JS call to:
+    //   foo(null, y).
+
+    ClassElement classElement = member.enclosingElement;
+    String nativeTagInfo = classElement.nativeTagInfo.slowToString();
+
+    List<js.Statement> statements = <js.Statement>[];
+    potentiallyConvertDartClosuresToJs(statements, member, stubParameters);
+
+    String target;
+    List<js.Expression> arguments;
+
+    if (!nativeMethods.contains(member)) {
+      // When calling a method that has a native body, we call it with our
+      // calling conventions.
+      target = backend.namer.getName(member);
+      arguments = argumentsBuffer;
+    } else {
+      // When calling a JS method, we call it with the native name, and only the
+      // arguments up until the last one provided.
+      target = member.fixedBackendName();
+      arguments = argumentsBuffer.getRange(
+          0, indexOfLastOptionalArgumentInParameters + 1);
+    }
+    statements.add(
+        new js.Return(
+            new js.VariableUse('this').dot(target).callWith(arguments)));
+
+    if (!overriddenMethods.contains(member)) {
+      // Call the method directly.
+      return statements;
+    } else {
+      return <js.Statement>[
+          generateMethodBodyWithPrototypeCheck(
+              invocationName, new js.Block(statements), stubParameters)];
+    }
+  }
+
+  // If a method is overridden, we must check if the prototype of 'this' has the
+  // method available. Otherwise, we may end up calling the method from the
+  // super class. If the method is not available, we make a direct call to
+  // Object.prototype.$methodName.  This method will patch the prototype of
+  // 'this' to the real method.
+  js.Statement generateMethodBodyWithPrototypeCheck(
+      String methodName,
+      js.Statement body,
+      List<js.Parameter> parameters) {
+    return js.if_(
+        js.use('Object').dot('getPrototypeOf')
+            .callWith([js.use('this')])
+            .dot('hasOwnProperty').callWith([js.string(methodName)]),
+        body,
+        js.return_(
+            js.use('Object').dot('prototype').dot(methodName).dot('call')
+            .callWith(
+                <js.Expression>[js.use('this')]..addAll(
+                    parameters.map((param) => js.use(param.name))))));
+  }
+
+  js.Block generateMethodBodyWithPrototypeCheckForElement(
+      FunctionElement element,
+      js.Block body,
+      List<js.Parameter> parameters) {
+    ElementKind kind = element.kind;
+    if (kind != ElementKind.FUNCTION &&
+        kind != ElementKind.GETTER &&
+        kind != ElementKind.SETTER) {
+      compiler.internalError("unexpected kind: '$kind'", element: element);
+    }
+
+    String methodName = backend.namer.getName(element);
+    return new js.Block(
+        [generateMethodBodyWithPrototypeCheck(methodName, body, parameters)]);
+  }
+
+
+  void emitDynamicDispatchMetadata() {
+    if (classesWithDynamicDispatch.isEmpty) return;
+    int length = classesWithDynamicDispatch.length;
+    if (!compiler.enableMinification) {
+      nativeBuffer.add('// $length dynamic classes.\n');
+    }
+
+    // Build a pre-order traversal over all the classes and their subclasses.
+    Set<ClassElement> seen = new Set<ClassElement>();
+    List<ClassElement> classes = <ClassElement>[];
+    void visit(ClassElement cls) {
+      if (seen.contains(cls)) return;
+      seen.add(cls);
+      getDirectSubclasses(cls).forEach(visit);
+      classes.add(cls);
+    }
+    classesWithDynamicDispatch.forEach(visit);
+
+    List<ClassElement> preorderDispatchClasses = classes.where(
+        (cls) => !getDirectSubclasses(cls).isEmpty &&
+                  classesWithDynamicDispatch.contains(cls)).toList();
+
+    if (!compiler.enableMinification) {
+      nativeBuffer.add('// ${classes.length} classes\n');
+    }
+    Iterable<ClassElement> classesThatHaveSubclasses = classes.where(
+        (ClassElement t) => !getDirectSubclasses(t).isEmpty);
+    if (!compiler.enableMinification) {
+      nativeBuffer.add('// ${classesThatHaveSubclasses.length} !leaf\n');
+    }
+
+    // Generate code that builds the map from cls tags used in dynamic dispatch
+    // to the set of cls tags of classes that extend (TODO: or implement) those
+    // classes.  The set is represented as a string of tags joined with '|'.
+    // This is easily split into an array of tags, or converted into a regexp.
+    //
+    // To reduce the size of the sets, subsets are CSE-ed out into variables.
+    // The sets could be much smaller if we could make assumptions about the
+    // cls tags of other classes (which are constructor names or part of the
+    // result of Object.protocls.toString).  For example, if objects that are
+    // Dart objects could be easily excluded, then we might be able to simplify
+    // the test, replacing dozens of HTMLxxxElement classes with the regexp
+    // /HTML.*Element/.
+
+    // Temporary variables for common substrings.
+    List<String> varNames = <String>[];
+    // Values of temporary variables.
+    Map<String, js.Expression> varDefns = new Map<String, js.Expression>();
+
+    // Expression to compute tags string for a class.  The expression will
+    // initially be a string or expression building a string, but may be
+    // replaced with a variable reference to the common substring.
+    Map<ClassElement, js.Expression> tagDefns =
+        new Map<ClassElement, js.Expression>();
+
+    js.Expression makeExpression(ClassElement classElement) {
+      // Expression fragments for this set of cls keys.
+      List<js.Expression> expressions = <js.Expression>[];
+      // TODO: Remove if cls is abstract.
+      List<String> subtags = [toNativeTag(classElement)];
+      void walk(ClassElement cls) {
+        for (final ClassElement subclass in getDirectSubclasses(cls)) {
+          ClassElement tag = subclass;
+          js.Expression existing = tagDefns[tag];
+          if (existing == null) {
+            // [subclass] is still within the subtree between dispatch classes.
+            subtags.add(toNativeTag(tag));
+            walk(subclass);
+          } else {
+            // [subclass] is one of the preorderDispatchClasses, so CSE this
+            // reference with the previous reference.
+            js.VariableUse use = existing.asVariableUse();
+            if (use != null && varDefns.containsKey(use.name)) {
+              // We end up here if the subclasses have a DAG structure.  We
+              // don't have DAGs yet, but if the dispatch is used for mixins
+              // that will be a possibility.
+              // Re-use the previously created temporary variable.
+              expressions.add(new js.VariableUse(use.name));
+            } else {
+              String varName = 'v${varNames.length}_${tag.name.slowToString()}';
+              varNames.add(varName);
+              varDefns[varName] = existing;
+              tagDefns[tag] = new js.VariableUse(varName);
+              expressions.add(new js.VariableUse(varName));
+            }
+          }
+        }
+      }
+      walk(classElement);
+
+      if (!subtags.isEmpty) {
+        expressions.add(js.string(Strings.join(subtags, '|')));
+      }
+      js.Expression expression;
+      if (expressions.length == 1) {
+        expression = expressions[0];
+      } else {
+        js.Expression array = new js.ArrayInitializer.from(expressions);
+        expression = js.call(array.dot('join'), [js.string('|')]);
+      }
+      return expression;
+    }
+
+    for (final ClassElement classElement in preorderDispatchClasses) {
+      tagDefns[classElement] = makeExpression(classElement);
+    }
+
+    // Write out a thunk that builds the metadata.
+    if (!tagDefns.isEmpty) {
+      List<js.Statement> statements = <js.Statement>[];
+
+      List<js.VariableInitialization> initializations =
+          <js.VariableInitialization>[];
+      for (final String varName in varNames) {
+        initializations.add(
+            new js.VariableInitialization(
+                new js.VariableDeclaration(varName),
+                varDefns[varName]));
+      }
+      if (!initializations.isEmpty) {
+        statements.add(
+            new js.ExpressionStatement(
+                new js.VariableDeclarationList(initializations)));
+      }
+
+      // [table] is a list of lists, each inner list of the form:
+      //   [dynamic-dispatch-tag, tags-of-classes-implementing-dispatch-tag]
+      // E.g.
+      //   [['Node', 'Text|HTMLElement|HTMLDivElement|...'], ...]
+      js.Expression table =
+          new js.ArrayInitializer.from(
+              preorderDispatchClasses.map((cls) =>
+                  new js.ArrayInitializer.from([
+                      js.string(toNativeTag(cls)),
+                      tagDefns[cls]])));
+
+      //  $.dynamicSetMetadata(table);
+      statements.add(
+          new js.ExpressionStatement(
+              new js.Call(
+                  new js.VariableUse(dynamicSetMetadataName),
+                  [table])));
+
+      //  (function(){statements})();
+      if (emitter.compiler.enableMinification) nativeBuffer.add(';');
+      nativeBuffer.add(
+          js.prettyPrint(
+              new js.ExpressionStatement(
+                  new js.Call(new js.Fun([], new js.Block(statements)), [])),
+              compiler));
+    }
+  }
+
+  bool isSupertypeOfNativeClass(Element element) {
+    if (element.isTypeVariable()) {
+      compiler.cancel("Is check for type variable", element: element);
+      return false;
+    }
+    if (element.computeType(compiler).unalias(compiler) is FunctionType) {
+      // The element type is a function type either directly or through
+      // typedef(s).
+      return false;
+    }
+
+    if (!element.isClass()) {
+      compiler.cancel("Is check does not handle element", element: element);
+      return false;
+    }
+
+    return subtypes[element] != null;
+  }
+
+  bool requiresNativeIsCheck(Element element) {
+    if (!element.isClass()) return false;
+    ClassElement cls = element;
+    if (cls.isNative()) return true;
+    return isSupertypeOfNativeClass(element);
+  }
+
+  void assembleCode(CodeBuffer targetBuffer) {
+    if (nativeClasses.isEmpty) return;
+    emitDynamicDispatchMetadata();
+    targetBuffer.add('$defineNativeClassName = '
+                     '$defineNativeClassFunction$N$n');
+
+    List<js.Property> objectProperties = <js.Property>[];
+
+    void addProperty(String name, js.Expression value) {
+      objectProperties.add(new js.Property(js.string(name), value));
+    }
+
+    // Because of native classes, we have to generate some is checks
+    // by calling a method, instead of accessing a property. So we
+    // attach to the JS Object prototype these methods that return
+    // false, and will be overridden by subclasses when they have to
+    // return true.
+    void emitIsChecks() {
+      for (ClassElement element in
+               Elements.sortedByPosition(emitter.checkedClasses)) {
+        if (!requiresNativeIsCheck(element)) continue;
+        if (element.isObject(compiler)) continue;
+        String name = backend.namer.operatorIs(element);
+        addProperty(name,
+            js.fun([], js.block1(js.return_(new js.LiteralBool(false)))));
+      }
+    }
+    emitIsChecks();
+
+    js.Expression makeCallOnThis(String functionName) =>
+        js.fun([],
+            js.block1(
+                js.return_(
+                    js.call(js.use(functionName), [js.use('this')]))));
+
+    // In order to have the toString method on every native class,
+    // we must patch the JS Object prototype with a helper method.
+    String toStringName = backend.namer.publicInstanceMethodNameByArity(
+        const SourceString('toString'), 0);
+    addProperty(toStringName, makeCallOnThis(toStringHelperName));
+
+    // Same as above, but for hashCode.
+    String hashCodeName =
+        backend.namer.publicGetterName(const SourceString('hashCode'));
+    addProperty(hashCodeName, makeCallOnThis(hashCodeHelperName));
+
+    // Same as above, but for operator==.
+    String equalsName = backend.namer.publicInstanceMethodNameByArity(
+        const SourceString('=='), 1);
+    addProperty(equalsName, js.fun(['a'], js.block1(
+        js.return_(js.strictEquals(new js.This(), js.use('a'))))));
+
+    // If the native emitter has been asked to take care of the
+    // noSuchMethod handlers, we do that now.
+    if (handleNoSuchMethod) {
+      emitter.emitNoSuchMethodHandlers(addProperty);
+    }
+
+    // If we have any properties to add to Object.prototype, we run
+    // through them and add them using defineProperty.
+    if (!objectProperties.isEmpty) {
+      js.Expression init =
+          js.call(
+              js.fun(['table'],
+                  js.block1(
+                      new js.ForIn(
+                          new js.VariableDeclarationList(
+                              [new js.VariableInitialization(
+                                  new js.VariableDeclaration('key'),
+                                  null)]),
+                          js.use('table'),
+                          new js.ExpressionStatement(
+                              js.call(
+                                  js.use(defPropName),
+                                  [js.use('Object').dot('prototype'),
+                                   js.use('key'),
+                                   new js.PropertyAccess(js.use('table'),
+                                                         js.use('key'))]))))),
+              [new js.ObjectInitializer(objectProperties)]);
+
+      if (emitter.compiler.enableMinification) targetBuffer.add(';');
+      targetBuffer.add(js.prettyPrint(
+          new js.ExpressionStatement(init), compiler));
+      targetBuffer.add('\n');
+    }
+
+    targetBuffer.add(nativeBuffer);
+    targetBuffer.add('\n');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart
new file mode 100644
index 0000000..a3a0339
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart
@@ -0,0 +1,173 @@
+// 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.
+
+part of js_backend;
+
+/// For each class, stores the possible class subtype tests that could succeed.
+abstract class TypeChecks {
+  /// Get the set of checks required for class [element].
+  Iterable<ClassElement> operator[](ClassElement element);
+  // Get the iterator for all classes that need type checks.
+  Iterator<ClassElement> get iterator;
+}
+
+class RuntimeTypeInformation {
+  final Compiler compiler;
+
+  RuntimeTypeInformation(this.compiler);
+
+  /// Contains the classes of all arguments that have been used in
+  /// instantiations and checks.
+  Set<ClassElement> allArguments;
+
+  bool isJsNative(Element element) {
+    return (element == compiler.intClass ||
+            element == compiler.boolClass ||
+            element == compiler.numClass ||
+            element == compiler.doubleClass ||
+            element == compiler.stringClass ||
+            element == compiler.listClass ||
+            element == compiler.objectClass ||
+            element == compiler.dynamicClass);
+  }
+
+  TypeChecks computeRequiredChecks() {
+    Set<ClassElement> instantiatedArguments = new Set<ClassElement>();
+    for (DartType type in compiler.codegenWorld.instantiatedTypes) {
+      addAllInterfaceTypeArguments(type, instantiatedArguments);
+    }
+
+    Set<ClassElement> checkedArguments = new Set<ClassElement>();
+    for (DartType type in compiler.enqueuer.codegen.universe.isChecks) {
+      addAllInterfaceTypeArguments(type, checkedArguments);
+    }
+
+    allArguments = new Set<ClassElement>.from(instantiatedArguments)
+        ..addAll(checkedArguments);
+
+    TypeCheckMapping requiredChecks = new TypeCheckMapping();
+    for (ClassElement element in instantiatedArguments) {
+      if (element == compiler.dynamicClass) continue;
+      if (checkedArguments.contains(element)) {
+        requiredChecks.add(element, element);
+      }
+      // Find all supertypes of [element] in [checkedArguments] and add checks.
+      for (DartType supertype in element.allSupertypes) {
+        ClassElement superelement = supertype.element;
+        if (checkedArguments.contains(superelement)) {
+          requiredChecks.add(element, superelement);
+        }
+      }
+    }
+
+    return requiredChecks;
+  }
+
+  void addAllInterfaceTypeArguments(DartType type, Set<ClassElement> classes) {
+    if (type is !InterfaceType) return;
+    for (DartType argument in type.typeArguments) {
+      forEachInterfaceType(argument, (InterfaceType t) {
+        ClassElement cls = t.element;
+        if (cls != compiler.dynamicClass && cls != compiler.objectClass) {
+          classes.add(cls);
+        }
+      });
+    }
+  }
+
+  void forEachInterfaceType(DartType type, f(InterfaceType type)) {
+    if (type.kind == TypeKind.INTERFACE) {
+      f(type);
+      InterfaceType interface = type;
+      for (DartType argument in interface.typeArguments) {
+        forEachInterfaceType(argument, f);
+      }
+    }
+  }
+
+  /// Return the unique name for the element as an unquoted string.
+  String getNameAsString(Element element) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.namer.getName(element);
+  }
+
+  /// Return the unique JS name for the element, which is a quoted string for
+  /// native classes and the isolate acccess to the constructor for classes.
+  String getJsName(Element element) {
+    JavaScriptBackend backend = compiler.backend;
+    Namer namer = backend.namer;
+    return namer.isolateAccess(element);
+  }
+
+  String getRawTypeRepresentation(DartType type) {
+    String name = getNameAsString(type.element);
+    if (!type.element.isClass()) return name;
+    InterfaceType interface = type;
+    Link<DartType> variables = interface.element.typeVariables;
+    if (variables.isEmpty) return name;
+    List<String> arguments = [];
+    variables.forEach((_) => arguments.add('dynamic'));
+    return '$name<${Strings.join(arguments, ', ')}>';
+  }
+
+  String getTypeRepresentation(DartType type, void onVariable(variable)) {
+    StringBuffer builder = new StringBuffer();
+    void build(DartType part) {
+      if (part is TypeVariableType) {
+        builder.add('#');
+        onVariable(part);
+      } else {
+        bool hasArguments = part is InterfaceType && !part.isRaw;
+        Element element = part.element;
+        if (hasArguments) {
+          builder.add('[');
+        }
+        builder.add(getJsName(element));
+        if (!hasArguments) return;
+        InterfaceType interface = part;
+        for (DartType argument in interface.typeArguments) {
+          builder.add(', ');
+          build(argument);
+        }
+        builder.add(']');
+      }
+    }
+    build(type);
+    return builder.toString();
+  }
+
+  static bool hasTypeArguments(DartType type) {
+    if (type is InterfaceType) {
+      InterfaceType interfaceType = type;
+      return !interfaceType.isRaw;
+    }
+    return false;
+  }
+
+  static int getTypeVariableIndex(TypeVariableType variable) {
+    ClassElement classElement = variable.element.getEnclosingClass();
+    Link<DartType> variables = classElement.typeVariables;
+    for (int index = 0; !variables.isEmpty;
+         index++, variables = variables.tail) {
+      if (variables.head == variable) return index;
+    }
+  }
+}
+
+class TypeCheckMapping implements TypeChecks {
+  final Map<ClassElement, Set<ClassElement>> map =
+      new Map<ClassElement, Set<ClassElement>>();
+
+  Iterable<ClassElement> operator[](ClassElement element) {
+    Set<ClassElement> result = map[element];
+    return result != null ? result : const <ClassElement>[];
+  }
+
+  void add(ClassElement cls, ClassElement check) {
+    map.putIfAbsent(cls, () => new Set<ClassElement>());
+    map[cls].add(check);
+  }
+
+  Iterator<ClassElement> get iterator => map.keys.iterator;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart
new file mode 100644
index 0000000..0fccce2
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart
@@ -0,0 +1,21 @@
+// 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.
+
+// Patch file for the dart:async library.
+
+import 'dart:_isolate_helper' show TimerImpl;
+
+patch class Timer {
+  patch factory Timer(int milliseconds, void callback(Timer timer)) {
+    return new TimerImpl(milliseconds, callback);
+  }
+
+  /**
+   * Creates a new repeating timer. The [callback] is invoked every
+   * [milliseconds] millisecond until cancelled.
+   */
+  patch factory Timer.repeating(int milliseconds, void callback(Timer timer)) {
+    return new TimerImpl.repeating(milliseconds, callback);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart
new file mode 100644
index 0000000..75446c7
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart
@@ -0,0 +1,75 @@
+// 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.
+
+part of _js_helper;
+
+// This class has no constructor. This is on purpose since the instantiation
+// is shortcut by the compiler.
+class ConstantMap<V> implements Map<String, V> {
+  final int length;
+  // A constant map is backed by a JavaScript object.
+  final _jsObject;
+  final List<String> _keys;
+
+  bool containsValue(V needle) {
+    return values.any((V value) => value == needle);
+  }
+
+  bool containsKey(String key) {
+    if (key == '__proto__') return false;
+    return jsHasOwnProperty(_jsObject, key);
+  }
+
+  V operator [](String key) {
+    if (!containsKey(key)) return null;
+    return jsPropertyAccess(_jsObject, key);
+  }
+
+  void forEach(void f(String key, V value)) {
+    _keys.forEach((String key) => f(key, this[key]));
+  }
+
+  Iterable<String> get keys {
+    return new _ConstantMapKeyIterable(this);
+  }
+
+  Iterable<V> get values {
+    return _keys.map((String key) => this[key]);
+  }
+
+  bool get isEmpty => length == 0;
+
+  String toString() => Maps.mapToString(this);
+
+  _throwUnmodifiable() {
+    throw new UnsupportedError("Cannot modify unmodifiable Map");
+  }
+  void operator []=(String key, V val) => _throwUnmodifiable();
+  V putIfAbsent(String key, V ifAbsent()) => _throwUnmodifiable();
+  V remove(String key) => _throwUnmodifiable();
+  void clear() => _throwUnmodifiable();
+}
+
+// This class has no constructor. This is on purpose since the instantiation
+// is shortcut by the compiler.
+class ConstantProtoMap<V> extends ConstantMap<V> {
+  final V _protoValue;
+
+  bool containsKey(String key) {
+    if (key == '__proto__') return true;
+    return super.containsKey(key);
+  }
+
+  V operator [](String key) {
+    if (key == '__proto__') return _protoValue;
+    return super[key];
+  }
+}
+
+class _ConstantMapKeyIterable extends Iterable<String> {
+  ConstantMap _map;
+  _ConstantMapKeyIterable(this._map);
+
+  Iterator<String> get iterator => _map._keys.iterator;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart
new file mode 100644
index 0000000..09e31da
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart
@@ -0,0 +1,250 @@
+// 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.
+
+// Patch file for dart:core classes.
+
+import 'dart:_interceptors';
+import 'dart:_js_helper' show checkNull,
+                              getRuntimeTypeString,
+                              isJsArray,
+                              JSSyntaxRegExp,
+                              Primitives,
+                              TypeImpl,
+                              stringJoinUnchecked,
+                              JsStringBuffer;
+
+patch void print(var object) {
+  Primitives.printString(object.toString());
+}
+
+// Patch for Object implementation.
+patch class Object {
+  patch int get hashCode => Primitives.objectHashCode(this);
+
+  patch String toString() => Primitives.objectToString(this);
+
+  patch dynamic noSuchMethod(InvocationMirror invocation) {
+    throw new NoSuchMethodError(this,
+                                invocation.memberName,
+                                invocation.positionalArguments,
+                                invocation.namedArguments);
+  }
+
+  patch Type get runtimeType {
+    String type = getRuntimeTypeString(this);
+    return new TypeImpl(type);
+  }
+}
+
+// Patch for Function implementation.
+patch class Function {
+  patch static apply(Function function,
+                     List positionalArguments,
+                     [Map<String,dynamic> namedArguments]) {
+    return Primitives.applyFunction(
+        function, positionalArguments, namedArguments);
+  }
+}
+
+// Patch for Expando implementation.
+patch class Expando<T> {
+  patch Expando([String name]) : this.name = name;
+
+  patch T operator[](Object object) {
+    var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME);
+    return (values == null) ? null : Primitives.getProperty(values, _getKey());
+  }
+
+  patch void operator[]=(Object object, T value) {
+    var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME);
+    if (values == null) {
+      values = new Object();
+      Primitives.setProperty(object, _EXPANDO_PROPERTY_NAME, values);
+    }
+    Primitives.setProperty(values, _getKey(), value);
+  }
+
+  String _getKey() {
+    String key = Primitives.getProperty(this, _KEY_PROPERTY_NAME);
+    if (key == null) {
+      key = "expando\$key\$${_keyCount++}";
+      Primitives.setProperty(this, _KEY_PROPERTY_NAME, key);
+    }
+    return key;
+  }
+
+  static const String _KEY_PROPERTY_NAME = 'expando\$key';
+  static const String _EXPANDO_PROPERTY_NAME = 'expando\$values';
+  static int _keyCount = 0;
+}
+
+patch class int {
+  patch static int parse(String source,
+                         { int radix,
+                           int onError(String source) }) {
+    return Primitives.parseInt(source, radix, onError);
+  }
+}
+
+patch class double {
+  patch static double parse(String source, [int handleError(String source)]) {
+    return Primitives.parseDouble(source, handleError);
+  }
+}
+
+patch class Error {
+  patch static String _objectToString(Object object) {
+    return Primitives.objectToString(object);
+  }
+}
+
+
+// Patch for DateTime implementation.
+patch class DateTime {
+  patch DateTime._internal(int year,
+                           int month,
+                           int day,
+                           int hour,
+                           int minute,
+                           int second,
+                           int millisecond,
+                           bool isUtc)
+      : this.isUtc = checkNull(isUtc),
+        millisecondsSinceEpoch = Primitives.valueFromDecomposedDate(
+            year, month, day, hour, minute, second, millisecond, isUtc) {
+    Primitives.lazyAsJsDate(this);
+  }
+
+  patch DateTime._now()
+      : isUtc = false,
+        millisecondsSinceEpoch = Primitives.dateNow() {
+    Primitives.lazyAsJsDate(this);
+  }
+
+  patch static int _brokenDownDateToMillisecondsSinceEpoch(
+      int year, int month, int day, int hour, int minute, int second,
+      int millisecond, bool isUtc) {
+    return Primitives.valueFromDecomposedDate(
+        year, month, day, hour, minute, second, millisecond, isUtc);
+  }
+
+  patch String get timeZoneName {
+    if (isUtc) return "UTC";
+    return Primitives.getTimeZoneName(this);
+  }
+
+  patch Duration get timeZoneOffset {
+    if (isUtc) return new Duration();
+    return new Duration(minutes: Primitives.getTimeZoneOffsetInMinutes(this));
+  }
+
+  patch int get year => Primitives.getYear(this);
+
+  patch int get month => Primitives.getMonth(this);
+
+  patch int get day => Primitives.getDay(this);
+
+  patch int get hour => Primitives.getHours(this);
+
+  patch int get minute => Primitives.getMinutes(this);
+
+  patch int get second => Primitives.getSeconds(this);
+
+  patch int get millisecond => Primitives.getMilliseconds(this);
+
+  patch int get weekday => Primitives.getWeekday(this);
+}
+
+
+// Patch for Stopwatch implementation.
+patch class Stopwatch {
+  patch static int _frequency() => 1000000;
+  patch static int _now() => Primitives.numMicroseconds();
+}
+
+
+// Patch for List implementation.
+patch class List<E> {
+  patch factory List([int length = 0]) {
+    // Explicit type test is necessary to protect Primitives.newGrowableList in
+    // unchecked mode.
+    if ((length is !int) || (length < 0)) {
+      throw new ArgumentError("Length must be a positive integer: $length.");
+    }
+    return Primitives.newGrowableList(length);
+  }
+
+  patch factory List.fixedLength(int length, {E fill: null}) {
+    // Explicit type test is necessary to protect Primitives.newFixedList in
+    // unchecked mode.
+    if ((length is !int) || (length < 0)) {
+      throw new ArgumentError("Length must be a positive integer: $length.");
+    }
+    List result = Primitives.newFixedList(length);
+    if (length != 0 && fill != null) {
+      for (int i = 0; i < result.length; i++) {
+        result[i] = fill;
+      }
+    }
+    return result;
+  }
+}
+
+
+patch class String {
+  patch factory String.fromCharCodes(List<int> charCodes) {
+    if (!isJsArray(charCodes)) {
+      if (charCodes is !List) throw new ArgumentError(charCodes);
+      charCodes = new List.from(charCodes);
+    }
+    return Primitives.stringFromCharCodes(charCodes);
+  }
+}
+
+// Patch for String implementation.
+patch class Strings {
+  patch static String join(Iterable<String> strings, String separator) {
+    checkNull(strings);
+    if (separator is !String) throw new ArgumentError(separator);
+    return stringJoinUnchecked(_toJsStringArray(strings), separator);
+  }
+
+  patch static String concatAll(Iterable<String> strings) {
+    return stringJoinUnchecked(_toJsStringArray(strings), "");
+  }
+
+  static List _toJsStringArray(Iterable<String> strings) {
+    checkNull(strings);
+    var array;
+    if (!isJsArray(strings)) {
+      strings = new List.from(strings);
+    }
+    final length = strings.length;
+    for (int i = 0; i < length; i++) {
+      final string = strings[i];
+      if (string is !String) throw new ArgumentError(string);
+    }
+    return strings;
+  }
+}
+
+patch class RegExp {
+  patch factory RegExp(String pattern,
+                       {bool multiLine: false,
+                        bool caseSensitive: true})
+    => new JSSyntaxRegExp(pattern,
+                          multiLine: multiLine,
+                          caseSensitive: caseSensitive);
+}
+
+// Patch for 'identical' function.
+patch bool identical(Object a, Object b) {
+  return Primitives.identicalImplementation(a, b);
+}
+
+patch class StringBuffer {
+  patch factory StringBuffer([Object content = ""]) {
+    return new JsStringBuffer(content);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart
new file mode 100644
index 0000000..2ad4a1e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart
@@ -0,0 +1,138 @@
+// 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.
+
+library _foreign_helper;
+
+/**
+ * Emits a JavaScript code fragment parameterized by arguments.
+ *
+ * Hash characters `#` in the [codeTemplate] are replaced in left-to-right order
+ * with expressions that contain the values of, or evaluate to, the arguments.
+ * The number of hash marks must match the number or arguments.  Although
+ * declared with arguments [arg0] through [arg2], the form actually has no limit
+ * on the number of arguments.
+ *
+ * The [typeDescription] argument is interpreted as a description of the
+ * behavior of the JavaScript code.  Currently it describes the types that may
+ * be returned by the expression, with the additional behavior that the returned
+ * values may be fresh instances of the types.  The type information must be
+ * correct as it is trusted by the compiler in optimizations, and it must be
+ * precise as possible since it is used for native live type analysis to
+ * tree-shake large parts of the DOM libraries.  If poorly written, the
+ * [typeDescription] will cause unnecessarily bloated programs.  (You can check
+ * for this by compiling with `--verbose`; there is an info message describing
+ * the number of native (DOM) types that can be removed, which usually should be
+ * greater than zero.)
+ *
+ * The [typeDescription] is a [String] which contains a union of types separated
+ * by vertical bar `|` symbols, e.g.  `"num|String"` describes the union of
+ * numbers and Strings.  There is no type in Dart that is this precise.  The
+ * Dart alternative would be `Object` or `dynamic`, but these types imply that
+ * the JS-code might also be creating instances of all the DOM types.  The
+ * [typeDescription] has several extensions to help describe the behavior more
+ * accurately.  In addition to the union type already described:
+ *
+ *  + `=List` is the JavaScript array type.  This is more precise than `List`,
+ *     which includes about fifty DOM types that also implement the List
+ *     interface.
+ *
+ *  + `=Object` is a plain JavaScript object.  Some DOM methods return instances
+ *     that have no corresponing Dart type (e.g. cross-frame documents),
+ *     `=Object` can be used to describe these untyped' values.
+ *
+ *  + `var`.  If the entire [typeDescription] is `var` then the type is
+ *    `dynamic` but the code is known to not create any instances.
+ *
+ * Examples:
+ *
+ *     // Create a JavaScript Array.
+ *     List a = JS('=List', 'new Array(#)', length);
+ *
+ *     // Parent window might be an opaque cross-frame window.
+ *     var thing = JS('=Object|Window', '#.parent', myWindow);
+ *
+ * Guidelines:
+ *
+ *  + Do not use any parameter, local, method or field names in the
+ *    [codeTemplate].  These names are all subject to arbitrary renaming by the
+ *    compiler.  Pass the values in via `#` substition, and test with the
+ *    `--minify` dart2js command-line option.
+ *
+ *  + The substituted expressions are values, not locations.
+ *
+ *        JS('void', '# += "x"', this.field);
+ *
+ *    `this.field` might not be a substituted as a reference to the field.  The
+ *    generated code might accidentally work as intended, but it also might be
+ *
+ *        var t1 = this.field;
+ *        t1 += "x";
+ *
+ *    or
+ *
+ *        this.get$field() += "x";
+ *
+ *    The remedy in this case is to expand the `+=` operator, leaving all
+ *    references to the Dart field as Dart code:
+ *
+ *        this.field = JS('String', '# + "x"', this.field);
+ *
+ *
+ * Additional notes.
+ *
+ * In the future we may extend [typeDescription] to include other aspects of the
+ * behavior, for example, separating the returned types from the instantiated
+ * types, or including effects to allow the compiler to perform more
+ * optimizations around the code.  This might be an extension of [JS] or a new
+ * function similar to [JS] with additional arguments for the new information.
+ */
+// Add additional optional arguments if needed. The method is treated internally
+// as a variable argument method.
+dynamic JS(String typeDescription, String codeTemplate,
+    [var arg0, var arg1, var arg2, var arg3, var arg4, var arg5, var arg6,
+     var arg7, var arg8, var arg9, var arg10, var arg11]) {}
+
+/**
+ * Returns the isolate in which this code is running.
+ */
+dynamic JS_CURRENT_ISOLATE() {}
+
+/**
+ * Invokes [function] in the context of [isolate].
+ */
+dynamic JS_CALL_IN_ISOLATE(var isolate, Function function) {}
+
+/**
+ * Converts the Dart closure [function] into a JavaScript closure.
+ */
+dynamic DART_CLOSURE_TO_JS(Function function) {}
+
+/**
+ * Returns a raw reference to the JavaScript function which implements
+ * [function].
+ *
+ * Warning: this is dangerous, you should probably use
+ * [DART_CLOSURE_TO_JS] instead. The returned object is not a valid
+ * Dart closure, does not store the isolate context or arity.
+ *
+ * A valid example of where this can be used is as the second argument
+ * to V8's Error.captureStackTrace. See
+ * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi.
+ */
+dynamic RAW_DART_FUNCTION_REF(Function function) {}
+
+/**
+ * Sets the current isolate to [isolate].
+ */
+void JS_SET_CURRENT_ISOLATE(var isolate) {}
+
+/**
+ * Creates an isolate and returns it.
+ */
+dynamic JS_CREATE_ISOLATE() {}
+
+/**
+ * Returns the prefix used for generated is checks on classes.
+ */
+String JS_OPERATOR_IS_PREFIX() {}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart
new file mode 100644
index 0000000..6a32f17
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart
@@ -0,0 +1,90 @@
+// 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.
+
+library _interceptors;
+
+import 'dart:collection';
+import 'dart:_collection-dev';
+import 'dart:_js_helper' show allMatchesInStringUnchecked,
+                              Null,
+                              JSSyntaxRegExp,
+                              Primitives,
+                              checkGrowable,
+                              checkMutable,
+                              checkNull,
+                              checkNum,
+                              checkString,
+                              getRuntimeTypeString,
+                              listInsertRange,
+                              regExpGetNative,
+                              stringContainsUnchecked,
+                              stringLastIndexOfUnchecked,
+                              stringReplaceAllFuncUnchecked,
+                              stringReplaceAllUnchecked,
+                              stringReplaceFirstUnchecked,
+                              TypeImpl;
+import 'dart:_foreign_helper' show JS;
+
+part 'js_array.dart';
+part 'js_number.dart';
+part 'js_string.dart';
+
+/**
+ * The interceptor class for all non-primitive objects. All its
+ * members are synthethized by the compiler's emitter.
+ */
+class ObjectInterceptor {
+  const ObjectInterceptor();
+}
+
+/**
+ * Get the interceptor for [object]. Called by the compiler when it needs
+ * to emit a call to an intercepted method, that is a method that is
+ * defined in an interceptor class.
+ */
+getInterceptor(object) {
+  // This is a magic method: the compiler does specialization of it
+  // depending on the uses of intercepted methods and instantiated
+  // primitive types.
+}
+
+/**
+ * The interceptor class for tear-off static methods. Unlike
+ * tear-off instance methods, tear-off static methods are just the JS
+ * function, and methods inherited from Object must therefore be
+ * intercepted.
+ */
+class JSFunction implements Function {
+  const JSFunction();
+  String toString() => 'Closure';
+}
+
+/**
+ * The interceptor class for [bool].
+ */
+class JSBool implements bool {
+  const JSBool();
+
+  // Note: if you change this, also change the function [S].
+  String toString() => JS('String', r'String(#)', this);
+
+  // The values here are SMIs, co-prime and differ about half of the bit
+  // positions, including the low bit, so they are different mod 2^k.
+  int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811);
+
+  Type get runtimeType => bool;
+}
+
+/**
+ * The interceptor class for [Null].
+ */
+class JSNull implements Null {
+  const JSNull();
+
+  // Note: if you change this, also change the function [S].
+  String toString() => 'null';
+
+  int get hashCode => 0;
+  Type get runtimeType => Null;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart
new file mode 100644
index 0000000..0374de5
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart
@@ -0,0 +1,217 @@
+// 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.
+
+patch class _BufferUtils {
+  patch static bool _isBuiltinList(List buffer) {
+    throw new UnsupportedError("_isBuiltinList");
+  }
+}
+
+patch class _Directory {
+  patch static String _current() {
+    throw new UnsupportedError("Directory._current");
+  }
+  patch static _createTemp(String template) {
+    throw new UnsupportedError("Directory._createTemp");
+  }
+  patch static int _exists(String path) {
+    throw new UnsupportedError("Directory._exists");
+  }
+  patch static _create(String path) {
+    throw new UnsupportedError("Directory._create");
+  }
+  patch static _delete(String path, bool recursive) {
+    throw new UnsupportedError("Directory._delete");
+  }
+  patch static _rename(String path, String newPath) {
+    throw new UnsupportedError("Directory._rename");
+  }
+  patch static List _list(String path, bool recursive) {
+    throw new UnsupportedError("Directory._list");
+  }
+  patch static SendPort _newServicePort() {
+    throw new UnsupportedError("Directory._newServicePort");
+  }
+}
+
+patch class _EventHandler {
+  patch static void _start() {
+    throw new UnsupportedError("EventHandler._start");
+  }
+
+  patch static _sendData(Object sender,
+                         ReceivePort receivePort,
+                         int data) {
+    throw new UnsupportedError("EventHandler._sendData");
+  }
+}
+
+patch class _FileUtils {
+  patch static SendPort _newServicePort() {
+    throw new UnsupportedError("FileUtils._newServicePort");
+  }
+}
+
+patch class _File {
+  patch static _exists(String name) {
+    throw new UnsupportedError("File._exists");
+  }
+  patch static _create(String name) {
+    throw new UnsupportedError("File._create");
+  }
+  patch static _delete(String name) {
+    throw new UnsupportedError("File._delete");
+  }
+  patch static _directory(String name) {
+    throw new UnsupportedError("File._directory");
+  }
+  patch static _lengthFromName(String name) {
+    throw new UnsupportedError("File._lengthFromName");
+  }
+  patch static _lastModified(String name) {
+    throw new UnsupportedError("File._lastModified");
+  }
+  patch static _open(String name, int mode) {
+    throw new UnsupportedError("File._open");
+  }
+  patch static int _openStdio(int fd) {
+    throw new UnsupportedError("File._openStdio");
+  }
+  patch static _fullPath(String name) {
+    throw new UnsupportedError("File._fullPath");
+  }
+}
+
+patch class _RandomAccessFile {
+  patch static int _close(int id) {
+    throw new UnsupportedError("RandomAccessFile._close");
+  }
+  patch static _readByte(int id) {
+    throw new UnsupportedError("RandomAccessFile._readByte");
+  }
+  patch static _read(int id, int bytes) {
+    throw new UnsupportedError("RandomAccessFile._read");
+  }
+  patch static _readList(int id, List<int> buffer, int offset, int bytes) {
+    throw new UnsupportedError("RandomAccessFile._readList");
+  }
+  patch static _writeByte(int id, int value) {
+    throw new UnsupportedError("RandomAccessFile._writeByte");
+  }
+  patch static _writeList(int id, List<int> buffer, int offset, int bytes) {
+    throw new UnsupportedError("RandomAccessFile._writeList");
+  }
+  patch static _position(int id) {
+    throw new UnsupportedError("RandomAccessFile._position");
+  }
+  patch static _setPosition(int id, int position) {
+    throw new UnsupportedError("RandomAccessFile._setPosition");
+  }
+  patch static _truncate(int id, int length) {
+    throw new UnsupportedError("RandomAccessFile._truncate");
+  }
+  patch static _length(int id) {
+    throw new UnsupportedError("RandomAccessFile._length");
+  }
+  patch static _flush(int id) {
+    throw new UnsupportedError("RandomAccessFile._flush");
+  }
+}
+
+patch class _HttpSessionManager {
+  patch static Uint8List _getRandomBytes(int count) {
+    throw new UnsupportedError("HttpSessionManager._getRandomBytes");
+  }
+}
+
+patch class _Platform {
+  patch static int _numberOfProcessors() {
+    throw new UnsupportedError("Platform._numberOfProcessors");
+  }
+  patch static String _pathSeparator() {
+    throw new UnsupportedError("Platform._pathSeparator");
+  }
+  patch static String _operatingSystem() {
+    throw new UnsupportedError("Platform._operatingSystem");
+  }
+  patch static _localHostname() {
+    throw new UnsupportedError("Platform._localHostname");
+  }
+  patch static _environment() {
+    throw new UnsupportedError("Platform._environment");
+  }
+}
+
+patch class _ProcessUtils {
+  patch static _exit(int status) {
+    throw new UnsupportedError("ProcessUtils._exit");
+  }
+  patch static _setExitCode(int status) {
+    throw new UnsupportedError("ProcessUtils._setExitCode");
+  }
+}
+
+patch class Process {
+  patch static Future<Process> start(String executable,
+                                     List<String> arguments,
+                                     [ProcessOptions options]) {
+    throw new UnsupportedError("Process.start");
+  }
+
+  patch static Future<ProcessResult> run(String executable,
+                                         List<String> arguments,
+                                         [ProcessOptions options]) {
+    throw new UnsupportedError("Process.run");
+  }
+}
+
+patch class ServerSocket {
+  patch factory ServerSocket(String bindAddress, int port, int backlog) {
+    throw new UnsupportedError("ServerSocket constructor");
+  }
+}
+
+patch class Socket {
+  patch factory Socket(String host, int port) {
+    throw new UnsupportedError("Socket constructor");
+  }
+}
+
+patch class SecureSocket {
+  patch static void initialize({String database,
+                                String password,
+                                bool useBuiltinRoots: true}) {
+    throw new UnsupportedError("SecureSocket.setCertificateDatabase");
+  }
+}
+
+patch class _SecureFilter {
+  patch factory _SecureFilter() {
+    throw new UnsupportedError("_SecureFilter._SecureFilter");
+  }
+}
+
+patch class _StdIOUtils {
+  patch static InputStream _getStdioInputStream() {
+    throw new UnsupportedError("StdIOUtils._getStdioInputStream");
+  }
+  patch static OutputStream _getStdioOutputStream(int fd) {
+    throw new UnsupportedError("StdIOUtils._getStdioOutputStream");
+  }
+  patch static int _socketType(Socket socket) {
+    throw new UnsupportedError("StdIOUtils._socketType");
+  }
+}
+
+patch class _WindowsCodePageDecoder {
+  patch static String _decodeBytes(List<int> bytes) {
+    throw new UnsupportedError("_WindowsCodePageDecoder._decodeBytes");
+  }
+}
+
+patch class _WindowsCodePageEncoder {
+  patch static List<int> _encodeString(String string) {
+    throw new UnsupportedError("_WindowsCodePageEncoder._encodeString");
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart
new file mode 100644
index 0000000..82a80fe
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart
@@ -0,0 +1,1329 @@
+// 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.
+
+library _isolate_helper;
+
+import 'dart:async';
+import 'dart:collection' show Queue, HashMap;
+import 'dart:isolate';
+import 'dart:_js_helper' show convertDartClosureToJS,
+                              Null;
+import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS,
+                                   JS,
+                                   JS_CREATE_ISOLATE,
+                                   JS_SET_CURRENT_ISOLATE;
+
+ReceivePort lazyPort;
+
+/**
+ * Called by the compiler to support switching
+ * between isolates when we get a callback from the DOM.
+ */
+void _callInIsolate(_IsolateContext isolate, Function function) {
+  isolate.eval(function);
+  _globalState.topEventLoop.run();
+}
+
+/**
+ * Called by the compiler to fetch the current isolate context.
+ */
+_IsolateContext _currentIsolate() => _globalState.currentContext;
+
+/**
+ * Wrapper that takes the dart entry point and runs it within an isolate. The
+ * dart2js compiler will inject a call of the form
+ * [: startRootIsolate(main); :] when it determines that this wrapping
+ * is needed. For single-isolate applications (e.g. hello world), this
+ * call is not emitted.
+ */
+void startRootIsolate(entry) {
+  _globalState = new _Manager();
+
+  // Don't start the main loop again, if we are in a worker.
+  if (_globalState.isWorker) return;
+  final rootContext = new _IsolateContext();
+  _globalState.rootContext = rootContext;
+
+  // BUG(5151491): Setting currentContext should not be necessary, but
+  // because closures passed to the DOM as event handlers do not bind their
+  // isolate automatically we try to give them a reasonable context to live in
+  // by having a "default" isolate (the first one created).
+  _globalState.currentContext = rootContext;
+
+  rootContext.eval(entry);
+  _globalState.topEventLoop.run();
+}
+
+/********************************************************
+  Inserted from lib/isolate/dart2js/isolateimpl.dart
+ ********************************************************/
+
+/**
+ * Concepts used here:
+ *
+ * "manager" - A manager contains one or more isolates, schedules their
+ * execution, and performs other plumbing on their behalf.  The isolate
+ * present at the creation of the manager is designated as its "root isolate".
+ * A manager may, for example, be implemented on a web Worker.
+ *
+ * [_Manager] - State present within a manager (exactly once, as a global).
+ *
+ * [_ManagerStub] - A handle held within one manager that allows interaction
+ * with another manager.  A target manager may be addressed by zero or more
+ * [_ManagerStub]s.
+ *
+ */
+
+/**
+ * A native object that is shared across isolates. This object is visible to all
+ * isolates running under the same manager (either UI or background web worker).
+ *
+ * This is code that is intended to 'escape' the isolate boundaries in order to
+ * implement the semantics of isolates in JavaScript. Without this we would have
+ * been forced to implement more code (including the top-level event loop) in
+ * JavaScript itself.
+ */
+// TODO(eub, sigmund): move the "manager" to be entirely in JS.
+// Running any Dart code outside the context of an isolate gives it
+// the chance to break the isolate abstraction.
+_Manager get _globalState => JS("_Manager", r"$globalState");
+
+set _globalState(_Manager val) {
+  JS("void", r"$globalState = #", val);
+}
+
+/** State associated with the current manager. See [globalState]. */
+// TODO(sigmund): split in multiple classes: global, thread, main-worker states?
+class _Manager {
+
+  /** Next available isolate id within this [_Manager]. */
+  int nextIsolateId = 0;
+
+  /** id assigned to this [_Manager]. */
+  int currentManagerId = 0;
+
+  /**
+   * Next available manager id. Only used by the main manager to assign a unique
+   * id to each manager created by it.
+   */
+  int nextManagerId = 1;
+
+  /** Context for the currently running [Isolate]. */
+  _IsolateContext currentContext = null;
+
+  /** Context for the root [Isolate] that first run in this [_Manager]. */
+  _IsolateContext rootContext = null;
+
+  /** The top-level event loop. */
+  _EventLoop topEventLoop;
+
+  /** Whether this program is running from the command line. */
+  bool fromCommandLine;
+
+  /** Whether this [_Manager] is running as a web worker. */
+  bool isWorker;
+
+  /** Whether we support spawning web workers. */
+  bool supportsWorkers;
+
+  /**
+   * Whether to use web workers when implementing isolates. Set to false for
+   * debugging/testing.
+   */
+  bool get useWorkers => supportsWorkers;
+
+  /**
+   * Whether to use the web-worker JSON-based message serialization protocol. By
+   * default this is only used with web workers. For debugging, you can force
+   * using this protocol by changing this field value to [true].
+   */
+  bool get needSerialization => useWorkers;
+
+  /**
+   * Registry of isolates. Isolates must be registered if, and only if, receive
+   * ports are alive.  Normally no open receive-ports means that the isolate is
+   * dead, but DOM callbacks could resurrect it.
+   */
+  Map<int, _IsolateContext> isolates;
+
+  /** Reference to the main [_Manager].  Null in the main [_Manager] itself. */
+  _ManagerStub mainManager;
+
+  /** Registry of active [_ManagerStub]s.  Only used in the main [_Manager]. */
+  Map<int, _ManagerStub> managers;
+
+  _Manager() {
+    _nativeDetectEnvironment();
+    topEventLoop = new _EventLoop();
+    isolates = new Map<int, _IsolateContext>();
+    managers = new Map<int, _ManagerStub>();
+    if (isWorker) {  // "if we are not the main manager ourself" is the intent.
+      mainManager = new _MainManagerStub();
+      _nativeInitWorkerMessageHandler();
+    }
+  }
+
+  void _nativeDetectEnvironment() {
+    bool isWindowDefined = globalWindow != null;
+    bool isWorkerDefined = globalWorker != null;
+
+    isWorker = !isWindowDefined && globalPostMessageDefined;
+    supportsWorkers = isWorker
+       || (isWorkerDefined && IsolateNatives.thisScript != null);
+    fromCommandLine = !isWindowDefined && !isWorker;
+  }
+
+  void _nativeInitWorkerMessageHandler() {
+    var function = JS('',
+                      "function (e) { #(#, e); }",
+                      DART_CLOSURE_TO_JS(IsolateNatives._processWorkerMessage),
+                      mainManager);
+    JS("void", r"#.onmessage = #", globalThis, function);
+    // We define dartPrint so that the implementation of the Dart
+    // print method knows what to call.
+    // TODO(ngeoffray): Should we forward to the main isolate? What if
+    // it exited?
+    JS('void', r'#.dartPrint = function (object) {}', globalThis);
+  }
+
+
+  /**
+   * Close the worker running this code if all isolates are done and
+   * there is no active timer.
+   */
+  void maybeCloseWorker() {
+    if (isWorker
+        && isolates.isEmpty
+        && topEventLoop.activeTimerCount == 0) {
+      mainManager.postMessage(_serializeMessage({'command': 'close'}));
+    }
+  }
+}
+
+/** Context information tracked for each isolate. */
+class _IsolateContext {
+  /** Current isolate id. */
+  int id;
+
+  /** Registry of receive ports currently active on this isolate. */
+  Map<int, ReceivePort> ports;
+
+  /** Holds isolate globals (statics and top-level properties). */
+  var isolateStatics; // native object containing all globals of an isolate.
+
+  _IsolateContext() {
+    id = _globalState.nextIsolateId++;
+    ports = new Map<int, ReceivePort>();
+    isolateStatics = JS_CREATE_ISOLATE();
+  }
+
+  /**
+   * Run [code] in the context of the isolate represented by [this].
+   */
+  dynamic eval(Function code) {
+    var old = _globalState.currentContext;
+    _globalState.currentContext = this;
+    this._setGlobals();
+    var result = null;
+    try {
+      result = code();
+    } finally {
+      _globalState.currentContext = old;
+      if (old != null) old._setGlobals();
+    }
+    return result;
+  }
+
+  void _setGlobals() {
+    JS_SET_CURRENT_ISOLATE(isolateStatics);
+  }
+
+  /** Lookup a port registered for this isolate. */
+  ReceivePort lookup(int portId) => ports[portId];
+
+  /** Register a port on this isolate. */
+  void register(int portId, ReceivePort port)  {
+    if (ports.containsKey(portId)) {
+      throw new Exception("Registry: ports must be registered only once.");
+    }
+    ports[portId] = port;
+    _globalState.isolates[id] = this; // indicate this isolate is active
+  }
+
+  /** Unregister a port on this isolate. */
+  void unregister(int portId) {
+    ports.remove(portId);
+    if (ports.isEmpty) {
+      _globalState.isolates.remove(id); // indicate this isolate is not active
+    }
+  }
+}
+
+/** Represent the event loop on a javascript thread (DOM or worker). */
+class _EventLoop {
+  final Queue<_IsolateEvent> events = new Queue<_IsolateEvent>();
+  int activeTimerCount = 0;
+
+  _EventLoop();
+
+  void enqueue(isolate, fn, msg) {
+    events.addLast(new _IsolateEvent(isolate, fn, msg));
+  }
+
+  _IsolateEvent dequeue() {
+    if (events.isEmpty) return null;
+    return events.removeFirst();
+  }
+
+  void checkOpenReceivePortsFromCommandLine() {
+    if (_globalState.rootContext != null
+        && _globalState.isolates.containsKey(_globalState.rootContext.id)
+        && _globalState.fromCommandLine
+        && _globalState.rootContext.ports.isEmpty) {
+      // We want to reach here only on the main [_Manager] and only
+      // on the command-line.  In the browser the isolate might
+      // still be alive due to DOM callbacks, but the presumption is
+      // that on the command-line, no future events can be injected
+      // into the event queue once it's empty.  Node has setTimeout
+      // so this presumption is incorrect there.  We think(?) that
+      // in d8 this assumption is valid.
+      throw new Exception("Program exited with open ReceivePorts.");
+    }
+  }
+
+  /** Process a single event, if any. */
+  bool runIteration() {
+    final event = dequeue();
+    if (event == null) {
+      checkOpenReceivePortsFromCommandLine();
+      _globalState.maybeCloseWorker();
+      return false;
+    }
+    event.process();
+    return true;
+  }
+
+  /**
+   * Runs multiple iterations of the run-loop. If possible, each iteration is
+   * run asynchronously.
+   */
+  void _runHelper() {
+    if (globalWindow != null) {
+      // Run each iteration from the browser's top event loop.
+      void next() {
+        if (!runIteration()) return;
+        new Timer(0, (_) => next());
+      }
+      next();
+    } else {
+      // Run synchronously until no more iterations are available.
+      while (runIteration()) {}
+    }
+  }
+
+  /**
+   * Call [_runHelper] but ensure that worker exceptions are propragated.
+   */
+  void run() {
+    if (!_globalState.isWorker) {
+      _runHelper();
+    } else {
+      try {
+        _runHelper();
+      } catch (e, trace) {
+        _globalState.mainManager.postMessage(_serializeMessage(
+            {'command': 'error', 'msg': '$e\n$trace' }));
+      }
+    }
+  }
+}
+
+/** An event in the top-level event queue. */
+class _IsolateEvent {
+  _IsolateContext isolate;
+  Function fn;
+  String message;
+
+  _IsolateEvent(this.isolate, this.fn, this.message);
+
+  void process() {
+    isolate.eval(fn);
+  }
+}
+
+/** An interface for a stub used to interact with a manager. */
+abstract class _ManagerStub {
+  get id;
+  void set id(int i);
+  void set onmessage(Function f);
+  void postMessage(msg);
+  void terminate();
+}
+
+/** A stub for interacting with the main manager. */
+class _MainManagerStub implements _ManagerStub {
+  get id => 0;
+  void set id(int i) { throw new UnimplementedError(); }
+  void set onmessage(f) {
+    throw new Exception("onmessage should not be set on MainManagerStub");
+  }
+  void postMessage(msg) {
+    JS("void", r"#.postMessage(#)", globalThis, msg);
+  }
+  void terminate() {}  // Nothing useful to do here.
+}
+
+/**
+ * A stub for interacting with a manager built on a web worker. This
+ * definition uses a 'hidden' type (* prefix on the native name) to
+ * enforce that the type is defined dynamically only when web workers
+ * are actually available.
+ */
+// @Native("*Worker");
+class _WorkerStub implements _ManagerStub {
+  get id => JS("", "#.id", this);
+  void set id(i) { JS("void", "#.id = #", this, i); }
+  void set onmessage(f) { JS("void", "#.onmessage = #", this, f); }
+  void postMessage(msg) { JS("void", "#.postMessage(#)", this, msg); }
+  void terminate() { JS("void", "#.terminate()", this); }
+}
+
+const String _SPAWNED_SIGNAL = "spawned";
+
+var globalThis = IsolateNatives.computeGlobalThis();
+var globalWindow = JS('', "#.window", globalThis);
+var globalWorker = JS('', "#.Worker", globalThis);
+bool globalPostMessageDefined =
+    JS('', "#.postMessage !== (void 0)", globalThis);
+
+class IsolateNatives {
+
+  static String thisScript = computeThisScript();
+
+  /**
+   * The src url for the script tag that loaded this code. Used to create
+   * JavaScript workers.
+   */
+  static String computeThisScript() {
+    // TODO(7369): Find a cross-platform non-brittle way of getting the
+    // currently running script.
+    var scripts = JS('', r"document.getElementsByTagName('script')");
+    // The scripts variable only contains the scripts that have already been
+    // executed. The last one is the currently running script.
+    for (int i = 0, len = JS('int', '#.length', scripts); i < len; i++) {
+      var script = JS('', '#[#]', scripts, i);
+      var src = JS('String|Null', '# && #.src', script, script);
+      // Filter out the test controller script, and the Dart
+      // bootstrap script.
+      if (src != null
+          && !src.endsWith('test_controller.js')
+          && !src.endsWith('dart.js')) {
+        return src;
+      }
+    }
+    return null;
+  }
+
+  static computeGlobalThis() => JS('', 'function() { return this; }()');
+
+  /** Starts a new worker with the given URL. */
+  static _WorkerStub _newWorker(url) => JS("_WorkerStub", r"new Worker(#)", url);
+
+  /**
+   * Assume that [e] is a browser message event and extract its message data.
+   * We don't import the dom explicitly so, when workers are disabled, this
+   * library can also run on top of nodejs.
+   */
+  static _getEventData(e) => JS("", "#.data", e);
+
+  /**
+   * Process messages on a worker, either to control the worker instance or to
+   * pass messages along to the isolate running in the worker.
+   */
+  static void _processWorkerMessage(sender, e) {
+    var msg = _deserializeMessage(_getEventData(e));
+    switch (msg['command']) {
+      case 'start':
+        _globalState.currentManagerId = msg['id'];
+        Function entryPoint = _getJSFunctionFromName(msg['functionName']);
+        var replyTo = _deserializeMessage(msg['replyTo']);
+        var context = new _IsolateContext();
+        _globalState.topEventLoop.enqueue(context, function() {
+          _startIsolate(entryPoint, replyTo);
+        }, 'worker-start');
+        // Make sure we always have a current context in this worker.
+        // TODO(7907): This is currently needed because we're using
+        // Timers to implement Futures, and this isolate library
+        // implementation uses Futures. We should either stop using
+        // Futures in this library, or re-adapt if Futures get a
+        // different implementation.
+        _globalState.currentContext = context;
+        _globalState.topEventLoop.run();
+        break;
+      case 'spawn-worker':
+        _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']);
+        break;
+      case 'message':
+        SendPort port = msg['port'];
+        // If the port has been closed, we ignore the message.
+        if (port != null) {
+          msg['port'].send(msg['msg'], msg['replyTo']);
+        }
+        _globalState.topEventLoop.run();
+        break;
+      case 'close':
+        _log("Closing Worker");
+        _globalState.managers.remove(sender.id);
+        sender.terminate();
+        _globalState.topEventLoop.run();
+        break;
+      case 'log':
+        _log(msg['msg']);
+        break;
+      case 'print':
+        if (_globalState.isWorker) {
+          _globalState.mainManager.postMessage(
+              _serializeMessage({'command': 'print', 'msg': msg}));
+        } else {
+          print(msg['msg']);
+        }
+        break;
+      case 'error':
+        throw msg['msg'];
+    }
+  }
+
+  /** Log a message, forwarding to the main [_Manager] if appropriate. */
+  static _log(msg) {
+    if (_globalState.isWorker) {
+      _globalState.mainManager.postMessage(
+          _serializeMessage({'command': 'log', 'msg': msg }));
+    } else {
+      try {
+        _consoleLog(msg);
+      } catch (e, trace) {
+        throw new Exception(trace);
+      }
+    }
+  }
+
+  static void _consoleLog(msg) {
+    JS("void", r"#.console.log(#)", globalThis, msg);
+  }
+
+  /** Find a constructor given its name. */
+  static dynamic _getJSConstructorFromName(String factoryName) {
+    return JS("", r"$[#]", factoryName);
+  }
+
+  static dynamic _getJSFunctionFromName(String functionName) {
+    return JS("", r"$[#]", functionName);
+  }
+
+  /**
+   * Get a string name for the function, if possible.  The result for
+   * anonymous functions is browser-dependent -- it may be "" or "anonymous"
+   * but you should probably not count on this.
+   */
+  static String _getJSFunctionName(Function f) {
+    return JS("String|Null", r"(#.$name || #)", f, null);
+  }
+
+  /** Create a new JavaScript object instance given its constructor. */
+  static dynamic _allocate(var ctor) {
+    return JS("", "new #()", ctor);
+  }
+
+  static SendPort spawnFunction(void topLevelFunction()) {
+    final name = _getJSFunctionName(topLevelFunction);
+    if (name == null) {
+      throw new UnsupportedError(
+          "only top-level functions can be spawned.");
+    }
+    return spawn(name, null, false);
+  }
+
+  static SendPort spawnDomFunction(void topLevelFunction()) {
+    final name = _getJSFunctionName(topLevelFunction);
+    if (name == null) {
+      throw new UnsupportedError(
+          "only top-level functions can be spawned.");
+    }
+    return spawn(name, null, true);
+  }
+
+  // TODO(sigmund): clean up above, after we make the new API the default:
+
+  static spawn(String functionName, String uri, bool isLight) {
+    Completer<SendPort> completer = new Completer<SendPort>();
+    ReceivePort port = new ReceivePort();
+    port.receive((msg, SendPort replyPort) {
+      port.close();
+      assert(msg == _SPAWNED_SIGNAL);
+      completer.complete(replyPort);
+    });
+
+    SendPort signalReply = port.toSendPort();
+
+    if (_globalState.useWorkers && !isLight) {
+      _startWorker(functionName, uri, signalReply);
+    } else {
+      _startNonWorker(functionName, uri, signalReply);
+    }
+    return new _BufferingSendPort(
+        _globalState.currentContext.id, completer.future);
+  }
+
+  static SendPort _startWorker(
+      String functionName, String uri, SendPort replyPort) {
+    if (_globalState.isWorker) {
+      _globalState.mainManager.postMessage(_serializeMessage({
+          'command': 'spawn-worker',
+          'functionName': functionName,
+          'uri': uri,
+          'replyPort': replyPort}));
+    } else {
+      _spawnWorker(functionName, uri, replyPort);
+    }
+  }
+
+  static SendPort _startNonWorker(
+      String functionName, String uri, SendPort replyPort) {
+    // TODO(eub): support IE9 using an iframe -- Dart issue 1702.
+    if (uri != null) throw new UnsupportedError(
+            "Currently spawnUri is not supported without web workers.");
+    _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
+      final func = _getJSFunctionFromName(functionName);
+      _startIsolate(func, replyPort);
+    }, 'nonworker start');
+  }
+
+  static void _startIsolate(Function topLevel, SendPort replyTo) {
+    lazyPort = new ReceivePort();
+    replyTo.send(_SPAWNED_SIGNAL, port.toSendPort());
+    topLevel();
+  }
+
+  /**
+   * Spawns an isolate in a worker. [factoryName] is the Javascript constructor
+   * name for the isolate entry point class.
+   */
+  static void _spawnWorker(functionName, uri, replyPort) {
+    if (functionName == null) functionName = 'main';
+    if (uri == null) uri = thisScript;
+    final worker = _newWorker(uri);
+    worker.onmessage = JS('',
+                          'function(e) { #(#, e); }',
+                          DART_CLOSURE_TO_JS(_processWorkerMessage),
+                          worker);
+    var workerId = _globalState.nextManagerId++;
+    // We also store the id on the worker itself so that we can unregister it.
+    worker.id = workerId;
+    _globalState.managers[workerId] = worker;
+    worker.postMessage(_serializeMessage({
+      'command': 'start',
+      'id': workerId,
+      // Note: we serialize replyPort twice because the child worker needs to
+      // first deserialize the worker id, before it can correctly deserialize
+      // the port (port deserialization is sensitive to what is the current
+      // workerId).
+      'replyTo': _serializeMessage(replyPort),
+      'functionName': functionName }));
+  }
+}
+
+/********************************************************
+  Inserted from lib/isolate/dart2js/ports.dart
+ ********************************************************/
+
+/** Common functionality to all send ports. */
+class _BaseSendPort implements SendPort {
+  /** Id for the destination isolate. */
+  final int _isolateId;
+
+  const _BaseSendPort(this._isolateId);
+
+  void _checkReplyTo(SendPort replyTo) {
+    if (replyTo != null
+        && replyTo is! _NativeJsSendPort
+        && replyTo is! _WorkerSendPort
+        && replyTo is! _BufferingSendPort) {
+      throw new Exception("SendPort.send: Illegal replyTo port type");
+    }
+  }
+
+  Future call(var message) {
+    final completer = new Completer();
+    final port = new ReceivePortImpl();
+    send(message, port.toSendPort());
+    port.receive((value, ignoreReplyTo) {
+      port.close();
+      if (value is Exception) {
+        completer.completeError(value);
+      } else {
+        completer.complete(value);
+      }
+    });
+    return completer.future;
+  }
+
+  void send(var message, [SendPort replyTo]);
+  bool operator ==(var other);
+  int get hashCode;
+}
+
+/** A send port that delivers messages in-memory via native JavaScript calls. */
+class _NativeJsSendPort extends _BaseSendPort implements SendPort {
+  final ReceivePortImpl _receivePort;
+
+  const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId);
+
+  void send(var message, [SendPort replyTo = null]) {
+    _waitForPendingPorts([message, replyTo], () {
+      _checkReplyTo(replyTo);
+      // Check that the isolate still runs and the port is still open
+      final isolate = _globalState.isolates[_isolateId];
+      if (isolate == null) return;
+      if (_receivePort._callback == null) return;
+
+      // We force serialization/deserialization as a simple way to ensure
+      // isolate communication restrictions are respected between isolates that
+      // live in the same worker. [_NativeJsSendPort] delivers both messages
+      // from the same worker and messages from other workers. In particular,
+      // messages sent from a worker via a [_WorkerSendPort] are received at
+      // [_processWorkerMessage] and forwarded to a native port. In such cases,
+      // here we'll see [_globalState.currentContext == null].
+      final shouldSerialize = _globalState.currentContext != null
+          && _globalState.currentContext.id != _isolateId;
+      var msg = message;
+      var reply = replyTo;
+      if (shouldSerialize) {
+        msg = _serializeMessage(msg);
+        reply = _serializeMessage(reply);
+      }
+      _globalState.topEventLoop.enqueue(isolate, () {
+        if (_receivePort._callback != null) {
+          if (shouldSerialize) {
+            msg = _deserializeMessage(msg);
+            reply = _deserializeMessage(reply);
+          }
+          _receivePort._callback(msg, reply);
+        }
+      }, 'receive $message');
+    });
+  }
+
+  bool operator ==(var other) => (other is _NativeJsSendPort) &&
+      (_receivePort == other._receivePort);
+
+  int get hashCode => _receivePort._id;
+}
+
+/** A send port that delivers messages via worker.postMessage. */
+// TODO(eub): abstract this for iframes.
+class _WorkerSendPort extends _BaseSendPort implements SendPort {
+  final int _workerId;
+  final int _receivePortId;
+
+  const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId)
+      : super(isolateId);
+
+  void send(var message, [SendPort replyTo = null]) {
+    _waitForPendingPorts([message, replyTo], () {
+      _checkReplyTo(replyTo);
+      final workerMessage = _serializeMessage({
+          'command': 'message',
+          'port': this,
+          'msg': message,
+          'replyTo': replyTo});
+
+      if (_globalState.isWorker) {
+        // Communication from one worker to another go through the
+        // main worker.
+        _globalState.mainManager.postMessage(workerMessage);
+      } else {
+        // Deliver the message only if the worker is still alive.
+        _ManagerStub manager = _globalState.managers[_workerId];
+        if (manager != null) {
+          manager.postMessage(workerMessage);
+        }
+      }
+    });
+  }
+
+  bool operator ==(var other) {
+    return (other is _WorkerSendPort) &&
+        (_workerId == other._workerId) &&
+        (_isolateId == other._isolateId) &&
+        (_receivePortId == other._receivePortId);
+  }
+
+  int get hashCode {
+    // TODO(sigmund): use a standard hash when we get one available in corelib.
+    return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId;
+  }
+}
+
+/** A port that buffers messages until an underlying port gets resolved. */
+class _BufferingSendPort extends _BaseSendPort implements SendPort {
+  /** Internal counter to assign unique ids to each port. */
+  static int _idCount = 0;
+
+  /** For implementing equals and hashcode. */
+  final int _id;
+
+  /** Underlying port, when resolved. */
+  SendPort _port;
+
+  /**
+   * Future of the underlying port, so that we can detect when this port can be
+   * sent on messages.
+   */
+  Future<SendPort> _futurePort;
+
+  /** Pending messages (and reply ports). */
+  List pending;
+
+  _BufferingSendPort(isolateId, this._futurePort)
+      : super(isolateId), _id = _idCount, pending = [] {
+    _idCount++;
+    _futurePort.then((p) {
+      _port = p;
+      for (final item in pending) {
+        p.send(item['message'], item['replyTo']);
+      }
+      pending = null;
+    });
+  }
+
+  _BufferingSendPort.fromPort(isolateId, this._port)
+      : super(isolateId), _id = _idCount {
+    _idCount++;
+  }
+
+  void send(var message, [SendPort replyTo]) {
+    if (_port != null) {
+      _port.send(message, replyTo);
+    } else {
+      pending.add({'message': message, 'replyTo': replyTo});
+    }
+  }
+
+  bool operator ==(var other) =>
+      other is _BufferingSendPort && _id == other._id;
+  int get hashCode => _id;
+}
+
+/** Implementation of a multi-use [ReceivePort] on top of JavaScript. */
+class ReceivePortImpl implements ReceivePort {
+  int _id;
+  Function _callback;
+  static int _nextFreeId = 1;
+
+  ReceivePortImpl()
+      : _id = _nextFreeId++ {
+    _globalState.currentContext.register(_id, this);
+  }
+
+  void receive(void onMessage(var message, SendPort replyTo)) {
+    _callback = onMessage;
+  }
+
+  void close() {
+    _callback = null;
+    _globalState.currentContext.unregister(_id);
+  }
+
+  SendPort toSendPort() {
+    return new _NativeJsSendPort(this, _globalState.currentContext.id);
+  }
+}
+
+/** Wait until all ports in a message are resolved. */
+_waitForPendingPorts(var message, void callback()) {
+  final finder = new _PendingSendPortFinder();
+  finder.traverse(message);
+  Future.wait(finder.ports).then((_) => callback());
+}
+
+
+/** Visitor that finds all unresolved [SendPort]s in a message. */
+class _PendingSendPortFinder extends _MessageTraverser {
+  List<Future<SendPort>> ports;
+  _PendingSendPortFinder() : super(), ports = [] {
+    _visited = new _JsVisitedMap();
+  }
+
+  visitPrimitive(x) {}
+
+  visitList(List list) {
+    final seen = _visited[list];
+    if (seen != null) return;
+    _visited[list] = true;
+    // TODO(sigmund): replace with the following: (bug #1660)
+    // list.forEach(_dispatch);
+    list.forEach((e) => _dispatch(e));
+  }
+
+  visitMap(Map map) {
+    final seen = _visited[map];
+    if (seen != null) return;
+
+    _visited[map] = true;
+    // TODO(sigmund): replace with the following: (bug #1660)
+    // map.values.forEach(_dispatch);
+    map.values.forEach((e) => _dispatch(e));
+  }
+
+  visitSendPort(SendPort port) {
+    if (port is _BufferingSendPort && port._port == null) {
+      ports.add(port._futurePort);
+    }
+  }
+}
+
+/********************************************************
+  Inserted from lib/isolate/dart2js/messages.dart
+ ********************************************************/
+
+// Defines message visitors, serialization, and deserialization.
+
+/** Serialize [message] (or simulate serialization). */
+_serializeMessage(message) {
+  if (_globalState.needSerialization) {
+    return new _JsSerializer().traverse(message);
+  } else {
+    return new _JsCopier().traverse(message);
+  }
+}
+
+/** Deserialize [message] (or simulate deserialization). */
+_deserializeMessage(message) {
+  if (_globalState.needSerialization) {
+    return new _JsDeserializer().deserialize(message);
+  } else {
+    // Nothing more to do.
+    return message;
+  }
+}
+
+class _JsSerializer extends _Serializer {
+
+  _JsSerializer() : super() { _visited = new _JsVisitedMap(); }
+
+  visitSendPort(SendPort x) {
+    if (x is _NativeJsSendPort) return visitNativeJsSendPort(x);
+    if (x is _WorkerSendPort) return visitWorkerSendPort(x);
+    if (x is _BufferingSendPort) return visitBufferingSendPort(x);
+    throw "Illegal underlying port $x";
+  }
+
+  visitNativeJsSendPort(_NativeJsSendPort port) {
+    return ['sendport', _globalState.currentManagerId,
+        port._isolateId, port._receivePort._id];
+  }
+
+  visitWorkerSendPort(_WorkerSendPort port) {
+    return ['sendport', port._workerId, port._isolateId, port._receivePortId];
+  }
+
+  visitBufferingSendPort(_BufferingSendPort port) {
+    if (port._port != null) {
+      return visitSendPort(port._port);
+    } else {
+      // TODO(floitsch): Use real exception (which one?).
+      throw
+          "internal error: must call _waitForPendingPorts to ensure all"
+          " ports are resolved at this point.";
+    }
+  }
+
+}
+
+
+class _JsCopier extends _Copier {
+
+  _JsCopier() : super() { _visited = new _JsVisitedMap(); }
+
+  visitSendPort(SendPort x) {
+    if (x is _NativeJsSendPort) return visitNativeJsSendPort(x);
+    if (x is _WorkerSendPort) return visitWorkerSendPort(x);
+    if (x is _BufferingSendPort) return visitBufferingSendPort(x);
+    throw "Illegal underlying port $p";
+  }
+
+  SendPort visitNativeJsSendPort(_NativeJsSendPort port) {
+    return new _NativeJsSendPort(port._receivePort, port._isolateId);
+  }
+
+  SendPort visitWorkerSendPort(_WorkerSendPort port) {
+    return new _WorkerSendPort(
+        port._workerId, port._isolateId, port._receivePortId);
+  }
+
+  SendPort visitBufferingSendPort(_BufferingSendPort port) {
+    if (port._port != null) {
+      return visitSendPort(port._port);
+    } else {
+      // TODO(floitsch): Use real exception (which one?).
+      throw
+          "internal error: must call _waitForPendingPorts to ensure all"
+          " ports are resolved at this point.";
+    }
+  }
+
+}
+
+class _JsDeserializer extends _Deserializer {
+
+  SendPort deserializeSendPort(List x) {
+    int managerId = x[1];
+    int isolateId = x[2];
+    int receivePortId = x[3];
+    // If two isolates are in the same manager, we use NativeJsSendPorts to
+    // deliver messages directly without using postMessage.
+    if (managerId == _globalState.currentManagerId) {
+      var isolate = _globalState.isolates[isolateId];
+      if (isolate == null) return null; // Isolate has been closed.
+      var receivePort = isolate.lookup(receivePortId);
+      if (receivePort == null) return null; // Port has been closed.
+      return new _NativeJsSendPort(receivePort, isolateId);
+    } else {
+      return new _WorkerSendPort(managerId, isolateId, receivePortId);
+    }
+  }
+
+}
+
+class _JsVisitedMap implements _MessageTraverserVisitedMap {
+  List tagged;
+
+  /** Retrieves any information stored in the native object [object]. */
+  operator[](var object) {
+    return _getAttachedInfo(object);
+  }
+
+  /** Injects some information into the native [object]. */
+  void operator[]=(var object, var info) {
+    tagged.add(object);
+    _setAttachedInfo(object, info);
+  }
+
+  /** Get ready to rumble. */
+  void reset() {
+    assert(tagged == null);
+    tagged = new List();
+  }
+
+  /** Remove all information injected in the native objects. */
+  void cleanup() {
+    for (int i = 0, length = tagged.length; i < length; i++) {
+      _clearAttachedInfo(tagged[i]);
+    }
+    tagged = null;
+  }
+
+  void _clearAttachedInfo(var o) {
+    JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null);
+  }
+
+  void _setAttachedInfo(var o, var info) {
+    JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info);
+  }
+
+  _getAttachedInfo(var o) {
+    return JS("", "#['__MessageTraverser__attached_info__']", o);
+  }
+}
+
+// only visible for testing purposes
+// TODO(sigmund): remove once we can disable privacy for testing (bug #1882)
+class TestingOnly {
+  static copy(x) {
+    return new _JsCopier().traverse(x);
+  }
+
+  // only visible for testing purposes
+  static serialize(x) {
+    _Serializer serializer = new _JsSerializer();
+    _Deserializer deserializer = new _JsDeserializer();
+    return deserializer.deserialize(serializer.traverse(x));
+  }
+}
+
+/********************************************************
+  Inserted from lib/isolate/serialization.dart
+ ********************************************************/
+
+class _MessageTraverserVisitedMap {
+
+  operator[](var object) => null;
+  void operator[]=(var object, var info) { }
+
+  void reset() { }
+  void cleanup() { }
+
+}
+
+/** Abstract visitor for dart objects that can be sent as isolate messages. */
+class _MessageTraverser {
+
+  _MessageTraverserVisitedMap _visited;
+  _MessageTraverser() : _visited = new _MessageTraverserVisitedMap();
+
+  /** Visitor's entry point. */
+  traverse(var x) {
+    if (isPrimitive(x)) return visitPrimitive(x);
+    _visited.reset();
+    var result;
+    try {
+      result = _dispatch(x);
+    } finally {
+      _visited.cleanup();
+    }
+    return result;
+  }
+
+  _dispatch(var x) {
+    if (isPrimitive(x)) return visitPrimitive(x);
+    if (x is List) return visitList(x);
+    if (x is Map) return visitMap(x);
+    if (x is SendPort) return visitSendPort(x);
+    if (x is SendPortSync) return visitSendPortSync(x);
+
+    // Overridable fallback.
+    return visitObject(x);
+  }
+
+  visitPrimitive(x);
+  visitList(List x);
+  visitMap(Map x);
+  visitSendPort(SendPort x);
+  visitSendPortSync(SendPortSync x);
+
+  visitObject(Object x) {
+    // TODO(floitsch): make this a real exception. (which one)?
+    throw "Message serialization: Illegal value $x passed";
+  }
+
+  static bool isPrimitive(x) {
+    return (x == null) || (x is String) || (x is num) || (x is bool);
+  }
+}
+
+
+/** A visitor that recursively copies a message. */
+class _Copier extends _MessageTraverser {
+
+  visitPrimitive(x) => x;
+
+  List visitList(List list) {
+    List copy = _visited[list];
+    if (copy != null) return copy;
+
+    int len = list.length;
+
+    // TODO(floitsch): we loose the generic type of the List.
+    copy = new List(len);
+    _visited[list] = copy;
+    for (int i = 0; i < len; i++) {
+      copy[i] = _dispatch(list[i]);
+    }
+    return copy;
+  }
+
+  Map visitMap(Map map) {
+    Map copy = _visited[map];
+    if (copy != null) return copy;
+
+    // TODO(floitsch): we loose the generic type of the map.
+    copy = new Map();
+    _visited[map] = copy;
+    map.forEach((key, val) {
+      copy[_dispatch(key)] = _dispatch(val);
+    });
+    return copy;
+  }
+
+}
+
+/** Visitor that serializes a message as a JSON array. */
+class _Serializer extends _MessageTraverser {
+  int _nextFreeRefId = 0;
+
+  visitPrimitive(x) => x;
+
+  visitList(List list) {
+    int copyId = _visited[list];
+    if (copyId != null) return ['ref', copyId];
+
+    int id = _nextFreeRefId++;
+    _visited[list] = id;
+    var jsArray = _serializeList(list);
+    // TODO(floitsch): we are losing the generic type.
+    return ['list', id, jsArray];
+  }
+
+  visitMap(Map map) {
+    int copyId = _visited[map];
+    if (copyId != null) return ['ref', copyId];
+
+    int id = _nextFreeRefId++;
+    _visited[map] = id;
+    var keys = _serializeList(map.keys.toList());
+    var values = _serializeList(map.values.toList());
+    // TODO(floitsch): we are losing the generic type.
+    return ['map', id, keys, values];
+  }
+
+  _serializeList(List list) {
+    int len = list.length;
+    var result = new List(len);
+    for (int i = 0; i < len; i++) {
+      result[i] = _dispatch(list[i]);
+    }
+    return result;
+  }
+}
+
+/** Deserializes arrays created with [_Serializer]. */
+class _Deserializer {
+  Map<int, dynamic> _deserialized;
+
+  _Deserializer();
+
+  static bool isPrimitive(x) {
+    return (x == null) || (x is String) || (x is num) || (x is bool);
+  }
+
+  deserialize(x) {
+    if (isPrimitive(x)) return x;
+    // TODO(floitsch): this should be new HashMap<int, var|Dynamic>()
+    _deserialized = new HashMap();
+    return _deserializeHelper(x);
+  }
+
+  _deserializeHelper(x) {
+    if (isPrimitive(x)) return x;
+    assert(x is List);
+    switch (x[0]) {
+      case 'ref': return _deserializeRef(x);
+      case 'list': return _deserializeList(x);
+      case 'map': return _deserializeMap(x);
+      case 'sendport': return deserializeSendPort(x);
+      default: return deserializeObject(x);
+    }
+  }
+
+  _deserializeRef(List x) {
+    int id = x[1];
+    var result = _deserialized[id];
+    assert(result != null);
+    return result;
+  }
+
+  List _deserializeList(List x) {
+    int id = x[1];
+    // We rely on the fact that Dart-lists are directly mapped to Js-arrays.
+    List dartList = x[2];
+    _deserialized[id] = dartList;
+    int len = dartList.length;
+    for (int i = 0; i < len; i++) {
+      dartList[i] = _deserializeHelper(dartList[i]);
+    }
+    return dartList;
+  }
+
+  Map _deserializeMap(List x) {
+    Map result = new Map();
+    int id = x[1];
+    _deserialized[id] = result;
+    List keys = x[2];
+    List values = x[3];
+    int len = keys.length;
+    assert(len == values.length);
+    for (int i = 0; i < len; i++) {
+      var key = _deserializeHelper(keys[i]);
+      var value = _deserializeHelper(values[i]);
+      result[key] = value;
+    }
+    return result;
+  }
+
+  deserializeSendPort(List x);
+
+  deserializeObject(List x) {
+    // TODO(floitsch): Use real exception (which one?).
+    throw "Unexpected serialized object";
+  }
+}
+
+class TimerImpl implements Timer {
+  final bool _once;
+  bool _inEventLoop = false;
+  int _handle;
+
+  TimerImpl(int milliseconds, void callback(Timer timer))
+      : _once = true {
+    if (milliseconds == 0 && (!hasTimer() || _globalState.isWorker)) {
+      // This makes a dependency between the async library and the
+      // event loop of the isolate library. The compiler makes sure
+      // that the event loop is compiled if [Timer] is used.
+      // TODO(7907): In case of web workers, we need to use the event
+      // loop instead of setTimeout, to make sure the futures get executed in
+      // order.
+      _globalState.topEventLoop.enqueue(_globalState.currentContext, () {
+        callback(this);
+      }, 'timer');
+      _inEventLoop = true;
+    } else if (hasTimer()) {
+      _globalState.topEventLoop.activeTimerCount++;
+      void internalCallback() {
+        callback(this);
+        _handle = null;
+        _globalState.topEventLoop.activeTimerCount--;
+      }
+      _handle = JS('int', '#.setTimeout(#, #)',
+                   globalThis,
+                   convertDartClosureToJS(internalCallback, 0),
+                   milliseconds);
+    } else {
+      assert(milliseconds > 0);
+      throw new UnsupportedError("Timer greater than 0.");
+    }
+  }
+
+  TimerImpl.repeating(int milliseconds, void callback(Timer timer))
+      : _once = false {
+    if (hasTimer()) {
+      _globalState.topEventLoop.activeTimerCount++;
+      _handle = JS('int', '#.setInterval(#, #)',
+                   globalThis,
+                   convertDartClosureToJS(() { callback(this); }, 0),
+                   milliseconds);
+    } else {
+      throw new UnsupportedError("Repeating timer.");
+    }
+  }
+
+  void cancel() {
+    if (hasTimer()) {
+      if (_inEventLoop) {
+        throw new UnsupportedError("Timer in event loop cannot be canceled.");
+      }
+      if (_handle == null) return;
+      _globalState.topEventLoop.activeTimerCount--;
+      if (_once) {
+        JS('void', '#.clearTimeout(#)', globalThis, _handle);
+      } else {
+        JS('void', '#.clearInterval(#)', globalThis, _handle);
+      }
+      _handle = null;
+    } else {
+      throw new UnsupportedError("Canceling a timer.");
+    }
+  }
+}
+
+bool hasTimer() => JS('', '#.setTimeout', globalThis) != null;
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart
new file mode 100644
index 0000000..aab847a
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart
@@ -0,0 +1,34 @@
+// 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.
+
+// Patch file for the dart:isolate library.
+
+import 'dart:_isolate_helper' show IsolateNatives,
+                                   lazyPort,
+                                   ReceivePortImpl;
+
+patch class _Isolate {
+  patch static ReceivePort get port {
+    if (lazyPort == null) {
+      lazyPort = new ReceivePort();
+    }
+    return lazyPort;
+  }
+
+  patch static SendPort spawnFunction(void topLevelFunction(),
+      [bool UnhandledExceptionCallback(IsolateUnhandledException e)]) {
+    return IsolateNatives.spawnFunction(topLevelFunction);
+  }
+
+  patch static SendPort spawnUri(String uri) {
+    return IsolateNatives.spawn(null, uri, false);
+  }
+}
+
+/** Default factory for receive ports. */
+patch class ReceivePort {
+  patch factory ReceivePort() {
+    return new ReceivePortImpl();
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart
new file mode 100644
index 0000000..e33ae3f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart
@@ -0,0 +1,295 @@
+// 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.
+
+part of _interceptors;
+
+/**
+ * The interceptor class for [List]. The compiler recognizes this
+ * class as an interceptor, and changes references to [:this:] to
+ * actually use the receiver of the method, which is generated as an extra
+ * argument added to each member.
+ */
+class JSArray<E> implements List<E> {
+  const JSArray();
+
+  void add(E value) {
+    checkGrowable(this, 'add');
+    JS('void', r'#.push(#)', this, value);
+  }
+
+  E removeAt(int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index < 0 || index >= length) {
+      throw new RangeError.value(index);
+    }
+    checkGrowable(this, 'removeAt');
+    return JS('var', r'#.splice(#, 1)[0]', this, index);
+  }
+
+  E removeLast() {
+    checkGrowable(this, 'removeLast');
+    if (length == 0) throw new RangeError.value(-1);
+    return JS('var', r'#.pop()', this);
+  }
+
+  void remove(Object element) {
+    checkGrowable(this, 'remove');
+    for (int i = 0; i < this.length; i++) {
+      if (this[i] == element) {
+        JS('var', r'#.splice(#, 1)', this, i);
+        return;
+      }
+    }
+  }
+
+  void removeAll(Iterable elements) {
+    IterableMixinWorkaround.removeAllList(this, elements);
+  }
+
+  void retainAll(Iterable elements) {
+    IterableMixinWorkaround.retainAll(this, elements);
+  }
+
+  void removeMatching(bool test(E element)) {
+    // This could, and should, be optimized.
+    IterableMixinWorkaround.removeMatchingList(this, test);
+  }
+
+  void retainMatching(bool test(E element)) {
+    IterableMixinWorkaround.removeMatchingList(this,
+                                               (E element) => !test(element));
+  }
+
+  Iterable<E> where(bool f(E element)) {
+    return IterableMixinWorkaround.where(this, f);
+  }
+
+  Iterable expand(Iterable f(E element)) {
+    return IterableMixinWorkaround.expand(this, f);
+  }
+
+  void addAll(Collection<E> collection) {
+    for (E e in collection) {
+      this.add(e);
+    }
+  }
+
+  void addLast(E value) {
+    checkGrowable(this, 'addLast');
+    JS('void', r'#.push(#)', this, value);
+  }
+
+  void clear() {
+    length = 0;
+  }
+
+  void forEach(void f(E element)) {
+    return IterableMixinWorkaround.forEach(this, f);
+  }
+
+  Iterable map(f(E element)) {
+    return IterableMixinWorkaround.mapList(this, f);
+  }
+
+  List mappedBy(f(E element)) {
+    return IterableMixinWorkaround.mappedByList(this, f);
+  }
+
+  String join([String separator]) {
+    if (separator == null) separator = "";
+    var list = new List(this.length);
+    for (int i = 0; i < this.length; i++) {
+      list[i] = "${this[i]}";
+    }
+    return JS('String', "#.join(#)", list, separator);
+  }
+
+  Iterable<E> take(int n) {
+    return IterableMixinWorkaround.takeList(this, n);
+  }
+
+  Iterable<E> takeWhile(bool test(E value)) {
+    return IterableMixinWorkaround.takeWhile(this, test);
+  }
+
+  Iterable<E> skip(int n) {
+    return IterableMixinWorkaround.skipList(this, n);
+  }
+
+  Iterable<E> skipWhile(bool test(E value)) {
+    return IterableMixinWorkaround.skipWhile(this, test);
+  }
+
+  reduce(initialValue, combine(previousValue, E element)) {
+    return IterableMixinWorkaround.reduce(this, initialValue, combine);
+  }
+
+  E firstMatching(bool test(E value), {E orElse()}) {
+    return IterableMixinWorkaround.firstMatching(this, test, orElse);
+  }
+
+  E lastMatching(bool test(E value), {E orElse()}) {
+    return IterableMixinWorkaround.lastMatchingInList(this, test, orElse);
+  }
+
+  E singleMatching(bool test(E value)) {
+    return IterableMixinWorkaround.singleMatching(this, test);
+  }
+
+  E elementAt(int index) {
+    return this[index];
+  }
+
+  List<E> getRange(int start, int length) {
+    // TODO(ngeoffray): Parameterize the return value.
+    if (0 == length) return [];
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (length < 0) throw new ArgumentError(length);
+    if (start < 0) throw new RangeError.value(start);
+    int end = start + length;
+    if (end > this.length) {
+      throw new RangeError.value(length);
+    }
+    if (length < 0) throw new ArgumentError(length);
+    return JS('=List', r'#.slice(#, #)', this, start, end);
+  }
+
+  void insertRange(int start, int length, [E initialValue]) {
+    return listInsertRange(this, start, length, initialValue);
+  }
+
+  E get first {
+    if (length > 0) return this[0];
+    throw new StateError("No elements");
+  }
+
+  E get last {
+    if (length > 0) return this[length - 1];
+    throw new StateError("No elements");
+  }
+
+  E get single {
+    if (length == 1) return this[0];
+    if (length == 0) throw new StateError("No elements");
+    throw new StateError("More than one element");
+  }
+
+  E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare);
+
+  E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare);
+
+  void removeRange(int start, int length) {
+    checkGrowable(this, 'removeRange');
+    if (length == 0) {
+      return;
+    }
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (length < 0) throw new ArgumentError(length);
+    var receiverLength = this.length;
+    if (start < 0 || start >= receiverLength) {
+      throw new RangeError.value(start);
+    }
+    if (start + length > receiverLength) {
+      throw new RangeError.value(start + length);
+    }
+    Arrays.copy(this,
+                start + length,
+                this,
+                start,
+                receiverLength - length - start);
+    this.length = receiverLength - length;
+  }
+
+  void setRange(int start, int length, List<E> from, [int startFrom = 0]) {
+    checkMutable(this, 'set range');
+    if (length == 0) return;
+    checkNull(start); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(length); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(from); // TODO(ahe): This is not specified but co19 tests it.
+    checkNull(startFrom); // TODO(ahe): This is not specified but co19 tests it.
+    if (start is !int) throw new ArgumentError(start);
+    if (length is !int) throw new ArgumentError(length);
+    if (startFrom is !int) throw new ArgumentError(startFrom);
+    if (length < 0) throw new ArgumentError(length);
+    if (start < 0) throw new RangeError.value(start);
+    if (start + length > this.length) {
+      throw new RangeError.value(start + length);
+    }
+
+    Arrays.copy(from, startFrom, this, start, length);
+  }
+
+  bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f);
+
+  bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f);
+
+  List<E> get reversed => IterableMixinWorkaround.reversedList(this);
+
+  void sort([int compare(E a, E b)]) {
+    checkMutable(this, 'sort');
+    IterableMixinWorkaround.sortList(this, compare);
+  }
+
+  int indexOf(E element, [int start = 0]) {
+    if (start is !int) throw new ArgumentError(start);
+    return Arrays.indexOf(this, element, start, length);
+  }
+
+  int lastIndexOf(E element, [int start]) {
+    if (start == null) start = this.length - 1;
+    return Arrays.lastIndexOf(this, element, start);
+  }
+
+  bool contains(E other) {
+    for (int i = 0; i < length; i++) {
+      if (other == this[i]) return true;
+    }
+    return false;
+  }
+
+  bool get isEmpty => length == 0;
+
+  String toString() => Collections.collectionToString(this);
+
+  List<E> toList() => new List<E>.from(this);
+
+  Set<E> toSet() => new Set<E>.from(this);
+
+  Iterator<E> get iterator => new ListIterator<E>(this);
+
+  int get hashCode => Primitives.objectHashCode(this);
+
+  Type get runtimeType {
+    // Call getRuntimeTypeString to get the name including type arguments.
+    return new TypeImpl(getRuntimeTypeString(this));
+  }
+
+  int get length => JS('int', r'#.length', this);
+
+  void set length(int newLength) {
+    if (newLength is !int) throw new ArgumentError(newLength);
+    if (newLength < 0) throw new RangeError.value(newLength);
+    checkGrowable(this, 'set length');
+    JS('void', r'#.length = #', this, newLength);
+  }
+
+  E operator [](int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index >= length || index < 0) throw new RangeError.value(index);
+    return JS('var', '#[#]', this, index);
+  }
+
+  void operator []=(int index, E value) {
+    checkMutable(this, 'indexed set');
+    if (index is !int) throw new ArgumentError(index);
+    if (index >= length || index < 0) throw new RangeError.value(index);
+    JS('void', r'#[#] = #', this, index, value);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart
new file mode 100644
index 0000000..0fbff9c
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart
@@ -0,0 +1,1513 @@
+// 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.
+
+library _js_helper;
+
+import 'dart:collection';
+import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS,
+                                   JS,
+                                   JS_CALL_IN_ISOLATE,
+                                   JS_CURRENT_ISOLATE,
+                                   JS_OPERATOR_IS_PREFIX,
+                                   JS_HAS_EQUALS,
+                                   RAW_DART_FUNCTION_REF,
+                                   UNINTERCEPTED;
+import 'dart:_interceptors' show getInterceptor;
+
+part 'constant_map.dart';
+part 'native_helper.dart';
+part 'regexp_helper.dart';
+part 'string_helper.dart';
+
+bool isJsArray(var value) {
+  return value != null && JS('bool', r'#.constructor === Array', value);
+}
+
+checkMutable(list, reason) {
+  if (JS('bool', r'!!(#.immutable$list)', list)) {
+    throw new UnsupportedError(reason);
+  }
+}
+
+checkGrowable(list, reason) {
+  if (JS('bool', r'!!(#.fixed$length)', list)) {
+    throw new UnsupportedError(reason);
+  }
+}
+
+String S(value) {
+  if (value is String) return value;
+  if ((value is num && value != 0) || value is bool) {
+    return JS('String', r'String(#)', value);
+  }
+  if (value == null) return 'null';
+  var res = value.toString();
+  if (res is !String) throw new ArgumentError(value);
+  return res;
+}
+
+createInvocationMirror(name, internalName, type, arguments, argumentNames) =>
+    new JSInvocationMirror(name, internalName, type, arguments, argumentNames);
+
+class JSInvocationMirror implements InvocationMirror {
+  static const METHOD = 0;
+  static const GETTER = 1;
+  static const SETTER = 2;
+
+  final String memberName;
+  final String _internalName;
+  final int _kind;
+  final List _arguments;
+  final List _namedArgumentNames;
+  /** Map from argument name to index in _arguments. */
+  Map<String,dynamic> _namedIndices = null;
+
+  JSInvocationMirror(this.memberName,
+                     this._internalName,
+                     this._kind,
+                     this._arguments,
+                     this._namedArgumentNames);
+
+  bool get isMethod => _kind == METHOD;
+  bool get isGetter => _kind == GETTER;
+  bool get isSetter => _kind == SETTER;
+  bool get isAccessor => _kind != METHOD;
+
+  List get positionalArguments {
+    if (isGetter) return null;
+    var list = [];
+    var argumentCount =
+        _arguments.length - _namedArgumentNames.length;
+    for (var index = 0 ; index < argumentCount ; index++) {
+      list.add(_arguments[index]);
+    }
+    return list;
+  }
+
+  Map<String,dynamic> get namedArguments {
+    if (isAccessor) return null;
+    var map = <String,dynamic>{};
+    int namedArgumentCount = _namedArgumentNames.length;
+    int namedArgumentsStartIndex = _arguments.length - namedArgumentCount;
+    for (int i = 0; i < namedArgumentCount; i++) {
+      map[_namedArgumentNames[i]] = _arguments[namedArgumentsStartIndex + i];
+    }
+    return map;
+  }
+
+  static final _objectInterceptor = getInterceptor(new Object());
+  invokeOn(Object object) {
+    var interceptor = getInterceptor(object);
+    var receiver = object;
+    var name = _internalName;
+    var arguments = _arguments;
+    if (identical(interceptor, _objectInterceptor)) {
+      if (!isJsArray(arguments)) arguments = new List.from(arguments);
+    } else {
+      arguments = [object]..addAll(arguments);
+      receiver = interceptor;
+    }
+    return JS("var", "#[#].apply(#, #)", receiver, name, receiver, arguments);
+  }
+}
+
+class Primitives {
+  static int hashCodeSeed = 0;
+
+  static int objectHashCode(object) {
+    int hash = JS('var', r'#.$identityHash', object);
+    if (hash == null) {
+      // TOOD(ahe): We should probably randomize this somehow.
+      hash = ++hashCodeSeed;
+      JS('void', r'#.$identityHash = #', object, hash);
+    }
+    return hash;
+  }
+
+  /**
+   * This is the low-level method that is used to implement
+   * [print]. It is possible to override this function from JavaScript
+   * by defining a function in JavaScript called "dartPrint".
+   */
+  static void printString(String string) {
+    if (JS('bool', r'typeof dartPrint == "function"')) {
+      // Support overriding print from JavaScript.
+      JS('void', r'dartPrint(#)', string);
+      return;
+    }
+
+    // Inside browser.
+    if (JS('bool', r'typeof window == "object"')) {
+      // On IE, the console is only defined if dev tools is open.
+      if (JS('bool', r'typeof console == "object"')) {
+        JS('void', r'console.log(#)', string);
+      }
+      return;
+    }
+
+    // Running in d8, the V8 developer shell, or in Firefox' js-shell.
+    if (JS('bool', r'typeof print == "function"')) {
+      JS('void', r'print(#)', string);
+      return;
+    }
+
+    // This is somewhat nasty, but we don't want to drag in a bunch of
+    // dependencies to handle a situation that cannot happen. So we
+    // avoid using Dart [:throw:] and Dart [toString].
+    JS('void', "throw 'Unable to print message: ' + String(#)", string);
+  }
+
+  static void _throwFormatException(String string) {
+    throw new FormatException(string);
+  }
+
+  static int parseInt(String source,
+                      int radix,
+                      int handleError(String source)) {
+    if (handleError == null) handleError = _throwFormatException;
+
+    checkString(source);
+    var match = JS('=List|Null',
+        r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(#)',
+        source);
+    int digitsIndex = 1;
+    int hexIndex = 2;
+    int decimalIndex = 3;
+    int nonDecimalHexIndex = 4;
+    if (radix == null) {
+      radix = 10;
+      if (match != null) {
+        if (match[hexIndex] != null) {
+          // Cannot fail because we know that the digits are all hex.
+          return JS('num', r'parseInt(#, 16)', source);
+        }
+        if (match[decimalIndex] != null) {
+          // Cannot fail because we know that the digits are all decimal.
+          return JS('num', r'parseInt(#, 10)', source);
+        }
+        return handleError(source);
+      }
+    } else {
+      if (radix is! int) throw new ArgumentError("Radix is not an integer");
+      if (radix < 2 || radix > 36) {
+        throw new RangeError("Radix $radix not in range 2..36");
+      }
+      if (match != null) {
+        if (radix == 10 && match[decimalIndex] != null) {
+          // Cannot fail because we know that the digits are all decimal.
+          return JS('num', r'parseInt(#, 10)', source);
+        }
+        if (radix < 10 || match[decimalIndex] == null) {
+          // We know that the characters must be ASCII as otherwise the
+          // regexp wouldn't have matched. Calling toLowerCase is thus
+          // guaranteed to be a safe operation. If it wasn't ASCII, then
+          // "İ" would become "i", and we would accept it for radices greater
+          // than 18.
+          int maxCharCode;
+          if (radix <= 10) {
+            // Allow all digits less than the radix. For example 0, 1, 2 for
+            // radix 3.
+            // "0".charCodeAt(0) + radix - 1;
+            maxCharCode = 0x30 + radix - 1;
+          } else {
+            // Characters are located after the digits in ASCII. Therefore we
+            // only check for the character code. The regexp above made already
+            // sure that the string does not contain anything but digits or
+            // characters.
+            // "0".charCodeAt(0) + radix - 1;
+            maxCharCode = 0x61 + radix - 10 - 1;
+          }
+          String digitsPart = match[digitsIndex].toLowerCase();
+          for (int i = 0; i < digitsPart.length; i++) {
+            if (digitsPart.charCodeAt(i) > maxCharCode) {
+              return handleError(source);
+            }
+          }
+        }
+      }
+    }
+    if (match == null) return handleError(source);
+    return JS('num', r'parseInt(#, #)', source, radix);
+  }
+
+  static double parseDouble(String source, int handleError(String source)) {
+    checkString(source);
+    if (handleError == null) handleError = _throwFormatException;
+    // Notice that JS parseFloat accepts garbage at the end of the string.
+    // Accept only:
+    // - NaN
+    // - [+/-]Infinity
+    // - a Dart double literal
+    // We do not allow leading or trailing whitespace.
+    if (!JS('bool',
+            r'/^\s*(?:NaN|[+-]?(?:Infinity|'
+                r'(?:\.\d+|\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))\s*$/.test(#)',
+            source)) {
+      return handleError(source);
+    }
+    var result = JS('num', r'parseFloat(#)', source);
+    if (result.isNaN && source != 'NaN') {
+      return handleError(source);
+    }
+    return result;
+  }
+
+  /** [: r"$".charCodeAt(0) :] */
+  static const int DOLLAR_CHAR_VALUE = 36;
+
+  static String objectTypeName(Object object) {
+    String name = constructorNameFallback(object);
+    if (name == 'Object') {
+      // Try to decompile the constructor by turning it into a string
+      // and get the name out of that. If the decompiled name is a
+      // string, we use that instead of the very generic 'Object'.
+      var decompiled = JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]',
+                          JS('var', r'String(#.constructor)', object));
+      if (decompiled is String) name = decompiled;
+    }
+    // TODO(kasperl): If the namer gave us a fresh global name, we may
+    // want to remove the numeric suffix that makes it unique too.
+    if (identical(name.charCodeAt(0), DOLLAR_CHAR_VALUE)) name = name.substring(1);
+    return name;
+  }
+
+  static String objectToString(Object object) {
+    String name = objectTypeName(object);
+    return "Instance of '$name'";
+  }
+
+  static List newGrowableList(length) {
+    return JS('=List', r'new Array(#)', length);
+  }
+
+  static List newFixedList(length) {
+    var result = JS('=List', r'new Array(#)', length);
+    JS('void', r'#.fixed$length = #', result, true);
+    return result;
+  }
+
+  static num dateNow() => JS('num', r'Date.now()');
+
+  static num numMicroseconds() {
+    if (JS('bool', 'typeof window != "undefined" && window !== null')) {
+      var performance = JS('var', 'window.performance');
+      if (performance != null &&
+          JS('bool', 'typeof #.webkitNow == "function"', performance)) {
+        return (1000 * JS('num', '#.webkitNow()', performance)).floor();
+      }
+    }
+    return 1000 * dateNow();
+  }
+
+  // This is to avoid stack overflows due to very large argument arrays in
+  // apply().  It fixes http://dartbug.com/6919
+  static String _fromCharCodeApply(List<int> array) {
+    String result = "";
+    const kMaxApply = 500;
+    int end = array.length;
+    for (var i = 0; i < end; i += kMaxApply) {
+      var subarray;
+      if (end <= kMaxApply) {
+        subarray = array;
+      } else {
+        subarray = JS('=List', r'#.slice(#, #)', array,
+                      i, i + kMaxApply < end ? i + kMaxApply : end);
+      }
+      result = JS('String', '# + String.fromCharCode.apply(#, #)',
+                  result, null, subarray);
+    }
+    return result;
+  }
+
+  static String stringFromCodePoints(codePoints) {
+    List<int> a = <int>[];
+    for (var i in codePoints) {
+      if (i is !int) throw new ArgumentError(i);
+      if (i <= 0xffff) {
+        a.add(i);
+      } else if (i <= 0x10ffff) {
+        a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
+        a.add(0xdc00 + (i & 0x3ff));
+      } else {
+        throw new ArgumentError(i);
+      }
+    }
+    return _fromCharCodeApply(a);
+  }
+
+  static String stringFromCharCodes(charCodes) {
+    for (var i in charCodes) {
+      if (i is !int) throw new ArgumentError(i);
+      if (i < 0) throw new ArgumentError(i);
+      if (i > 0xffff) return stringFromCodePoints(charCodes);
+    }
+    return _fromCharCodeApply(charCodes);
+  }
+
+  static String getTimeZoneName(receiver) {
+    // When calling toString on a Date it will emit the timezone in parenthesis.
+    // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
+    // We extract this name using a regexp.
+    var d = lazyAsJsDate(receiver);
+    return JS('String', r'/\((.*)\)/.exec(#.toString())[1]', d);
+  }
+
+  static int getTimeZoneOffsetInMinutes(receiver) {
+    // Note that JS and Dart disagree on the sign of the offset.
+    return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
+  }
+
+  static valueFromDecomposedDate(years, month, day, hours, minutes, seconds,
+                                 milliseconds, isUtc) {
+    final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
+    checkInt(years);
+    checkInt(month);
+    checkInt(day);
+    checkInt(hours);
+    checkInt(minutes);
+    checkInt(seconds);
+    checkInt(milliseconds);
+    checkBool(isUtc);
+    var jsMonth = month - 1;
+    var value;
+    if (isUtc) {
+      value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)',
+                 years, jsMonth, day, hours, minutes, seconds, milliseconds);
+    } else {
+      value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()',
+                 years, jsMonth, day, hours, minutes, seconds, milliseconds);
+    }
+    if (value.isNaN ||
+        value < -MAX_MILLISECONDS_SINCE_EPOCH ||
+        value > MAX_MILLISECONDS_SINCE_EPOCH) {
+      throw new ArgumentError();
+    }
+    if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc);
+    return value;
+  }
+
+  static patchUpY2K(value, years, isUtc) {
+    var date = JS('', r'new Date(#)', value);
+    if (isUtc) {
+      JS('num', r'#.setUTCFullYear(#)', date, years);
+    } else {
+      JS('num', r'#.setFullYear(#)', date, years);
+    }
+    return JS('num', r'#.valueOf()', date);
+  }
+
+  // Lazily keep a JS Date stored in the JS object.
+  static lazyAsJsDate(receiver) {
+    if (JS('bool', r'#.date === (void 0)', receiver)) {
+      JS('void', r'#.date = new Date(#)', receiver,
+         receiver.millisecondsSinceEpoch);
+    }
+    return JS('var', r'#.date', receiver);
+  }
+
+  // The getters for date and time parts below add a positive integer to ensure
+  // that the result is really an integer, because the JavaScript implementation
+  // may return -0.0 instead of 0.
+
+  static getYear(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getMonth(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
+      : JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver));
+  }
+
+  static getDay(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getHours(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getMinutes(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getSeconds(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getMilliseconds(receiver) {
+    return (receiver.isUtc)
+      ? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
+      : JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
+  }
+
+  static getWeekday(receiver) {
+    int weekday = (receiver.isUtc)
+      ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
+      : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver));
+    // Adjust by one because JS weeks start on Sunday.
+    return (weekday + 6) % 7 + 1;
+  }
+
+  static valueFromDateString(str) {
+    if (str is !String) throw new ArgumentError(str);
+    var value = JS('num', r'Date.parse(#)', str);
+    if (value.isNaN) throw new ArgumentError(str);
+    return value;
+  }
+
+  static getProperty(object, key) {
+    if (object == null || object is bool || object is num || object is String) {
+      throw new ArgumentError(object);
+    }
+    return JS('var', '#[#]', object, key);
+  }
+
+  static void setProperty(object, key, value) {
+    if (object == null || object is bool || object is num || object is String) {
+      throw new ArgumentError(object);
+    }
+    JS('void', '#[#] = #', object, key, value);
+  }
+
+  static applyFunction(Function function,
+                       List positionalArguments,
+                       Map<String, dynamic> namedArguments) {
+    int argumentCount = 0;
+    StringBuffer buffer = new StringBuffer();
+    List arguments = [];
+
+    if (positionalArguments != null) {
+      argumentCount += positionalArguments.length;
+      arguments.addAll(positionalArguments);
+    }
+
+    // Sort the named arguments to get the right selector name and
+    // arguments order.
+    if (namedArguments != null && !namedArguments.isEmpty) {
+      // Call new List.from to make sure we get a JavaScript array.
+      List<String> listOfNamedArguments =
+          new List<String>.from(namedArguments.keys);
+      argumentCount += namedArguments.length;
+      // We're sorting on strings, and the behavior is the same between
+      // Dart string sort and JS string sort. To avoid needing the Dart
+      // sort implementation, we use the JavaScript one instead.
+      JS('void', '#.sort()', listOfNamedArguments);
+      listOfNamedArguments.forEach((String name) {
+        buffer.add('\$$name');
+        arguments.add(namedArguments[name]);
+      });
+    }
+
+    String selectorName = 'call\$$argumentCount$buffer';
+    var jsFunction = JS('var', '#[#]', function, selectorName);
+    if (jsFunction == null) {
+      throw new NoSuchMethodError(function, selectorName, arguments, {});
+    }
+    // We bound 'this' to [function] because of how we compile
+    // closures: escaped local variables are stored and accessed through
+    // [function].
+    return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
+  }
+
+  static getConstructor(String className) {
+    // TODO(ahe): How to safely access $?
+    return JS('var', r'$[#]', className);
+  }
+
+  static bool identicalImplementation(a, b) {
+    return JS('bool', '# == null', a)
+      ? JS('bool', '# == null', b)
+      : JS('bool', '# === #', a, b);
+  }
+}
+
+/**
+ * Called by generated code to throw an illegal-argument exception,
+ * for example, if a non-integer index is given to an optimized
+ * indexed access.
+ */
+iae(argument) {
+  throw new ArgumentError(argument);
+}
+
+/**
+ * Called by generated code to throw an index-out-of-range exception,
+ * for example, if a bounds check fails in an optimized indexed
+ * access.
+ */
+ioore(index) {
+  throw new RangeError.value(index);
+}
+
+listInsertRange(receiver, start, length, initialValue) {
+  if (length == 0) {
+    return;
+  }
+  if (length is !int) throw new ArgumentError(length);
+  if (length < 0) throw new ArgumentError(length);
+  if (start is !int) throw new ArgumentError(start);
+
+  var receiverLength = JS('num', r'#.length', receiver);
+  if (start < 0 || start > receiverLength) {
+    throw new RangeError.value(start);
+  }
+  receiver.length = receiverLength + length;
+  Arrays.copy(receiver,
+              start,
+              receiver,
+              start + length,
+              receiverLength - start);
+  if (initialValue != null) {
+    for (int i = start; i < start + length; i++) {
+      receiver[i] = initialValue;
+    }
+  }
+  receiver.length = receiverLength + length;
+}
+
+stringLastIndexOfUnchecked(receiver, element, start)
+  => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
+
+
+checkNull(object) {
+  if (object == null) throw new ArgumentError(null);
+  return object;
+}
+
+checkNum(value) {
+  if (value is !num) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+checkInt(value) {
+  if (value is !int) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+checkBool(value) {
+  if (value is !bool) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+checkString(value) {
+  if (value is !String) {
+    throw new ArgumentError(value);
+  }
+  return value;
+}
+
+class MathNatives {
+  static double sqrt(num value)
+    => JS('double', r'Math.sqrt(#)', checkNum(value));
+
+  static double sin(num value)
+    => JS('double', r'Math.sin(#)', checkNum(value));
+
+  static double cos(num value)
+    => JS('double', r'Math.cos(#)', checkNum(value));
+
+  static double tan(num value)
+    => JS('double', r'Math.tan(#)', checkNum(value));
+
+  static double acos(num value)
+    => JS('double', r'Math.acos(#)', checkNum(value));
+
+  static double asin(num value)
+    => JS('double', r'Math.asin(#)', checkNum(value));
+
+  static double atan(num value)
+    => JS('double', r'Math.atan(#)', checkNum(value));
+
+  static double atan2(num a, num b)
+    => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b));
+
+  static double exp(num value)
+    => JS('double', r'Math.exp(#)', checkNum(value));
+
+  static double log(num value)
+    => JS('double', r'Math.log(#)', checkNum(value));
+
+  static num pow(num value, num exponent) {
+    checkNum(value);
+    checkNum(exponent);
+    return JS('num', r'Math.pow(#, #)', value, exponent);
+  }
+
+  static double random() => JS('double', r'Math.random()');
+}
+
+/**
+ * Wrap the given Dart object and record a stack trace.
+ *
+ * The code in [unwrapException] deals with getting the original Dart
+ * object out of the wrapper again.
+ */
+$throw(ex) {
+  if (ex == null) ex = const NullThrownError();
+  var wrapper = new DartError(ex);
+
+  if (JS('bool', '!!Error.captureStackTrace')) {
+    // Use V8 API for recording a "fast" stack trace (this installs a
+    // "stack" property getter on [wrapper]).
+    JS('void', r'Error.captureStackTrace(#, #)',
+       wrapper, RAW_DART_FUNCTION_REF($throw));
+  } else {
+    // Otherwise, produce a stack trace and record it in the wrapper.
+    // This is a slower way to create a stack trace which works on
+    // some browsers, but may simply evaluate to null.
+    String stackTrace = JS('', 'new Error().stack');
+    JS('void', '#.stack = #', wrapper, stackTrace);
+  }
+  return wrapper;
+}
+
+/**
+ * Wrapper class for throwing exceptions.
+ */
+class DartError {
+  /// The Dart object (or primitive JavaScript value) which was thrown is
+  /// attached to this object as a field named 'dartException'.  We do this
+  /// only in raw JS so that we can use the 'in' operator and so that the
+  /// minifier does not rename the field.  Therefore it is not declared as a
+  /// real field.
+
+  DartError(var dartException) {
+    JS('void', '#.dartException = #', this, dartException);
+    // Install a toString method that the JavaScript system will call
+    // to format uncaught exceptions.
+    JS('void', '#.toString = #', this, DART_CLOSURE_TO_JS(toStringWrapper));
+  }
+
+  /**
+   * V8/Chrome installs a property getter, "stack", when calling
+   * Error.captureStackTrace (see [$throw]). In [$throw], we make sure
+   * that this property is always set.
+   */
+  String get stack => JS('', '#.stack', this);
+
+  /**
+   * This method can be invoked by calling toString from
+   * JavaScript. See the constructor of this class.
+   *
+   * We only expect this method to be called (indirectly) by the
+   * browser when an uncaught exception occurs. Instance of this class
+   * should never escape into Dart code (except for [$throw] above).
+   */
+  String toString() {
+    // If Error.captureStackTrace is available, accessing stack from
+    // this method would cause recursion because the stack property
+    // (on this object) is actually a getter which calls toString on
+    // this object (via the wrapper installed in this class'
+    // constructor). Fortunately, both Chrome and d8 prints the stack
+    // trace and Chrome even applies source maps to the stack
+    // trace. Remeber, this method is only ever invoked by the browser
+    // when an uncaught exception occurs.
+    var dartException = JS('var', r'#.dartException', this);
+    if (JS('bool', '!!Error.captureStackTrace') || (stack == null)) {
+      return dartException.toString();
+    } else {
+      return '$dartException\n$stack';
+    }
+  }
+
+  /**
+   * This method is installed as JavaScript toString method on
+   * [DartError].  So JavaScript 'this' binds to an instance of
+   * DartError.
+   */
+  static toStringWrapper() => JS('', r'this').toString();
+}
+
+makeLiteralListConst(list) {
+  JS('bool', r'#.immutable$list = #', list, true);
+  JS('bool', r'#.fixed$length = #', list, true);
+  return list;
+}
+
+throwRuntimeError(message) {
+  throw new RuntimeError(message);
+}
+
+/**
+ * The SSA builder generates a call to this method when a malformed type is used
+ * in a subtype test.
+ */
+throwMalformedSubtypeError(value, type, reasons) {
+  throw new TypeErrorImplementation.malformedSubtype(value, type, reasons);
+}
+
+throwAbstractClassInstantiationError(className) {
+  throw new AbstractClassInstantiationError(className);
+}
+
+/**
+ * Called from catch blocks in generated code to extract the Dart
+ * exception from the thrown value. The thrown value may have been
+ * created by [$throw] or it may be a 'native' JS exception.
+ *
+ * Some native exceptions are mapped to new Dart instances, others are
+ * returned unmodified.
+ */
+unwrapException(ex) {
+  // Note that we are checking if the object has the property. If it
+  // has, it could be set to null if the thrown value is null.
+  if (JS('bool', r'"dartException" in #', ex)) {
+    return JS('', r'#.dartException', ex);
+  }
+
+  // Grab hold of the exception message. This field is available on
+  // all supported browsers.
+  var message = JS('var', r'#.message', ex);
+
+  if (JS('bool', r'# instanceof TypeError', ex)) {
+    // The type and arguments fields are Chrome specific but they
+    // allow us to get very detailed information about what kind of
+    // exception occurred.
+    var type = JS('var', r'#.type', ex);
+    var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex);
+    if (contains(message, 'JSNull') ||
+        type == 'property_not_function' ||
+        type == 'called_non_callable' ||
+        type == 'non_object_property_call' ||
+        type == 'non_object_property_load') {
+      return new NoSuchMethodError(null, name, [], {});
+    } else if (type == 'undefined_method') {
+      return new NoSuchMethodError('', name, [], {});
+    }
+
+    var ieErrorCode = JS('int', '#.number & 0xffff', ex);
+    var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex);
+    // If we cannot use [type] to determine what kind of exception
+    // we're dealing with we fall back on looking at the exception
+    // message if it is available and a string.
+    if (message is String) {
+      if (message.endsWith('is null') ||
+          message.endsWith('is undefined') ||
+          message.endsWith('is null or undefined') ||
+          message.endsWith('of undefined') ||
+          message.endsWith('of null')) {
+        return new NoSuchMethodError(null, '<unknown>', [], {});
+      } else if (contains(message, ' has no method ') ||
+                 contains(message, ' is not a function') ||
+                 (ieErrorCode == 438 && ieFacilityNumber == 10)) {
+        // Examples:
+        //  x.foo is not a function
+        //  'undefined' is not a function (evaluating 'x.foo(1,2,3)')
+        // Object doesn't support property or method 'foo' which sets the error
+        // code 438 in IE.
+        // TODO(kasperl): Compute the right name if possible.
+        return new NoSuchMethodError('', '<unknown>', [], {});
+      }
+    }
+
+    // If we cannot determine what kind of error this is, we fall back
+    // to reporting this as a generic exception. It's probably better
+    // than nothing.
+    return new Exception(message is String ? message : '');
+  }
+
+  if (JS('bool', r'# instanceof RangeError', ex)) {
+    if (message is String && contains(message, 'call stack')) {
+      return new StackOverflowError();
+    }
+
+    // In general, a RangeError is thrown when trying to pass a number
+    // as an argument to a function that does not allow a range that
+    // includes that number.
+    return new ArgumentError();
+  }
+
+  // Check for the Firefox specific stack overflow signal.
+  if (JS('bool',
+         r"typeof InternalError == 'function' && # instanceof InternalError",
+         ex)) {
+    if (message is String && message == 'too much recursion') {
+      return new StackOverflowError();
+    }
+  }
+
+  // Just return the exception. We should not wrap it because in case
+  // the exception comes from the DOM, it is a JavaScript
+  // object backed by a native Dart class.
+  return ex;
+}
+
+/**
+ * Called by generated code to fetch the stack trace from an
+ * exception.
+ */
+StackTrace getTraceFromException(exception) {
+  return new StackTrace(JS("var", r"#.stack", exception));
+}
+
+class StackTrace {
+  var stack;
+  StackTrace(this.stack);
+  String toString() => stack != null ? stack : '';
+}
+
+
+/**
+ * Called by generated code to build a map literal. [keyValuePairs] is
+ * a list of key, value, key, value, ..., etc.
+ */
+makeLiteralMap(List keyValuePairs) {
+  Iterator iterator = keyValuePairs.iterator;
+  Map result = new LinkedHashMap();
+  while (iterator.moveNext()) {
+    String key = iterator.current;
+    iterator.moveNext();
+    var value = iterator.current;
+    result[key] = value;
+  }
+  return result;
+}
+
+invokeClosure(Function closure,
+              var isolate,
+              int numberOfArguments,
+              var arg1,
+              var arg2) {
+  if (numberOfArguments == 0) {
+    return JS_CALL_IN_ISOLATE(isolate, () => closure());
+  } else if (numberOfArguments == 1) {
+    return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1));
+  } else if (numberOfArguments == 2) {
+    return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2));
+  } else {
+    throw new Exception(
+        'Unsupported number of arguments for wrapped closure');
+  }
+}
+
+/**
+ * Called by generated code to convert a Dart closure to a JS
+ * closure when the Dart closure is passed to the DOM.
+ */
+convertDartClosureToJS(closure, int arity) {
+  if (closure == null) return null;
+  var function = JS('var', r'#.$identity', closure);
+  if (JS('bool', r'!!#', function)) return function;
+  // By fetching the current isolate before creating the JavaScript
+  // function, we prevent the compiler from inlining its use in
+  // the JavaScript function below (the compiler generates code for
+  // fetching the isolate before creating the JavaScript function).
+  // If it was inlined, the JavaScript function would not get the
+  // current isolate, but the one that is active when the callback
+  // executes.
+  var currentIsolate = JS_CURRENT_ISOLATE();
+
+  // We use $0 and $1 to not clash with variable names used by the
+  // compiler and/or minifier.
+  function = JS("var",
+                r"""function($0, $1) { return #(#, #, #, $0, $1); }""",
+                DART_CLOSURE_TO_JS(invokeClosure),
+                closure,
+                JS_CURRENT_ISOLATE(),
+                arity);
+
+  JS('void', r'#.$identity = #', closure, function);
+  return function;
+}
+
+/**
+ * Super class for Dart closures.
+ */
+class Closure implements Function {
+  String toString() => "Closure";
+}
+
+bool jsHasOwnProperty(var jsObject, String property) {
+  return JS('bool', r'#.hasOwnProperty(#)', jsObject, property);
+}
+
+jsPropertyAccess(var jsObject, String property) {
+  return JS('var', r'#[#]', jsObject, property);
+}
+
+/**
+ * Called at the end of unaborted switch cases to get the singleton
+ * FallThroughError exception that will be thrown.
+ */
+getFallThroughError() => const FallThroughErrorImplementation();
+
+/**
+ * Represents the type Dynamic. The compiler treats this specially.
+ */
+abstract class Dynamic_ {
+}
+
+/**
+ * A metadata annotation describing the types instantiated by a native element.
+ *
+ * The annotation is valid on a native method and a field of a native class.
+ *
+ * By default, a field of a native class is seen as an instantiation point for
+ * all native classes that are a subtype of the field's type, and a native
+ * method is seen as an instantiation point fo all native classes that are a
+ * subtype of the method's return type, or the argument types of the declared
+ * type of the method's callback parameter.
+ *
+ * An @[Creates] annotation overrides the default set of instantiated types.  If
+ * one or more @[Creates] annotations are present, the type of the native
+ * element is ignored, and the union of @[Creates] annotations is used instead.
+ * The names in the strings are resolved and the program will fail to compile
+ * with dart2js if they do not name types.
+ *
+ * The argument to [Creates] is a string.  The string is parsed as the names of
+ * one or more types, separated by vertical bars `|`.  There are some special
+ * names:
+ *
+ * * `=List`. This means 'exactly List', which is the JavaScript Array
+ *   implementation of [List] and no other implementation.
+ *
+ * * `=Object`. This means 'exactly Object', which is a plain JavaScript object
+ *   with properties and none of the subtypes of Object.
+ *
+ * Example: we may know that a method always returns a specific implementation:
+ *
+ *     @Creates('_NodeList')
+ *     List<Node> getElementsByTagName(String tag) native;
+ *
+ * Useful trick: A method can be marked as not instantiating any native classes
+ * with the annotation `@Creates('Null')`.  This is useful for fields on native
+ * classes that are used only in Dart code.
+ *
+ *     @Creates('Null')
+ *     var _cachedFoo;
+ */
+class Creates {
+  final String types;
+  const Creates(this.types);
+}
+
+/**
+ * A metadata annotation describing the types returned or yielded by a native
+ * element.
+ *
+ * The annotation is valid on a native method and a field of a native class.
+ *
+ * By default, a native method or field is seen as returning or yielding all
+ * subtypes if the method return type or field type.  This annotation allows a
+ * more precise set of types to be specified.
+ *
+ * See [Creates] for the syntax of the argument.
+ *
+ * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys.
+ *
+ *     @Returns('String|num|=List')
+ *     dynamic key;
+ *
+ *     // Equivalent:
+ *     @Returns('String') @Returns('num') @Returns('=List')
+ *     dynamic key;
+ */
+class Returns {
+  final String types;
+  const Returns(this.types);
+}
+
+/**
+ * A metadata annotation placed on native methods and fields of native classes
+ * to specify the JavaScript name.
+ *
+ * This example declares a Dart field + getter + setter called `$dom_title` that
+ * corresponds to the JavaScript property `title`.
+ *
+ *     class Docmument native "*Foo" {
+ *       @JSName('title')
+ *       String $dom_title;
+ *     }
+ */
+class JSName {
+  final String name;
+  const JSName(this.name);
+}
+
+/**
+ * Represents the type of Null. The compiler treats this specially.
+ * TODO(lrn): Null should be defined in core. It's a class, like int.
+ * It just happens to act differently in assignability tests and,
+ * like int, can't be extended or implemented.
+ */
+class Null {
+  factory Null() {
+    throw new UnsupportedError('new Null()');
+  }
+}
+
+setRuntimeTypeInfo(target, typeInfo) {
+  assert(typeInfo == null || isJsArray(typeInfo));
+  // We have to check for null because factories may return null.
+  if (target != null) JS('var', r'#.$builtinTypeInfo = #', target, typeInfo);
+}
+
+getRuntimeTypeInfo(target) {
+  if (target == null) return null;
+  var res = JS('var', r'#.$builtinTypeInfo', target);
+  // If the object does not have runtime type information, return an
+  // empty literal, to avoid null checks.
+  // TODO(ngeoffray): Make the object a top-level field to avoid
+  // allocating a new object every single time.
+  return (res == null) ? JS('var', '{}') : res;
+}
+
+/**
+ * The following methods are called by the runtime to implement
+ * checked mode and casts. We specialize each primitive type (eg int, bool), and
+ * use the compiler's convention to do is-checks on regular objects.
+ */
+boolConversionCheck(value) {
+  boolTypeCheck(value);
+  assert(value != null);
+  return value;
+}
+
+stringTypeCheck(value) {
+  if (value == null) return value;
+  if (value is String) return value;
+  throw new TypeErrorImplementation(value, 'String');
+}
+
+stringTypeCast(value) {
+  if (value is String || value == null) return value;
+  // TODO(lrn): When reified types are available, pass value.class and String.
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'String');
+}
+
+doubleTypeCheck(value) {
+  if (value == null) return value;
+  if (value is double) return value;
+  throw new TypeErrorImplementation(value, 'double');
+}
+
+doubleTypeCast(value) {
+  if (value is double || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'double');
+}
+
+numTypeCheck(value) {
+  if (value == null) return value;
+  if (value is num) return value;
+  throw new TypeErrorImplementation(value, 'num');
+}
+
+numTypeCast(value) {
+  if (value is num || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'num');
+}
+
+boolTypeCheck(value) {
+  if (value == null) return value;
+  if (value is bool) return value;
+  throw new TypeErrorImplementation(value, 'bool');
+}
+
+boolTypeCast(value) {
+  if (value is bool || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'bool');
+}
+
+functionTypeCheck(value) {
+  if (value == null) return value;
+  if (value is Function) return value;
+  throw new TypeErrorImplementation(value, 'Function');
+}
+
+functionTypeCast(value) {
+  if (value is Function || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'Function');
+}
+
+intTypeCheck(value) {
+  if (value == null) return value;
+  if (value is int) return value;
+  throw new TypeErrorImplementation(value, 'int');
+}
+
+intTypeCast(value) {
+  if (value is int || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'int');
+}
+
+void propertyTypeError(value, property) {
+  // Cuts the property name to the class name.
+  String name = property.substring(3, property.length);
+  throw new TypeErrorImplementation(value, name);
+}
+
+void propertyTypeCastError(value, property) {
+  // Cuts the property name to the class name.
+  String actualType = Primitives.objectTypeName(value);
+  String expectedType = property.substring(3, property.length);
+  throw new CastErrorImplementation(actualType, expectedType);
+}
+
+/**
+ * For types that are not supertypes of native (eg DOM) types,
+ * we emit a simple property check to check that an object implements
+ * that type.
+ */
+propertyTypeCheck(value, property) {
+  if (value == null) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+/**
+ * For types that are not supertypes of native (eg DOM) types,
+ * we emit a simple property check to check that an object implements
+ * that type.
+ */
+propertyTypeCast(value, property) {
+  if (value == null || JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * For types that are supertypes of native (eg DOM) types, we emit a
+ * call because we cannot add a JS property to their prototype at load
+ * time.
+ */
+callTypeCheck(value, property) {
+  if (value == null) return value;
+  if ((identical(JS('String', 'typeof #', value), 'object'))
+      && JS('bool', '#[#]()', value, property)) {
+    return value;
+  }
+  propertyTypeError(value, property);
+}
+
+/**
+ * For types that are supertypes of native (eg DOM) types, we emit a
+ * call because we cannot add a JS property to their prototype at load
+ * time.
+ */
+callTypeCast(value, property) {
+  if (value == null
+      || ((JS('bool', 'typeof # === "object"', value))
+          && JS('bool', '#[#]()', value, property))) {
+    return value;
+  }
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * Specialization of the type check for num and String and their
+ * supertype since [value] can be a JS primitive.
+ */
+numberOrStringSuperTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (value is num) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+numberOrStringSuperTypeCast(value, property) {
+  if (value is String) return value;
+  if (value is num) return value;
+  return propertyTypeCast(value, property);
+}
+
+numberOrStringSuperNativeTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (value is num) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+numberOrStringSuperNativeTypeCast(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (value is num) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * Specialization of the type check for String and its supertype
+ * since [value] can be a JS primitive.
+ */
+stringSuperTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+stringSuperTypeCast(value, property) {
+  if (value is String) return value;
+  return propertyTypeCast(value, property);
+}
+
+stringSuperNativeTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is String) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+stringSuperNativeTypeCast(value, property) {
+  if (value is String || value == null) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+/**
+ * Specialization of the type check for List and its supertypes,
+ * since [value] can be a JS array.
+ */
+listTypeCheck(value) {
+  if (value == null) return value;
+  if (value is List) return value;
+  throw new TypeErrorImplementation(value, 'List');
+}
+
+listTypeCast(value) {
+  if (value is List || value == null) return value;
+  throw new CastErrorImplementation(
+      Primitives.objectTypeName(value), 'List');
+}
+
+listSuperTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is List) return value;
+  if (JS('bool', '!!#[#]', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+listSuperTypeCast(value, property) {
+  if (value is List) return value;
+  return propertyTypeCast(value, property);
+}
+
+listSuperNativeTypeCheck(value, property) {
+  if (value == null) return value;
+  if (value is List) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeError(value, property);
+}
+
+listSuperNativeTypeCast(value, property) {
+  if (value is List || value == null) return value;
+  if (JS('bool', '#[#]()', value, property)) return value;
+  propertyTypeCastError(value, property);
+}
+
+voidTypeCheck(value) {
+  if (value == null) return value;
+  throw new TypeErrorImplementation(value, 'void');
+}
+
+malformedTypeCheck(value, type, reasons) {
+  if (value == null) return value;
+  throwMalformedSubtypeError(value, type, reasons);
+}
+
+/**
+ * Special interface recognized by the compiler and implemented by DOM
+ * objects that support integer indexing. This interface is not
+ * visible to anyone, and is only injected into special libraries.
+ */
+abstract class JavaScriptIndexingBehavior {
+}
+
+// TODO(lrn): These exceptions should be implemented in core.
+// When they are, remove the 'Implementation' here.
+
+/** Thrown by type assertions that fail. */
+class TypeErrorImplementation implements TypeError {
+  final String message;
+
+  /**
+   * Normal type error caused by a failed subtype test.
+   */
+  TypeErrorImplementation(Object value, String type)
+      : message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
+                  "of type '$type'";
+
+  /**
+   * Type error caused by a subtype test on a malformed type.
+   */
+  TypeErrorImplementation.malformedSubtype(Object value,
+                                           String type, String reasons)
+      : message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
+                  "of type '$type' because '$type' is malformed: $reasons.";
+
+  String toString() => message;
+}
+
+/** Thrown by the 'as' operator if the cast isn't valid. */
+class CastErrorImplementation implements CastError {
+  // TODO(lrn): Rename to CastError (and move implementation into core).
+  // TODO(lrn): Change actualType and expectedType to "Type" when reified
+  // types are available.
+  final Object actualType;
+  final Object expectedType;
+
+  CastErrorImplementation(this.actualType, this.expectedType);
+
+  String toString() {
+    return "CastError: Casting value of type $actualType to"
+           " incompatible type $expectedType";
+  }
+}
+
+class FallThroughErrorImplementation implements FallThroughError {
+  const FallThroughErrorImplementation();
+  String toString() => "Switch case fall-through.";
+}
+
+/**
+ * Helper function for implementing asserts. The compiler treats this specially.
+ */
+void assertHelper(condition) {
+  if (condition is Function) condition = condition();
+  if (condition is !bool) {
+    throw new TypeErrorImplementation(condition, 'bool');
+  }
+  // Compare to true to avoid boolean conversion check in checked
+  // mode.
+  if (!identical(condition, true)) throw new AssertionError();
+}
+
+/**
+ * Called by generated code when a method that must be statically
+ * resolved cannot be found.
+ */
+void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) {
+  throw new NoSuchMethodError(obj, name, arguments, const {},
+                              expectedArgumentNames);
+}
+
+/**
+ * Called by generated code when a static field's initializer references the
+ * field that is currently being initialized.
+ */
+void throwCyclicInit(String staticName) {
+  throw new RuntimeError("Cyclic initialization for static $staticName");
+}
+
+class TypeImpl implements Type {
+  final String typeName;
+  TypeImpl(this.typeName);
+  toString() => typeName;
+  int get hashCode => typeName.hashCode;
+  bool operator ==(other) {
+    if (other is !TypeImpl) return false;
+    return typeName == other.typeName;
+  }
+}
+
+String getClassName(var object) {
+  return JS('String', r'#.constructor.builtin$cls', object);
+}
+
+String getTypeArgumentAsString(List runtimeType) {
+  String className = getConstructorName(runtimeType[0]);
+  if (runtimeType.length == 1) return className;
+  return '$className<${joinArguments(runtimeType, 1)}>';
+}
+
+String getConstructorName(type) => JS('String', r'#.builtin$cls', type);
+
+String runtimeTypeToString(type) {
+  if (type == null) {
+    return 'dynamic';
+  } else if (isJsArray(type)) {
+    // A list representing a type with arguments.
+    return getTypeArgumentAsString(type);
+  } else {
+    // A reference to the constructor.
+    return getConstructorName(type);
+  }
+}
+
+String joinArguments(var types, int startIndex) {
+  bool firstArgument = true;
+  StringBuffer buffer = new StringBuffer();
+  for (int index = startIndex; index < types.length; index++) {
+    if (firstArgument) {
+      firstArgument = false;
+    } else {
+      buffer. add(', ');
+    }
+    var argument = types[index];
+    buffer.add(runtimeTypeToString(argument));
+  }
+  return buffer.toString();
+}
+
+String getRuntimeTypeString(var object) {
+  String className = isJsArray(object) ? 'List' : getClassName(object);
+  var typeInfo = JS('var', r'#.$builtinTypeInfo', object);
+  if (typeInfo == null) return className;
+  return "$className<${joinArguments(typeInfo, 0)}>";
+}
+
+/**
+ * Check whether the type represented by [s] is a subtype of the type
+ * represented by [t].
+ *
+ * Type representations can be:
+ *  1) a JavaScript constructor for a class C: the represented type is the raw
+ *     type C.
+ *  2) a JavaScript object: this represents a class for which there is no
+ *     JavaScript constructor, because it is only used in type arguments or it
+ *     is native. The represented type is the raw type of this class.
+ *  3) a JavaScript array: the first entry is of type 1 or 2 and identifies the
+ *     class of the type and the rest of the array are the type arguments.
+ *  4) [:null:]: the dynamic type.
+ */
+bool isSubtype(var s, var t) {
+  // If either type is dynamic, [s] is a subtype of [t].
+  if (JS('bool', '# == null', s) || JS('bool', '# == null', t)) return true;
+  // Subtyping is reflexive.
+  if (JS('bool', '# === #', s, t)) return true;
+  // Get the object describing the class and check for the subtyping flag
+  // constructed from the type of [t].
+  var typeOfS = isJsArray(s) ? s[0] : s;
+  var typeOfT = isJsArray(t) ? t[0] : t;
+  var test = '${JS_OPERATOR_IS_PREFIX()}${runtimeTypeToString(typeOfT)}';
+  if (JS('var', r'#[#]', typeOfS, test) == null) return false;
+  // The class of [s] is a subclass of the class of [t]. If either of the types
+  // is raw, [s] is a subtype of [t].
+  if (!isJsArray(s) || !isJsArray(t)) return true;
+  // Recursively check the type arguments.
+  int len = s.length;
+  if (len != t.length) return false;
+  for (int i = 1; i < len; i++) {
+    if (!isSubtype(s[i], t[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+createRuntimeType(String name) => new TypeImpl(name);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart
new file mode 100644
index 0000000..fa5677f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart
@@ -0,0 +1,272 @@
+// 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.
+
+part of _interceptors;
+
+/**
+ * The super interceptor class for [JSInt] and [JSDouble]. The compiler
+ * recognizes this class as an interceptor, and changes references to
+ * [:this:] to actually use the receiver of the method, which is
+ * generated as an extra argument added to each member.
+ */
+class JSNumber implements num {
+  const JSNumber();
+
+  int compareTo(num b) {
+    if (b is! num) throw new ArgumentError(b);
+    if (this < b) {
+      return -1;
+    } else if (this > b) {
+      return 1;
+    } else if (this == b) {
+      if (this == 0) {
+        bool bIsNegative = b.isNegative;
+        if (isNegative == bIsNegative) return 0;
+        if (isNegative) return -1;
+        return 1;
+      }
+      return 0;
+    } else if (isNaN) {
+      if (b.isNaN) {
+        return 0;
+      }
+      return 1;
+    } else {
+      return -1;
+    }
+  }
+
+  bool get isNegative => (this == 0) ? (1 / this) < 0 : this < 0;
+
+  bool get isNaN => JS('bool', r'isNaN(#)', this);
+
+  num remainder(num b) {
+    checkNull(b); // TODO(ngeoffray): This is not specified but co19 tests it.
+    if (b is! num) throw new ArgumentError(b);
+    return JS('num', r'# % #', this, b);
+  }
+
+  num abs() => JS('num', r'Math.abs(#)', this);
+
+  int toInt() {
+    if (isNaN) throw new UnsupportedError('NaN');
+    if (isInfinite) throw new UnsupportedError('Infinity');
+    num truncated = truncate();
+    return JS('bool', r'# == -0.0', truncated) ? 0 : truncated;
+  }
+
+  num ceil() => JS('num', r'Math.ceil(#)', this);
+
+  num floor() => JS('num', r'Math.floor(#)', this);
+
+  bool get isInfinite {
+    return JS('bool', r'# == Infinity', this)
+      || JS('bool', r'# == -Infinity', this);
+  }
+
+  num round() {
+    if (this < 0) {
+      return JS('num', r'-Math.round(-#)', this);
+    } else {
+      return JS('num', r'Math.round(#)', this);
+    }
+  }
+
+  num clamp(lowerLimit, upperLimit) {
+    if (lowerLimit is! num) throw new ArgumentError(lowerLimit);
+    if (upperLimit is! num) throw new ArgumentError(upperLimit);
+    if (lowerLimit.compareTo(upperLimit) > 0) {
+      throw new ArgumentError(lowerLimit);
+    }
+    if (this.compareTo(lowerLimit) < 0) return lowerLimit;
+    if (this.compareTo(upperLimit) > 0) return upperLimit;
+    return this;
+  }
+
+  double toDouble() => this;
+
+  num truncate() => this < 0 ? ceil() : floor();
+
+  String toStringAsFixed(int fractionDigits) {
+    checkNum(fractionDigits);
+    // TODO(floitsch): fractionDigits must be an integer.
+    if (fractionDigits < 0 || fractionDigits > 20) {
+      throw new RangeError(fractionDigits);
+    }
+    String result = JS('String', r'#.toFixed(#)', this, fractionDigits);
+    if (this == 0 && isNegative) return "-$result";
+    return result;
+  }
+
+  String toStringAsExponential([int fractionDigits]) {
+    String result;
+    if (fractionDigits != null) {
+      // TODO(floitsch): fractionDigits must be an integer.
+      checkNum(fractionDigits);
+      if (fractionDigits < 0 || fractionDigits > 20) {
+        throw new RangeError(fractionDigits);
+      }
+      result = JS('String', r'#.toExponential(#)', this, fractionDigits);
+    } else {
+      result = JS('String', r'#.toExponential()', this);
+    }
+    if (this == 0 && isNegative) return "-$result";
+    return result;
+  }
+
+  String toStringAsPrecision(int precision) {
+    // TODO(floitsch): precision must be an integer.
+    checkNum(precision);
+    if (precision < 1 || precision > 21) {
+      throw new RangeError(precision);
+    }
+    String result = JS('String', r'#.toPrecision(#)',
+                       this, precision);
+    if (this == 0 && isNegative) return "-$result";
+    return result;
+  }
+
+  String toRadixString(int radix) {
+    checkNum(radix);
+    if (radix < 2 || radix > 36) throw new RangeError(radix);
+    return JS('String', r'#.toString(#)', this, radix);
+  }
+
+  // Note: if you change this, also change the function [S].
+  String toString() {
+    if (this == 0 && JS('bool', '(1 / #) < 0', this)) {
+      return '-0.0';
+    } else {
+      return JS('String', r'String(#)', this);
+    }
+  }
+
+  int get hashCode => JS('int', '# & 0x1FFFFFFF', this);
+
+  num operator -() => JS('num', r'-#', this);
+
+  num operator +(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# + #', this, other);
+  }
+
+  num operator -(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# - #', this, other);
+  }
+
+  num operator /(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# / #', this, other);
+  }
+
+  num operator *(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# * #', this, other);
+  }
+
+  num operator %(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    // Euclidean Modulo.
+    num result = JS('num', r'# % #', this, other);
+    if (result == 0) return 0;  // Make sure we don't return -0.0.
+    if (result > 0) return result;
+    if (JS('num', '#', other) < 0) {
+      return result - JS('num', '#', other);
+    } else {
+      return result + JS('num', '#', other);
+    }
+  }
+
+  num operator ~/(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return (JS('num', r'# / #', this, other)).truncate();
+  }
+
+  // TODO(ngeoffray): Move the bit operations below to [JSInt] and
+  // make them take an int. Because this will make operations slower,
+  // we define these methods on number for now but we need to decide
+  // the grain at which we do the type checks.
+
+  num operator <<(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    if (JS('num', '#', other) < 0) throw new ArgumentError(other);
+    // JavaScript only looks at the last 5 bits of the shift-amount. Shifting
+    // by 33 is hence equivalent to a shift by 1.
+    if (JS('bool', r'# > 31', other)) return 0;
+    return JS('num', r'(# << #) >>> 0', this, other);
+  }
+
+  num operator >>(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    if (JS('num', '#', other) < 0) throw new ArgumentError(other);
+    if (JS('num', '#', this) > 0) {
+      // JavaScript only looks at the last 5 bits of the shift-amount. In JS
+      // shifting by 33 is hence equivalent to a shift by 1. Shortcut the
+      // computation when that happens.
+      if (JS('bool', r'# > 31', other)) return 0;
+      // Given that 'a' is positive we must not use '>>'. Otherwise a number
+      // that has the 31st bit set would be treated as negative and shift in
+      // ones.
+      return JS('num', r'# >>> #', this, other);
+    }
+    // For negative numbers we just clamp the shift-by amount. 'a' could be
+    // negative but not have its 31st bit set. The ">>" would then shift in
+    // 0s instead of 1s. Therefore we cannot simply return 0xFFFFFFFF.
+    if (JS('num', '#', other) > 31) other = 31;
+    return JS('num', r'(# >> #) >>> 0', this, other);
+  }
+
+  num operator &(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', r'(# & #) >>> 0', this, other);    
+  }
+
+  num operator |(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', r'(# | #) >>> 0', this, other);    
+  }
+
+  num operator ^(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', r'(# ^ #) >>> 0', this, other);    
+  }
+
+  bool operator <(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# < #', this, other);
+  }
+
+  bool operator >(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# > #', this, other);
+  }
+
+  bool operator <=(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# <= #', this, other);
+  }
+
+  bool operator >=(num other) {
+    if (other is !num) throw new ArgumentError(other);
+    return JS('num', '# >= #', this, other);
+  }
+}
+
+class JSInt extends JSNumber implements int {
+  const JSInt();
+
+  bool get isEven => (this & 1) == 0;
+
+  bool get isOdd => (this & 1) == 1;
+
+  Type get runtimeType => int;
+
+  int operator ~() => JS('int', r'(~#) >>> 0', this);
+}
+
+class JSDouble extends JSNumber implements double {
+  const JSDouble();
+  Type get runtimeType => double;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart
new file mode 100644
index 0000000..381a690
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart
@@ -0,0 +1,229 @@
+// 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.
+
+part of _interceptors;
+
+/**
+ * The interceptor class for [String]. The compiler recognizes this
+ * class as an interceptor, and changes references to [:this:] to
+ * actually use the receiver of the method, which is generated as an extra
+ * argument added to each member.
+ */
+class JSString implements String {
+  const JSString();
+
+  int charCodeAt(index) => codeUnitAt(index);
+
+  int codeUnitAt(int index) {
+    if (index is !num) throw new ArgumentError(index);
+    if (index < 0) throw new RangeError.value(index);
+    if (index >= length) throw new RangeError.value(index);
+    return JS('int', r'#.charCodeAt(#)', this, index);
+  }
+
+  Iterable<Match> allMatches(String str) {
+    checkString(str);
+    return allMatchesInStringUnchecked(this, str);
+  }
+
+  String concat(String other) {
+    if (other is !String) throw new ArgumentError(other);
+    return JS('String', r'# + #', this, other);
+  }
+
+  bool endsWith(String other) {
+    checkString(other);
+    int otherLength = other.length;
+    if (otherLength > length) return false;
+    return other == substring(length - otherLength);
+  }
+
+  String replaceAll(Pattern from, String to) {
+    checkString(to);
+    return stringReplaceAllUnchecked(this, from, to);
+  }
+
+  String replaceAllMapped(Pattern from, String convert(Match match)) {
+    return this.splitMapJoin(from, onMatch: convert);
+  }
+
+  String splitMapJoin(Pattern from,
+                      {String onMatch(Match match),
+                       String onNonMatch(String nonMatch)}) {
+    return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch);
+  }
+
+  String replaceFirst(Pattern from, String to) {
+    checkString(to);
+    return stringReplaceFirstUnchecked(this, from, to);
+  }
+
+  List<String> split(Pattern pattern) {
+    checkNull(pattern);
+    if (pattern is String) {
+      return JS('=List', r'#.split(#)', this, pattern);
+    } else if (pattern is JSSyntaxRegExp) {
+      var re = regExpGetNative(pattern);
+      return JS('=List', r'#.split(#)', this, re);
+    } else {
+      throw "String.split(Pattern) UNIMPLEMENTED";
+    }
+  }
+
+  List<String> splitChars() {
+    return JS('=List', r'#.split("")', this);
+  }
+
+  bool startsWith(String other) {
+    checkString(other);
+    int otherLength = other.length;
+    if (otherLength > length) return false;
+    return JS('bool', r'# == #', other,
+              JS('String', r'#.substring(0, #)', this, otherLength));
+  }
+
+  String substring(int startIndex, [int endIndex]) {
+    checkNum(startIndex);
+    if (endIndex == null) endIndex = length;
+    checkNum(endIndex);
+    if (startIndex < 0 ) throw new RangeError.value(startIndex);
+    if (startIndex > endIndex) throw new RangeError.value(startIndex);
+    if (endIndex > length) throw new RangeError.value(endIndex);
+    return JS('String', r'#.substring(#, #)', this, startIndex, endIndex);
+  }
+
+  String slice([int startIndex, int endIndex]) {
+    int start, end;
+    if (startIndex == null) {
+      start = 0;
+    } else if (startIndex is! int) {
+      throw new ArgumentError("startIndex is not int");
+    } else if (startIndex >= 0) {
+      start = startIndex;
+    } else {
+      start = this.length + startIndex;
+    }
+    if (start < 0 || start > this.length) {
+      throw new RangeError(
+          "startIndex out of range: $startIndex (length: $length)");
+    }
+    if (endIndex == null) {
+      end = this.length;
+    } else if (endIndex is! int) {
+      throw new ArgumentError("endIndex is not int");
+    } else if (endIndex >= 0) {
+      end = endIndex;
+    } else {
+      end = this.length + endIndex;
+    }
+    if (end < 0 || end > this.length) {
+      throw new RangeError(
+          "endIndex out of range: $endIndex (length: $length)");
+    }
+    if (end < start) {
+      throw new ArgumentError(
+          "End before start: $endIndex < $startIndex (length: $length)");
+    }
+    return JS('String', '#.substring(#, #)', this, start, end);
+  }
+
+
+  String toLowerCase() {
+    return JS('String', r'#.toLowerCase()', this);
+  }
+
+  String toUpperCase() {
+    return JS('String', r'#.toUpperCase()', this);
+  }
+
+  String trim() {
+    return JS('String', r'#.trim()', this);
+  }
+
+  List<int> get charCodes  {
+    List<int> result = new List<int>.fixedLength(length);
+    for (int i = 0; i < length; i++) {
+      result[i] = JS('int', '#.charCodeAt(#)', this, i);
+    }
+    return result;
+  }
+
+  Iterable<int> get codeUnits {
+    throw new UnimplementedError("String.codeUnits");
+  }
+
+  Iterable<int> get runes {
+    throw new UnimplementedError("String.runes");
+  }
+
+  int indexOf(String other, [int start = 0]) {
+    checkNull(other);
+    if (start is !int) throw new ArgumentError(start);
+    if (other is !String) throw new ArgumentError(other);
+    if (start < 0) return -1;
+    return JS('int', r'#.indexOf(#, #)', this, other, start);
+  }
+
+  int lastIndexOf(String other, [int start]) {
+    checkNull(other);
+    if (other is !String) throw new ArgumentError(other);
+    if (start != null) {
+      if (start is !num) throw new ArgumentError(start);
+      if (start < 0) return -1;
+      if (start >= length) {
+        if (other == "") return length;
+        start = length - 1;
+      }
+    } else {
+      start = length - 1;
+    }
+    return stringLastIndexOfUnchecked(this, other, start);
+  }
+
+  bool contains(String other, [int startIndex = 0]) {
+    checkNull(other);
+    return stringContainsUnchecked(this, other, startIndex);
+  }
+
+  bool get isEmpty => length == 0;
+
+  int compareTo(String other) {
+    if (other is !String) throw new ArgumentError(other);
+    return this == other ? 0
+      : JS('bool', r'# < #', this, other) ? -1 : 1;
+  }
+
+  // Note: if you change this, also change the function [S].
+  String toString() => this;
+
+  /**
+   * This is the [Jenkins hash function][1] but using masking to keep
+   * values in SMI range.
+   *
+   * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function
+   */
+  int get hashCode {
+    // TODO(ahe): This method shouldn't have to use JS. Update when our
+    // optimizations are smarter.
+    int hash = 0;
+    for (int i = 0; i < length; i++) {
+      hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i));
+      hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+      hash = JS('int', '# ^ (# >> 6)', hash, hash);
+    }
+    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) <<  3));
+    hash = JS('int', '# ^ (# >> 11)', hash, hash);
+    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+  }
+
+  Type get runtimeType => String;
+
+  int get length => JS('int', r'#.length', this);
+
+  String operator [](int index) {
+    if (index is !int) throw new ArgumentError(index);
+    if (index >= length || index < 0) throw new RangeError.value(index);
+    return JS('String', '#[#]', this, index);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart
new file mode 100644
index 0000000..3bccb2c
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart
@@ -0,0 +1,68 @@
+// 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.
+
+// Patch file for dart:math library.
+import 'dart:_foreign_helper' show JS;
+
+patch double sqrt(num x)
+  => JS('double', r'Math.sqrt(#)', checkNum(x));
+
+patch double sin(num x)
+  => JS('double', r'Math.sin(#)', checkNum(x));
+
+patch double cos(num x)
+  => JS('double', r'Math.cos(#)', checkNum(x));
+
+patch double tan(num x)
+  => JS('double', r'Math.tan(#)', checkNum(x));
+
+patch double acos(num x)
+  => JS('double', r'Math.acos(#)', checkNum(x));
+
+patch double asin(num x)
+  => JS('double', r'Math.asin(#)', checkNum(x));
+
+patch double atan(num x)
+  => JS('double', r'Math.atan(#)', checkNum(x));
+
+patch double atan2(num a, num b)
+  => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b));
+
+patch double exp(num x)
+  => JS('double', r'Math.exp(#)', checkNum(x));
+
+patch double log(num x)
+  => JS('double', r'Math.log(#)', checkNum(x));
+
+patch num pow(num x, num exponent) {
+  checkNum(x);
+  checkNum(exponent);
+  return JS('num', r'Math.pow(#, #)', x, exponent);
+}
+
+patch class Random {
+  patch factory Random([int seed]) => const _Random();
+}
+
+class _Random implements Random {
+  // The Dart2JS implementation of Random doesn't use a seed.
+  const _Random();
+
+  int nextInt(int max) {
+    if (max < 0) throw new ArgumentError("negative max: $max");
+    if (max > 0xFFFFFFFF) max = 0xFFFFFFFF;
+    return JS("int", "(Math.random() * #) >>> 0", max);
+  }
+
+  /**
+   * Generates a positive random floating point value uniformly distributed on
+   * the range from 0.0, inclusive, to 1.0, exclusive.
+   */
+  double nextDouble() => JS("double", "Math.random()");
+
+  /**
+   * Generates a random boolean value.
+   */
+  bool nextBool() => JS("bool", "Math.random() < 0.5");
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart
new file mode 100644
index 0000000..1236507
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart
@@ -0,0 +1,114 @@
+// 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.
+
+// Patch library for dart:mirrors.
+
+import 'dart:_foreign_helper' show JS;
+
+// Yeah, seriously: mirrors in dart2js are experimental...
+const String _MIRROR_OPT_IN_MESSAGE = """
+
+This program is using an experimental feature called \"mirrors\".  As
+currently implemented, mirrors do not work with minification, and will
+cause spurious errors depending on how code was optimized.
+
+The authors of this program are aware of these problems and have
+decided the thrill of using an experimental feature is outweighing the
+risks.  Furthermore, the authors of this program understand that
+long-term, to fix the problems mentioned above, mirrors may have
+negative impact on size and performance of Dart programs compiled to
+JavaScript.
+""";
+
+bool _mirrorsEnabled = false;
+
+/**
+ * Stub class for the mirror system.
+ */
+patch MirrorSystem currentMirrorSystem() {
+  _ensureEnabled();
+  throw new UnsupportedError("MirrorSystem not implemented");
+}
+
+patch Future<MirrorSystem> mirrorSystemOf(SendPort port) {
+  _ensureEnabled();
+  throw new UnsupportedError("MirrorSystem not implemented");
+}
+
+patch InstanceMirror reflect(Object reflectee) {
+  if (!_mirrorsEnabled && (_MIRROR_OPT_IN_MESSAGE == reflectee)) {
+    // Turn on mirrors and warn that it is an experimental feature.
+    _mirrorsEnabled = true;
+    print(reflectee);
+  }
+  _ensureEnabled();
+  return new _InstanceMirror(reflectee);
+}
+
+class _InstanceMirror extends InstanceMirror {
+  static final Expando<ClassMirror> classMirrors = new Expando<ClassMirror>();
+
+  final reflectee;
+
+  _InstanceMirror(this.reflectee) {
+    _ensureEnabled();
+  }
+
+  bool get hasReflectee => true;
+
+  ClassMirror get type {
+    String className = Primitives.objectTypeName(reflectee);
+    var constructor = Primitives.getConstructor(className);
+    var mirror = classMirrors[constructor];
+    if (mirror == null) {
+      mirror = new _ClassMirror(className, constructor);
+      classMirrors[constructor] = mirror;
+    }
+    return mirror;
+  }
+
+  Future<InstanceMirror> invoke(String memberName,
+                                List<Object> positionalArguments,
+                                [Map<String,Object> namedArguments]) {
+    if (namedArguments != null && !namedArguments.isEmpty) {
+      throw new UnsupportedError('Named arguments are not implemented');
+    }
+    // Copy the list to ensure that it can safely be passed to
+    // JavaScript.
+    var jsList = new List.from(positionalArguments);
+    var mangledName = '${memberName}\$${positionalArguments.length}';
+    var method = JS('var', '#[#]', reflectee, mangledName);
+    var completer = new Completer<InstanceMirror>();
+    // TODO(ahe): [Completer] or [Future] should have API to create a
+    // delayed action.  Simulating with a [Timer].
+    new Timer(0, (timer) {
+      if (JS('String', 'typeof #', method) == 'function') {
+        var result =
+            JS('var', '#.apply(#, #)', method, reflectee, jsList);
+        completer.complete(new _InstanceMirror(result));
+      } else {
+        completer.completeError('not a method $memberName');
+      }
+    });
+    return completer.future;
+  }
+
+  String toString() => 'InstanceMirror($reflectee)';
+}
+
+class _ClassMirror extends ClassMirror {
+  final String _name;
+  final _jsConstructor;
+
+  _ClassMirror(this._name, this._jsConstructor) {
+    _ensureEnabled();
+  }
+
+  String toString() => 'ClassMirror($_name)';
+}
+
+_ensureEnabled() {
+  if (_mirrorsEnabled) return;
+  throw new UnsupportedError('dart:mirrors is an experimental feature');
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart
new file mode 100644
index 0000000..c75e554
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart
@@ -0,0 +1,431 @@
+// 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.
+
+part of _js_helper;
+
+String typeNameInChrome(obj) {
+  String name = JS('String', "#.constructor.name", obj);
+  return typeNameInWebKitCommon(name);
+}
+
+String typeNameInSafari(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  // Safari is very similar to Chrome.
+  return typeNameInWebKitCommon(name);
+}
+
+String typeNameInWebKitCommon(tag) {
+  String name = JS('String', '#', tag);
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'CanvasPixelArray') return 'Uint8ClampedArray';
+  if (name == 'WebKitMutationObserver') return 'MutationObserver';
+  if (name == 'AudioChannelMerger') return 'ChannelMergerNode';
+  if (name == 'AudioChannelSplitter') return 'ChannelSplitterNode';
+  if (name == 'AudioGainNode') return 'GainNode';
+  if (name == 'AudioPannerNode') return 'PannerNode';
+  if (name == 'JavaScriptAudioNode') return 'ScriptProcessorNode';
+  if (name == 'Oscillator') return 'OscillatorNode';
+  if (name == 'RealtimeAnalyserNode') return 'AnalyserNode';
+  if (name == 'IDBVersionChangeRequest') return 'IDBOpenDBRequest';
+  return name;
+}
+
+String typeNameInOpera(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'ApplicationCache') return 'DOMApplicationCache';
+  return name;
+}
+
+String typeNameInFirefox(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'CSS2Properties') return 'CSSStyleDeclaration';
+  if (name == 'DataTransfer') return 'Clipboard';
+  if (name == 'DragEvent') return 'MouseEvent';
+  if (name == 'GeoGeolocation') return 'Geolocation';
+  if (name == 'MouseScrollEvent') return 'WheelEvent';
+  if (name == 'OfflineResourceList') return 'DOMApplicationCache';
+  if (name == 'WorkerMessageEvent') return 'MessageEvent';
+  if (name == 'XMLDocument') return 'Document';
+  return name;
+}
+
+String typeNameInIE(obj) {
+  String name = JS('String', '#', constructorNameFallback(obj));
+  if (name == 'Window') return 'DOMWindow';
+  if (name == 'Document') {
+    // IE calls both HTML and XML documents 'Document', so we check for the
+    // xmlVersion property, which is the empty string on HTML documents.
+    if (JS('bool', '!!#.xmlVersion', obj)) return 'Document';
+    return 'HTMLDocument';
+  }
+  if (name == 'ApplicationCache') return 'DOMApplicationCache';
+  if (name == 'CanvasPixelArray') return 'Uint8ClampedArray';
+  if (name == 'DataTransfer') return 'Clipboard';
+  if (name == 'DragEvent') return 'MouseEvent';
+  if (name == 'HTMLDDElement') return 'HTMLElement';
+  if (name == 'HTMLDTElement') return 'HTMLElement';
+  if (name == 'HTMLTableDataCellElement') return 'HTMLTableCellElement';
+  if (name == 'HTMLTableHeaderCellElement') return 'HTMLTableCellElement';
+  if (name == 'HTMLPhraseElement') return 'HTMLElement';
+  if (name == 'MSStyleCSSProperties') return 'CSSStyleDeclaration';
+  if (name == 'MouseWheelEvent') return 'WheelEvent';
+  if (name == 'Position') return 'Geoposition';
+
+  // Patches for types which report themselves as Objects.
+  if (name == 'Object') {
+    if (JS('bool', 'window.DataView && (# instanceof window.DataView)', obj)) {
+      return 'DataView';
+    }
+  }
+  return name;
+}
+
+String constructorNameFallback(object) {
+  if (object == null) return 'Null';
+  var constructor = JS('var', "#.constructor", object);
+  if (identical(JS('String', "typeof(#)", constructor), 'function')) {
+    // The constructor isn't null or undefined at this point. Try
+    // to grab hold of its name.
+    var name = JS('var', '#.name', constructor);
+    // If the name is a non-empty string, we use that as the type
+    // name of this object. On Firefox, we often get 'Object' as
+    // the constructor name even for more specialized objects so
+    // we have to fall through to the toString() based implementation
+    // below in that case.
+    if (name is String
+        && !identical(name, '')
+        && !identical(name, 'Object')
+        && !identical(name, 'Function.prototype')) {  // Can happen in Opera.
+      return name;
+    }
+  }
+  String string = JS('String', 'Object.prototype.toString.call(#)', object);
+  return JS('String', '#.substring(8, # - 1)', string, string.length);
+}
+
+/**
+ * If a lookup on an object [object] that has [tag] fails, this function is
+ * called to provide an alternate tag.  This allows us to fail gracefully if we
+ * can make a good guess, for example, when browsers add novel kinds of
+ * HTMLElement that we have never heard of.
+ */
+String alternateTag(object, String tag) {
+  // Does it smell like some kind of HTML element?
+  if (JS('bool', r'!!/^HTML[A-Z].*Element$/.test(#)', tag)) {
+    // Check that it is not a simple JavaScript object.
+    String string = JS('String', 'Object.prototype.toString.call(#)', object);
+    if (string == '[object Object]') return null;
+    return 'HTMLElement';
+  }
+  return null;
+}
+
+// TODO(ngeoffray): stop using this method once our optimizers can
+// change str1.contains(str2) into str1.indexOf(str2) != -1.
+bool contains(String userAgent, String name) {
+  return JS('int', '#.indexOf(#)', userAgent, name) != -1;
+}
+
+int arrayLength(List array) {
+  return JS('int', '#.length', array);
+}
+
+arrayGet(List array, int index) {
+  return JS('var', '#[#]', array, index);
+}
+
+void arraySet(List array, int index, var value) {
+  JS('var', '#[#] = #', array, index, value);
+}
+
+propertyGet(var object, String property) {
+  return JS('var', '#[#]', object, property);
+}
+
+bool callHasOwnProperty(var function, var object, String property) {
+  return JS('bool', '#.call(#, #)', function, object, property);
+}
+
+void propertySet(var object, String property, var value) {
+  JS('var', '#[#] = #', object, property, value);
+}
+
+getPropertyFromPrototype(var object, String name) {
+  return JS('var', 'Object.getPrototypeOf(#)[#]', object, name);
+}
+
+newJsObject() {
+  return JS('var', '{}');
+}
+
+/**
+ * Returns the function to use to get the type name of an object.
+ */
+Function getFunctionForTypeNameOf() {
+  // If we're not in the browser, we're almost certainly running on v8.
+  if (!identical(JS('String', 'typeof(navigator)'), 'object')) return typeNameInChrome;
+
+  String userAgent = JS('String', "navigator.userAgent");
+  if (contains(userAgent, 'Chrome') || contains(userAgent, 'DumpRenderTree')) {
+    return typeNameInChrome;
+  } else if (contains(userAgent, 'Firefox')) {
+    return typeNameInFirefox;
+  } else if (contains(userAgent, 'MSIE')) {
+    return typeNameInIE;
+  } else if (contains(userAgent, 'Opera')) {
+    return typeNameInOpera;
+  } else if (contains(userAgent, 'AppleWebKit')) {
+    // Chrome matches 'AppleWebKit' too, but we test for Chrome first, so this
+    // is not a problem.
+    // Note: Just testing for "Safari" doesn't work when the page is embedded
+    // in a UIWebView on iOS 6.
+    return typeNameInSafari;
+  } else {
+    return constructorNameFallback;
+  }
+}
+
+
+/**
+ * Cached value for the function to use to get the type name of an
+ * object.
+ */
+Function _getTypeNameOf;
+
+/**
+ * Returns the type name of [obj].
+ */
+String getTypeNameOf(var obj) {
+  if (_getTypeNameOf == null) _getTypeNameOf = getFunctionForTypeNameOf();
+  return _getTypeNameOf(obj);
+}
+
+String toStringForNativeObject(var obj) {
+  String name = JS('String', '#', getTypeNameOf(obj));
+  return 'Instance of $name';
+}
+
+int hashCodeForNativeObject(object) => Primitives.objectHashCode(object);
+
+/**
+ * Sets a JavaScript property on an object.
+ */
+void defineProperty(var obj, String property, var value) {
+  JS('void',
+      'Object.defineProperty(#, #, '
+          '{value: #, enumerable: false, writable: true, configurable: true})',
+      obj,
+      property,
+      value);
+}
+
+/**
+ * This method looks up the type name of [obj] in [methods]. [methods]
+ * is a Javascript object. If it cannot find it, it looks into the
+ * [_dynamicMetadata] array. If the method can still not be found, it
+ * creates a method that will throw a [NoSuchMethodError].
+ *
+ * Once it has a method, the prototype of [obj] is patched with that
+ * method, on the property [name]. The method is then invoked.
+ *
+ * This method returns the result of invoking the found method.
+ */
+dynamicBind(var obj,
+            String name,
+            var methods,
+            List arguments) {
+  // The tag is related to the class name.  E.g. the dart:html class
+  // '_ButtonElement' has the tag 'HTMLButtonElement'.  TODO(erikcorry): rename
+  // getTypeNameOf to getTypeTag.
+  String tag = getTypeNameOf(obj);
+  var hasOwnPropertyFunction = JS('var', 'Object.prototype.hasOwnProperty');
+
+  var method = dynamicBindLookup(hasOwnPropertyFunction, tag, methods);
+  if (method == null) {
+    String secondTag = alternateTag(obj, tag);
+    if (secondTag != null) {
+      method = dynamicBindLookup(hasOwnPropertyFunction, secondTag, methods);
+    }
+  }
+
+  // If we didn't find the method then look up in the Dart Object class, using
+  // getTypeNameOf in case the minifier has renamed Object.
+  if (method == null) {
+    String nameOfObjectClass = getTypeNameOf(const Object());
+    method =
+        lookupDynamicClass(hasOwnPropertyFunction, methods, nameOfObjectClass);
+  }
+
+  var proto = JS('var', 'Object.getPrototypeOf(#)', obj);
+  if (method == null) {
+    // If the method cannot be found, we use a trampoline method that
+    // will throw a [NoSuchMethodError] if the object is of the
+    // exact prototype, or will call [dynamicBind] again if the object
+    // is a subclass.
+    method = JS('var',
+        'function () {'
+          'if (Object.getPrototypeOf(this) === #) {'
+            'throw new TypeError(# + " is not a function");'
+          '} else {'
+            'return Object.prototype[#].apply(this, arguments);'
+          '}'
+        '}',
+      proto, name, name);
+  }
+
+  if (!callHasOwnProperty(hasOwnPropertyFunction, proto, name)) {
+    defineProperty(proto, name, method);
+  }
+
+  return JS('var', '#.apply(#, #)', method, obj, arguments);
+}
+
+dynamicBindLookup(var hasOwnPropertyFunction, String tag, var methods) {
+  var method = lookupDynamicClass(hasOwnPropertyFunction, methods, tag);
+  // Look at the inheritance data, getting the class tags and using them
+  // to check the methods table for this method name.
+  if (method == null && _dynamicMetadata != null) {
+    for (int i = 0; i < arrayLength(_dynamicMetadata); i++) {
+      MetaInfo entry = arrayGet(_dynamicMetadata, i);
+      if (callHasOwnProperty(hasOwnPropertyFunction, entry._set, tag)) {
+        method =
+            lookupDynamicClass(hasOwnPropertyFunction, methods, entry._tag);
+        // Stop if we found it in the methods array.
+        if (method != null) break;
+      }
+    }
+  }
+  return method;
+}
+
+// For each method name and class inheritance subtree, we use an ordinary JS
+// object as a hash map to store the method for each class.  Entries are added
+// in native_emitter.dart (see dynamicName).  In order to avoid the class names
+// clashing with the method names on Object.prototype (needed for native
+// objects) we must always use hasOwnProperty.
+var lookupDynamicClass(var hasOwnPropertyFunction,
+                       var methods,
+                       String className) {
+  return callHasOwnProperty(hasOwnPropertyFunction, methods, className) ?
+         propertyGet(methods, className) :
+         null;
+}
+
+/**
+ * Code for doing the dynamic dispatch on JavaScript prototypes that are not
+ * available at compile-time. Each property of a native Dart class
+ * is registered through this function, which is called with the
+ * following pattern:
+ *
+ * dynamicFunction('propertyName').prototypeName = // JS code
+ *
+ * What this function does is:
+ * - Creates a map of { prototypeName: JS code }.
+ * - Attaches 'propertyName' to the JS Object prototype that will
+ *   intercept at runtime all calls to propertyName.
+ * - Sets the value of 'propertyName' to the returned method from
+ *   [dynamicBind].
+ *
+ */
+dynamicFunction(name) {
+  var f = JS('var', 'Object.prototype[#]', name);
+  if (f != null && JS('bool', '!!#.methods', f)) {
+    return JS('var', '#.methods', f);
+  }
+
+  // TODO(ngeoffray): We could make this a map if the code we
+  // generate plays well with a Dart map.
+  var methods = JS('var', '{}');
+  // If there is a method attached to the Dart Object class, use it as
+  // the method to call in case no method is registered for that type.
+  var dartMethod = getPropertyFromPrototype(const Object(), name);
+  // Take the method from the Dart Object class if we didn't find it yet and it
+  // is there.
+  if (dartMethod != null) propertySet(methods, 'Object', dartMethod);
+
+  var bind = JS('var',
+      'function() {'
+        'return #(this, #, #, Array.prototype.slice.call(arguments));'
+      '}',
+    DART_CLOSURE_TO_JS(dynamicBind), name, methods);
+
+  JS('void', '#.methods = #', bind, methods);
+  defineProperty(JS('var', 'Object.prototype'), name, bind);
+  return methods;
+}
+
+/**
+ * This class encodes the class hierarchy when we need it for dynamic
+ * dispatch.
+ */
+class MetaInfo {
+  /**
+   * The type name this [MetaInfo] relates to.
+   */
+  String _tag;
+
+  /**
+   * A string containing the names of subtypes of [tag], separated by
+   * '|'.
+   */
+  String _tags;
+
+  /**
+   * A list of names of subtypes of [tag].
+   */
+  Object _set;
+
+  MetaInfo(this._tag, this._tags, this._set);
+}
+
+List<MetaInfo> get _dynamicMetadata {
+  // Because [dynamicMetadata] has to be shared with multiple isolates
+  // that access native classes (eg multiple DOM isolates),
+  // [_dynamicMetadata] cannot be a field, otherwise all non-main
+  // isolates would not have any value for it.
+  if (identical(JS('var', 'typeof(\$dynamicMetadata)'), 'undefined')) {
+    _dynamicMetadata = <MetaInfo>[];
+  }
+  return JS('var', '\$dynamicMetadata');
+}
+
+void set _dynamicMetadata(List<MetaInfo> table) {
+  JS('void', '\$dynamicMetadata = #', table);
+}
+
+/**
+ * Builds the metadata used for encoding the class hierarchy of native
+ * classes. The following example:
+ *
+ * class A native "*A" {}
+ * class B extends A native "*B" {}
+ *
+ * Will generate:
+ * ['A', 'A|B']
+ *
+ * This method returns a list of [MetaInfo] objects.
+ */
+List <MetaInfo> buildDynamicMetadata(List<List<String>> inputTable) {
+  List<MetaInfo> result = <MetaInfo>[];
+  for (int i = 0; i < arrayLength(inputTable); i++) {
+    String tag = JS('String', '#', arrayGet(arrayGet(inputTable, i), 0));
+    String tags = JS('String', '#', arrayGet(arrayGet(inputTable, i), 1));
+    var set = newJsObject();
+    List<String> tagNames = tags.split('|');
+    for (int j = 0; j < arrayLength(tagNames); j++) {
+      propertySet(set, arrayGet(tagNames, j), true);
+    }
+    result.add(new MetaInfo(tag, tags, set));
+  }
+  return result;
+}
+
+/**
+ * Called by the compiler to setup [_dynamicMetadata].
+ */
+void dynamicSetMetadata(List<List<String>> inputTable) {
+  _dynamicMetadata = buildDynamicMetadata(inputTable);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart
new file mode 100644
index 0000000..b62b0b5
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart
@@ -0,0 +1,151 @@
+// 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.
+
+part of _js_helper;
+
+List regExpExec(JSSyntaxRegExp regExp, String str) {
+  var nativeRegExp = regExpGetNative(regExp);
+  var result = JS('=List', r'#.exec(#)', nativeRegExp, str);
+  if (JS('bool', r'# == null', result)) return null;
+  return result;
+}
+
+bool regExpTest(JSSyntaxRegExp regExp, String str) {
+  var nativeRegExp = regExpGetNative(regExp);
+  return JS('bool', r'#.test(#)', nativeRegExp, str);
+}
+
+regExpGetNative(JSSyntaxRegExp regExp) {
+  var r = JS('var', r'#._re', regExp);
+  if (r == null) {
+    r = JS('var', r'#._re = #', regExp, regExpMakeNative(regExp));
+  }
+  return r;
+}
+
+regExpAttachGlobalNative(JSSyntaxRegExp regExp) {
+  JS('void', r'#._re = #', regExp, regExpMakeNative(regExp, global: true));
+}
+
+regExpMakeNative(JSSyntaxRegExp regExp, {bool global: false}) {
+  String pattern = regExp.pattern;
+  bool isMultiLine = regExp.isMultiLine;
+  bool isCaseSensitive = regExp.isCaseSensitive;
+  checkString(pattern);
+  StringBuffer sb = new StringBuffer();
+  if (isMultiLine) sb.add('m');
+  if (!isCaseSensitive) sb.add('i');
+  if (global) sb.add('g');
+  try {
+    return JS('var', r'new RegExp(#, #)', pattern, sb.toString());
+  } catch (e) {
+    throw new IllegalJSRegExpException(pattern,
+                                       JS('String', r'String(#)', e));
+  }
+}
+
+int regExpMatchStart(m) => JS('int', r'#.index', m);
+
+class JSSyntaxRegExp implements RegExp {
+  final String _pattern;
+  final bool _isMultiLine;
+  final bool _isCaseSensitive;
+
+  const JSSyntaxRegExp(String pattern,
+                       {bool multiLine: false,
+                        bool caseSensitive: true})
+      : _pattern = pattern,
+        _isMultiLine = multiLine,
+        _isCaseSensitive = caseSensitive;
+
+  Match firstMatch(String str) {
+    List<String> m = regExpExec(this, checkString(str));
+    if (m == null) return null;
+    var matchStart = regExpMatchStart(m);
+    // m.lastIndex only works with flag 'g'.
+    var matchEnd = matchStart + m[0].length;
+    return new _MatchImplementation(pattern, str, matchStart, matchEnd, m);
+  }
+
+  bool hasMatch(String str) => regExpTest(this, checkString(str));
+
+  String stringMatch(String str) {
+    var match = firstMatch(str);
+    return match == null ? null : match.group(0);
+  }
+
+  Iterable<Match> allMatches(String str) {
+    checkString(str);
+    return new _AllMatchesIterable(this, str);
+  }
+
+  String get pattern => _pattern;
+  bool get isMultiLine => _isMultiLine;
+  bool get isCaseSensitive => _isCaseSensitive;
+
+  static JSSyntaxRegExp _globalVersionOf(JSSyntaxRegExp other) {
+    JSSyntaxRegExp re =
+        new JSSyntaxRegExp(other.pattern,
+                           multiLine: other.isMultiLine,
+                           caseSensitive: other.isCaseSensitive);
+    regExpAttachGlobalNative(re);
+    return re;
+  }
+
+  _getNative() => regExpGetNative(this);
+}
+
+class _MatchImplementation implements Match {
+  final String pattern;
+  final String str;
+  final int start;
+  final int end;
+  final List<String> _groups;
+
+  const _MatchImplementation(
+      String this.pattern,
+      String this.str,
+      int this.start,
+      int this.end,
+      List<String> this._groups);
+
+  String group(int index) => _groups[index];
+  String operator [](int index) => group(index);
+  int get groupCount => _groups.length - 1;
+
+  List<String> groups(List<int> groups) {
+    List<String> out = [];
+    for (int i in groups) {
+      out.add(group(i));
+    }
+    return out;
+  }
+}
+
+class _AllMatchesIterable extends Iterable<Match> {
+  final JSSyntaxRegExp _re;
+  final String _str;
+
+  const _AllMatchesIterable(this._re, this._str);
+
+  Iterator<Match> get iterator => new _AllMatchesIterator(_re, _str);
+}
+
+class _AllMatchesIterator implements Iterator<Match> {
+  final RegExp _re;
+  final String _str;
+  Match _current;
+
+  _AllMatchesIterator(JSSyntaxRegExp re, String this._str)
+    : _re = JSSyntaxRegExp._globalVersionOf(re);
+
+  Match get current => _current;
+
+  bool moveNext() {
+    // firstMatch actually acts as nextMatch because of
+    // hidden global flag.
+    _current = _re.firstMatch(_str);
+    return _current != null;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart
new file mode 100644
index 0000000..326b892
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart
@@ -0,0 +1,130 @@
+// 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.
+
+// This is an empty dummy patch file for the VM dart:scalarlist library.
+// This is needed in order to be able to generate documentation for the
+// scalarlist library.
+
+patch class Int8List {
+  patch factory Int8List(int length) {
+    throw new UnsupportedError('Int8List');
+  }
+
+  patch factory Int8List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int8List.view');
+  }
+}
+
+
+patch class Uint8List {
+  patch factory Uint8List(int length) {
+    throw new UnsupportedError('Uint8List');
+  }
+
+  patch factory Uint8List.view(ByteArray array,
+                               [int start = 0, int length]) {
+    throw new UnsupportedError('Uint8List.view');
+  }
+}
+
+
+patch class Uint8ClampedList {
+  patch factory Uint8ClampedList(int length) {
+    throw new UnsupportedError('Uint8ClampedList');
+  }
+
+  patch factory Uint8ClampedList.view(ByteArray array,
+                                      [int start = 0, int length]) {
+    throw new UnsupportedError('Uint8ClampedList.view');
+  }
+}
+
+
+patch class Int16List {
+  patch factory Int16List(int length) {
+    throw new UnsupportedError('Int16List');
+
+  }
+
+  patch factory Int16List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int16List.view');
+  }
+}
+
+
+patch class Uint16List {
+  patch factory Uint16List(int length) {
+    throw new UnsupportedError('Uint16List');
+  }
+
+  patch factory Uint16List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Uint16List.view');
+  }
+}
+
+
+patch class Int32List {
+  patch factory Int32List(int length) {
+    throw new UnsupportedError('Int32List');
+  }
+
+  patch factory Int32List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int32List.view');
+  }
+}
+
+
+patch class Uint32List {
+  patch factory Uint32List(int length) {
+    throw new UnsupportedError('Uint32List');
+  }
+
+  patch factory Uint32List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Uint32List.view');
+  }
+}
+
+
+patch class Int64List {
+  patch factory Int64List(int length) {
+    throw new UnsupportedError('Int64List');
+  }
+
+  patch factory Int64List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Int64List.view');
+  }
+}
+
+
+patch class Uint64List {
+  patch factory Uint64List(int length) {
+    throw new UnsupportedError('Uint64List');
+  }
+
+  patch factory Uint64List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Uint64List.view');
+  }
+}
+
+
+patch class Float32List {
+  patch factory Float32List(int length) {
+    throw new UnsupportedError('Float32List');
+  }
+
+  patch factory Float32List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Float32List.view');
+  }
+}
+
+
+patch class Float64List {
+  patch factory Float64List(int length) {
+    throw new UnsupportedError('Float64List');
+  }
+
+  patch factory Float64List.view(ByteArray array, [int start = 0, int length]) {
+    throw new UnsupportedError('Float64List.view');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart
new file mode 100644
index 0000000..8ac1fa4
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart
@@ -0,0 +1,234 @@
+// 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.
+
+part of _js_helper;
+
+class StringMatch implements Match {
+  const StringMatch(int this.start,
+                    String this.str,
+                    String this.pattern);
+
+  int get end => start + pattern.length;
+  String operator[](int g) => group(g);
+  int get groupCount => 0;
+
+  String group(int group_) {
+    if (group_ != 0) {
+      throw new RangeError.value(group_);
+    }
+    return pattern;
+  }
+
+  List<String> groups(List<int> groups_) {
+    List<String> result = new List<String>();
+    for (int g in groups_) {
+      result.add(group(g));
+    }
+    return result;
+  }
+
+  final int start;
+  final String str;
+  final String pattern;
+}
+
+List<Match> allMatchesInStringUnchecked(String needle, String haystack) {
+  // Copied from StringBase.allMatches in
+  // /runtime/lib/string_base.dart
+  List<Match> result = new List<Match>();
+  int length = haystack.length;
+  int patternLength = needle.length;
+  int startIndex = 0;
+  while (true) {
+    int position = haystack.indexOf(needle, startIndex);
+    if (position == -1) {
+      break;
+    }
+    result.add(new StringMatch(position, haystack, needle));
+    int endIndex = position + patternLength;
+    if (endIndex == length) {
+      break;
+    } else if (position == endIndex) {
+      ++startIndex;  // empty match, advance and restart
+    } else {
+      startIndex = endIndex;
+    }
+  }
+  return result;
+}
+
+stringContainsUnchecked(receiver, other, startIndex) {
+  if (other is String) {
+    return receiver.indexOf(other, startIndex) != -1;
+  } else if (other is JSSyntaxRegExp) {
+    return other.hasMatch(receiver.substring(startIndex));
+  } else {
+    var substr = receiver.substring(startIndex);
+    return other.allMatches(substr).iterator.moveNext();
+  }
+}
+
+stringReplaceJS(receiver, replacer, to) {
+  // The JavaScript String.replace method recognizes replacement
+  // patterns in the replacement string. Dart does not have that
+  // behavior.
+  to = JS('String', r"#.replace('$', '$$$$')", to);
+  return JS('String', r'#.replace(#, #)', receiver, replacer, to);
+}
+
+final RegExp quoteRegExp = new JSSyntaxRegExp(r'[-[\]{}()*+?.,\\^$|#\s]');
+
+stringReplaceAllUnchecked(receiver, from, to) {
+  checkString(to);
+  if (from is String) {
+    if (from == "") {
+      if (receiver == "") {
+        return to;
+      } else {
+        StringBuffer result = new StringBuffer();
+        int length = receiver.length;
+        result.add(to);
+        for (int i = 0; i < length; i++) {
+          result.add(receiver[i]);
+          result.add(to);
+        }
+        return result.toString();
+      }
+    } else {
+      var quoter = regExpMakeNative(quoteRegExp, global: true);
+      var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter);
+      RegExp replaceRegExp = new JSSyntaxRegExp(quoted);
+      var replacer = regExpMakeNative(replaceRegExp, global: true);
+      return stringReplaceJS(receiver, replacer, to);
+    }
+  } else if (from is JSSyntaxRegExp) {
+    var re = regExpMakeNative(from, global: true);
+    return stringReplaceJS(receiver, re, to);
+  } else {
+    checkNull(from);
+    // TODO(floitsch): implement generic String.replace (with patterns).
+    throw "String.replaceAll(Pattern) UNIMPLEMENTED";
+  }
+}
+
+String _matchString(Match match) => match[0];
+String _stringIdentity(String string) => string;
+
+stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) {
+  if (pattern is! Pattern) {
+    throw new ArgumentError("${pattern} is not a Pattern");
+  }
+  if (onMatch == null) onMatch = _matchString;
+  if (onNonMatch == null) onNonMatch = _stringIdentity;
+  if (pattern is String) {
+    return stringReplaceAllStringFuncUnchecked(receiver, pattern,
+                                               onMatch, onNonMatch);
+  }
+  StringBuffer buffer = new StringBuffer();
+  int startIndex = 0;
+  for (Match match in pattern.allMatches(receiver)) {
+    buffer.add(onNonMatch(receiver.substring(startIndex, match.start)));
+    buffer.add(onMatch(match));
+    startIndex = match.end;
+  }
+  buffer.add(onNonMatch(receiver.substring(startIndex)));
+  return buffer.toString();
+}
+
+stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) {
+  // Pattern is the empty string.
+  StringBuffer buffer = new StringBuffer();
+  int length = receiver.length;
+  int i = 0;
+  buffer.add(onNonMatch(""));
+  while (i < length) {
+    buffer.add(onMatch(new StringMatch(i, receiver, "")));
+    // Special case to avoid splitting a surrogate pair.
+    int code = receiver.charCodeAt(i);
+    if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
+      // Leading surrogate;
+      code = receiver.charCodeAt(i + 1);
+      if ((code & ~0x3FF) == 0xDC00) {
+        // Matching trailing surrogate.
+        buffer.add(onNonMatch(receiver.substring(i, i + 2)));
+        i += 2;
+        continue;
+      }
+    }
+    buffer.add(onNonMatch(receiver[i]));
+    i++;
+  }
+  buffer.add(onMatch(new StringMatch(i, receiver, "")));
+  buffer.add(onNonMatch(""));
+  return buffer.toString();
+}
+
+stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) {
+  int patternLength = pattern.length;
+  if (patternLength == 0) {
+    return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch);
+  }
+  int length = receiver.length;
+  StringBuffer buffer = new StringBuffer();
+  int startIndex = 0;
+  while (startIndex < length) {
+    int position = receiver.indexOf(pattern, startIndex);
+    if (position == -1) {
+      break;
+    }
+    buffer.add(onNonMatch(receiver.substring(startIndex, position)));
+    buffer.add(onMatch(new StringMatch(position, receiver, pattern)));
+    startIndex = position + patternLength;
+  }
+  buffer.add(onNonMatch(receiver.substring(startIndex)));
+  return buffer.toString();
+}
+
+
+stringReplaceFirstUnchecked(receiver, from, to) {
+  if (from is String) {
+    return stringReplaceJS(receiver, from, to);
+  } else if (from is JSSyntaxRegExp) {
+    var re = regExpGetNative(from);
+    return stringReplaceJS(receiver, re, to);
+  } else {
+    checkNull(from);
+    // TODO(floitsch): implement generic String.replace (with patterns).
+    throw "String.replace(Pattern) UNIMPLEMENTED";
+  }
+}
+
+stringJoinUnchecked(array, separator) {
+  return JS('String', r'#.join(#)', array, separator);
+}
+
+class JsStringBuffer implements StringBuffer {
+  String _contents;
+
+  JsStringBuffer(content)
+      : _contents = (content is String) ? content : '$content';
+
+  int get length => _contents.length;
+
+  bool get isEmpty => length == 0;
+
+  void add(Object obj) {
+    _contents = JS('String', '# + #', _contents,
+                   (obj is String) ? obj : '$obj');
+  }
+
+  void addAll(Iterable objects) {
+    for (Object obj in objects) add(obj);
+  }
+
+  void addCharCode(int charCode) {
+    add(new String.fromCharCodes([charCode]));
+  }
+
+  void clear() {
+    _contents = "";
+  }
+
+  String toString() => _contents;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart b/pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart
new file mode 100644
index 0000000..074cece
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart
@@ -0,0 +1,837 @@
+// 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.
+
+part of dart2js;
+
+/**
+ * [CompilerTask] for loading libraries and setting up the import/export scopes.
+ *
+ * The library loader uses four different kinds of URIs in different parts of
+ * the loading process.
+ *
+ * ## User URI ##
+ *
+ * A 'user URI' is a URI provided by the user in code and as the main entry URI
+ * at the command line. These generally come in 3 versions:
+ *
+ *   * A relative URI such as 'foo.dart', '../bar.dart', and 'baz/boz.dart'.
+ *
+ *   * A dart URI such as 'dart:core' and 'dart:_js_helper'.
+ *
+ *   * A package URI such as 'package:foo.dart' and 'package:bar/baz.dart'.
+ *
+ * A user URI can also be absolute, like 'file:///foo.dart' or
+ * 'http://example.com/bar.dart', but such URIs cannot necessarily be used for
+ * locating source files, since the scheme must be supported by the input
+ * provider. The standard input provider for dart2js only supports the 'file'
+ * scheme.
+ *
+ * ## Resolved URI ##
+ *
+ * A 'resolved URI' is a (user) URI that has been resolved to an absolute URI
+ * based on the readable URI (see below) from which it was loaded. A URI with an
+ * explicit scheme (such as 'dart:', 'package:' or 'file:') is already resolved.
+ * A relative URI like for instance '../foo/bar.dart' is translated into an
+ * resolved URI in one of three ways:
+ *
+ *  * If provided as the main entry URI at the command line, the URI is resolved
+ *    relative to the current working directory, say
+ *    'file:///current/working/dir/', and the resolved URI is therefore
+ *    'file:///current/working/foo/bar.dart'.
+ *
+ *  * If the relative URI is provided in an import, export or part tag, and the
+ *    readable URI of the enclosing compilation unit is a file URI,
+ *    'file://some/path/baz.dart', then the resolved URI is
+ *    'file://some/foo/bar.dart'.
+ *
+ *  * If the relative URI is provided in an import, export or part tag, and the
+ *    readable URI of the enclosing compilation unit is a package URI,
+ *    'package:some/path/baz.dart', then the resolved URI is
+ *    'package:some/foo/bar.dart'.
+ *
+ * The resolved URI thus preserves the scheme through resolution: A readable
+ * file URI results in an resolved file URI and a readable package URI results
+ * in an resolved package URI. Note that since a dart URI is not a readable URI,
+ * import, export or part tags within platform libraries are not interpreted as
+ * dart URIs but instead relative to the library source file location.
+ *
+ * The resolved URI of a library is also used as the canonical URI
+ * ([LibraryElement.canonicalUri]) by which we identify which libraries are
+ * identical. This means that libraries loaded through the 'package' scheme will
+ * resolve to the same library when loaded from within using relative URIs (see
+ * for instance the test 'standalone/package/package1_test.dart'). But loading a
+ * platform library using a relative URI will _not_ result in the same library
+ * as when loaded through the dart URI.
+ *
+ * ## Readable URI ##
+ *
+ * A 'readable URI' is an absolute URI whose scheme is either 'package' or
+ * something supported by the input provider, normally 'file'. Dart URIs such as
+ * 'dart:core' and 'dart:_js_helper' are not readable themselves but are instead
+ * resolved into a readable URI using the library root URI provided from the
+ * command line and the list of platform libraries found in
+ * 'sdk/lib/_internal/libraries.dart'. This is done through the
+ * [Compiler.translateResolvedUri] method which checks whether a library by that
+ * name exists and in case of internal libraries whether access is granted.
+ *
+ * ## Resource URI ##
+ *
+ * A 'resource URI' is an absolute URI with a scheme supported by the input
+ * provider. For the standard implementation this means a URI with the 'file'
+ * scheme. Readable URIs are converted into resource URIs as part of the
+ * [Compiler.readScript] method. In the standard implementation the package URIs
+ * are converted to file URIs using the package root URI provided on the
+ * command line as base. If the package root URI is
+ * 'file:///current/working/dir/' then the package URI 'package:foo/bar.dart'
+ * will be resolved to the resource URI
+ * 'file:///current/working/dir/foo/bar.dart'.
+ *
+ * The distinction between readable URI and resource URI is necessary to ensure
+ * that these imports
+ *
+ *     import 'package:foo.dart' as a;
+ *     import 'packages/foo.dart' as b;
+ *
+ * do _not_ resolve to the same library when the package root URI happens to
+ * point to the 'packages' folder.
+ *
+ */
+abstract class LibraryLoader extends CompilerTask {
+  LibraryLoader(Compiler compiler) : super(compiler);
+
+  /**
+   * Loads the library specified by the [resolvedUri] 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].
+   */
+  // TODO(johnniwinther): Remove [canonicalUri] together with
+  // [Compiler.scanBuiltinLibrary].
+  LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri);
+
+  // TODO(johnniwinther): Remove this when patches don't need special parsing.
+  void registerLibraryFromTag(LibraryDependencyHandler handler,
+                              LibraryElement library,
+                              LibraryDependency tag);
+
+  /**
+   * Adds the elements in the export scope of [importedLibrary] to the import
+   * scope of [importingLibrary].
+   */
+  // TODO(johnniwinther): Move handling of 'js_helper' to the library loader
+  // to remove this method from the [LibraryLoader] interface.
+  void importLibrary(LibraryElement importingLibrary,
+                     LibraryElement importedLibrary,
+                     Import tag);
+}
+
+/**
+ * [CombinatorFilter] is a succinct representation of a list of combinators from
+ * a library dependency tag.
+ */
+class CombinatorFilter {
+  const CombinatorFilter();
+
+  /**
+   * Returns [:true:] if [element] is excluded by this filter.
+   */
+  bool exclude(Element element) => false;
+
+  /**
+   * Creates a filter based on the combinators of [tag].
+   */
+  factory CombinatorFilter.fromTag(LibraryDependency tag) {
+    if (tag == null || tag.combinators == null) {
+      return const CombinatorFilter();
+    }
+
+    // If the list of combinators contain at least one [:show:] we can create
+    // a positive list of elements to include, otherwise we create a negative
+    // list of elements to exclude.
+    bool show = false;
+    Set<SourceString> nameSet;
+    for (Combinator combinator in tag.combinators) {
+      if (combinator.isShow) {
+        show = true;
+        var set = new Set<SourceString>();
+        for (Identifier identifier in combinator.identifiers) {
+          set.add(identifier.source);
+        }
+        if (nameSet == null) {
+          nameSet = set;
+        } else {
+          nameSet = nameSet.intersection(set);
+        }
+      }
+    }
+    if (nameSet == null) {
+      nameSet = new Set<SourceString>();
+    }
+    for (Combinator combinator in tag.combinators) {
+      if (combinator.isHide) {
+        for (Identifier identifier in combinator.identifiers) {
+          if (show) {
+            // We have a positive list => Remove hidden elements.
+            nameSet.remove(identifier.source);
+          } else {
+            // We have no positive list => Accumulate hidden elements.
+            nameSet.add(identifier.source);
+          }
+        }
+      }
+    }
+    return show ? new ShowFilter(nameSet) : new HideFilter(nameSet);
+  }
+}
+
+/**
+ * A list of combinators represented as a list of element names to include.
+ */
+class ShowFilter extends CombinatorFilter {
+  final Set<SourceString> includedNames;
+
+  ShowFilter(this.includedNames);
+
+  bool exclude(Element element) => !includedNames.contains(element.name);
+}
+
+/**
+ * A list of combinators represented as a list of element names to exclude.
+ */
+class HideFilter extends CombinatorFilter {
+  final Set<SourceString> excludedNames;
+
+  HideFilter(this.excludedNames);
+
+  bool exclude(Element element) => excludedNames.contains(element.name);
+}
+
+/**
+ * Implementation class for [LibraryLoader]. The distinction between
+ * [LibraryLoader] and [LibraryLoaderTask] is made to hide internal members from
+ * the [LibraryLoader] interface.
+ */
+class LibraryLoaderTask extends LibraryLoader {
+  LibraryLoaderTask(Compiler compiler) : super(compiler);
+  String get name => 'LibraryLoader';
+
+  final Map<String, LibraryElement> libraryNames =
+      new LinkedHashMap<String, LibraryElement>();
+
+  LibraryDependencyHandler currentHandler;
+
+  LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri) {
+    return measure(() {
+      assert(currentHandler == null);
+      currentHandler = new LibraryDependencyHandler(compiler);
+      LibraryElement library =
+          createLibrary(currentHandler, null, resolvedUri, 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) {
+    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) {
+        Import import = tag;
+        tagState = checkTag(TagState.IMPORT_OR_EXPORT, import);
+        if (import.uri.dartString.slowToString() == 'dart:core') {
+          importsDartCore = true;
+        }
+        libraryDependencies.addLast(import);
+      } 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) {
+        Part part = tag;
+        StringNode uri = part.uri;
+        Uri resolvedUri = base.resolve(uri.dartString.slowToString());
+        tagState = checkTag(TagState.SOURCE, part);
+        scanPart(part, resolvedUri, library);
+      } else {
+        compiler.internalError("Unhandled library tag.", node: tag);
+      }
+    }
+
+    // Apply patch, if any.
+    if (library.isPlatformLibrary) {
+      patchDartLibrary(handler, library, library.canonicalUri.path);
+    }
+
+    // Import dart:core if not already imported.
+    if (!importsDartCore && !isDartCore(library.canonicalUri)) {
+      handler.registerDependency(library, null, loadCoreLibrary(handler));
+    }
+
+    for (LibraryDependency tag in libraryDependencies.toLink()) {
+      registerLibraryFromTag(handler, library, tag);
+    }
+  }
+
+  void checkDuplicatedLibraryName(LibraryElement library) {
+    LibraryName tag = library.libraryTag;
+    if (tag != null) {
+      String name = library.getLibraryOrScriptName();
+      LibraryElement existing =
+          libraryNames.putIfAbsent(name, () => library);
+      if (!identical(existing, library)) {
+        Uri uri = library.entryCompilationUnit.script.uri;
+        compiler.reportMessage(
+            compiler.spanFromSpannable(tag.name, uri),
+            MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': name}),
+            api.Diagnostic.WARNING);
+        Uri existingUri = existing.entryCompilationUnit.script.uri;
+        compiler.reportMessage(
+            compiler.spanFromSpannable(existing.libraryTag.name, existingUri),
+            MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': 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
+          = createLibrary(handler, null, coreUri, null, coreUri);
+    }
+    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 [resolvedUri] given is
+   * used as is, any URI resolution should be done beforehand.
+   */
+  void scanPart(Part part, Uri resolvedUri, LibraryElement library) {
+    if (!resolvedUri.isAbsolute()) throw new ArgumentError(resolvedUri);
+    Uri readableUri = compiler.translateResolvedUri(library, resolvedUri, part);
+    Script sourceScript = compiler.readScript(readableUri, part);
+    CompilationUnitElement unit =
+        new CompilationUnitElementX(sourceScript, library);
+    compiler.withCurrentElement(unit, () {
+      compiler.scanner.scan(unit);
+      if (unit.partTag == null) {
+        bool wasDiagnosticEmitted = false;
+        compiler.withCurrentElement(library, () {
+          wasDiagnosticEmitted =
+              compiler.onDeprecatedFeature(part, 'missing part-of tag');
+        });
+        if (wasDiagnosticEmitted) {
+          compiler.reportMessage(
+              compiler.spanFromElement(unit),
+              MessageKind.MISSING_PART_OF_TAG.error(),
+              api.Diagnostic.INFO);
+        }
+      }
+    });
+  }
+
+  /**
+   * 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 registerLibraryFromTag(LibraryDependencyHandler handler,
+                              LibraryElement library,
+                              LibraryDependency tag) {
+    Uri base = library.entryCompilationUnit.script.uri;
+    Uri resolvedUri = base.resolve(tag.uri.dartString.slowToString());
+    LibraryElement loadedLibrary =
+        createLibrary(handler, library, resolvedUri, tag.uri, resolvedUri);
+    handler.registerDependency(library, tag, loadedLibrary);
+
+    if (!loadedLibrary.hasLibraryName()) {
+      compiler.withCurrentElement(library, () {
+        compiler.reportError(tag == null ? null : tag.uri,
+            'no library name found in ${loadedLibrary.canonicalUri}');
+      });
+    }
+  }
+
+  /**
+   * Create (or reuse) a library element for the library specified by the
+   * [resolvedUri].
+   *
+   * If a new library is created, the [handler] is notified.
+   */
+  // TODO(johnniwinther): Remove [canonicalUri] and make [resolvedUri] the
+  // canonical uri when [Compiler.scanBuiltinLibrary] is removed.
+  LibraryElement createLibrary(LibraryDependencyHandler handler,
+                               LibraryElement importingLibrary,
+                               Uri resolvedUri, Node node, Uri canonicalUri) {
+    bool newLibrary = false;
+    Uri readableUri =
+        compiler.translateResolvedUri(importingLibrary, resolvedUri, node);
+    if (readableUri == null) return null;
+    LibraryElement createLibrary() {
+      newLibrary = true;
+      Script script = compiler.readScript(readableUri, node);
+      LibraryElement element = new LibraryElementX(script, canonicalUri);
+      handler.registerNewLibrary(element);
+      native.maybeEnableNative(compiler, element);
+      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.onLibraryScanned(library, resolvedUri);
+      });
+    }
+    return library;
+  }
+
+  // TODO(johnniwinther): Remove this method when 'js_helper' is handled by
+  // [LibraryLoaderTask].
+  void importLibrary(LibraryElement importingLibrary,
+                     LibraryElement importedLibrary,
+                     Import tag) {
+    new ImportLink(tag, importedLibrary).importLibrary(compiler,
+                                                       importingLibrary);
+  }
+}
+
+
+/**
+ * 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 [importedLibrary] imported through [import].
+ */
+class ImportLink {
+  final Import import;
+  final LibraryElement importedLibrary;
+
+  ImportLink(this.import, this.importedLibrary);
+
+  /**
+   * Imports the library into the [importingLibrary].
+   */
+  void importLibrary(Compiler compiler, LibraryElement importingLibrary) {
+    assert(invariant(importingLibrary,
+                     importedLibrary.exportsHandled,
+                     message: 'Exports not handled on $importedLibrary'));
+    var combinatorFilter = new CombinatorFilter.fromTag(import);
+    if (import != null && import.prefix != null) {
+      SourceString prefix = import.prefix.source;
+      Element e = importingLibrary.find(prefix);
+      if (e == null) {
+        e = new PrefixElementX(prefix, importingLibrary.entryCompilationUnit,
+                               import.getBeginToken());
+        importingLibrary.addToScope(e, compiler);
+      }
+      if (!identical(e.kind, ElementKind.PREFIX)) {
+        compiler.withCurrentElement(e, () {
+          compiler.reportWarning(new Identifier(e.position()),
+          'duplicated definition');
+        });
+        compiler.reportError(import.prefix, 'duplicate definition');
+      }
+      PrefixElement prefixElement = e;
+      importedLibrary.forEachExport((Element element) {
+        if (combinatorFilter.exclude(element)) return;
+        // TODO(johnniwinther): Clean-up like [checkDuplicateLibraryName].
+        Element existing =
+            prefixElement.imported.putIfAbsent(element.name, () => element);
+        if (!identical(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, () {
+          if (combinatorFilter.exclude(element)) return;
+          importingLibrary.addImport(element, compiler);
+        });
+      });
+    }
+  }
+}
+
+/**
+ * The combinator filter computed from an export tag and the library dependency
+ * node for the library that declared the export tag. This represents an edge in
+ * the library dependency graph.
+ */
+class ExportLink {
+  final CombinatorFilter combinatorFilter;
+  final LibraryDependencyNode exportNode;
+
+  ExportLink(Export export, LibraryDependencyNode this.exportNode)
+      : this.combinatorFilter = new CombinatorFilter.fromTag(export);
+
+  /**
+   * Exports [element] to the dependent library unless [element] is filtered by
+   * the export combinators. Returns [:true:] if the set pending exports of the
+   * dependent library was modified.
+   */
+  bool exportElement(Element element) {
+    if (combinatorFilter.exclude(element)) return false;
+    return exportNode.addElementToPendingExports(element);
+  }
+}
+
+/**
+ * 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;
+
+  // TODO(ahe): Remove [hashCodeCounter] and [hashCode] when
+  // VM implementation of Object.hashCode is not slow.
+  final int hashCode = ++hashCodeCounter;
+  static int hashCodeCounter = 0;
+
+
+  /**
+   * 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 Link<ImportLink>();
+
+  /**
+   * A linked list of the export tags the dependent upon this node library.
+   * This is used to propagate exports during the computation of export scopes.
+   */
+  Link<ExportLink> dependencies = const Link<ExportLink>();
+
+  /**
+   * The export scope for [library] which is gradually computed by the work-list
+   * computation in [LibraryDependencyHandler.computeExports].
+   */
+  Map<SourceString, Element> exportScope =
+      new LinkedHashMap<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) {
+    dependencies =
+        dependencies.prepend(new ExportLink(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.getNonPrivateElementsInScope());
+  }
+
+  void registerHandledExports(LibraryElement exportedLibraryElement,
+                              CombinatorFilter filter) {
+    assert(invariant(library, exportedLibraryElement.exportsHandled));
+    for (Element exportedElement in exportedLibraryElement.exports) {
+      if (!filter.exclude(exportedElement)) {
+        pendingExportSet.add(exportedElement);
+      }
+    }
+  }
+
+  /**
+   * Registers the compute export scope with the node library.
+   */
+  void registerExports() {
+    library.setExports(exportScope.values.toList());
+  }
+
+  /**
+   * Registers the imports of the node library.
+   */
+  void registerImports(Compiler compiler) {
+    for (ImportLink link in imports) {
+      link.importLibrary(compiler, library);
+    }
+  }
+
+  /**
+   * 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 export scope.
+   */
+  Element addElementToExportScope(Compiler compiler, Element element) {
+    SourceString name = element.name;
+    Element existingElement = exportScope[name];
+    if (existingElement != null) {
+      if (existingElement.isErroneous()) {
+        compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT,
+                                 {'name': name});
+        element = existingElement;
+      } else if (existingElement.getLibrary() != library) {
+        // Declared elements hide exported elements.
+        compiler.reportErrorCode(existingElement, MessageKind.DUPLICATE_EXPORT,
+                                 {'name': name});
+        compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT,
+                                 {'name': name});
+        element = exportScope[name] = new ErroneousElementX(
+            MessageKind.DUPLICATE_EXPORT, {'name': name}, name, library);
+      }
+    } else {
+      exportScope[name] = element;
+    }
+    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;
+    for (ExportLink link in dependencies) {
+      if (link.exportElement(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(Element element) {
+    if (!identical(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 LinkedHashMap<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;
+      Map<LibraryDependencyNode, List<Element>> tasks =
+          new LinkedHashMap<LibraryDependencyNode, List<Element>>();
+
+      // Locally defined elements take precedence over exported
+      // elements.  So we must propagate local elements first.  We
+      // ensure this by pulling the pending exports before
+      // propagating.  This enforces that we handle exports
+      // breadth-first, with locally defined elements being level 0.
+      nodeMap.forEach((_, LibraryDependencyNode node) {
+        List<Element> pendingExports = node.pullPendingExports();
+        tasks[node] = pendingExports;
+      });
+      tasks.forEach((LibraryDependencyNode node, List<Element> pendingExports) {
+        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].
+      LibraryDependencyNode exportingNode = nodeMap[library];
+      if (loadedLibrary.exportsHandled) {
+        // Export scope already computed on [loadedLibrary].
+        var combinatorFilter = new CombinatorFilter.fromTag(tag);
+        exportingNode.registerHandledExports(loadedLibrary, combinatorFilter);
+        return;
+      }
+      LibraryDependencyNode exportedNode = nodeMap[loadedLibrary];
+      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();
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart
new file mode 100644
index 0000000..13a15f8
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart
@@ -0,0 +1,1742 @@
+// 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.
+
+library mirrors_dart2js;
+
+import 'dart:async';
+import 'dart:collection' show LinkedHashMap;
+import 'dart:io';
+import 'dart:uri';
+
+import '../../compiler.dart' as diagnostics;
+import '../elements/elements.dart';
+import '../resolution/resolution.dart' show ResolverTask, ResolverVisitor;
+import '../apiimpl.dart' show Compiler;
+import '../scanner/scannerlib.dart' hide SourceString;
+import '../ssa/ssa.dart';
+import '../dart2jslib.dart' hide Compiler;
+import '../dart_types.dart';
+import '../filenames.dart';
+import '../source_file.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../util/uri_extras.dart';
+import '../dart2js.dart';
+import '../util/characters.dart';
+import '../source_file_provider.dart';
+
+import 'mirrors.dart';
+import 'mirrors_util.dart';
+import 'util.dart';
+
+//------------------------------------------------------------------------------
+// Utility types and functions for the dart2js mirror system
+//------------------------------------------------------------------------------
+
+bool _isPrivate(String name) {
+  return name.startsWith('_');
+}
+
+List<ParameterMirror> _parametersFromFunctionSignature(
+    Dart2JsMirrorSystem system,
+    Dart2JsMethodMirror method,
+    FunctionSignature signature) {
+  var parameters = <ParameterMirror>[];
+  Link<Element> link = signature.requiredParameters;
+  while (!link.isEmpty) {
+    parameters.add(new Dart2JsParameterMirror(
+        system, method, link.head, false, false));
+    link = link.tail;
+  }
+  link = signature.optionalParameters;
+  bool isNamed = signature.optionalParametersAreNamed;
+  while (!link.isEmpty) {
+    parameters.add(new Dart2JsParameterMirror(
+        system, method, link.head, true, isNamed));
+    link = link.tail;
+  }
+  return parameters;
+}
+
+Dart2JsTypeMirror _convertTypeToTypeMirror(
+    Dart2JsMirrorSystem system,
+    DartType type,
+    InterfaceType defaultType,
+    [FunctionSignature functionSignature]) {
+  if (type == null) {
+    return new Dart2JsInterfaceTypeMirror(system, defaultType);
+  } else if (type is InterfaceType) {
+    if (type == system.compiler.types.dynamicType) {
+      return new Dart2JsDynamicMirror(system, type);
+    } else {
+      return new Dart2JsInterfaceTypeMirror(system, type);
+    }
+  } else if (type is TypeVariableType) {
+    return new Dart2JsTypeVariableMirror(system, type);
+  } else if (type is FunctionType) {
+    return new Dart2JsFunctionTypeMirror(system, type, functionSignature);
+  } else if (type is VoidType) {
+    return new Dart2JsVoidMirror(system, type);
+  } else if (type is TypedefType) {
+    return new Dart2JsTypedefMirror(system, type);
+  } else if (type is MalformedType) {
+    // TODO(johnniwinther): We need a mirror on malformed types.
+    return system.dynamicType;
+  }
+  _diagnosticListener.internalError(
+      "Unexpected type $type of kind ${type.kind}");
+  system.compiler.internalError("Unexpected type $type of kind ${type.kind}");
+}
+
+Collection<Dart2JsMemberMirror> _convertElementMemberToMemberMirrors(
+    Dart2JsContainerMirror library, Element element) {
+  if (element.isSynthesized) {
+    return const <Dart2JsMemberMirror>[];
+  } else if (element is VariableElement) {
+    return <Dart2JsMemberMirror>[new Dart2JsFieldMirror(library, element)];
+  } else if (element is FunctionElement) {
+    return <Dart2JsMemberMirror>[new Dart2JsMethodMirror(library, element)];
+  } else if (element is AbstractFieldElement) {
+    var members = <Dart2JsMemberMirror>[];
+    if (element.getter != null) {
+      members.add(new Dart2JsMethodMirror(library, element.getter));
+    }
+    if (element.setter != null) {
+      members.add(new Dart2JsMethodMirror(library, element.setter));
+    }
+    return members;
+  }
+  library.mirrors.compiler.internalError(
+      "Unexpected member type $element ${element.kind}");
+}
+
+MethodMirror _convertElementMethodToMethodMirror(Dart2JsContainerMirror library,
+                                                 Element element) {
+  if (element is FunctionElement) {
+    return new Dart2JsMethodMirror(library, element);
+  } else {
+    return null;
+  }
+}
+
+InstanceMirror _convertConstantToInstanceMirror(Dart2JsMirrorSystem mirrors,
+                                                Constant constant) {
+  if (constant is BoolConstant) {
+    return new Dart2JsBoolConstantMirror(mirrors, constant);
+  } else if (constant is NumConstant) {
+    return new Dart2JsNumConstantMirror(mirrors, constant);
+  } else if (constant is StringConstant) {
+    return new Dart2JsStringConstantMirror(mirrors, constant);
+  } else if (constant is ListConstant) {
+    return new Dart2JsListConstantMirror(mirrors, constant);
+  } else if (constant is MapConstant) {
+    return new Dart2JsMapConstantMirror(mirrors, constant);
+  } else if (constant is TypeConstant) {
+    return new Dart2JsTypeConstantMirror(mirrors, constant);
+  } else if (constant is FunctionConstant) {
+    return new Dart2JsConstantMirror(mirrors, constant);
+  } else if (constant is NullConstant) {
+    return new Dart2JsNullConstantMirror(mirrors, constant);
+  } else if (constant is ConstructedConstant) {
+    return new Dart2JsConstructedConstantMirror(mirrors, constant);
+  }
+  mirrors.compiler.internalError("Unexpected constant $constant");
+}
+
+class Dart2JsMethodKind {
+  static const Dart2JsMethodKind REGULAR = const Dart2JsMethodKind("regular");
+  static const Dart2JsMethodKind GENERATIVE =
+      const Dart2JsMethodKind("generative");
+  static const Dart2JsMethodKind REDIRECTING =
+      const Dart2JsMethodKind("redirecting");
+  static const Dart2JsMethodKind CONST = const Dart2JsMethodKind("const");
+  static const Dart2JsMethodKind FACTORY = const Dart2JsMethodKind("factory");
+  static const Dart2JsMethodKind GETTER = const Dart2JsMethodKind("getter");
+  static const Dart2JsMethodKind SETTER = const Dart2JsMethodKind("setter");
+  static const Dart2JsMethodKind OPERATOR = const Dart2JsMethodKind("operator");
+
+  final String text;
+
+  const Dart2JsMethodKind(this.text);
+
+  String toString() => text;
+}
+
+
+String _getOperatorFromOperatorName(String name) {
+  Map<String, String> mapping = const {
+    'eq': '==',
+    'not': '~',
+    'index': '[]',
+    'indexSet': '[]=',
+    'mul': '*',
+    'div': '/',
+    'mod': '%',
+    'tdiv': '~/',
+    'add': '+',
+    'sub': '-',
+    'shl': '<<',
+    'shr': '>>',
+    'ge': '>=',
+    'gt': '>',
+    'le': '<=',
+    'lt': '<',
+    'and': '&',
+    'xor': '^',
+    'or': '|',
+  };
+  String newName = mapping[name];
+  if (newName == null) {
+    throw new Exception('Unhandled operator name: $name');
+  }
+  return newName;
+}
+
+DiagnosticListener get _diagnosticListener {
+  return const Dart2JsDiagnosticListener();
+}
+
+class Dart2JsDiagnosticListener implements DiagnosticListener {
+  const Dart2JsDiagnosticListener();
+
+  void cancel(String reason, {node, token, instruction, element}) {
+    print(reason);
+  }
+
+  void log(message) {
+    print(message);
+  }
+
+  void internalError(String message,
+                     {Node node, Token token, HInstruction instruction,
+                      Element element}) {
+    cancel('Internal error: $message', node: node, token: token,
+           instruction: instruction, element: element);
+  }
+
+  void internalErrorOnElement(Element element, String message) {
+    internalError(message, element: element);
+  }
+
+  SourceSpan spanFromSpannable(Node node, [Uri uri]) {
+    // TODO(johnniwinther): implement this.
+    throw 'unimplemented';
+  }
+
+  void reportMessage(SourceSpan span, Diagnostic message,
+                     diagnostics.Diagnostic kind) {
+    // TODO(johnniwinther): implement this.
+    throw 'unimplemented';
+  }
+
+  bool onDeprecatedFeature(Spannable span, String feature) {
+    // TODO(johnniwinther): implement this?
+    throw 'unimplemented';
+  }
+}
+
+//------------------------------------------------------------------------------
+// Compilation implementation
+//------------------------------------------------------------------------------
+
+// TODO(johnniwinther): Support client configurable handlers/providers.
+class Dart2JsCompilation implements Compilation {
+  Compiler _compiler;
+  final Uri cwd;
+  final SourceFileProvider provider;
+
+  Dart2JsCompilation(Path script, Path libraryRoot,
+                     [Path packageRoot, List<String> opts = const <String>[]])
+      : cwd = getCurrentDirectory(),
+        provider = new SourceFileProvider() {
+    var handler = new FormattingDiagnosticHandler(provider);
+    var libraryUri = cwd.resolve(libraryRoot.toString());
+    var packageUri;
+    if (packageRoot != null) {
+      packageUri = cwd.resolve(packageRoot.toString());
+    } else {
+      packageUri = libraryUri;
+    }
+    _compiler = new Compiler(provider.readStringFromUri,
+                                 null,
+                                 handler.diagnosticHandler,
+                                 libraryUri, packageUri, opts);
+    var scriptUri = cwd.resolve(script.toString());
+    // TODO(johnniwinther): Detect file not found
+    _compiler.run(scriptUri);
+  }
+
+  Dart2JsCompilation.library(List<Path> libraries, Path libraryRoot,
+                     [Path packageRoot, List<String> opts = const <String>[]])
+      : cwd = getCurrentDirectory(),
+        provider = new SourceFileProvider() {
+    var libraryUri = cwd.resolve(libraryRoot.toString());
+    var packageUri;
+    if (packageRoot != null) {
+      packageUri = cwd.resolve(packageRoot.toString());
+    } else {
+      packageUri = libraryUri;
+    }
+    opts = new List<String>.from(opts);
+    opts.add('--analyze-only');
+    opts.add('--analyze-all');
+    _compiler = new Compiler(provider.readStringFromUri,
+                                 null,
+                                 silentDiagnosticHandler,
+                                 libraryUri, packageUri, opts);
+    var librariesUri = <Uri>[];
+    for (Path library in libraries) {
+      librariesUri.add(cwd.resolve(library.toString()));
+      // TODO(johnniwinther): Detect file not found
+    }
+    _compiler.librariesToAnalyzeWhenRun = librariesUri;
+    _compiler.run(null);
+  }
+
+  MirrorSystem get mirrors => new Dart2JsMirrorSystem(_compiler);
+
+  Future<String> compileToJavaScript() =>
+      new Future<String>.immediate(_compiler.assembledCode);
+}
+
+
+//------------------------------------------------------------------------------
+// Dart2Js specific extensions of mirror interfaces
+//------------------------------------------------------------------------------
+
+abstract class Dart2JsMirror implements Mirror {
+  Dart2JsMirrorSystem get mirrors;
+}
+
+abstract class Dart2JsDeclarationMirror extends Dart2JsMirror
+    implements DeclarationMirror {
+
+  bool get isTopLevel => owner != null && owner is LibraryMirror;
+
+  bool get isPrivate => _isPrivate(simpleName);
+
+  /**
+   * Returns the first token for the source of this declaration, not including
+   * metadata annotations.
+   */
+  Token getBeginToken();
+
+  /**
+   * Returns the last token for the source of this declaration.
+   */
+  Token getEndToken();
+
+  /**
+   * Returns the script for the source of this declaration.
+   */
+  Script getScript();
+}
+
+abstract class Dart2JsTypeMirror extends Dart2JsDeclarationMirror
+    implements TypeMirror {
+}
+
+abstract class Dart2JsElementMirror extends Dart2JsDeclarationMirror {
+  final Dart2JsMirrorSystem mirrors;
+  final Element _element;
+  List<InstanceMirror> _metadata;
+
+  Dart2JsElementMirror(this.mirrors, this._element) {
+    assert (mirrors != null);
+    assert (_element != null);
+  }
+
+  String get simpleName => _element.name.slowToString();
+
+  String get displayName => simpleName;
+
+  /**
+   * Computes the first token for this declaration using the begin token of the
+   * element node or element position as indicator.
+   */
+  Token getBeginToken() {
+    // TODO(johnniwinther): Avoid calling [parseNode].
+    Node node = _element.parseNode(mirrors.compiler);
+    if (node == null) {
+      return _element.position();
+    }
+    return node.getBeginToken();
+  }
+
+  /**
+   * Computes the last token for this declaration using the end token of the
+   * element node or element position as indicator.
+   */
+  Token getEndToken() {
+    // TODO(johnniwinther): Avoid calling [parseNode].
+    Node node = _element.parseNode(mirrors.compiler);
+    if (node == null) {
+      return _element.position();
+    }
+    return node.getEndToken();
+  }
+
+  /**
+   * Returns the first token for the source of this declaration, including
+   * metadata annotations.
+   */
+  Token getFirstToken() {
+    if (!_element.metadata.isEmpty) {
+      for (MetadataAnnotation metadata in _element.metadata) {
+        if (metadata.beginToken != null) {
+          return metadata.beginToken;
+        }
+      }
+    }
+    return getBeginToken();
+  }
+
+  Script getScript() => _element.getCompilationUnit().script;
+
+  SourceLocation get location {
+    Token beginToken = getFirstToken();
+    Script script = getScript();
+    SourceSpan span;
+    if (beginToken == null) {
+      span = new SourceSpan(script.uri, 0, 0);
+    } else {
+      Token endToken = getEndToken();
+      span = mirrors.compiler.spanFromTokens(beginToken, endToken, script.uri);
+    }
+    return new Dart2JsSourceLocation(script, span);
+  }
+
+  String toString() => _element.toString();
+
+  int get hashCode => qualifiedName.hashCode;
+
+  void _appendCommentTokens(Token commentToken) {
+    while (commentToken != null && commentToken.kind == COMMENT_TOKEN) {
+      _metadata.add(new Dart2JsCommentInstanceMirror(
+          mirrors, commentToken.slowToString()));
+      commentToken = commentToken.next;
+    }
+  }
+
+  List<InstanceMirror> get metadata {
+    if (_metadata == null) {
+      _metadata = <InstanceMirror>[];
+      for (MetadataAnnotation metadata in _element.metadata) {
+        _appendCommentTokens(mirrors.compiler.commentMap[metadata.beginToken]);
+        metadata.ensureResolved(mirrors.compiler);
+        _metadata.add(
+            _convertConstantToInstanceMirror(mirrors, metadata.value));
+      }
+      _appendCommentTokens(mirrors.compiler.commentMap[getBeginToken()]);
+    }
+    // TODO(johnniwinther): Return an unmodifiable list instead.
+    return new List<InstanceMirror>.from(_metadata);
+  }
+}
+
+abstract class Dart2JsMemberMirror extends Dart2JsElementMirror
+    implements MemberMirror {
+
+  Dart2JsMemberMirror(Dart2JsMirrorSystem system, Element element)
+      : super(system, element);
+
+  bool get isConstructor => false;
+
+  bool get isVariable => false;
+
+  bool get isMethod => false;
+
+  bool get isStatic => false;
+
+  bool get isParameter => false;
+}
+
+//------------------------------------------------------------------------------
+// Mirror system implementation.
+//------------------------------------------------------------------------------
+
+class Dart2JsMirrorSystem implements MirrorSystem {
+  final Compiler compiler;
+  Map<String, Dart2JsLibraryMirror> _libraries;
+  Map<LibraryElement, Dart2JsLibraryMirror> _libraryMap;
+
+  Dart2JsMirrorSystem(this.compiler)
+    : _libraryMap = new Map<LibraryElement, Dart2JsLibraryMirror>();
+
+  void _ensureLibraries() {
+    if (_libraries == null) {
+      _libraries = <String, Dart2JsLibraryMirror>{};
+      compiler.libraries.forEach((_, LibraryElement v) {
+        var mirror = new Dart2JsLibraryMirror(mirrors, v);
+        _libraries[mirror.simpleName] = mirror;
+        _libraryMap[v] = mirror;
+      });
+    }
+  }
+
+  Map<String, LibraryMirror> get libraries {
+    _ensureLibraries();
+    return new ImmutableMapWrapper<String, LibraryMirror>(_libraries);
+  }
+
+  Dart2JsLibraryMirror _getLibrary(LibraryElement element) =>
+      _libraryMap[element];
+
+  Dart2JsMirrorSystem get mirrors => this;
+
+  TypeMirror get dynamicType =>
+      _convertTypeToTypeMirror(this, compiler.types.dynamicType, null);
+
+  TypeMirror get voidType =>
+      _convertTypeToTypeMirror(this, compiler.types.voidType, null);
+}
+
+abstract class Dart2JsContainerMirror extends Dart2JsElementMirror
+    implements ContainerMirror {
+  Map<String, MemberMirror> _members;
+
+  Dart2JsContainerMirror(Dart2JsMirrorSystem system, Element element)
+      : super(system, element);
+
+  void _ensureMembers();
+
+  Map<String, MemberMirror> get members {
+    _ensureMembers();
+    return new ImmutableMapWrapper<String, MemberMirror>(_members);
+  }
+
+  Map<String, MethodMirror> get functions {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members,
+        (MemberMirror member) => member is MethodMirror ? member : null);
+  }
+
+  Map<String, MethodMirror> get getters {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members,
+        (MemberMirror member) =>
+            member is MethodMirror && (member as MethodMirror).isGetter ?
+                member : null);
+  }
+
+  Map<String, MethodMirror> get setters {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members,
+        (MemberMirror member) =>
+            member is MethodMirror && (member as MethodMirror).isSetter ?
+                member : null);
+  }
+
+  Map<String, VariableMirror> get variables {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, VariableMirror>(
+        _members,
+        (MemberMirror member) => member is VariableMirror ? member : null);
+  }
+}
+
+class Dart2JsLibraryMirror extends Dart2JsContainerMirror
+    implements LibraryMirror {
+  Map<String, ClassMirror> _classes;
+
+  Dart2JsLibraryMirror(Dart2JsMirrorSystem system, LibraryElement library)
+      : super(system, library);
+
+  LibraryElement get _library => _element;
+
+  Uri get uri => _library.canonicalUri;
+
+  DeclarationMirror get owner => null;
+
+  bool get isPrivate => false;
+
+  LibraryMirror library() => this;
+
+  /**
+   * Returns the library name (for libraries with a #library tag) or the script
+   * file name (for scripts without a #library tag). The latter case is used to
+   * provide a 'library name' for scripts, to use for instance in dartdoc.
+   */
+  String get simpleName {
+    if (_library.libraryTag != null) {
+      // TODO(ahe): Remove StringNode check when old syntax is removed.
+      StringNode name = _library.libraryTag.name.asStringNode();
+      if (name != null) {
+        return name.dartString.slowToString();
+      } else {
+        return _library.libraryTag.name.toString();
+      }
+    } else {
+      // Use the file name as script name.
+      String path = _library.canonicalUri.path;
+      return path.substring(path.lastIndexOf('/') + 1);
+    }
+  }
+
+  String get qualifiedName => simpleName;
+
+  void _ensureClasses() {
+    if (_classes == null) {
+      _classes = <String, ClassMirror>{};
+      _library.forEachLocalMember((Element e) {
+        if (e.isClass()) {
+          ClassElement classElement = e;
+          classElement.ensureResolved(mirrors.compiler);
+          var type = new Dart2JsClassMirror.fromLibrary(this, classElement);
+          assert(invariant(_library, !_classes.containsKey(type.simpleName),
+              message: "Type name '${type.simpleName}' "
+                       "is not unique in $_library."));
+          _classes[type.simpleName] = type;
+        } else if (e.isTypedef()) {
+          var type = new Dart2JsTypedefMirror.fromLibrary(this,
+              e.computeType(mirrors.compiler));
+          assert(invariant(_library, !_classes.containsKey(type.simpleName),
+              message: "Type name '${type.simpleName}' "
+                       "is not unique in $_library."));
+          _classes[type.simpleName] = type;
+        }
+      });
+    }
+  }
+
+  void _ensureMembers() {
+    if (_members == null) {
+      _members = <String, MemberMirror>{};
+      _library.forEachLocalMember((Element e) {
+        if (!e.isClass() && !e.isTypedef()) {
+          for (var member in _convertElementMemberToMemberMirrors(this, e)) {
+            assert(!_members.containsKey(member.simpleName));
+            _members[member.simpleName] = member;
+          }
+        }
+      });
+    }
+  }
+
+  Map<String, ClassMirror> get classes {
+    _ensureClasses();
+    return new ImmutableMapWrapper<String, ClassMirror>(_classes);
+  }
+
+  /**
+   * Computes the first token of this library using the first library tag as
+   * indicator.
+   */
+  Token getBeginToken() {
+    if (_library.libraryTag != null) {
+      return _library.libraryTag.getBeginToken();
+    } else if (!_library.tags.isEmpty) {
+      return _library.tags.reverse().head.getBeginToken();
+    }
+    return null;
+  }
+
+  /**
+   * Computes the first token of this library using the last library tag as
+   * indicator.
+   */
+  Token getEndToken() {
+    if (!_library.tags.isEmpty) {
+      return _library.tags.head.getEndToken();
+    }
+    return null;
+  }
+}
+
+class Dart2JsSourceLocation implements SourceLocation {
+  final Script _script;
+  final SourceSpan _span;
+  int _line;
+  int _column;
+
+  Dart2JsSourceLocation(this._script, this._span);
+
+  int _computeLine() {
+    var sourceFile = _script.file as SourceFile;
+    if (sourceFile != null) {
+      return sourceFile.getLine(offset) + 1;
+    }
+    var index = 0;
+    var lineNumber = 0;
+    while (index <= offset && index < sourceText.length) {
+      index = sourceText.indexOf('\n', index) + 1;
+      if (index <= 0) break;
+      lineNumber++;
+    }
+    return lineNumber;
+  }
+
+  int get line {
+    if (_line == null) {
+      _line = _computeLine();
+    }
+    return _line;
+  }
+
+  int _computeColumn() {
+    if (length == 0) return 0;
+
+    var sourceFile = _script.file as SourceFile;
+    if (sourceFile != null) {
+      return sourceFile.getColumn(sourceFile.getLine(offset), offset) + 1;
+    }
+    int index = offset - 1;
+    var columnNumber = 0;
+    while (0 <= index && index < sourceText.length) {
+      columnNumber++;
+      var charCode = sourceText.charCodeAt(index);
+      if (charCode == $CR || charCode == $LF) {
+        break;
+      }
+      index--;
+    }
+    return columnNumber;
+  }
+
+  int get column {
+    if (_column == null) {
+      _column = _computeColumn();
+    }
+    return _column;
+  }
+
+  int get offset => _span.begin;
+
+  int get length => _span.end - _span.begin;
+
+  String get text => _script.text.substring(_span.begin, _span.end);
+
+  Uri get sourceUri => _script.uri;
+
+  String get sourceText => _script.text;
+}
+
+class Dart2JsParameterMirror extends Dart2JsMemberMirror
+    implements ParameterMirror {
+  final MethodMirror _method;
+  final bool isOptional;
+  final bool isNamed;
+
+  factory Dart2JsParameterMirror(Dart2JsMirrorSystem system,
+                                 MethodMirror method,
+                                 VariableElement element,
+                                 bool isOptional,
+                                 bool isNamed) {
+    if (element is FieldParameterElement) {
+      return new Dart2JsFieldParameterMirror(system,
+          method, element, isOptional, isNamed);
+    }
+    return new Dart2JsParameterMirror._normal(system,
+        method, element, isOptional, isNamed);
+  }
+
+  Dart2JsParameterMirror._normal(Dart2JsMirrorSystem system,
+                         this._method,
+                         VariableElement element,
+                         this.isOptional,
+                         this.isNamed)
+    : super(system, element);
+
+  DeclarationMirror get owner => _method;
+
+  VariableElement get _variableElement => _element;
+
+  String get qualifiedName => '${_method.qualifiedName}#${simpleName}';
+
+  TypeMirror get type => _convertTypeToTypeMirror(mirrors,
+      _variableElement.computeType(mirrors.compiler),
+      mirrors.compiler.types.dynamicType,
+      _variableElement.variables.functionSignature);
+
+
+  bool get isFinal => false;
+
+  bool get isConst => false;
+
+  String get defaultValue {
+    if (hasDefaultValue) {
+      SendSet expression = _variableElement.cachedNode.asSendSet();
+      return unparse(expression.arguments.head);
+    }
+    return null;
+  }
+
+  bool get hasDefaultValue {
+    return _variableElement.cachedNode != null &&
+        _variableElement.cachedNode is SendSet;
+  }
+
+  bool get isInitializingFormal => false;
+
+  VariableMirror get initializedField => null;
+}
+
+class Dart2JsFieldParameterMirror extends Dart2JsParameterMirror {
+
+  Dart2JsFieldParameterMirror(Dart2JsMirrorSystem system,
+                              MethodMirror method,
+                              FieldParameterElement element,
+                              bool isOptional,
+                              bool isNamed)
+      : super._normal(system, method, element, isOptional, isNamed);
+
+  FieldParameterElement get _fieldParameterElement => _element;
+
+  TypeMirror get type {
+    if (_fieldParameterElement.variables.cachedNode.type != null) {
+      return super.type;
+    }
+    return _convertTypeToTypeMirror(mirrors,
+      _fieldParameterElement.fieldElement.computeType(mirrors.compiler),
+      mirrors.compiler.types.dynamicType,
+      _variableElement.variables.functionSignature);
+  }
+
+  bool get isInitializingFormal => true;
+
+  VariableMirror get initializedField => new Dart2JsFieldMirror(
+      _method.owner, _fieldParameterElement.fieldElement);
+}
+
+//------------------------------------------------------------------------------
+// Declarations
+//------------------------------------------------------------------------------
+class Dart2JsClassMirror extends Dart2JsContainerMirror
+    implements Dart2JsTypeMirror, ClassMirror {
+  final Dart2JsLibraryMirror library;
+  List<TypeVariableMirror> _typeVariables;
+
+  Dart2JsClassMirror(Dart2JsMirrorSystem system, ClassElement _class)
+      : this.library = system._getLibrary(_class.getLibrary()),
+        super(system, _class);
+
+  ClassElement get _class => _element;
+
+  Dart2JsClassMirror.fromLibrary(Dart2JsLibraryMirror library,
+                                 ClassElement _class)
+      : this.library = library,
+        super(library.mirrors, _class);
+
+  DeclarationMirror get owner => library;
+
+  String get qualifiedName => '${library.qualifiedName}.${simpleName}';
+
+  void _ensureMembers() {
+    if (_members == null) {
+      _members = <String, Dart2JsMemberMirror>{};
+      _class.forEachMember((_, e) {
+        for (var member in _convertElementMemberToMemberMirrors(this, e)) {
+          assert(!_members.containsKey(member.simpleName));
+          _members[member.simpleName] = member;
+        }
+      });
+    }
+  }
+
+  Map<String, MethodMirror> get methods => functions;
+
+  Map<String, MethodMirror> get constructors {
+    _ensureMembers();
+    return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>(
+        _members, (m) => m.isConstructor ? m : null);
+  }
+
+  bool get isObject => _class == mirrors.compiler.objectClass;
+
+  bool get isDynamic => false;
+
+  bool get isVoid => false;
+
+  bool get isTypeVariable => false;
+
+  bool get isTypedef => false;
+
+  bool get isFunction => false;
+
+  ClassMirror get originalDeclaration => this;
+
+  ClassMirror get superclass {
+    if (_class.supertype != null) {
+      return new Dart2JsInterfaceTypeMirror(mirrors, _class.supertype);
+    }
+    return null;
+  }
+
+  List<ClassMirror> get superinterfaces {
+    var list = <ClassMirror>[];
+    Link<DartType> link = _class.interfaces;
+    while (!link.isEmpty) {
+      var type = _convertTypeToTypeMirror(mirrors, link.head,
+                                          mirrors.compiler.types.dynamicType);
+      list.add(type);
+      link = link.tail;
+    }
+    return list;
+  }
+
+  bool get isClass => !_class.isInterface();
+
+  bool get isInterface => _class.isInterface();
+
+  bool get isAbstract => _class.modifiers.isAbstract();
+
+  bool get isOriginalDeclaration => true;
+
+  List<TypeMirror> get typeArguments {
+    throw new UnsupportedError(
+        'Declarations do not have type arguments');
+  }
+
+  List<TypeVariableMirror> get typeVariables {
+    if (_typeVariables == null) {
+      _typeVariables = <TypeVariableMirror>[];
+      _class.ensureResolved(mirrors.compiler);
+      for (TypeVariableType typeVariable in _class.typeVariables) {
+        _typeVariables.add(
+            new Dart2JsTypeVariableMirror(mirrors, typeVariable));
+      }
+    }
+    return _typeVariables;
+  }
+
+  /**
+   * Returns the default type for this interface.
+   */
+  ClassMirror get defaultFactory {
+    if (_class.defaultClass != null) {
+      return new Dart2JsInterfaceTypeMirror(mirrors, _class.defaultClass);
+    }
+    return null;
+  }
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! ClassMirror) {
+      return false;
+    }
+    if (library != other.library) {
+      return false;
+    }
+    if (!identical(isOriginalDeclaration, other.isOriginalDeclaration)) {
+      return false;
+    }
+    return qualifiedName == other.qualifiedName;
+  }
+}
+
+class Dart2JsTypedefMirror extends Dart2JsTypeElementMirror
+    implements Dart2JsTypeMirror, TypedefMirror {
+  final Dart2JsLibraryMirror _library;
+  List<TypeVariableMirror> _typeVariables;
+  TypeMirror _definition;
+
+  Dart2JsTypedefMirror(Dart2JsMirrorSystem system, TypedefType _typedef)
+      : this._library = system._getLibrary(_typedef.element.getLibrary()),
+        super(system, _typedef);
+
+  Dart2JsTypedefMirror.fromLibrary(Dart2JsLibraryMirror library,
+                                   TypedefType _typedef)
+      : this._library = library,
+        super(library.mirrors, _typedef);
+
+  TypedefType get _typedef => _type;
+
+  String get qualifiedName => '${library.qualifiedName}.${simpleName}';
+
+  LibraryMirror get library => _library;
+
+  bool get isTypedef => true;
+
+  List<TypeMirror> get typeArguments {
+    throw new UnsupportedError(
+        'Declarations do not have type arguments');
+  }
+
+  List<TypeVariableMirror> get typeVariables {
+    if (_typeVariables == null) {
+      _typeVariables = <TypeVariableMirror>[];
+      for (TypeVariableType typeVariable in _typedef.typeArguments) {
+        _typeVariables.add(
+            new Dart2JsTypeVariableMirror(mirrors, typeVariable));
+      }
+    }
+    return _typeVariables;
+  }
+
+  TypeMirror get value {
+    if (_definition == null) {
+      // TODO(johnniwinther): Should be [ensureResolved].
+      mirrors.compiler.resolveTypedef(_typedef.element);
+      _definition = _convertTypeToTypeMirror(
+          mirrors,
+          _typedef.element.alias,
+          mirrors.compiler.types.dynamicType,
+          _typedef.element.functionSignature);
+    }
+    return _definition;
+  }
+
+  ClassMirror get originalDeclaration => this;
+
+  // TODO(johnniwinther): How should a typedef respond to these?
+  ClassMirror get superclass => null;
+
+  List<ClassMirror> get superinterfaces => const <ClassMirror>[];
+
+  bool get isClass => false;
+
+  bool get isInterface => false;
+
+  bool get isOriginalDeclaration => true;
+
+  bool get isAbstract => false;
+}
+
+class Dart2JsTypeVariableMirror extends Dart2JsTypeElementMirror
+    implements TypeVariableMirror {
+  final TypeVariableType _typeVariableType;
+  ClassMirror _declarer;
+
+  Dart2JsTypeVariableMirror(Dart2JsMirrorSystem system,
+                            TypeVariableType typeVariableType)
+    : this._typeVariableType = typeVariableType,
+      super(system, typeVariableType) {
+      assert(_typeVariableType != null);
+  }
+
+
+  String get qualifiedName => '${declarer.qualifiedName}.${simpleName}';
+
+  ClassMirror get declarer {
+    if (_declarer == null) {
+      if (_typeVariableType.element.enclosingElement.isClass()) {
+        _declarer = new Dart2JsClassMirror(mirrors,
+            _typeVariableType.element.enclosingElement);
+      } else if (_typeVariableType.element.enclosingElement.isTypedef()) {
+        _declarer = new Dart2JsTypedefMirror(mirrors,
+            _typeVariableType.element.enclosingElement.computeType(
+                mirrors.compiler));
+      }
+    }
+    return _declarer;
+  }
+
+  LibraryMirror get library => declarer.library;
+
+  DeclarationMirror get owner => declarer;
+
+  bool get isTypeVariable => true;
+
+  TypeMirror get upperBound => _convertTypeToTypeMirror(
+      mirrors,
+      _typeVariableType.element.bound,
+      mirrors.compiler.objectClass.computeType(mirrors.compiler));
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! TypeVariableMirror) {
+      return false;
+    }
+    if (declarer != other.declarer) {
+      return false;
+    }
+    return qualifiedName == other.qualifiedName;
+  }
+}
+
+
+//------------------------------------------------------------------------------
+// Types
+//------------------------------------------------------------------------------
+
+abstract class Dart2JsTypeElementMirror extends Dart2JsElementMirror
+    implements Dart2JsTypeMirror {
+  final DartType _type;
+
+  Dart2JsTypeElementMirror(Dart2JsMirrorSystem system, DartType type)
+    : super(system, type.element),
+      this._type = type;
+
+  String get simpleName => _type.name.slowToString();
+
+  DeclarationMirror get owner => library;
+
+  LibraryMirror get library {
+    return mirrors._getLibrary(_type.element.getLibrary());
+  }
+
+  bool get isObject => false;
+
+  bool get isVoid => false;
+
+  bool get isDynamic => false;
+
+  bool get isTypeVariable => false;
+
+  bool get isTypedef => false;
+
+  bool get isFunction => false;
+
+  String toString() => _type.toString();
+
+  Map<String, MemberMirror> get members => const <String, MemberMirror>{};
+
+  Map<String, MethodMirror> get constructors => const <String, MethodMirror>{};
+
+  Map<String, MethodMirror> get methods => const <String, MethodMirror>{};
+
+  Map<String, MethodMirror> get getters => const <String, MethodMirror>{};
+
+  Map<String, MethodMirror> get setters => const <String, MethodMirror>{};
+
+  Map<String, VariableMirror> get variables => const <String, VariableMirror>{};
+
+  ClassMirror get defaultFactory => null;
+}
+
+class Dart2JsInterfaceTypeMirror extends Dart2JsTypeElementMirror
+    implements ClassMirror {
+  List<TypeMirror> _typeArguments;
+
+  Dart2JsInterfaceTypeMirror(Dart2JsMirrorSystem system,
+                             InterfaceType interfaceType)
+      : super(system, interfaceType);
+
+  InterfaceType get _interfaceType => _type;
+
+  String get qualifiedName => originalDeclaration.qualifiedName;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MemberMirror> get members => originalDeclaration.members;
+
+  bool get isObject => mirrors.compiler.objectClass == _type.element;
+
+  bool get isDynamic => mirrors.compiler.dynamicClass == _type.element;
+
+  ClassMirror get originalDeclaration
+      => new Dart2JsClassMirror(mirrors, _type.element);
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  ClassMirror get superclass => originalDeclaration.superclass;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces;
+
+  bool get isClass => originalDeclaration.isClass;
+
+  bool get isInterface => originalDeclaration.isInterface;
+
+  bool get isAbstract => originalDeclaration.isAbstract;
+
+  bool get isPrivate => originalDeclaration.isPrivate;
+
+  bool get isOriginalDeclaration => false;
+
+  List<TypeMirror> get typeArguments {
+    if (_typeArguments == null) {
+      _typeArguments = <TypeMirror>[];
+      if (!_interfaceType.isRaw) {
+        Link<DartType> type = _interfaceType.typeArguments;
+        while (type != null && type.head != null) {
+          _typeArguments.add(_convertTypeToTypeMirror(mirrors, type.head,
+              mirrors.compiler.types.dynamicType));
+          type = type.tail;
+        }
+      }
+    }
+    return _typeArguments;
+  }
+
+  List<TypeVariableMirror> get typeVariables =>
+      originalDeclaration.typeVariables;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get constructors =>
+      originalDeclaration.constructors;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get methods => originalDeclaration.methods;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get setters => originalDeclaration.setters;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MethodMirror> get getters => originalDeclaration.getters;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, VariableMirror> get variables => originalDeclaration.variables;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables?
+  ClassMirror get defaultFactory => originalDeclaration.defaultFactory;
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! ClassMirror) {
+      return false;
+    }
+    if (other.isOriginalDeclaration) {
+      return false;
+    }
+    if (originalDeclaration != other.originalDeclaration) {
+      return false;
+    }
+    var thisTypeArguments = typeArguments.iterator;
+    var otherTypeArguments = other.typeArguments.iterator;
+    while (thisTypeArguments.moveNext()) {
+      if (!otherTypeArguments.moveNext()) return false;
+      if (thisTypeArguments.current != otherTypeArguments.current) {
+        return false;
+      }
+    }
+    return !otherTypeArguments.moveNext();
+  }
+}
+
+
+class Dart2JsFunctionTypeMirror extends Dart2JsTypeElementMirror
+    implements FunctionTypeMirror {
+  final FunctionSignature _functionSignature;
+  List<ParameterMirror> _parameters;
+
+  Dart2JsFunctionTypeMirror(Dart2JsMirrorSystem system,
+                             FunctionType functionType, this._functionSignature)
+      : super(system, functionType) {
+    assert (_functionSignature != null);
+  }
+
+  FunctionType get _functionType => _type;
+
+  // TODO(johnniwinther): Is this the qualified name of a function type?
+  String get qualifiedName => originalDeclaration.qualifiedName;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  Map<String, MemberMirror> get members {
+    var method = callMethod;
+    if (method != null) {
+      var map = new Map<String, MemberMirror>.from(
+          originalDeclaration.members);
+      var name = method.qualifiedName;
+      assert(!map.containsKey(name));
+      map[name] = method;
+      return new ImmutableMapWrapper<String, MemberMirror>(map);
+    }
+    return originalDeclaration.members;
+  }
+
+  bool get isFunction => true;
+
+  MethodMirror get callMethod => _convertElementMethodToMethodMirror(
+      mirrors._getLibrary(_functionType.element.getLibrary()),
+      _functionType.element);
+
+  ClassMirror get originalDeclaration
+      => new Dart2JsClassMirror(mirrors, mirrors.compiler.functionClass);
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  ClassMirror get superclass => originalDeclaration.superclass;
+
+  // TODO(johnniwinther): Substitute type arguments for type variables.
+  List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces;
+
+  bool get isClass => originalDeclaration.isClass;
+
+  bool get isInterface => originalDeclaration.isInterface;
+
+  bool get isPrivate => originalDeclaration.isPrivate;
+
+  bool get isOriginalDeclaration => false;
+
+  bool get isAbstract => false;
+
+  List<TypeMirror> get typeArguments => const <TypeMirror>[];
+
+  List<TypeVariableMirror> get typeVariables =>
+      originalDeclaration.typeVariables;
+
+  TypeMirror get returnType {
+    return _convertTypeToTypeMirror(mirrors, _functionType.returnType,
+                                    mirrors.compiler.types.dynamicType);
+  }
+
+  List<ParameterMirror> get parameters {
+    if (_parameters == null) {
+      _parameters = _parametersFromFunctionSignature(mirrors, callMethod,
+                                                     _functionSignature);
+    }
+    return _parameters;
+  }
+}
+
+class Dart2JsVoidMirror extends Dart2JsTypeElementMirror {
+
+  Dart2JsVoidMirror(Dart2JsMirrorSystem system, VoidType voidType)
+      : super(system, voidType);
+
+  VoidType get _voidType => _type;
+
+  String get qualifiedName => simpleName;
+
+  /**
+   * The void type has no location.
+   */
+  SourceLocation get location => null;
+
+  /**
+   * The void type has no library.
+   */
+  LibraryMirror get library => null;
+
+  bool get isVoid => true;
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! TypeMirror) {
+      return false;
+    }
+    return other.isVoid;
+  }
+}
+
+
+class Dart2JsDynamicMirror extends Dart2JsTypeElementMirror {
+  Dart2JsDynamicMirror(Dart2JsMirrorSystem system, InterfaceType voidType)
+      : super(system, voidType);
+
+  InterfaceType get _dynamicType => _type;
+
+  String get qualifiedName => simpleName;
+
+  /**
+   * The dynamic type has no location.
+   */
+  SourceLocation get location => null;
+
+  /**
+   * The dynamic type has no library.
+   */
+  LibraryMirror get library => null;
+
+  bool get isDynamic => true;
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is! TypeMirror) {
+      return false;
+    }
+    return other.isDynamic;
+  }
+}
+
+//------------------------------------------------------------------------------
+// Member mirrors implementation.
+//------------------------------------------------------------------------------
+
+class Dart2JsMethodMirror extends Dart2JsMemberMirror
+    implements MethodMirror {
+  final Dart2JsContainerMirror _objectMirror;
+  final String simpleName;
+  final String displayName;
+  final String constructorName;
+  final String operatorName;
+  final Dart2JsMethodKind _kind;
+
+  Dart2JsMethodMirror._internal(Dart2JsContainerMirror objectMirror,
+      FunctionElement function,
+      String this.simpleName,
+      String this.displayName,
+      String this.constructorName,
+      String this.operatorName,
+      Dart2JsMethodKind this._kind)
+      : this._objectMirror = objectMirror,
+        super(objectMirror.mirrors, function);
+
+  factory Dart2JsMethodMirror(Dart2JsContainerMirror objectMirror,
+                              FunctionElement function) {
+    String realName = function.name.slowToString();
+    // TODO(ahe): This method should not be calling
+    // Elements.operatorNameToIdentifier.
+    String simpleName =
+        Elements.operatorNameToIdentifier(function.name).slowToString();
+    String displayName;
+    String constructorName = null;
+    String operatorName = null;
+    Dart2JsMethodKind kind;
+    if (function.kind == ElementKind.GETTER) {
+      kind = Dart2JsMethodKind.GETTER;
+      displayName = simpleName;
+    } else if (function.kind == ElementKind.SETTER) {
+      kind = Dart2JsMethodKind.SETTER;
+      displayName = simpleName;
+      simpleName = '$simpleName=';
+    } else if (function.kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
+      // TODO(johnniwinther): Support detection of redirecting constructors.
+      constructorName = '';
+      int dollarPos = simpleName.indexOf('\$');
+      if (dollarPos != -1) {
+        constructorName = simpleName.substring(dollarPos + 1);
+        simpleName = simpleName.substring(0, dollarPos);
+        // Simple name is TypeName.constructorName.
+        simpleName = '$simpleName.$constructorName';
+      } else {
+        // Simple name is TypeName.
+      }
+      if (function.modifiers.isConst()) {
+        kind = Dart2JsMethodKind.CONST;
+      } else {
+        kind = Dart2JsMethodKind.GENERATIVE;
+      }
+      displayName = simpleName;
+    } else if (function.modifiers.isFactory()) {
+      kind = Dart2JsMethodKind.FACTORY;
+      constructorName = '';
+      int dollarPos = simpleName.indexOf('\$');
+      if (dollarPos != -1) {
+        constructorName = simpleName.substring(dollarPos+1);
+        simpleName = simpleName.substring(0, dollarPos);
+        simpleName = '$simpleName.$constructorName';
+      }
+      // Simple name is TypeName.constructorName.
+      displayName = simpleName;
+    } else if (realName == 'unary-') {
+      kind = Dart2JsMethodKind.OPERATOR;
+      operatorName = '-';
+      // Simple name is 'unary-'.
+      simpleName = Mirror.UNARY_MINUS;
+      // Display name is 'operator operatorName'.
+      displayName = 'operator -';
+    } else if (simpleName.startsWith('operator\$')) {
+      String str = simpleName.substring(9);
+      simpleName = 'operator';
+      kind = Dart2JsMethodKind.OPERATOR;
+      operatorName = _getOperatorFromOperatorName(str);
+      // Simple name is 'operator operatorName'.
+      simpleName = operatorName;
+      // Display name is 'operator operatorName'.
+      displayName = 'operator $operatorName';
+    } else {
+      kind = Dart2JsMethodKind.REGULAR;
+      displayName = simpleName;
+    }
+    return new Dart2JsMethodMirror._internal(objectMirror, function,
+        simpleName, displayName, constructorName, operatorName, kind);
+  }
+
+  FunctionElement get _function => _element;
+
+  String get qualifiedName
+      => '${owner.qualifiedName}.$simpleName';
+
+  DeclarationMirror get owner => _objectMirror;
+
+  bool get isTopLevel => _objectMirror is LibraryMirror;
+
+  bool get isConstructor
+      => isGenerativeConstructor || isConstConstructor ||
+         isFactoryConstructor || isRedirectingConstructor;
+
+  bool get isMethod => !isConstructor;
+
+  bool get isPrivate =>
+      isConstructor ? _isPrivate(constructorName) : _isPrivate(simpleName);
+
+  bool get isStatic => _function.modifiers.isStatic();
+
+  List<ParameterMirror> get parameters {
+    return _parametersFromFunctionSignature(mirrors, this,
+        _function.computeSignature(mirrors.compiler));
+  }
+
+  TypeMirror get returnType => _convertTypeToTypeMirror(
+      mirrors, _function.computeSignature(mirrors.compiler).returnType,
+      mirrors.compiler.types.dynamicType);
+
+  bool get isAbstract => _function.isAbstract(mirrors.compiler);
+
+  bool get isRegularMethod => !(isGetter || isSetter || isConstructor);
+
+  bool get isConstConstructor => _kind == Dart2JsMethodKind.CONST;
+
+  bool get isGenerativeConstructor => _kind == Dart2JsMethodKind.GENERATIVE;
+
+  bool get isRedirectingConstructor => _kind == Dart2JsMethodKind.REDIRECTING;
+
+  bool get isFactoryConstructor => _kind == Dart2JsMethodKind.FACTORY;
+
+  bool get isGetter => _kind == Dart2JsMethodKind.GETTER;
+
+  bool get isSetter => _kind == Dart2JsMethodKind.SETTER;
+
+  bool get isOperator => _kind == Dart2JsMethodKind.OPERATOR;
+}
+
+class Dart2JsFieldMirror extends Dart2JsMemberMirror implements VariableMirror {
+  Dart2JsContainerMirror _objectMirror;
+  VariableElement _variable;
+
+  Dart2JsFieldMirror(Dart2JsContainerMirror objectMirror,
+                     VariableElement variable)
+      : this._objectMirror = objectMirror,
+        this._variable = variable,
+        super(objectMirror.mirrors, variable);
+
+  String get qualifiedName
+      => '${owner.qualifiedName}.$simpleName';
+
+  DeclarationMirror get owner => _objectMirror;
+
+  bool get isTopLevel => _objectMirror is LibraryMirror;
+
+  bool get isVariable => true;
+
+  bool get isStatic => _variable.modifiers.isStatic();
+
+  bool get isFinal => _variable.modifiers.isFinal();
+
+  bool get isConst => _variable.modifiers.isConst();
+
+  TypeMirror get type => _convertTypeToTypeMirror(mirrors,
+      _variable.computeType(mirrors.compiler),
+      mirrors.compiler.types.dynamicType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Mirrors on constant values used for metadata.
+////////////////////////////////////////////////////////////////////////////////
+
+class Dart2JsConstantMirror extends InstanceMirror {
+  final Dart2JsMirrorSystem mirrors;
+  final Constant _constant;
+
+  Dart2JsConstantMirror(this.mirrors, this._constant);
+
+  ClassMirror get type {
+    return new Dart2JsClassMirror(mirrors,
+        _constant.computeType(mirrors.compiler).element);
+  }
+
+  bool get hasReflectee => false;
+
+  get reflectee {
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+
+  Future<InstanceMirror> getField(String fieldName) {
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+}
+
+class Dart2JsNullConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsNullConstantMirror(Dart2JsMirrorSystem mirrors, NullConstant constant)
+      : super(mirrors, constant);
+
+  NullConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => null;
+}
+
+class Dart2JsBoolConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsBoolConstantMirror(Dart2JsMirrorSystem mirrors, BoolConstant constant)
+      : super(mirrors, constant);
+
+  Dart2JsBoolConstantMirror.fromBool(Dart2JsMirrorSystem mirrors, bool value)
+      : super(mirrors, value ? new TrueConstant() : new FalseConstant());
+
+  BoolConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => _constant is TrueConstant;
+}
+
+class Dart2JsStringConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsStringConstantMirror(Dart2JsMirrorSystem mirrors,
+                              StringConstant constant)
+      : super(mirrors, constant);
+
+  Dart2JsStringConstantMirror.fromString(Dart2JsMirrorSystem mirrors,
+                                         String text)
+      : super(mirrors,
+              new StringConstant(new DartString.literal(text), null));
+
+  StringConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => _constant.value.slowToString();
+}
+
+class Dart2JsNumConstantMirror extends Dart2JsConstantMirror {
+  Dart2JsNumConstantMirror(Dart2JsMirrorSystem mirrors,
+                           NumConstant constant)
+      : super(mirrors, constant);
+
+  NumConstant get _constant => super._constant;
+
+  bool get hasReflectee => true;
+
+  get reflectee => _constant.value;
+}
+
+class Dart2JsListConstantMirror extends Dart2JsConstantMirror
+    implements ListInstanceMirror {
+  Dart2JsListConstantMirror(Dart2JsMirrorSystem mirrors,
+                            ListConstant constant)
+      : super(mirrors, constant);
+
+  ListConstant get _constant => super._constant;
+
+  int get length => _constant.length;
+
+  Future<InstanceMirror> operator[](int index) {
+    if (index < 0) throw new RangeError('Negative index');
+    if (index >= _constant.length) throw new RangeError('Index out of bounds');
+    return new Future<InstanceMirror>.immediate(
+        _convertConstantToInstanceMirror(mirrors, _constant.entries[index]));
+  }
+}
+
+class Dart2JsMapConstantMirror extends Dart2JsConstantMirror
+    implements MapInstanceMirror {
+  List<String> _listCache;
+
+  Dart2JsMapConstantMirror(Dart2JsMirrorSystem mirrors,
+                           MapConstant constant)
+      : super(mirrors, constant);
+
+  MapConstant get _constant => super._constant;
+
+  List<String> get _list {
+    if (_listCache == null) {
+      _listCache = new List<String>(_constant.keys.entries.length);
+      int index = 0;
+      for (StringConstant keyConstant in _constant.keys.entries) {
+        _listCache[index] = keyConstant.value.slowToString();
+        index++;
+      }
+    }
+    return _listCache;
+  }
+
+  int get length => _constant.length;
+
+  Collection<String> get keys {
+    // TODO(johnniwinther): Return an unmodifiable list instead.
+    return new List<String>.from(_list);
+  }
+
+  Future<InstanceMirror> operator[](String key) {
+    int index = _list.indexOf(key);
+    if (index == -1) return null;
+    return new Future<InstanceMirror>.immediate(
+        _convertConstantToInstanceMirror(mirrors, _constant.values[index]));
+  }
+}
+
+class Dart2JsTypeConstantMirror extends Dart2JsConstantMirror
+    implements TypeInstanceMirror {
+
+  Dart2JsTypeConstantMirror(Dart2JsMirrorSystem mirrors,
+                            TypeConstant constant)
+      : super(mirrors, constant);
+
+  TypeConstant get _constant => super._constant;
+
+  TypeMirror get representedType => _convertTypeToTypeMirror(
+      mirrors, _constant.representedType, mirrors.compiler.types.dynamicType);
+}
+
+class Dart2JsConstructedConstantMirror extends Dart2JsConstantMirror {
+  Map<String,Constant> _fieldMapCache;
+
+  Dart2JsConstructedConstantMirror(Dart2JsMirrorSystem mirrors,
+                                   ConstructedConstant constant)
+      : super(mirrors, constant);
+
+  ConstructedConstant get _constant => super._constant;
+
+  Map<String,Constant> get _fieldMap {
+    if (_fieldMapCache == null) {
+      _fieldMapCache = new LinkedHashMap<String,Constant>();
+      if (identical(_constant.type.element.kind, ElementKind.CLASS)) {
+        var index = 0;
+        ClassElement element = _constant.type.element;
+        element.forEachInstanceField((_, Element field) {
+          String fieldName = field.name.slowToString();
+          _fieldMapCache.putIfAbsent(fieldName, () => _constant.fields[index]);
+          index++;
+        }, includeBackendMembers: true, includeSuperMembers: true);
+      }
+    }
+    return _fieldMapCache;
+  }
+
+  Future<InstanceMirror> getField(String fieldName) {
+    Constant fieldConstant = _fieldMap[fieldName];
+    if (fieldConstant != null) {
+      return new Future<InstanceMirror>.immediate(
+          _convertConstantToInstanceMirror(mirrors, fieldConstant));
+    }
+    return super.getField(fieldName);
+  }
+}
+
+class Dart2JsCommentInstanceMirror implements CommentInstanceMirror {
+  final Dart2JsMirrorSystem mirrors;
+  final String text;
+  String _trimmedText;
+
+  Dart2JsCommentInstanceMirror(this.mirrors, this.text);
+
+  ClassMirror get type {
+    return new Dart2JsClassMirror(mirrors, mirrors.compiler.documentClass);
+  }
+
+  bool get isDocComment => text.startsWith('/**') || text.startsWith('///');
+
+  String get trimmedText {
+    if (_trimmedText == null) {
+      _trimmedText = stripComment(text);
+    }
+    return _trimmedText;
+  }
+
+  bool get hasReflectee => false;
+
+  get reflectee {
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+
+  Future<InstanceMirror> getField(String fieldName) {
+    if (fieldName == 'isDocComment') {
+      return new Future.immediate(
+          new Dart2JsBoolConstantMirror.fromBool(mirrors, isDocComment));
+    } else if (fieldName == 'text') {
+      return new Future.immediate(
+          new Dart2JsStringConstantMirror.fromString(mirrors, text));
+    } else if (fieldName == 'trimmedText') {
+      return new Future.immediate(
+          new Dart2JsStringConstantMirror.fromString(mirrors, trimmedText));
+    }
+    // TODO(johnniwinther): Which exception/error should be thrown here?
+    throw new UnsupportedError('InstanceMirror does not have a reflectee');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart
new file mode 100644
index 0000000..6f5189e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart
@@ -0,0 +1,738 @@
+// 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.
+
+library mirrors;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:uri';
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+import 'dart2js_mirror.dart';
+
+/**
+ * [Compilation] encapsulates the compilation of a program.
+ */
+abstract class Compilation {
+  /**
+   * Creates a new compilation which has [script] as its entry point.
+   */
+  factory Compilation(Path script,
+                      Path libraryRoot,
+                      [Path packageRoot,
+                       List<String> opts = const <String>[]]) {
+    return new Dart2JsCompilation(script, libraryRoot, packageRoot, opts);
+  }
+
+  /**
+   * Creates a new compilation which consists of a set of libraries, but which
+   * has no entry point. This compilation cannot generate output but can only
+   * be used for static inspection of the source code.
+   */
+  factory Compilation.library(List<Path> libraries,
+                              Path libraryRoot,
+                              [Path packageRoot,
+                               List<String> opts = const []]) {
+    return new Dart2JsCompilation.library(libraries, libraryRoot,
+                                          packageRoot, opts);
+  }
+
+  /**
+   * Returns the mirror system for this compilation.
+   */
+  final MirrorSystem mirrors;
+
+  /**
+   * Returns a future for the compiled JavaScript code.
+   */
+  Future<String> compileToJavaScript();
+}
+
+/**
+ * The main interface for the whole mirror system.
+ */
+abstract class MirrorSystem {
+  /**
+   * Returns an unmodifiable map of all libraries in this mirror system.
+   */
+  Map<String, LibraryMirror> get libraries;
+
+  /**
+   * A mirror on the [:dynamic:] type.
+   */
+  TypeMirror get dynamicType;
+
+  /**
+   * A mirror on the [:void:] type.
+   */
+  TypeMirror get voidType;
+}
+
+
+/**
+ * An entity in the mirror system.
+ */
+abstract class Mirror {
+  static const String UNARY_MINUS = 'unary-';
+
+  // TODO(johnniwinther): Do we need this on all mirrors?
+  /**
+   * Returns the mirror system which contains this mirror.
+   */
+  MirrorSystem get mirrors;
+}
+
+abstract class DeclarationMirror implements Mirror {
+  /**
+   * The simple name of the entity. The simple name is unique within the
+   * scope of the entity declaration.
+   *
+   * The simple name is in most cases the declared single identifier name of
+   * the entity, such as 'method' for a method [:void method() {...}:]. For an
+   * unnamed constructor for [:class Foo:] the simple name is 'Foo'. For a
+   * constructor for [:class Foo:] named 'named' the simple name is 'Foo.named'.
+   * For a property [:foo:] the simple name of the getter method is 'foo' and
+   * the simple name of the setter is 'foo='. For operators the simple name is
+   * the operator itself, for example '+' for [:operator +:].
+   *
+   * The simple name for the unary minus operator is [UNARY_MINUS].
+   */
+  String get simpleName;
+
+  /**
+   * The display name is the normal representation of the entity name. In most
+   * cases the display name is the simple name, but for a setter 'foo=' the
+   * display name is simply 'foo' and for the unary minus operator the display
+   * name is 'operator -'. The display name is not unique.
+   */
+  String get displayName;
+
+  /**
+   * Returns the name of this entity qualified by is enclosing context. For
+   * instance, the qualified name of a method 'method' in class 'Class' in
+   * library 'library' is 'library.Class.method'.
+   */
+  String get qualifiedName;
+
+  /**
+   * The source location of this Dart language entity.
+   */
+  SourceLocation get location;
+
+  /**
+   * A mirror on the owner of this function. This is the declaration immediately
+   * surrounding the reflectee.
+   *
+   * Note that for libraries, the owner will be [:null:].
+   */
+  DeclarationMirror get owner;
+
+  /**
+   * Is this declaration private?
+   *
+   * Note that for libraries, this will be [:false:].
+   */
+  bool get isPrivate;
+
+  /**
+   * Is this declaration top-level?
+   *
+   * This is defined to be equivalent to:
+   *    [:mirror.owner != null && mirror.owner is LibraryMirror:]
+   */
+  bool get isTopLevel;
+
+  /**
+   * A list of the metadata associated with this declaration.
+   */
+  List<InstanceMirror> get metadata;
+}
+
+abstract class ObjectMirror implements Mirror {
+  /**
+   * Invokes a getter and returns a mirror on the result. The getter
+   * can be the implicit getter for a field or a user-defined getter
+   * method.
+   */
+  Future<InstanceMirror> getField(String fieldName);
+}
+
+/**
+ * An [InstanceMirror] reflects an instance of a Dart language object.
+ */
+abstract class InstanceMirror implements ObjectMirror {
+  /**
+   * A mirror on the type of the reflectee.
+   */
+  ClassMirror get type;
+
+  /**
+   * Does [reflectee] contain the instance reflected by this mirror?
+   * This will always be true in the local case (reflecting instances
+   * in the same isolate), but only true in the remote case if this
+   * mirror reflects a simple value.
+   *
+   * A value is simple if one of the following holds:
+   *  - the value is null
+   *  - the value is of type [num]
+   *  - the value is of type [bool]
+   *  - the value is of type [String]
+   */
+  bool get hasReflectee;
+
+  /**
+   * If the [InstanceMirror] reflects an instance it is meaningful to
+   * have a local reference to, we provide access to the actual
+   * instance here.
+   *
+   * If you access [reflectee] when [hasReflectee] is false, an
+   * exception is thrown.
+   */
+  get reflectee;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on constant lists.
+ */
+abstract class ListInstanceMirror implements InstanceMirror {
+  Future<InstanceMirror> operator[](int index);
+  int get length;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on constant maps.
+ */
+abstract class MapInstanceMirror implements InstanceMirror {
+  /**
+   * Returns a collection containing all the keys in the map.
+   */
+  Collection<String> get keys;
+
+  /**
+   * Returns a future on the instance mirror of the value for the given key or
+   * null if key is not in the map.
+   */
+  Future<InstanceMirror> operator[](String key);
+
+  /**
+   * The number of {key, value} pairs in the map.
+   */
+  int get length;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on type constants.
+ */
+abstract class TypeInstanceMirror implements InstanceMirror {
+  /**
+   * Returns the type mirror for the type represented by the reflected type
+   * constant.
+   */
+  TypeMirror get representedType;
+}
+
+/**
+ * Specialized [InstanceMirror] used for reflection on comments as metadata.
+ */
+abstract class CommentInstanceMirror implements InstanceMirror {
+  /**
+   * The comment text as written in the source text.
+   */
+  String get text;
+
+  /**
+   * The comment text without the start, end, and padding text.
+   *
+   * For example, if [text] is [: /** Comment text. */ :] then the [trimmedText]
+   * is [: Comment text. :].
+   */
+  String get trimmedText;
+
+  /**
+   * Is [:true:] if this comment is a documentation comment.
+   *
+   * That is, that the comment is either enclosed in [: /** ... */ :] or starts
+   * with [: /// :].
+   */
+  bool get isDocComment;
+}
+
+/**
+ * Common interface for classes and libraries.
+ */
+abstract class ContainerMirror implements Mirror {
+
+  /**
+   * An immutable map from from names to mirrors for all members in this
+   * container.
+   */
+  Map<String, MemberMirror> get members;
+}
+
+/**
+ * A library.
+ */
+abstract class LibraryMirror implements ContainerMirror, DeclarationMirror {
+  /**
+   * An immutable map from from names to mirrors for all members in this
+   * library.
+   *
+   * The members of a library are its top-level classes, functions, variables,
+   * getters, and setters.
+   */
+  Map<String, MemberMirror> get members;
+
+  /**
+   * An immutable map from names to mirrors for all class
+   * declarations in this library.
+   */
+  Map<String, ClassMirror> get classes;
+
+  /**
+   * An immutable map from names to mirrors for all function, getter,
+   * and setter declarations in this library.
+   */
+  Map<String, MethodMirror> get functions;
+
+  /**
+   * An immutable map from names to mirrors for all getter
+   * declarations in this library.
+   */
+  Map<String, MethodMirror> get getters;
+
+  /**
+   * An immutable map from names to mirrors for all setter
+   * declarations in this library.
+   */
+  Map<String, MethodMirror> get setters;
+
+  /**
+   * An immutable map from names to mirrors for all variable
+   * declarations in this library.
+   */
+  Map<String, VariableMirror> get variables;
+
+  /**
+   * Returns the canonical URI for this library.
+   */
+  Uri get uri;
+}
+
+/**
+ * Common interface for classes, interfaces, typedefs and type variables.
+ */
+abstract class TypeMirror implements DeclarationMirror {
+  /**
+   * Returns the library in which this member resides.
+   */
+  LibraryMirror get library;
+
+  /**
+   * Is [:true:] iff this type is the [:Object:] type.
+   */
+  bool get isObject;
+
+  /**
+   * Is [:true:] iff this type is the [:dynamic:] type.
+   */
+  bool get isDynamic;
+
+  /**
+   * Is [:true:] iff this type is the void type.
+   */
+  bool get isVoid;
+
+  /**
+   * Is [:true:] iff this type is a type variable.
+   */
+  bool get isTypeVariable;
+
+  /**
+   * Is [:true:] iff this type is a typedef.
+   */
+  bool get isTypedef;
+
+  /**
+   * Is [:true:] iff this type is a function type.
+   */
+  bool get isFunction;
+}
+
+/**
+ * A class or interface type.
+ */
+abstract class ClassMirror implements TypeMirror, ContainerMirror {
+  /**
+   * A mirror on the original declaration of this type.
+   *
+   * For most classes, they are their own original declaration.  For
+   * generic classes, however, there is a distinction between the
+   * original class declaration, which has unbound type variables, and
+   * the instantiations of generic classes, which have bound type
+   * variables.
+   */
+  ClassMirror get originalDeclaration;
+
+  /**
+   * Returns the super class of this type, or null if this type is [Object] or a
+   * typedef.
+   */
+  ClassMirror get superclass;
+
+  /**
+   * Returns a list of the interfaces directly implemented by this type.
+   */
+  List<ClassMirror> get superinterfaces;
+
+  /**
+   * Is [:true:] iff this type is a class.
+   */
+  bool get isClass;
+
+  /**
+   * Is [:true:] iff this type is an interface.
+   */
+  bool get isInterface;
+
+  /**
+   * Is this the original declaration of this type?
+   *
+   * For most classes, they are their own original declaration.  For
+   * generic classes, however, there is a distinction between the
+   * original class declaration, which has unbound type variables, and
+   * the instantiations of generic classes, which have bound type
+   * variables.
+   */
+  bool get isOriginalDeclaration;
+
+  /**
+   * Is [:true:] if this class is declared abstract.
+   */
+  bool get isAbstract;
+
+  /**
+   * Returns a list of the type arguments for this type.
+   */
+  List<TypeMirror> get typeArguments;
+
+  /**
+   * Returns the list of type variables for this type.
+   */
+  List<TypeVariableMirror> get typeVariables;
+
+  /**
+   * An immutable map from from names to mirrors for all members of
+   * this type.
+   *
+   * The members of a type are its methods, fields, getters, and
+   * setters.  Note that constructors and type variables are not
+   * considered to be members of a type.
+   *
+   * This does not include inherited members.
+   */
+  Map<String, MemberMirror> get members;
+
+  /**
+   * An immutable map from names to mirrors for all method,
+   * declarations for this type.  This does not include getters and
+   * setters.
+   */
+  Map<String, MethodMirror> get methods;
+
+  /**
+   * An immutable map from names to mirrors for all getter
+   * declarations for this type.
+   */
+  Map<String, MethodMirror> get getters;
+
+  /**
+   * An immutable map from names to mirrors for all setter
+   * declarations for this type.
+   */
+  Map<String, MethodMirror> get setters;
+
+  /**
+   * An immutable map from names to mirrors for all variable
+   * declarations for this type.
+   */
+  Map<String, VariableMirror> get variables;
+
+  /**
+   * An immutable map from names to mirrors for all constructor
+   * declarations for this type.
+   */
+  Map<String, MethodMirror> get constructors;
+
+  /**
+   * Returns the default type for this interface.
+   */
+  ClassMirror get defaultFactory;
+}
+
+/**
+ * A type parameter as declared on a generic type.
+ */
+abstract class TypeVariableMirror implements TypeMirror {
+  /**
+   * Returns the bound of the type parameter.
+   */
+  TypeMirror get upperBound;
+}
+
+/**
+ * A function type.
+ */
+abstract class FunctionTypeMirror implements ClassMirror {
+  /**
+   * Returns the return type of this function type.
+   */
+  TypeMirror get returnType;
+
+  /**
+   * Returns the parameters for this function type.
+   */
+  List<ParameterMirror> get parameters;
+
+  /**
+   * Returns the call method for this function type.
+   */
+  MethodMirror get callMethod;
+}
+
+/**
+ * A typedef.
+ */
+abstract class TypedefMirror implements ClassMirror {
+  /**
+   * The defining type for this typedef.
+   *
+   * For instance [:void f(int):] for a [:typedef void f(int):].
+   */
+  TypeMirror get value;
+}
+
+/**
+ * A member of a type, i.e. a field, method or constructor.
+ */
+abstract class MemberMirror implements DeclarationMirror {
+  /**
+   * Is this member a constructor?
+   */
+  bool get isConstructor;
+
+  /**
+   * Is this member a variable?
+   *
+   * This is [:false:] for locals.
+   */
+  bool get isVariable;
+
+  /**
+   * Is this member a method?.
+   *
+   * This is [:false:] for constructors.
+   */
+  bool get isMethod;
+
+  /**
+   * Is this member declared static?
+   */
+  bool get isStatic;
+
+  /**
+   * Is this member a parameter?
+   */
+  bool get isParameter;
+}
+
+/**
+ * A field.
+ */
+abstract class VariableMirror implements MemberMirror {
+
+  /**
+   * Returns true if this field is final.
+   */
+  bool get isFinal;
+
+  /**
+   * Returns true if this field is const.
+   */
+  bool get isConst;
+
+  /**
+   * Returns the type of this field.
+   */
+  TypeMirror get type;
+}
+
+/**
+ * Common interface constructors and methods, including factories, getters and
+ * setters.
+ */
+abstract class MethodMirror implements MemberMirror {
+  /**
+   * Returns the list of parameters for this method.
+   */
+  List<ParameterMirror> get parameters;
+
+  /**
+   * Returns the return type of this method.
+   */
+  TypeMirror get returnType;
+
+  /**
+   * Is the reflectee abstract?
+   */
+  bool get isAbstract;
+
+  /**
+   * Is the reflectee a regular function or method?
+   *
+   * A function or method is regular if it is not a getter, setter, or
+   * constructor.  Note that operators, by this definition, are
+   * regular methods.
+   */
+  bool get isRegularMethod;
+
+  /**
+   * Is the reflectee a const constructor?
+   */
+  bool get isConstConstructor;
+
+  /**
+   * Is the reflectee a generative constructor?
+   */
+  bool get isGenerativeConstructor;
+
+  /**
+   * Is the reflectee a redirecting constructor?
+   */
+  bool get isRedirectingConstructor;
+
+  /**
+   * Is the reflectee a factory constructor?
+   */
+  bool get isFactoryConstructor;
+
+  /**
+   * Returns the constructor name for named constructors and factory methods,
+   * e.g. [:'bar':] for constructor [:Foo.bar:] of type [:Foo:].
+   */
+  String get constructorName;
+
+  /**
+   * Is [:true:] if this method is a getter method.
+   */
+  bool get isGetter;
+
+  /**
+   * Is [:true:] if this method is a setter method.
+   */
+  bool get isSetter;
+
+  /**
+   * Is [:true:] if this method is an operator method.
+   */
+  bool get isOperator;
+
+  /**
+   * Returns the operator name for operator methods, e.g. [:'<':] for
+   * [:operator <:]
+   */
+  String get operatorName;
+}
+
+/**
+ * A formal parameter.
+ */
+abstract class ParameterMirror implements VariableMirror {
+  /**
+   * Returns the type of this parameter.
+   */
+  TypeMirror get type;
+
+  /**
+   * Returns the default value for this parameter.
+   */
+  String get defaultValue;
+
+  /**
+   * Does this parameter have a default value?
+   */
+  bool get hasDefaultValue;
+
+  /**
+   * Is this parameter optional?
+   */
+  bool get isOptional;
+
+  /**
+   * Is this parameter named?
+   */
+  bool get isNamed;
+
+  /**
+   * Returns [:true:] iff this parameter is an initializing formal of a
+   * constructor. That is, if it is of the form [:this.x:] where [:x:] is a
+   * field.
+   */
+  bool get isInitializingFormal;
+
+  /**
+   * Returns the initialized field, if this parameter is an initializing formal.
+   */
+  VariableMirror get initializedField;
+}
+
+/**
+ * A [SourceLocation] describes the span of an entity in Dart source code.
+ * A [SourceLocation] with a non-zero [length] should be the minimum span that
+ * encloses the declaration of the mirrored entity.
+ */
+abstract class SourceLocation {
+  /**
+   * The 1-based line number for this source location.
+   *
+   * A value of 0 means that the line number is unknown.
+   */
+  int get line;
+
+  /**
+   * The 1-based column number for this source location.
+   *
+   * A value of 0 means that the column number is unknown.
+   */
+  int get column;
+
+  /**
+   * The 0-based character offset into the [sourceText] where this source
+   * location begins.
+   *
+   * A value of -1 means that the offset is unknown.
+   */
+  int get offset;
+
+  /**
+   * The number of characters in this source location.
+   *
+   * A value of 0 means that the [offset] is approximate.
+   */
+  int get length;
+
+  /**
+   * The text of the location span.
+   */
+  String get text;
+
+  /**
+   * Returns the URI where the source originated.
+   */
+  Uri get sourceUri;
+
+  /**
+   * Returns the text of this source.
+   */
+  String get sourceText;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart
new file mode 100644
index 0000000..d904089
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart
@@ -0,0 +1,161 @@
+// 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.
+
+library mirrors_util;
+
+import 'dart:collection' show Queue;
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+import 'mirrors.dart';
+
+//------------------------------------------------------------------------------
+// Utility functions for using the Mirror API
+//------------------------------------------------------------------------------
+
+/**
+ * Returns an iterable over the type declarations directly inheriting from
+ * the declaration of this type.
+ */
+Iterable<ClassMirror> computeSubdeclarations(ClassMirror type) {
+  type = type.originalDeclaration;
+  var subtypes = <ClassMirror>[];
+  type.mirrors.libraries.forEach((_, library) {
+    for (ClassMirror otherType in library.classes.values) {
+      var superClass = otherType.superclass;
+      if (superClass != null) {
+        superClass = superClass.originalDeclaration;
+        if (type.library == superClass.library) {
+          if (superClass == type) {
+             subtypes.add(otherType);
+          }
+        }
+      }
+      final superInterfaces = otherType.superinterfaces;
+      for (ClassMirror superInterface in superInterfaces) {
+        superInterface = superInterface.originalDeclaration;
+        if (type.library == superInterface.library) {
+          if (superInterface == type) {
+            subtypes.add(otherType);
+          }
+        }
+      }
+    }
+  });
+  return subtypes;
+}
+
+LibraryMirror findLibrary(MemberMirror member) {
+  DeclarationMirror owner = member.owner;
+  if (owner is LibraryMirror) {
+    return owner;
+  } else if (owner is TypeMirror) {
+    return owner.library;
+  }
+  throw new Exception('Unexpected owner: ${owner}');
+}
+
+class HierarchyIterable extends Iterable<ClassMirror> {
+  final bool includeType;
+  final ClassMirror type;
+
+  HierarchyIterable(this.type, {bool includeType})
+      : this.includeType = includeType;
+
+  Iterator<ClassMirror> get iterator =>
+      new HierarchyIterator(type, includeType: includeType);
+}
+
+/**
+ * [HierarchyIterator] iterates through the class hierarchy of the provided
+ * type.
+ *
+ * First the superclass relation is traversed, skipping [Object], next the
+ * superinterface relation and finally is [Object] visited. The supertypes are
+ * visited in breadth first order and a superinterface is visited more than once
+ * if implemented through multiple supertypes.
+ */
+class HierarchyIterator implements Iterator<ClassMirror> {
+  final Queue<ClassMirror> queue = new Queue<ClassMirror>();
+  ClassMirror object;
+  ClassMirror _current;
+
+  HierarchyIterator(ClassMirror type, {bool includeType}) {
+    if (includeType) {
+      queue.add(type);
+    } else {
+      push(type);
+    }
+  }
+
+  ClassMirror push(ClassMirror type) {
+    if (type.superclass != null) {
+      if (type.superclass.isObject) {
+        object = type.superclass;
+      } else {
+        queue.addFirst(type.superclass);
+      }
+    }
+    queue.addAll(type.superinterfaces);
+    return type;
+  }
+
+  ClassMirror get current => _current;
+
+  bool moveNext() {
+    _current = null;
+    if (queue.isEmpty) {
+      if (object == null) return false;
+      _current = object;
+      object = null;
+      return true;
+    } else {
+      _current = push(queue.removeFirst());
+      return true;
+    }
+  }
+}
+
+final RegExp _singleLineCommentStart = new RegExp(r'^///? ?(.*)');
+final RegExp _multiLineCommentStartEnd =
+    new RegExp(r'^/\*\*? ?([\s\S]*)\*/$', multiLine: true);
+final RegExp _multiLineCommentLineStart = new RegExp(r'^[ \t]*\* ?(.*)');
+
+/**
+ * Pulls the raw text out of a comment (i.e. removes the comment
+ * characters).
+ */
+String stripComment(String comment) {
+  Match match = _singleLineCommentStart.firstMatch(comment);
+  if (match != null) {
+    return match[1];
+  }
+  match = _multiLineCommentStartEnd.firstMatch(comment);
+  if (match != null) {
+    comment = match[1];
+    var sb = new StringBuffer();
+    List<String> lines = comment.split('\n');
+    for (int index = 0 ; index < lines.length ; index++) {
+      String line = lines[index];
+      if (index == 0) {
+        sb.add(line); // Add the first line unprocessed.
+        continue;
+      }
+      sb.add('\n');
+      match = _multiLineCommentLineStart.firstMatch(line);
+      if (match != null) {
+        sb.add(match[1]);
+      } else if (index < lines.length-1 || !line.trim().isEmpty) {
+        // Do not add the last line if it only contains white space.
+        // This interprets cases like
+        //     /*
+        //      * Foo
+        //      */
+        // as "\nFoo\n" and not as "\nFoo\n     ".
+        sb.add(line);
+      }
+    }
+    return sb.toString();
+  }
+  throw new ArgumentError('Invalid comment $comment');
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart
new file mode 100644
index 0000000..3470211
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart
@@ -0,0 +1,173 @@
+// 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.
+
+library util;
+
+/**
+ * An abstract map implementation. This class can be used as a superclass for
+ * implementing maps, requiring only the further implementation of the
+ * [:operator []:], [:forEach:] and [:length:] methods to provide a fully
+ * implemented immutable map.
+ */
+abstract class AbstractMap<K,V> implements Map<K,V> {
+  AbstractMap();
+
+  AbstractMap.from(Map<K,V> other) {
+    other.forEach((k,v) => this[k] = v);
+  }
+
+  void operator []=(K key, value) {
+    throw new UnsupportedError('[]= is not supported');
+  }
+
+  void clear() {
+    throw new UnsupportedError('clear() is not supported');
+  }
+
+  bool containsKey(K key) {
+    var found = false;
+    forEach((k,_) {
+      if (k == key) {
+        found = true;
+      }
+    });
+    return found;
+  }
+
+  bool containsValue(V value) {
+    var found = false;
+    forEach((_,v) {
+      if (v == value) {
+        found = true;
+      }
+    });
+    return found;
+  }
+
+  Collection<K> get keys {
+    var keys = <K>[];
+    forEach((k,_) => keys.add(k));
+    return keys;
+  }
+
+  Collection<V> get values {
+    var values = <V>[];
+    forEach((_,v) => values.add(v));
+    return values;
+  }
+
+  bool get isEmpty => length == 0;
+  V putIfAbsent(K key, V ifAbsent()) {
+    if (!containsKey(key)) {
+      V value = this[key];
+      this[key] = ifAbsent();
+      return value;
+    }
+    return null;
+  }
+
+  V remove(K key) {
+    throw new UnsupportedError('V remove(K key) is not supported');
+  }
+}
+
+/**
+ * [ImmutableMapWrapper] wraps a (mutable) map as an immutable map where all
+ * mutating operations throw [UnsupportedError] upon invocation.
+ */
+class ImmutableMapWrapper<K,V> extends AbstractMap<K,V> {
+  final Map<K,V> _map;
+
+  ImmutableMapWrapper(this._map);
+
+  int get length => _map.length;
+
+  V operator [](K key) {
+    if (key is K) {
+      return _map[key];
+    }
+    return null;
+  }
+
+  void forEach(void f(K key, V value)) {
+    _map.forEach(f);
+  }
+}
+
+/**
+ * A [Filter] function returns [:true:] iff [value] should be included.
+ */
+typedef bool Filter<V>(V value);
+
+/**
+ * An immutable map wrapper capable of filtering the input map.
+ */
+class FilteredImmutableMap<K,V> extends ImmutableMapWrapper<K,V> {
+  final Filter<V> _filter;
+
+  FilteredImmutableMap(Map<K,V> map, this._filter) : super(map);
+
+  int get length {
+    var count = 0;
+    forEach((k,v) {
+      count++;
+    });
+    return count;
+  }
+
+  void forEach(void f(K key, V value)) {
+    _map.forEach((K k, V v) {
+      if (_filter(v)) {
+        f(k, v);
+      }
+    });
+  }
+}
+
+/**
+ * An [AsFilter] takes a [value] of type [V1] and returns [value] iff it is of
+ * type [V2] or [:null:] otherwise. An [AsFilter] therefore behaves like the
+ * [:as:] expression.
+ */
+typedef V2 AsFilter<V1, V2>(V1 value);
+
+/**
+ * An immutable map wrapper capable of filtering the input map based on types.
+ * It takes an [AsFilter] function which converts the original values of type
+ * [Vin] into values of type [Vout], or returns [:null:] if the value should
+ * not be included in the filtered map.
+ */
+class AsFilteredImmutableMap<K, Vin, Vout> extends AbstractMap<K, Vout> {
+  final Map<K, Vin> _map;
+  final AsFilter<Vin, Vout> _filter;
+
+  AsFilteredImmutableMap(this._map, this._filter);
+
+  int get length {
+    var count = 0;
+    forEach((k,v) {
+      count++;
+    });
+    return count;
+  }
+
+  Vout operator [](K key) {
+    if (key is K) {
+      Vin value = _map[key];
+      if (value != null) {
+        return _filter(value);
+      }
+    }
+    return null;
+  }
+
+  void forEach(void f(K key, Vout value)) {
+    _map.forEach((K k, Vin v) {
+      var value = _filter(v);
+      if (value != null) {
+        f(k, value);
+      }
+    });
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart b/pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart
new file mode 100644
index 0000000..373f16e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart
@@ -0,0 +1,902 @@
+// 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.
+
+library native;
+
+import 'dart:collection' show Queue;
+import 'dart:uri';
+import 'dart2jslib.dart' hide SourceString;
+import 'dart_types.dart';
+import 'elements/elements.dart';
+import 'js_backend/js_backend.dart';
+import 'resolution/resolution.dart' show ResolverVisitor;
+import 'scanner/scannerlib.dart';
+import 'ssa/ssa.dart';
+import 'tree/tree.dart';
+import 'util/util.dart';
+
+
+/// This class is a temporary work-around until we get a more powerful DartType.
+class SpecialType {
+  final String name;
+  const SpecialType._(this.name);
+
+  /// The type Object, but no subtypes:
+  static const JsObject = const SpecialType._('=Object');
+
+  /// The specific implementation of List that is JavaScript Array:
+  static const JsArray = const SpecialType._('=List');
+}
+
+
+/**
+ * This could be an abstract class but we use it as a stub for the dart_backend.
+ */
+class NativeEnqueuer {
+  /// Initial entry point to native enqueuer.
+  void processNativeClasses(Iterable<LibraryElement> libraries) {}
+
+  /// Notification of a main Enqueuer worklist element.  For methods, adds
+  /// information from metadata attributes, and computes types instantiated due
+  /// to calling the method.
+  void registerElement(Element element) {}
+
+  /// Notification of native field.  Adds information from metadata attributes.
+  void handleFieldAnnotations(Element field) {}
+
+  /// Computes types instantiated due to getting a native field.
+  void registerFieldLoad(Element field) {}
+
+  /// Computes types instantiated due to setting a native field.
+  void registerFieldStore(Element field) {}
+
+  NativeBehavior getNativeBehaviorOf(Send node) => null;
+
+  /**
+   * Handles JS-calls, which can be an instantiation point for types.
+   *
+   * For example, the following code instantiates and returns native classes
+   * that are `_DOMWindowImpl` or a subtype.
+   *
+   *     JS('_DOMWindowImpl', 'window')
+   *
+   */
+  // TODO(sra): The entry from codegen will not have a resolver.
+  void registerJsCall(Send node, ResolverVisitor resolver) {}
+
+  /// Emits a summary information using the [log] function.
+  void logSummary(log(message)) {}
+}
+
+
+abstract class NativeEnqueuerBase implements NativeEnqueuer {
+
+  /**
+   * The set of all native classes.  Each native class is in [nativeClasses] and
+   * exactly one of [unusedClasses], [pendingClasses] and [registeredClasses].
+   */
+  final Set<ClassElement> nativeClasses = new Set<ClassElement>();
+
+  final Set<ClassElement> registeredClasses = new Set<ClassElement>();
+  final Set<ClassElement> pendingClasses = new Set<ClassElement>();
+  final Set<ClassElement> unusedClasses = new Set<ClassElement>();
+
+  /**
+   * Records matched constraints ([SpecialType] or [DartType]).  Once a type
+   * constraint has been matched, there is no need to match it again.
+   */
+  final Set matchedTypeConstraints = new Set();
+
+  /// Pending actions.  Classes in [pendingClasses] have action thunks in
+  /// [queue] to register the class.
+  final queue = new Queue();
+  bool flushing = false;
+
+  /// Maps JS foreign calls to their computed native behavior.
+  final Map<Node, NativeBehavior> nativeBehaviors =
+      new Map<Node, NativeBehavior>();
+
+  final Enqueuer world;
+  final Compiler compiler;
+  final bool enableLiveTypeAnalysis;
+
+  ClassElement _annotationCreatesClass;
+  ClassElement _annotationReturnsClass;
+  ClassElement _annotationJsNameClass;
+
+  /// Subclasses of [NativeEnqueuerBase] are constructed by the backend.
+  NativeEnqueuerBase(this.world, this.compiler, this.enableLiveTypeAnalysis);
+
+  void processNativeClasses(Iterable<LibraryElement> libraries) {
+    libraries.forEach(processNativeClassesInLibrary);
+    processNativeClassesInLibrary(compiler.isolateHelperLibrary);
+    if (!enableLiveTypeAnalysis) {
+      nativeClasses.forEach((c) => enqueueClass(c, 'forced'));
+      flushQueue();
+    }
+  }
+
+  void processNativeClassesInLibrary(LibraryElement library) {
+    // Use implementation to ensure the inclusion of injected members.
+    library.implementation.forEachLocalMember((Element element) {
+      if (element.isClass() && element.isNative()) {
+        processNativeClass(element);
+      }
+    });
+  }
+
+  void processNativeClass(ClassElement classElement) {
+    nativeClasses.add(classElement);
+    unusedClasses.add(classElement);
+    // Resolve class to ensure the class has valid inheritance info.
+    classElement.ensureResolved(compiler);
+  }
+
+  ClassElement get annotationCreatesClass {
+    findAnnotationClasses();
+    return _annotationCreatesClass;
+  }
+
+  ClassElement get annotationReturnsClass {
+    findAnnotationClasses();
+    return _annotationReturnsClass;
+  }
+
+  ClassElement get annotationJsNameClass {
+    findAnnotationClasses();
+    return _annotationJsNameClass;
+  }
+
+  void findAnnotationClasses() {
+    if (_annotationCreatesClass != null) return;
+    ClassElement find(name) {
+      Element e = compiler.findHelper(name);
+      if (e == null || e is! ClassElement) {
+        compiler.cancel("Could not find implementation class '${name}'");
+      }
+      return e;
+    }
+    _annotationCreatesClass = find(const SourceString('Creates'));
+    _annotationReturnsClass = find(const SourceString('Returns'));
+    _annotationJsNameClass = find(const SourceString('JSName'));
+  }
+
+  /// Returns the JSName annotation string or `null` if no JSName annotation is
+  /// present.
+  String findJsNameFromAnnotation(Element element) {
+    String name = null;
+    ClassElement annotationClass = annotationJsNameClass;
+    for (Link<MetadataAnnotation> link = element.metadata;
+         !link.isEmpty;
+         link = link.tail) {
+      MetadataAnnotation annotation = link.head.ensureResolved(compiler);
+      var value = annotation.value;
+      if (value is! ConstructedConstant) continue;
+      if (value.type is! InterfaceType) continue;
+      if (!identical(value.type.element, annotationClass)) continue;
+
+      var fields = value.fields;
+      // TODO(sra): Better validation of the constant.
+      if (fields.length != 1 || fields[0] is! StringConstant) {
+        PartialMetadataAnnotation partial = annotation;
+        compiler.cancel(
+            'Annotations needs one string: ${partial.parseNode(compiler)}');
+      }
+      String specString = fields[0].toDartString().slowToString();
+      if (name == null) {
+        name = specString;
+      } else {
+        PartialMetadataAnnotation partial = annotation;
+        compiler.cancel(
+            'Too many JSName annotations: ${partial.parseNode(compiler)}');
+      }
+    }
+    return name;
+  }
+
+  enqueueClass(ClassElement classElement, cause) {
+    assert(unusedClasses.contains(classElement));
+    unusedClasses.remove(classElement);
+    pendingClasses.add(classElement);
+    queue.add(() { processClass(classElement, cause); });
+  }
+
+  void flushQueue() {
+    if (flushing) return;
+    flushing = true;
+    while (!queue.isEmpty) {
+      (queue.removeFirst())();
+    }
+    flushing = false;
+  }
+
+  processClass(ClassElement classElement, cause) {
+    assert(!registeredClasses.contains(classElement));
+
+    bool firstTime = registeredClasses.isEmpty;
+    pendingClasses.remove(classElement);
+    registeredClasses.add(classElement);
+
+    world.registerInstantiatedClass(classElement);
+
+    // Also parse the node to know all its methods because otherwise it will
+    // only be parsed if there is a call to one of its constructors.
+    classElement.parseNode(compiler);
+
+    if (firstTime) {
+      queue.add(onFirstNativeClass);
+    }
+  }
+
+  registerElement(Element element) {
+    compiler.withCurrentElement(element, () {
+      if (element.isFunction() || element.isGetter() || element.isSetter()) {
+        handleMethodAnnotations(element);
+        if (element.isNative()) {
+          registerMethodUsed(element);
+        }
+      } else if (element.isField()) {
+        handleFieldAnnotations(element);
+        if (element.isNative()) {
+          registerFieldLoad(element);
+          registerFieldStore(element);
+        }
+      }
+    });
+  }
+
+  handleFieldAnnotations(Element element) {
+    if (element.enclosingElement.isNative()) {
+      // Exclude non-instance (static) fields - they not really native and are
+      // compiled as isolate globals.  Access of a property of a constructor
+      // function or a non-method property in the prototype chain, must be coded
+      // using a JS-call.
+      if (element.isInstanceMember()) {
+        setNativeName(element);
+      }
+    }
+  }
+
+  handleMethodAnnotations(Element method) {
+    if (isNativeMethod(method)) {
+      setNativeName(method);
+    }
+  }
+
+  /// Sets the native name of [element], either from an annotation, or
+  /// defaulting to the Dart name.
+  void setNativeName(Element element) {
+    String name = findJsNameFromAnnotation(element);
+    if (name == null) name = element.name.slowToString();
+    element.setNative(name);
+  }
+
+  bool isNativeMethod(Element element) {
+    if (!element.getLibrary().canUseNative) return false;
+    // Native method?
+    return compiler.withCurrentElement(element, () {
+      Node node = element.parseNode(compiler);
+      if (node is! FunctionExpression) return false;
+      node = node.body;
+      Token token = node.getBeginToken();
+      if (identical(token.stringValue, 'native')) return true;
+      return false;
+    });
+  }
+
+  void registerMethodUsed(Element method) {
+    processNativeBehavior(
+        NativeBehavior.ofMethod(method, compiler),
+        method);
+      flushQueue();
+  }
+
+  void registerFieldLoad(Element field) {
+    processNativeBehavior(
+        NativeBehavior.ofFieldLoad(field, compiler),
+        field);
+    flushQueue();
+  }
+
+  void registerFieldStore(Element field) {
+    processNativeBehavior(
+        NativeBehavior.ofFieldStore(field, compiler),
+        field);
+    flushQueue();
+  }
+
+  void registerJsCall(Send node, ResolverVisitor resolver) {
+    NativeBehavior behavior = NativeBehavior.ofJsCall(node, compiler, resolver);
+    processNativeBehavior(behavior, node);
+    nativeBehaviors[node] = behavior;
+    flushQueue();
+  }
+
+  NativeBehavior getNativeBehaviorOf(Send node) => nativeBehaviors[node];
+
+  processNativeBehavior(NativeBehavior behavior, cause) {
+    bool allUsedBefore = unusedClasses.isEmpty;
+    for (var type in behavior.typesInstantiated) {
+      if (matchedTypeConstraints.contains(type)) continue;
+      matchedTypeConstraints.add(type);
+      if (type is SpecialType) {
+        if (type == SpecialType.JsArray) {
+          world.registerInstantiatedClass(compiler.listClass);
+        } else if (type == SpecialType.JsObject) {
+          world.registerInstantiatedClass(compiler.objectClass);
+        }
+        continue;
+      }
+      if (type is InterfaceType) {
+        if (type.element == compiler.intClass) {
+          world.registerInstantiatedClass(compiler.intClass);
+        } else if (type.element == compiler.doubleClass) {
+          world.registerInstantiatedClass(compiler.doubleClass);
+        } else if (type.element == compiler.numClass) {
+          world.registerInstantiatedClass(compiler.doubleClass);
+          world.registerInstantiatedClass(compiler.intClass);
+        } else if (type.element == compiler.stringClass) {
+          world.registerInstantiatedClass(compiler.stringClass);
+        } else if (type.element == compiler.nullClass) {
+          world.registerInstantiatedClass(compiler.nullClass);
+        } else if (type.element == compiler.boolClass) {
+          world.registerInstantiatedClass(compiler.boolClass);
+        }
+      }
+      assert(type is DartType);
+      enqueueUnusedClassesMatching(
+          (nativeClass) => compiler.types.isSubtype(nativeClass.thisType, type),
+          cause,
+          'subtypeof($type)');
+    }
+
+    // Give an info so that library developers can compile with -v to find why
+    // all the native classes are included.
+    if (unusedClasses.isEmpty && !allUsedBefore) {
+      compiler.log('All native types marked as used due to $cause.');
+    }
+  }
+
+  enqueueUnusedClassesMatching(bool predicate(classElement),
+                               cause,
+                               [String reason]) {
+    Iterable matches = unusedClasses.where(predicate);
+    matches.forEach((c) => enqueueClass(c, cause));
+  }
+
+  onFirstNativeClass() {
+    staticUse(name) => world.registerStaticUse(compiler.findHelper(name));
+
+    staticUse(const SourceString('dynamicFunction'));
+    staticUse(const SourceString('dynamicSetMetadata'));
+    staticUse(const SourceString('defineProperty'));
+    staticUse(const SourceString('toStringForNativeObject'));
+    staticUse(const SourceString('hashCodeForNativeObject'));
+
+    addNativeExceptions();
+  }
+
+  addNativeExceptions() {
+    enqueueUnusedClassesMatching((classElement) {
+        // TODO(sra): Annotate exception classes in dart:html.
+        String name = classElement.name.slowToString();
+        if (name.contains('Exception')) return true;
+        if (name.contains('Error')) return true;
+        return false;
+      },
+      'native exception');
+  }
+}
+
+
+class NativeResolutionEnqueuer extends NativeEnqueuerBase {
+
+  NativeResolutionEnqueuer(Enqueuer world, Compiler compiler)
+    : super(world, compiler, compiler.enableNativeLiveTypeAnalysis);
+
+  void logSummary(log(message)) {
+    log('Resolved ${registeredClasses.length} native elements used, '
+        '${unusedClasses.length} native elements dead.');
+  }
+}
+
+
+class NativeCodegenEnqueuer extends NativeEnqueuerBase {
+
+  final CodeEmitterTask emitter;
+
+  final Set<ClassElement> doneAddSubtypes = new Set<ClassElement>();
+
+  NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter)
+    : super(world, compiler, compiler.enableNativeLiveTypeAnalysis);
+
+  void processNativeClasses(Iterable<LibraryElement> libraries) {
+    super.processNativeClasses(libraries);
+
+    // HACK HACK - add all the resolved classes.
+    NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer;
+    for (final classElement in enqueuer.registeredClasses) {
+      if (unusedClasses.contains(classElement)) {
+        enqueueClass(classElement, 'was resolved');
+      }
+    }
+    flushQueue();
+  }
+
+  processClass(ClassElement classElement, cause) {
+    super.processClass(classElement, cause);
+    // Add the information that this class is a subtype of its supertypes.  The
+    // code emitter and the ssa builder use that information.
+    addSubtypes(classElement, emitter.nativeEmitter);
+  }
+
+  void addSubtypes(ClassElement cls, NativeEmitter emitter) {
+    if (!cls.isNative()) return;
+    if (doneAddSubtypes.contains(cls)) return;
+    doneAddSubtypes.add(cls);
+
+    // Walk the superclass chain since classes on the superclass chain might not
+    // be instantiated (abstract or simply unused).
+    addSubtypes(cls.superclass, emitter);
+
+    for (DartType type in cls.allSupertypes) {
+      List<Element> subtypes = emitter.subtypes.putIfAbsent(
+          type.element,
+          () => <ClassElement>[]);
+      subtypes.add(cls);
+    }
+
+    // Skip through all the mixin applications in the super class
+    // chain. That way, the direct subtypes set only contain the
+    // natives classes.
+    ClassElement superclass = cls.superclass;
+    while (superclass != null && superclass.isMixinApplication) {
+      assert(!superclass.isNative());
+      superclass = superclass.superclass;
+    }
+
+    List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent(
+        superclass,
+        () => <ClassElement>[]);
+    directSubtypes.add(cls);
+  }
+
+  void logSummary(log(message)) {
+    log('Compiled ${registeredClasses.length} native classes, '
+        '${unusedClasses.length} native classes omitted.');
+  }
+}
+
+void maybeEnableNative(Compiler compiler,
+                       LibraryElement library) {
+  String libraryName = library.canonicalUri.toString();
+  if (library.entryCompilationUnit.script.name.contains(
+          'dart/tests/compiler/dart2js_native')
+      || libraryName == 'dart:async'
+      || libraryName == 'dart:html'
+      || libraryName == 'dart:html_common'
+      || libraryName == 'dart:indexed_db'
+      || libraryName == 'dart:svg'
+      || libraryName == 'dart:web_audio') {
+    library.canUseNative = true;
+  }
+}
+
+/**
+ * A summary of the behavior of a native element.
+ *
+ * Native code can return values of one type and cause native subtypes of
+ * another type to be instantiated.  By default, we compute both from the
+ * declared type.
+ *
+ * A field might yield any native type that 'is' the field type.
+ *
+ * A method might create and return instances of native subclasses of its
+ * declared return type, and a callback argument may be called with instances of
+ * the callback parameter type (e.g. Event).
+ *
+ * If there is one or more @Creates annotations, the union of the named types
+ * replaces the inferred instantiated type, and the return type is ignored for
+ * the purpose of inferring instantiated types.
+ *
+ *     @Creates(IDBCursor)    // Created asynchronously.
+ *     @Creates(IDBRequest)   // Created synchronously (for return value).
+ *     IDBRequest request = objectStore.openCursor();
+ *
+ * If there is one or more @Returns annotations, the union of the named types
+ * replaces the declared return type.
+ *
+ *     @Returns(IDBRequest)
+ *     IDBRequest request = objectStore.openCursor();
+ */
+class NativeBehavior {
+
+  /// [DartType]s or [SpecialType]s returned or yielded by the native element.
+  final List typesReturned = [];
+
+  /// [DartType]s or [SpecialType]s instantiated by the native element.
+  final List typesInstantiated = [];
+
+  static final NativeBehavior NONE = new NativeBehavior();
+
+  //NativeBehavior();
+
+  static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) {
+    // The first argument of a JS-call is a string encoding various attributes
+    // of the code.
+    //
+    //  'Type1|Type2'.  A union type.
+    //  '=Object'.      A JavaScript Object, no subtype.
+    //  '=List'.        A JavaScript Array, no subtype.
+
+    var argNodes = jsCall.arguments;
+    if (argNodes.isEmpty) {
+      compiler.cancel("JS expression has no type", node: jsCall);
+    }
+
+    var firstArg = argNodes.head;
+    LiteralString specLiteral = firstArg.asLiteralString();
+    if (specLiteral != null) {
+      String specString = specLiteral.dartString.slowToString();
+      // Various things that are not in fact types.
+      if (specString == 'void') return NativeBehavior.NONE;
+      if (specString == '' || specString == 'var') {
+        var behavior = new NativeBehavior();
+        behavior.typesReturned.add(compiler.objectClass.computeType(compiler));
+        return behavior;
+      }
+      var behavior = new NativeBehavior();
+      for (final typeString in specString.split('|')) {
+        var type = _parseType(typeString, compiler,
+            (name) => resolver.resolveTypeFromString(name),
+            jsCall);
+        behavior.typesInstantiated.add(type);
+        behavior.typesReturned.add(type);
+      }
+      return behavior;
+    }
+
+    // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2').  It
+    // is not very satisfactory because it does not work for void, dynamic.
+
+    compiler.cancel("Unexpected JS first argument", node: firstArg);
+  }
+
+  static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) {
+    FunctionType type = method.computeType(compiler);
+    var behavior = new NativeBehavior();
+    behavior.typesReturned.add(type.returnType);
+    behavior._capture(type, compiler);
+
+    // TODO(sra): Optional arguments are currently missing from the
+    // DartType. This should be fixed so the following work-around can be
+    // removed.
+    method.computeSignature(compiler).forEachOptionalParameter(
+        (Element parameter) {
+          behavior._escape(parameter.computeType(compiler), compiler);
+        });
+
+    behavior._overrideWithAnnotations(method, compiler);
+    return behavior;
+  }
+
+  static NativeBehavior ofFieldLoad(Element field, Compiler compiler) {
+    DartType type = field.computeType(compiler);
+    var behavior = new NativeBehavior();
+    behavior.typesReturned.add(type);
+    behavior._capture(type, compiler);
+    behavior._overrideWithAnnotations(field, compiler);
+    return behavior;
+  }
+
+  static NativeBehavior ofFieldStore(Element field, Compiler compiler) {
+    DartType type = field.computeType(compiler);
+    var behavior = new NativeBehavior();
+    behavior._escape(type, compiler);
+    // We don't override the default behaviour - the annotations apply to
+    // loading the field.
+    return behavior;
+  }
+
+  void _overrideWithAnnotations(Element element, Compiler compiler) {
+    if (element.metadata.isEmpty) return;
+
+    DartType lookup(String name) {
+      Element e = element.buildScope().lookup(new SourceString(name));
+      if (e == null) return null;
+      if (e is! ClassElement) return null;
+      e.ensureResolved(compiler);
+      return e.computeType(compiler);
+    }
+
+    NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer;
+    var creates = _collect(element, compiler, enqueuer.annotationCreatesClass,
+                           lookup);
+    var returns = _collect(element, compiler, enqueuer.annotationReturnsClass,
+                           lookup);
+
+    if (creates != null) {
+      typesInstantiated..clear()..addAll(creates);
+    }
+    if (returns != null) {
+      typesReturned..clear()..addAll(returns);
+    }
+  }
+
+  /**
+   * Returns a list of type constraints from the annotations of
+   * [annotationClass].
+   * Returns `null` if no constraints.
+   */
+  static _collect(Element element, Compiler compiler, Element annotationClass,
+                  lookup(str)) {
+    var types = null;
+    for (Link<MetadataAnnotation> link = element.metadata;
+         !link.isEmpty;
+         link = link.tail) {
+      MetadataAnnotation annotation = link.head.ensureResolved(compiler);
+      var value = annotation.value;
+      if (value is! ConstructedConstant) continue;
+      if (value.type is! InterfaceType) continue;
+      if (!identical(value.type.element, annotationClass)) continue;
+
+      var fields = value.fields;
+      // TODO(sra): Better validation of the constant.
+      if (fields.length != 1 || fields[0] is! StringConstant) {
+        PartialMetadataAnnotation partial = annotation;
+        compiler.cancel(
+            'Annotations needs one string: ${partial.parseNode(compiler)}');
+      }
+      String specString = fields[0].toDartString().slowToString();
+      for (final typeString in specString.split('|')) {
+        var type = _parseType(typeString, compiler, lookup, annotation);
+        if (types == null) types = [];
+        types.add(type);
+      }
+    }
+    return types;
+  }
+
+  /// Models the behavior of having intances of [type] escape from Dart code
+  /// into native code.
+  void _escape(DartType type, Compiler compiler) {
+    type = type.unalias(compiler);
+    if (type is FunctionType) {
+      // A function might be called from native code, passing us novel
+      // parameters.
+      _escape(type.returnType, compiler);
+      for (Link<DartType> parameters = type.parameterTypes;
+           !parameters.isEmpty;
+           parameters = parameters.tail) {
+        _capture(parameters.head, compiler);
+      }
+    }
+  }
+
+  /// Models the behavior of Dart code receiving instances and methods of [type]
+  /// from native code.  We usually start the analysis by capturing a native
+  /// method that has been used.
+  void _capture(DartType type, Compiler compiler) {
+    type = type.unalias(compiler);
+    if (type is FunctionType) {
+      _capture(type.returnType, compiler);
+      for (Link<DartType> parameters = type.parameterTypes;
+           !parameters.isEmpty;
+           parameters = parameters.tail) {
+        _escape(parameters.head, compiler);
+      }
+    } else {
+      typesInstantiated.add(type);
+    }
+  }
+
+  static _parseType(String typeString, Compiler compiler,
+      lookup(name), locationNodeOrElement) {
+    if (typeString == '=Object') return SpecialType.JsObject;
+    if (typeString == '=List') return SpecialType.JsArray;
+    if (typeString == 'dynamic') {
+      return  compiler.dynamicClass.computeType(compiler);
+    }
+    DartType type = lookup(typeString);
+    if (type != null) return type;
+
+    int index = typeString.indexOf('<');
+    if (index < 1) {
+      compiler.cancel("Type '$typeString' not found",
+          node: _errorNode(locationNodeOrElement, compiler));
+    }
+    type = lookup(typeString.substring(0, index));
+    if (type != null)  {
+      // TODO(sra): Parse type parameters.
+      return type;
+    }
+    compiler.cancel("Type '$typeString' not found",
+        node: _errorNode(locationNodeOrElement, compiler));
+  }
+
+  static _errorNode(locationNodeOrElement, compiler) {
+    if (locationNodeOrElement is Node) return locationNodeOrElement;
+    return locationNodeOrElement.parseNode(compiler);
+  }
+}
+
+void checkAllowedLibrary(ElementListener listener, Token token) {
+  LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary();
+  if (!currentLibrary.canUseNative) {
+    listener.recoverableError("Unexpected token", token: token);
+  }
+}
+
+Token handleNativeBlockToSkip(Listener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  token = token.next;
+  if (identical(token.kind, STRING_TOKEN)) {
+    token = token.next;
+  }
+  if (identical(token.stringValue, '{')) {
+    BeginGroupToken beginGroupToken = token;
+    token = beginGroupToken.endGroup;
+  }
+  return token;
+}
+
+Token handleNativeClassBodyToSkip(Listener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  listener.handleIdentifier(token);
+  token = token.next;
+  if (!identical(token.kind, STRING_TOKEN)) {
+    return listener.unexpected(token);
+  }
+  token = token.next;
+  if (!identical(token.stringValue, '{')) {
+    return listener.unexpected(token);
+  }
+  BeginGroupToken beginGroupToken = token;
+  token = beginGroupToken.endGroup;
+  return token;
+}
+
+Token handleNativeClassBody(Listener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  token = token.next;
+  if (!identical(token.kind, STRING_TOKEN)) {
+    listener.unexpected(token);
+  } else {
+    token = token.next;
+  }
+  return token;
+}
+
+Token handleNativeFunctionBody(ElementListener listener, Token token) {
+  checkAllowedLibrary(listener, token);
+  Token begin = token;
+  listener.beginReturnStatement(token);
+  token = token.next;
+  bool hasExpression = false;
+  if (identical(token.kind, STRING_TOKEN)) {
+    hasExpression = true;
+    listener.beginLiteralString(token);
+    listener.endLiteralString(0);
+    token = token.next;
+  }
+  listener.endReturnStatement(hasExpression, begin, token);
+  // TODO(ngeoffray): expect a ';'.
+  // Currently there are method with both native marker and Dart body.
+  return token.next;
+}
+
+SourceString checkForNativeClass(ElementListener listener) {
+  SourceString nativeTagInfo;
+  Node node = listener.nodes.head;
+  if (node != null
+      && node.asIdentifier() != null
+      && node.asIdentifier().source.stringValue == 'native') {
+    nativeTagInfo = node.asIdentifier().token.next.value;
+    listener.popNode();
+  }
+  return nativeTagInfo;
+}
+
+bool isOverriddenMethod(FunctionElement element,
+                        ClassElement cls,
+                        NativeEmitter nativeEmitter) {
+  List<ClassElement> subtypes = nativeEmitter.subtypes[cls];
+  if (subtypes == null) return false;
+  for (ClassElement subtype in subtypes) {
+    if (subtype.lookupLocalMember(element.name) != null) return true;
+  }
+  return false;
+}
+
+final RegExp nativeRedirectionRegExp = new RegExp(r'^[a-zA-Z][a-zA-Z_$0-9]*$');
+
+void handleSsaNative(SsaBuilder builder, Expression nativeBody) {
+  Compiler compiler = builder.compiler;
+  FunctionElement element = builder.work.element;
+  NativeEmitter nativeEmitter = builder.emitter.nativeEmitter;
+
+  HInstruction convertDartClosure(Element parameter, FunctionType type) {
+    HInstruction local = builder.localsHandler.readLocal(parameter);
+    Constant arityConstant =
+        builder.constantSystem.createInt(type.computeArity());
+    HInstruction arity = builder.graph.addConstant(arityConstant);
+    // TODO(ngeoffray): For static methods, we could pass a method with a
+    // defined arity.
+    Element helper = builder.backend.getClosureConverter();
+    builder.pushInvokeHelper2(helper, local, arity, HType.UNKNOWN);
+    HInstruction closure = builder.pop();
+    return closure;
+  }
+
+  // Check which pattern this native method follows:
+  // 1) foo() native;
+  //      hasBody = false
+  // 2) foo() native "bar";
+  //      No longer supported, this is now done with @JSName('foo') and case 1.
+  // 3) foo() native "return 42";
+  //      hasBody = true
+  bool hasBody = false;
+  assert(element.isNative());
+  String nativeMethodName = element.fixedBackendName();
+  if (nativeBody != null) {
+    LiteralString jsCode = nativeBody.asLiteralString();
+    String str = jsCode.dartString.slowToString();
+    if (nativeRedirectionRegExp.hasMatch(str)) {
+      compiler.cancel("Deprecated syntax, use @JSName('name') instead.",
+                      node: nativeBody);
+    }
+    hasBody = true;
+  }
+
+  if (!hasBody) {
+    nativeEmitter.nativeMethods.add(element);
+  }
+
+  FunctionSignature parameters = element.computeSignature(builder.compiler);
+  if (!hasBody) {
+    List<String> arguments = <String>[];
+    List<HInstruction> inputs = <HInstruction>[];
+    String receiver = '';
+    if (element.isInstanceMember()) {
+      receiver = '#.';
+      inputs.add(builder.localsHandler.readThis());
+    }
+    parameters.forEachParameter((Element parameter) {
+      DartType type = parameter.computeType(compiler).unalias(compiler);
+      HInstruction input = builder.localsHandler.readLocal(parameter);
+      if (type is FunctionType) {
+        // The parameter type is a function type either directly or through
+        // typedef(s).
+        input = convertDartClosure(parameter, type);
+      }
+      inputs.add(input);
+      arguments.add('#');
+    });
+
+    String foreignParameters = Strings.join(arguments, ',');
+    String nativeMethodCall;
+    if (element.kind == ElementKind.FUNCTION) {
+      nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)';
+    } else if (element.kind == ElementKind.GETTER) {
+      nativeMethodCall = '$receiver$nativeMethodName';
+    } else if (element.kind == ElementKind.SETTER) {
+      nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters';
+    } else {
+      builder.compiler.internalError('unexpected kind: "${element.kind}"',
+                                     element: element);
+    }
+
+    DartString jsCode = new DartString.literal(nativeMethodCall);
+    builder.push(new HForeign(jsCode, HType.UNKNOWN, inputs));
+    builder.close(new HReturn(builder.pop())).addSuccessor(builder.graph.exit);
+  } else {
+    if (parameters.parameterCount != 0) {
+      compiler.cancel(
+          'native "..." syntax is restricted to functions with zero parameters',
+          node: nativeBody);
+    }
+    LiteralString jsCode = nativeBody.asLiteralString();
+    builder.push(new HForeign.statement(jsCode.dartString, <HInstruction>[]));
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart
new file mode 100644
index 0000000..5a0da01
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart
@@ -0,0 +1,596 @@
+// 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.
+
+/**
+ * This library contains the infrastructure to parse and integrate patch files.
+ *
+ * Three types of elements can be patched: [LibraryElement], [ClassElement],
+ * [FunctionElement]. Patches are introduced in patch libraries which are loaded
+ * together with the corresponding origin library. Which libraries that are
+ * patched is determined by the [dart2jsPatchPath] field of [LibraryInfo] found
+ * in [:lib/_internal/libraries.dart:].
+ *
+ * Patch libraries are parsed like regular library and thus provided with their
+ * own elements. These elements which are distinct from the elements from the
+ * patched library and the relation between patched and patch elements is
+ * established through the [:patch:] and [:origin:] fields found on
+ * [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields
+ * are set on the patched elements to point to their corresponding patch
+ * element, and the [:origin:] elements are set on the patch elements to point
+ * their corresponding patched elements.
+ *
+ * The fields [Element.isPatched] and [Element.isPatch] can be used to determine
+ * whether the [:patch:] or [:origin:] field, respectively, has been set on an
+ * element, regardless of whether the element is one of the three patchable
+ * element types or not.
+ *
+ * ## Variants of Classes and Functions ##
+ *
+ * With patches there are four variants of classes and function:
+ *
+ * Regular: A class or function which is not declared in a patch library and
+ *   which has no corresponding patch.
+ * Origin: A class or function which is not declared in a patch library and
+ *   which has a corresponding patch. Origin functions must use the [:external:]
+ *   modifier and can have no body. Origin classes and functions are also
+ *   called 'patched'.
+ * Patch: A class or function which is declared in a patch library and which
+ *   has a corresponding origin. Both patch classes and patch functions must use
+ *   the [:patch:] modifier.
+ * Injected: A class or function (or even field) which is declared in a
+ *   patch library and which has no corresponding origin. An injected element
+ *   cannot use the [:patch:] modifier. Injected elements are never visible from
+ *   outside the patch library in which they have been declared. For this
+ *   reason, injected elements are often declared private and therefore called
+ *   also called 'patch private'.
+ *
+ * Examples of the variants is shown in the code below:
+ *
+ *     // In the origin library:
+ *     class RegularClass { // A regular class.
+ *       void regularMethod() {} // A regular method.
+ *     }
+ *     class PatchedClass { // An origin class.
+ *       int regularField; // A regular field.
+ *       void regularMethod() {} // A regular method.
+ *       external void patchedMethod(); // An origin method.
+ *     }
+ *
+ *     // In the patch library:
+ *     class _InjectedClass { // An injected class.
+ *       void _injectedMethod() {} // An injected method.
+ *     }
+ *     patch class PatchedClass { // A patch class.
+ *       int _injectedField; { // An injected field.
+ *       patch void patchedMethod() {} // A patch method.
+ *     }
+ *
+ *
+ * ## Declaration and Implementation ##
+ *
+ * With patches we have two views on elements: as the 'declaration' which
+ * introduces the entity and defines its interface, and as the 'implementation'
+ * which defines the actual implementation of the entity.
+ *
+ * Every element has a 'declaration' and an 'implementation' element. For
+ * regular and injected elements these are the same. For origin elements the
+ * declaration is the element itself and the implementation is the patch element
+ * found through its [:patch:] field. For patch elements the implementation is
+ * the element itself and the declaration is the origin element found through
+ * its [:origin:] field. The declaration and implementation of any element is
+ * conveniently available through the [Element.declaration] and
+ * [Element.implementation] getters.
+ *
+ * Most patch-related invariants enforced through-out the compiler are defined
+ * in terms of 'declaration' and 'implementation', and tested through the
+ * predicate getters [Element.isDeclaration] and [Element.isImplementation].
+ * Patch invariants are stated both in comments and as assertions.
+ *
+ *
+ * ## General invariant guidelines ##
+ *
+ * For [LibraryElement] we always use declarations. This means the
+ * [Element.getLibrary] method will only return library declarations. Patch
+ * library implementations are only accessed through calls to
+ * [Element.getImplementationLibrary] which is used to setup the correct
+ * [Element.enclosingElement] relation between patch/injected elements and the
+ * patch library.
+ *
+ * For [ClassElement] and [FunctionElement] we use declarations for determining
+ * identity and implementations for work based on the AST nodes, such as
+ * resolution, type-checking, type inference, building SSA graphs, etc.
+ * - Worklist only contain declaration elements.
+ * - Most maps and sets use declarations exclusively, and their individual
+ *   invariants are stated in the field comments.
+ * - [TreeElements] only map to patch elements from inside a patch library.
+ *   TODO(johnniwinther): Simplify this invariant to use only declarations in
+ *   [TreeElements].
+ * - Builders shift between declaration and implementation depending on usages.
+ * - Compile-time constants use constructor implementation exclusively.
+ * - Work on function parameters is performed on the declaration of the function
+ *   element.
+ */
+
+library patchparser;
+
+import "dart:uri";
+import "tree/tree.dart" as tree;
+import "dart2jslib.dart" as leg;  // CompilerTask, Compiler.
+import "apiimpl.dart";
+import "../compiler.dart" as api;
+import "scanner/scannerlib.dart";  // Scanner, Parsers, Listeners
+import "elements/elements.dart";
+import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX;
+import 'util/util.dart';
+
+class PatchParserTask extends leg.CompilerTask {
+  PatchParserTask(leg.Compiler compiler): super(compiler);
+  final String name = "Patching Parser";
+
+  /**
+   * Scans a library patch file, applies the method patches and
+   * injections to the library, and returns a list of class
+   * patches.
+   */
+  void patchLibrary(leg.LibraryDependencyHandler handler,
+                    Uri patchUri, LibraryElement originLibrary) {
+
+    leg.Script script = compiler.readScript(patchUri, null);
+    var patchLibrary = new LibraryElementX(script, null, originLibrary);
+    compiler.withCurrentElement(patchLibrary, () {
+      handler.registerNewLibrary(patchLibrary);
+      LinkBuilder<tree.LibraryTag> imports = new LinkBuilder<tree.LibraryTag>();
+      compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () {
+        // This patches the elements of the patch library into [library].
+        // Injected elements are added directly under the compilation unit.
+        // Patch elements are stored on the patched functions or classes.
+        scanLibraryElements(patchLibrary.entryCompilationUnit, imports);
+      });
+      // After scanning declarations, we handle the import tags in the patch.
+      // TODO(lrn): These imports end up in the original library and are in
+      // scope for the original methods too. This should be fixed.
+      compiler.importHelperLibrary(originLibrary);
+      for (tree.LibraryTag tag in imports.toLink()) {
+        compiler.libraryLoader.registerLibraryFromTag(
+            handler, patchLibrary, tag);
+      }
+    });
+  }
+
+  void scanLibraryElements(
+        CompilationUnitElement compilationUnit,
+        LinkBuilder<tree.LibraryTag> imports) {
+    measure(() {
+      // TODO(lrn): Possibly recursively handle #source directives in patch.
+      leg.Script script = compilationUnit.script;
+      Token tokens = new StringScanner(script.text).tokenize();
+      Function idGenerator = compiler.getNextFreeClassId;
+      PatchListener patchListener =
+          new PatchElementListener(compiler,
+                                   compilationUnit,
+                                   idGenerator,
+                                   imports);
+      new PatchParser(patchListener).parseUnit(tokens);
+    });
+  }
+
+  void parsePatchClassNode(PartialClassElement element) {
+    // Parse [PartialClassElement] using a "patch"-aware parser instead
+    // of calling its [parseNode] method.
+    if (element.cachedNode != null) return;
+
+    return measure(() => compiler.withCurrentElement(element, () {
+      PatchMemberListener listener = new PatchMemberListener(compiler, element);
+      Parser parser = new PatchClassElementParser(listener);
+      Token token = parser.parseTopLevelDeclaration(element.beginToken);
+      assert(identical(token, element.endToken.next));
+      element.cachedNode = listener.popNode();
+      assert(listener.nodes.isEmpty);
+
+      Link<Element> patches = element.localMembers;
+      applyContainerPatch(element.origin, patches);
+    }));
+  }
+
+  void applyContainerPatch(ClassElement originClass,
+                           Link<Element> patches) {
+    for (Element patch in patches) {
+      if (!isPatchElement(patch)) continue;
+
+      Element origin = originClass.localLookup(patch.name);
+      patchElement(compiler, origin, patch);
+    }
+  }
+}
+
+/**
+ * Extension of the [Listener] interface to handle the extra "patch" pseudo-
+ * keyword in patch files.
+ * Patch files shouldn't have a type named "patch".
+ */
+abstract class PatchListener extends Listener {
+  void beginPatch(Token patch);
+  void endPatch(Token patch);
+}
+
+/**
+ * Partial parser that extends the top-level and class grammars to allow the
+ * word "patch" in front of some declarations.
+ */
+class PatchParser extends PartialParser {
+  PatchParser(PatchListener listener) : super(listener);
+
+  PatchListener get patchListener => listener;
+
+  bool isPatch(Token token) {
+    return token.stringValue == null &&
+           token.slowToString() == "patch";
+  }
+
+  /**
+   * Parse top-level declarations, and allow "patch" in front of functions
+   * and classes.
+   */
+  Token parseTopLevelDeclaration(Token token) {
+    if (!isPatch(token)) {
+      return super.parseTopLevelDeclaration(token);
+    }
+    Token patch = token;
+    token = token.next;
+    String value = token.stringValue;
+    if (identical(value, 'interface')
+        || identical(value, 'typedef')
+        || identical(value, '#')
+        || identical(value, 'abstract')) {
+      // At the top level, you can only patch functions and classes.
+      // Patch classes and functions can't be marked abstract.
+      return listener.unexpected(patch);
+    }
+    patchListener.beginPatch(patch);
+    token = super.parseTopLevelDeclaration(token);
+    patchListener.endPatch(patch);
+    return token;
+  }
+
+  /**
+   * Parse a class member.
+   * If the member starts with "patch", it's a member override.
+   * Only methods can be overridden, including constructors, getters and
+   * setters, but not fields. If "patch" occurs in front of a field, the error
+   * is caught elsewhere.
+   */
+  Token parseMember(Token token) {
+    if (!isPatch(token)) {
+      return super.parseMember(token);
+    }
+    Token patch = token;
+    patchListener.beginPatch(patch);
+    token = super.parseMember(token.next);
+    patchListener.endPatch(patch);
+    return token;
+  }
+}
+
+/**
+ * Partial parser for patch files that also handles the members of class
+ * declarations.
+ */
+class PatchClassElementParser extends PatchParser {
+  PatchClassElementParser(PatchListener listener) : super(listener);
+
+  Token parseClassBody(Token token) => fullParseClassBody(token);
+}
+
+/**
+ * Extension of [ElementListener] for parsing patch files.
+ */
+class PatchElementListener extends ElementListener implements PatchListener {
+  final LinkBuilder<tree.LibraryTag> imports;
+  bool isMemberPatch = false;
+  bool isClassPatch = false;
+
+  PatchElementListener(leg.DiagnosticListener listener,
+                       CompilationUnitElement patchElement,
+                       int idGenerator(),
+                       this.imports)
+    : super(listener, patchElement, idGenerator);
+
+  MetadataAnnotation popMetadataHack() {
+    // TODO(ahe): Remove this method.
+    popNode(); // Discard null.
+    return new PatchMetadataAnnotation();
+  }
+
+  void beginPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = true;
+    } else {
+      isMemberPatch = true;
+    }
+    handleIdentifier(token);
+  }
+
+  void endPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = false;
+    } else {
+      isMemberPatch = false;
+    }
+  }
+
+  /**
+    * Allow script tags (import only, the parser rejects the rest for now) in
+    * patch files. The import tags will be added to the library.
+    */
+  bool allowLibraryTags() => true;
+
+  void addLibraryTag(tree.LibraryTag tag) {
+    super.addLibraryTag(tag);
+    imports.addLast(tag);
+  }
+
+  void pushElement(Element patch) {
+    if (isMemberPatch || (isClassPatch && patch is ClassElement)) {
+      // Apply patch.
+      patch.addMetadata(popMetadataHack());
+      LibraryElement originLibrary = compilationUnitElement.getLibrary();
+      assert(originLibrary.isPatched);
+      Element origin = originLibrary.localLookup(patch.name);
+      patchElement(listener, origin, patch);
+    }
+    super.pushElement(patch);
+  }
+}
+
+/**
+ * Extension of [MemberListener] for parsing patch class bodies.
+ */
+class PatchMemberListener extends MemberListener implements PatchListener {
+  bool isMemberPatch = false;
+  bool isClassPatch = false;
+  PatchMemberListener(leg.DiagnosticListener listener,
+                      Element enclosingElement)
+    : super(listener, enclosingElement);
+
+  MetadataAnnotation popMetadataHack() {
+    // TODO(ahe): Remove this method.
+    popNode(); // Discard null.
+    return new PatchMetadataAnnotation();
+  }
+
+  void beginPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = true;
+    } else {
+      isMemberPatch = true;
+    }
+    handleIdentifier(token);
+  }
+
+  void endPatch(Token token) {
+    if (identical(token.next.stringValue, "class")) {
+      isClassPatch = false;
+    } else {
+      isMemberPatch = false;
+    }
+  }
+
+  void addMember(Element element) {
+    if (isMemberPatch || (isClassPatch && element is ClassElement)) {
+      element.addMetadata(popMetadataHack());
+    }
+    super.addMember(element);
+  }
+}
+
+// TODO(ahe): Get rid of this class.
+class PatchMetadataAnnotation extends MetadataAnnotationX {
+  final leg.Constant value = null;
+
+  PatchMetadataAnnotation() : super(STATE_DONE);
+
+  Token get beginToken => null;
+  Token get endToken => null;
+}
+
+void patchElement(leg.DiagnosticListener listener,
+                   Element origin,
+                   Element patch) {
+  if (origin == null) {
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_NON_EXISTING.error({'name': patch.name}),
+        api.Diagnostic.ERROR);
+    return;
+  }
+  if (!(origin.isClass() ||
+        origin.isConstructor() ||
+        origin.isFunction() ||
+        origin.isAbstractField())) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NONPATCHABLE.error(),
+        api.Diagnostic.ERROR);
+    return;
+  }
+  if (patch.isClass()) {
+    tryPatchClass(listener, origin, patch);
+  } else if (patch.isGetter()) {
+    tryPatchGetter(listener, origin, patch);
+  } else if (patch.isSetter()) {
+    tryPatchSetter(listener, origin, patch);
+  } else if (patch.isConstructor()) {
+    tryPatchConstructor(listener, origin, patch);
+  } else if(patch.isFunction()) {
+    tryPatchFunction(listener, origin, patch);
+  } else {
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_NONPATCHABLE.error(),
+        api.Diagnostic.ERROR);
+  }
+}
+
+void tryPatchClass(leg.DiagnosticListener listener,
+                    Element origin,
+                    ClassElement patch) {
+  if (!origin.isClass()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_CLASS.error({'className': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_CLASS.error({'className': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchClass(listener, origin, patch);
+}
+
+void patchClass(leg.DiagnosticListener listener,
+                 ClassElement origin,
+                 ClassElement patch) {
+  if (origin.isPatched) {
+    listener.internalErrorOnElement(
+        origin, "Patching the same class more than once.");
+  }
+  // TODO(johnniwinther): Change to functions on the ElementX class.
+  origin.patch = patch;
+  patch.origin = origin;
+}
+
+void tryPatchGetter(leg.DiagnosticListener listener,
+                     Element origin,
+                     FunctionElement patch) {
+  if (!origin.isAbstractField()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_GETTER.error({'name': origin.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  AbstractFieldElement originField = origin;
+  if (originField.getter == null) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NO_GETTER.error({'getterName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, originField.getter, patch);
+}
+
+void tryPatchSetter(leg.DiagnosticListener listener,
+                     Element origin,
+                     FunctionElement patch) {
+  if (!origin.isAbstractField()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_SETTER.error({'name': origin.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  AbstractFieldElement originField = origin;
+  if (originField.setter == null) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NO_SETTER.error({'setterName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, originField.setter, patch);
+}
+
+void tryPatchConstructor(leg.DiagnosticListener listener,
+                          Element origin,
+                          FunctionElement patch) {
+  if (!origin.isConstructor()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_CONSTRUCTOR.error(
+            {'constructorName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR.error(
+            {'constructorName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, origin, patch);
+}
+
+void tryPatchFunction(leg.DiagnosticListener listener,
+                       Element origin,
+                       FunctionElement patch) {
+  if (!origin.isFunction()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_FUNCTION.error({'functionName': patch.name}),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_FUNCTION.error(
+            {'functionName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  patchFunction(listener, origin, patch);
+}
+
+void patchFunction(leg.DiagnosticListener listener,
+                    FunctionElement origin,
+                    FunctionElement patch) {
+  if (!origin.modifiers.isExternal()) {
+    listener.reportMessage(
+        listener.spanFromSpannable(origin),
+        leg.MessageKind.PATCH_NON_EXTERNAL.error(),
+        api.Diagnostic.ERROR);
+    listener.reportMessage(
+        listener.spanFromSpannable(patch),
+        leg.MessageKind.PATCH_POINT_TO_FUNCTION.error(
+            {'functionName': patch.name}),
+        api.Diagnostic.INFO);
+    return;
+  }
+  if (origin.isPatched) {
+    listener.internalErrorOnElement(origin,
+        "Trying to patch a function more than once.");
+  }
+  if (origin.cachedNode != null) {
+    listener.internalErrorOnElement(origin,
+        "Trying to patch an already compiled function.");
+  }
+  // Don't just assign the patch field. This also updates the cachedNode.
+  // TODO(johnniwinther): Change to functions on the ElementX class.
+  origin.setPatch(patch);
+  patch.origin = origin;
+}
+
+// TODO(johnniwinther): Add unittest when patch is (real) metadata.
+bool isPatchElement(Element element) {
+  // TODO(lrn): More checks needed if we introduce metadata for real.
+  // In that case, it must have the identifier "native" as metadata.
+  for (Link link = element.metadata; !link.isEmpty; link = link.tail) {
+    if (link.head is PatchMetadataAnnotation) return true;
+  }
+  return false;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart
new file mode 100644
index 0000000..e62aa70
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart
@@ -0,0 +1,3663 @@
+// 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.
+
+part of resolution;
+
+abstract class TreeElements {
+  Element operator[](Node node);
+  Selector getSelector(Send send);
+  DartType getType(Node node);
+  bool isParameterChecked(Element element);
+  Set<Node> get superUses;
+}
+
+class TreeElementMapping implements TreeElements {
+  final Element currentElement;
+  final Map<Node, Selector> selectors = new LinkedHashMap<Node, Selector>();
+  final Map<Node, DartType> types = new LinkedHashMap<Node, DartType>();
+  final Set<Element> checkedParameters = new Set<Element>();
+  final Set<Node> superUses = new Set<Node>();
+
+  TreeElementMapping(this.currentElement);
+
+  operator []=(Node node, Element element) {
+    assert(invariant(node, () {
+      if (node is FunctionExpression) {
+        return !node.modifiers.isExternal();
+      }
+      return true;
+    }));
+    // TODO(johnniwinther): Simplify this invariant to use only declarations in
+    // [TreeElements].
+    assert(invariant(node, () {
+      if (!element.isErroneous() && currentElement != null && element.isPatch) {
+        return currentElement.getImplementationLibrary().isPatch;
+      }
+      return true;
+    }));
+    // TODO(ahe): Investigate why the invariant below doesn't hold.
+    // assert(invariant(node,
+    //                  getTreeElement(node) == element ||
+    //                  getTreeElement(node) == null,
+    //                  message: '${getTreeElement(node)}; $element'));
+
+    setTreeElement(node, element);
+  }
+
+  operator [](Node node) => getTreeElement(node);
+
+  void remove(Node node) {
+    setTreeElement(node, null);
+  }
+
+  void setType(Node node, DartType type) {
+    types[node] = type;
+  }
+
+  DartType getType(Node node) => types[node];
+
+  void setSelector(Node node, Selector selector) {
+    selectors[node] = selector;
+  }
+
+  Selector getSelector(Node node) => selectors[node];
+
+  bool isParameterChecked(Element element) {
+    return checkedParameters.contains(element);
+  }
+}
+
+class ResolverTask extends CompilerTask {
+  ResolverTask(Compiler compiler) : super(compiler);
+
+  String get name => 'Resolver';
+
+  TreeElements resolve(Element element) {
+    return measure(() {
+      if (Elements.isErroneousElement(element)) return null;
+
+      for (MetadataAnnotation metadata in element.metadata) {
+        metadata.ensureResolved(compiler);
+      }
+
+      ElementKind kind = element.kind;
+      if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR) ||
+          identical(kind, ElementKind.FUNCTION) ||
+          identical(kind, ElementKind.GETTER) ||
+          identical(kind, ElementKind.SETTER)) {
+        return resolveMethodElement(element);
+      }
+
+      if (identical(kind, ElementKind.FIELD)) return resolveField(element);
+
+      if (identical(kind, ElementKind.PARAMETER) ||
+          identical(kind, ElementKind.FIELD_PARAMETER)) {
+        return resolveParameter(element);
+      }
+      if (element.isClass()) {
+        ClassElement cls = element;
+        cls.ensureResolved(compiler);
+        return null;
+      } else if (element.isTypedef()) {
+        TypedefElement typdef = element;
+        resolveTypedef(typdef);
+        return null;
+      } else if (element.isTypeVariable()) {
+        element.computeType(compiler);
+        return null;
+      }
+
+      compiler.unimplemented("resolve($element)",
+                             node: element.parseNode(compiler));
+    });
+  }
+
+  String constructorNameForDiagnostics(SourceString className,
+                                   SourceString constructorName) {
+    String classNameString = className.slowToString();
+    String constructorNameString = constructorName.slowToString();
+    return (constructorName == const SourceString(''))
+        ? classNameString
+        : "$classNameString.$constructorNameString";
+   }
+
+  void resolveRedirectingConstructor(InitializerResolver resolver,
+                                     Node node,
+                                     FunctionElement constructor,
+                                     FunctionElement redirection) {
+    Set<FunctionElement> seen = new Set<FunctionElement>();
+    seen.add(constructor);
+    while (redirection != null) {
+      if (seen.contains(redirection)) {
+        resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE);
+        return;
+      }
+      seen.add(redirection);
+
+      if (redirection.isPatched) {
+        checkMatchingPatchSignatures(constructor, redirection.patch);
+        redirection = redirection.patch;
+      }
+      redirection = resolver.visitor.resolveConstructorRedirection(redirection);
+    }
+  }
+
+  void checkMatchingPatchParameters(FunctionElement origin,
+                                    Link<Element> originParameters,
+                                    Link<Element> patchParameters) {
+    while (!originParameters.isEmpty) {
+      Element originParameter = originParameters.head;
+      Element patchParameter = patchParameters.head;
+      // Hack: Use unparser to test parameter equality. This only works because
+      // we are restricting patch uses and the approach cannot be used
+      // elsewhere.
+      String originParameterText =
+          originParameter.parseNode(compiler).toString();
+      String patchParameterText =
+          patchParameter.parseNode(compiler).toString();
+      if (originParameterText != patchParameterText) {
+        error(originParameter.parseNode(compiler),
+              MessageKind.PATCH_PARAMETER_MISMATCH,
+              {'methodName': origin.name,
+               'originParameter': originParameterText,
+               'patchParameter': patchParameterText});
+      }
+
+      originParameters = originParameters.tail;
+      patchParameters = patchParameters.tail;
+    }
+  }
+
+  void checkMatchingPatchSignatures(FunctionElement origin,
+                                    FunctionElement patch) {
+    // TODO(johnniwinther): Show both origin and patch locations on errors.
+    FunctionExpression originTree = compiler.withCurrentElement(origin, () {
+      return origin.parseNode(compiler);
+    });
+    FunctionSignature originSignature = compiler.withCurrentElement(origin, () {
+      return origin.computeSignature(compiler);
+    });
+    FunctionExpression patchTree = compiler.withCurrentElement(patch, () {
+      return patch.parseNode(compiler);
+    });
+    FunctionSignature patchSignature = compiler.withCurrentElement(patch, () {
+      return patch.computeSignature(compiler);
+    });
+
+    if (originSignature.returnType != patchSignature.returnType) {
+      compiler.withCurrentElement(patch, () {
+        Node errorNode =
+            patchTree.returnType != null ? patchTree.returnType : patchTree;
+        error(errorNode, MessageKind.PATCH_RETURN_TYPE_MISMATCH,
+              {'methodName': origin.name,
+               'originReturnType': originSignature.returnType,
+               'patchReturnType': patchSignature.returnType});
+      });
+    }
+    if (originSignature.requiredParameterCount !=
+        patchSignature.requiredParameterCount) {
+      compiler.withCurrentElement(patch, () {
+        error(patchTree,
+              MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH,
+              {'methodName': origin.name,
+               'originParameterCount': originSignature.requiredParameterCount,
+               'patchParameterCount': patchSignature.requiredParameterCount});
+      });
+    } else {
+      checkMatchingPatchParameters(origin,
+                                   originSignature.requiredParameters,
+                                   patchSignature.requiredParameters);
+    }
+    if (originSignature.optionalParameterCount != 0 &&
+        patchSignature.optionalParameterCount != 0) {
+      if (originSignature.optionalParametersAreNamed !=
+          patchSignature.optionalParametersAreNamed) {
+        compiler.withCurrentElement(patch, () {
+          error(patchTree,
+                MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH,
+                {'methodName': origin.name});
+        });
+      }
+    }
+    if (originSignature.optionalParameterCount !=
+        patchSignature.optionalParameterCount) {
+      compiler.withCurrentElement(patch, () {
+        error(patchTree,
+              MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH,
+              {'methodName': origin.name,
+               'originParameterCount': originSignature.optionalParameterCount,
+               'patchParameterCount': patchSignature.optionalParameterCount});
+      });
+    } else {
+      checkMatchingPatchParameters(origin,
+                                   originSignature.optionalParameters,
+                                   patchSignature.optionalParameters);
+    }
+  }
+
+  TreeElements resolveMethodElement(FunctionElement element) {
+    assert(invariant(element, element.isDeclaration));
+    return compiler.withCurrentElement(element, () {
+      bool isConstructor =
+          identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR);
+      TreeElements elements =
+          compiler.enqueuer.resolution.getCachedElements(element);
+      if (elements != null) {
+        assert(isConstructor);
+        return elements;
+      }
+      if (element.isPatched) {
+        checkMatchingPatchSignatures(element, element.patch);
+        element = element.patch;
+      }
+      return compiler.withCurrentElement(element, () {
+        FunctionExpression tree = element.parseNode(compiler);
+        if (tree.modifiers.isExternal()) {
+          error(tree, MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION);
+          return;
+        }
+        if (isConstructor) {
+          if (tree.returnType != null) {
+            error(tree, MessageKind.CONSTRUCTOR_WITH_RETURN_TYPE);
+          }
+          resolveConstructorImplementation(element, tree);
+        }
+        ResolverVisitor visitor = visitorFor(element);
+        visitor.useElement(tree, element);
+        visitor.setupFunction(tree, element);
+
+        if (isConstructor) {
+          // Even if there is no initializer list we still have to do the
+          // resolution in case there is an implicit super constructor call.
+          InitializerResolver resolver = new InitializerResolver(visitor);
+          FunctionElement redirection =
+              resolver.resolveInitializers(element, tree);
+          if (redirection != null) {
+            resolveRedirectingConstructor(resolver, tree, element, redirection);
+          }
+        } else if (tree.initializers != null) {
+          error(tree, MessageKind.FUNCTION_WITH_INITIALIZER);
+        }
+        visitBody(visitor, tree.body);
+
+        // Get the resolution tree and check that the resolved
+        // function doesn't use 'super' if it is mixed into another
+        // class. This is the part of the 'super' mixin check that
+        // happens when a function is resolved after the mixin
+        // application has been performed.
+        TreeElements resolutionTree = visitor.mapping;
+        ClassElement enclosingClass = element.getEnclosingClass();
+        if (enclosingClass != null) {
+          Set<MixinApplicationElement> mixinUses =
+              compiler.world.mixinUses[enclosingClass];
+          if (mixinUses != null) {
+            ClassElement mixin = enclosingClass;
+            for (MixinApplicationElement mixinApplication in mixinUses) {
+              checkMixinSuperUses(resolutionTree, mixinApplication, mixin);
+            }
+          }
+        }
+        return resolutionTree;
+      });
+    });
+  }
+
+  /// This method should only be used by this library (or tests of
+  /// this library).
+  ResolverVisitor visitorFor(Element element) {
+    var mapping = new TreeElementMapping(element);
+    return new ResolverVisitor(compiler, element, mapping);
+  }
+
+  void visitBody(ResolverVisitor visitor, Statement body) {
+    visitor.visit(body);
+  }
+
+  void resolveConstructorImplementation(FunctionElement constructor,
+                                        FunctionExpression node) {
+    if (!identical(constructor.defaultImplementation, constructor)) return;
+    ClassElement intrface = constructor.getEnclosingClass();
+    if (!intrface.isInterface()) return;
+    DartType defaultType = intrface.defaultClass;
+    if (defaultType == null) {
+      error(node, MessageKind.NO_DEFAULT_CLASS,
+            {'interfaceName': intrface.name});
+    }
+    ClassElement defaultClass = defaultType.element;
+    defaultClass.ensureResolved(compiler);
+    assert(defaultClass.resolutionState == STATE_DONE);
+    assert(defaultClass.supertypeLoadState == STATE_DONE);
+    if (defaultClass.isInterface()) {
+      error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE,
+            {'interfaceName': defaultClass.name});
+    }
+    // We have now established the following:
+    // [intrface] is an interface, let's say "MyInterface".
+    // [defaultClass] is a class, let's say "MyClass".
+
+    Selector selector;
+    // If the default class implements the interface then we must use the
+    // default class' name. Otherwise we look for a factory with the name
+    // of the interface.
+    if (defaultClass.implementsInterface(intrface)) {
+      var constructorNameString = constructor.name.slowToString();
+      // Create selector based on constructor.name but where interface
+      // is replaced with default class name.
+      // TODO(ahe): Don't use string manipulations here.
+      int classNameSeparatorIndex = constructorNameString.indexOf('\$');
+      if (classNameSeparatorIndex < 0) {
+        selector = new Selector.callDefaultConstructor(
+            defaultClass.getLibrary());
+      } else {
+        selector = new Selector.callConstructor(
+            new SourceString(
+                constructorNameString.substring(classNameSeparatorIndex + 1)),
+            defaultClass.getLibrary());
+      }
+      constructor.defaultImplementation =
+          defaultClass.lookupConstructor(selector);
+    } else {
+      selector =
+          new Selector.callConstructor(constructor.name,
+                                       defaultClass.getLibrary());
+      constructor.defaultImplementation =
+          defaultClass.lookupFactoryConstructor(selector);
+    }
+    if (constructor.defaultImplementation == null) {
+      // We failed to find a constructor named either
+      // "MyInterface.name" or "MyClass.name".
+      // TODO(aprelev@gmail.com): Use constructorNameForDiagnostics in
+      // the error message below.
+      error(node,
+            MessageKind.CANNOT_FIND_CONSTRUCTOR2,
+            {'constructorName': selector.name, 'className': defaultClass.name});
+    }
+  }
+
+  TreeElements resolveField(VariableElement element) {
+    Node tree = element.parseNode(compiler);
+    if(element.modifiers.isStatic() && element.variables.isTopLevel()) {
+      error(element.modifiers.getStatic(),
+            MessageKind.TOP_LEVEL_VARIABLE_DECLARED_STATIC);
+    }
+    ResolverVisitor visitor = visitorFor(element);
+    initializerDo(tree, visitor.visit);
+
+    // Perform various checks as side effect of "computing" the type.
+    element.computeType(compiler);
+
+    return visitor.mapping;
+  }
+
+  TreeElements resolveParameter(Element element) {
+    Node tree = element.parseNode(compiler);
+    ResolverVisitor visitor = visitorFor(element.enclosingElement);
+    initializerDo(tree, visitor.visit);
+    return visitor.mapping;
+  }
+
+  DartType resolveTypeAnnotation(Element element, TypeAnnotation annotation) {
+    DartType type = resolveReturnType(element, annotation);
+    if (type == compiler.types.voidType) {
+      error(annotation, MessageKind.VOID_NOT_ALLOWED);
+    }
+    return type;
+  }
+
+  DartType resolveReturnType(Element element, TypeAnnotation annotation) {
+    if (annotation == null) return compiler.types.dynamicType;
+    DartType result = visitorFor(element).resolveTypeAnnotation(annotation);
+    if (result == null) {
+      // TODO(karklose): warning.
+      return compiler.types.dynamicType;
+    }
+    return result;
+  }
+
+  /**
+   * Load and resolve the supertypes of [cls].
+   *
+   * Warning: do not call this method directly. It should only be
+   * called by [resolveClass] and [ClassSupertypeResolver].
+   */
+  void loadSupertypes(ClassElement cls, Spannable from) {
+    compiler.withCurrentElement(cls, () => measure(() {
+      if (cls.supertypeLoadState == STATE_DONE) return;
+      if (cls.supertypeLoadState == STATE_STARTED) {
+        compiler.reportErrorCode(from, MessageKind.CYCLIC_CLASS_HIERARCHY,
+                                 {'className': cls.name});
+        cls.supertypeLoadState = STATE_DONE;
+        cls.allSupertypes = const Link<DartType>().prepend(
+            compiler.objectClass.computeType(compiler));
+        // TODO(ahe): We should also set cls.supertype here to avoid
+        // creating a malformed class hierarchy.
+        return;
+      }
+      cls.supertypeLoadState = STATE_STARTED;
+      compiler.withCurrentElement(cls, () {
+        // TODO(ahe): Cache the node in cls.
+        cls.parseNode(compiler).accept(
+            new ClassSupertypeResolver(compiler, cls));
+        if (cls.supertypeLoadState != STATE_DONE) {
+          cls.supertypeLoadState = STATE_DONE;
+        }
+      });
+    }));
+  }
+
+  // TODO(johnniwinther): Remove this queue when resolution has been split into
+  // syntax and semantic resolution.
+  ClassElement currentlyResolvedClass;
+  Queue<ClassElement> pendingClassesToBeResolved = new Queue<ClassElement>();
+
+  /**
+   * Resolve the class [element].
+   *
+   * Before calling this method, [element] was constructed by the
+   * scanner and most fields are null or empty. This method fills in
+   * these fields and also ensure that the supertypes of [element] are
+   * resolved.
+   *
+   * Warning: Do not call this method directly. Instead use
+   * [:element.ensureResolved(compiler):].
+   */
+  void resolveClass(ClassElement element) {
+    ClassElement previousResolvedClass = currentlyResolvedClass;
+    currentlyResolvedClass = element;
+    resolveClassInternal(element);
+    if (previousResolvedClass == null) {
+      while (!pendingClassesToBeResolved.isEmpty) {
+        pendingClassesToBeResolved.removeFirst().ensureResolved(compiler);
+      }
+    }
+    currentlyResolvedClass = previousResolvedClass;
+  }
+
+  void _ensureClassWillBeResolved(ClassElement element) {
+    if (currentlyResolvedClass == null) {
+      element.ensureResolved(compiler);
+    } else {
+      pendingClassesToBeResolved.add(element);
+    }
+  }
+
+  void resolveClassInternal(ClassElement element) {
+    if (!element.isPatch) {
+      compiler.withCurrentElement(element, () => measure(() {
+        assert(element.resolutionState == STATE_NOT_STARTED);
+        element.resolutionState = STATE_STARTED;
+        Node tree = element.parseNode(compiler);
+        loadSupertypes(element, tree);
+
+        ClassResolverVisitor visitor =
+            new ClassResolverVisitor(compiler, element);
+        visitor.visit(tree);
+        element.resolutionState = STATE_DONE;
+      }));
+      if (element.isPatched) {
+        // Ensure handling patch after origin.
+        element.patch.ensureResolved(compiler);
+      }
+    } else { // Handle patch classes:
+      element.resolutionState = STATE_STARTED;
+      // Ensure handling origin before patch.
+      element.origin.ensureResolved(compiler);
+      // Ensure that the type is computed.
+      element.computeType(compiler);
+      // Copy class hiearchy from origin.
+      element.supertype = element.origin.supertype;
+      element.defaultClass = element.origin.defaultClass;
+      element.interfaces = element.origin.interfaces;
+      element.allSupertypes = element.origin.allSupertypes;
+      // Stepwise assignment to ensure invariant.
+      element.supertypeLoadState = STATE_STARTED;
+      element.supertypeLoadState = STATE_DONE;
+      element.resolutionState = STATE_DONE;
+      // TODO(johnniwinther): Check matching type variables and
+      // empty extends/implements clauses.
+    }
+    for (MetadataAnnotation metadata in element.metadata) {
+      metadata.ensureResolved(compiler);
+    }
+  }
+
+  void checkClass(ClassElement element) {
+    if (element.isMixinApplication) {
+      checkMixinApplication(element);
+    } else {
+      checkClassMembers(element);
+    }
+  }
+
+  void checkMixinApplication(MixinApplicationElement mixinApplication) {
+    Modifiers modifiers = mixinApplication.modifiers;
+    int illegalFlags = modifiers.flags & ~Modifiers.FLAG_ABSTRACT;
+    if (illegalFlags != 0) {
+      Modifiers illegalModifiers = new Modifiers.withFlags(null, illegalFlags);
+      compiler.reportErrorCode(
+          modifiers,
+          MessageKind.ILLEGAL_MIXIN_APPLICATION_MODIFIERS,
+          {'modifiers': illegalModifiers});
+    }
+
+    // In case of cyclic mixin applications, the mixin chain will have
+    // been cut. If so, we have already reported the error to the
+    // user so we just return from here.
+    ClassElement mixin = mixinApplication.mixin;
+    if (mixin == null) return;
+
+    // Check that the mixed in class has Object as its superclass.
+    if (!mixin.superclass.isObject(compiler)) {
+      compiler.reportErrorCode(mixin, MessageKind.ILLEGAL_MIXIN_SUPERCLASS);
+    }
+
+    // Check that the mixed in class doesn't have any constructors and
+    // make sure we aren't mixing in methods that use 'super'.
+    mixin.forEachLocalMember((Element member) {
+      if (member.isGenerativeConstructor() && !member.isSynthesized) {
+        compiler.reportErrorCode(member, MessageKind.ILLEGAL_MIXIN_CONSTRUCTOR);
+      } else {
+        // Get the resolution tree and check that the resolved member
+        // doesn't use 'super'. This is the part of the 'super' mixin
+        // check that happens when a function is resolved before the
+        // mixin application has been performed.
+        checkMixinSuperUses(
+            compiler.enqueuer.resolution.resolvedElements[member],
+            mixinApplication,
+            mixin);
+      }
+    });
+  }
+
+  void checkMixinSuperUses(TreeElements resolutionTree,
+                           MixinApplicationElement mixinApplication,
+                           ClassElement mixin) {
+    if (resolutionTree == null) return;
+    Set<Node> superUses = resolutionTree.superUses;
+    if (superUses.isEmpty) return;
+    compiler.reportErrorCode(mixinApplication,
+                             MessageKind.ILLEGAL_MIXIN_WITH_SUPER,
+                             {'className': mixin.name});
+    // Show the user the problematic uses of 'super' in the mixin.
+    for (Node use in superUses) {
+      CompilationError error = MessageKind.ILLEGAL_MIXIN_SUPER_USE.error();
+      compiler.reportMessage(compiler.spanFromNode(use),
+                             error, Diagnostic.INFO);
+    }
+  }
+
+  void checkClassMembers(ClassElement cls) {
+    assert(invariant(cls, cls.isDeclaration));
+    if (cls.isObject(compiler)) return;
+    // TODO(johnniwinther): Should this be done on the implementation element as
+    // well?
+    cls.forEachMember((holder, member) {
+      compiler.withCurrentElement(member, () {
+        // Perform various checks as side effect of "computing" the type.
+        member.computeType(compiler);
+
+        // Check modifiers.
+        if (member.isFunction() && member.modifiers.isFinal()) {
+          compiler.reportErrorCode(
+              member, MessageKind.ILLEGAL_FINAL_METHOD_MODIFIER);
+        }
+        if (member.isConstructor()) {
+          final mismatchedFlagsBits =
+              member.modifiers.flags &
+              (Modifiers.FLAG_STATIC | Modifiers.FLAG_ABSTRACT);
+          if (mismatchedFlagsBits != 0) {
+            final mismatchedFlags =
+                new Modifiers.withFlags(null, mismatchedFlagsBits);
+            compiler.reportErrorCode(
+                member,
+                MessageKind.ILLEGAL_CONSTRUCTOR_MODIFIERS,
+                {'modifiers': mismatchedFlags});
+          }
+          checkConstructorNameHack(holder, member);
+        }
+        checkAbstractField(member);
+        checkValidOverride(member, cls.lookupSuperMember(member.name));
+        checkUserDefinableOperator(member);
+      });
+    });
+  }
+
+  // TODO(ahe): Remove this method.  It is only needed while we store
+  // constructor names as ClassName$id.  Once we start storing
+  // constructors as just id, this will be caught by the general
+  // mechanism for duplicate members.
+  /// Check that a constructor name does not conflict with a member.
+  void checkConstructorNameHack(ClassElement holder, FunctionElement member) {
+    // If the name of the constructor is the same as the name of the
+    // class, there cannot be a problem.
+    if (member.name == holder.name) return;
+
+    SourceString name =
+      Elements.deconstructConstructorName(member.name, holder);
+
+    // If the name could not be deconstructed, this is is from a
+    // factory method from a deprecated interface implementation.
+    if (name == null) return;
+
+    Element otherMember = holder.lookupLocalMember(name);
+    if (otherMember != null) {
+      if (compiler.onDeprecatedFeature(member, 'conflicting constructor')) {
+        compiler.reportMessage(
+            compiler.spanFromElement(otherMember),
+            MessageKind.GENERIC.error({'text': 'This member conflicts with a'
+                                               ' constructor.'}),
+            Diagnostic.INFO);
+      }
+    }
+  }
+
+  void checkAbstractField(Element member) {
+    // Only check for getters. The test can only fail if there is both a setter
+    // and a getter with the same name, and we only need to check each abstract
+    // field once, so we just ignore setters.
+    if (!member.isGetter()) return;
+
+    // Find the associated abstract field.
+    ClassElement classElement = member.getEnclosingClass();
+    Element lookupElement = classElement.lookupLocalMember(member.name);
+    if (lookupElement == null) {
+      compiler.internalErrorOnElement(member,
+                                      "No abstract field for accessor");
+    } else if (!identical(lookupElement.kind, ElementKind.ABSTRACT_FIELD)) {
+       compiler.internalErrorOnElement(
+           member, "Inaccessible abstract field for accessor");
+    }
+    AbstractFieldElement field = lookupElement;
+
+    if (field.getter == null) return;
+    if (field.setter == null) return;
+    int getterFlags = field.getter.modifiers.flags | Modifiers.FLAG_ABSTRACT;
+    int setterFlags = field.setter.modifiers.flags | Modifiers.FLAG_ABSTRACT;
+    if (!identical(getterFlags, setterFlags)) {
+      final mismatchedFlags =
+        new Modifiers.withFlags(null, getterFlags ^ setterFlags);
+      compiler.reportErrorCode(
+          field.getter,
+          MessageKind.GETTER_MISMATCH,
+          {'modifiers': mismatchedFlags});
+      compiler.reportErrorCode(
+          field.setter,
+          MessageKind.SETTER_MISMATCH,
+          {'modifiers': mismatchedFlags});
+    }
+  }
+
+  void checkUserDefinableOperator(Element member) {
+    FunctionElement function = member.asFunctionElement();
+    if (function == null) return;
+    String value = member.name.stringValue;
+    if (value == null) return;
+    if (!(isUserDefinableOperator(value) || identical(value, 'unary-'))) return;
+
+    bool isMinus = false;
+    int requiredParameterCount;
+    MessageKind messageKind;
+    FunctionSignature signature = function.computeSignature(compiler);
+    if (identical(value, 'unary-')) {
+      isMinus = true;
+      messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 0;
+    } else if (isMinusOperator(value)) {
+      isMinus = true;
+      messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 1;
+    } else if (isUnaryOperator(value)) {
+      messageKind = MessageKind.UNARY_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 0;
+    } else if (isBinaryOperator(value)) {
+      messageKind = MessageKind.BINARY_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 1;
+    } else if (isTernaryOperator(value)) {
+      messageKind = MessageKind.TERNARY_OPERATOR_BAD_ARITY;
+      requiredParameterCount = 2;
+    } else {
+      compiler.internalErrorOnElement(function,
+          'Unexpected user defined operator $value');
+    }
+    checkArity(function, requiredParameterCount, messageKind, isMinus);
+  }
+
+  void checkArity(FunctionElement function,
+                  int requiredParameterCount, MessageKind messageKind,
+                  bool isMinus) {
+    FunctionExpression node = function.parseNode(compiler);
+    FunctionSignature signature = function.computeSignature(compiler);
+    if (signature.requiredParameterCount != requiredParameterCount) {
+      Node errorNode = node;
+      if (node.parameters != null) {
+        if (isMinus ||
+            signature.requiredParameterCount < requiredParameterCount) {
+          // If there are too few parameters, point to the whole parameter list.
+          // For instance
+          //
+          //     int operator +() {}
+          //                   ^^
+          //
+          //     int operator []=(value) {}
+          //                     ^^^^^^^
+          //
+          // For operator -, always point the whole parameter list, like
+          //
+          //     int operator -(a, b) {}
+          //                   ^^^^^^
+          //
+          // instead of
+          //
+          //     int operator -(a, b) {}
+          //                       ^
+          //
+          // since the correction might not be to remove 'b' but instead to
+          // remove 'a, b'.
+          errorNode = node.parameters;
+        } else {
+          errorNode = node.parameters.nodes.skip(requiredParameterCount).head;
+        }
+      }
+      compiler.reportErrorCode(
+          errorNode, messageKind, {'operatorName': function.name});
+    }
+    if (signature.optionalParameterCount != 0) {
+      Node errorNode =
+          node.parameters.nodes.skip(signature.requiredParameterCount).head;
+      if (signature.optionalParametersAreNamed) {
+        compiler.reportErrorCode(
+            errorNode,
+            MessageKind.OPERATOR_NAMED_PARAMETERS,
+            {'operatorName': function.name});
+      } else {
+        compiler.reportErrorCode(
+            errorNode,
+            MessageKind.OPERATOR_OPTIONAL_PARAMETERS,
+            {'operatorName': function.name});
+      }
+    }
+  }
+
+  reportErrorWithContext(Element errorneousElement,
+                         MessageKind errorMessage,
+                         Element contextElement,
+                         MessageKind contextMessage) {
+    compiler.reportErrorCode(
+        errorneousElement,
+        errorMessage,
+        {'memberName': contextElement.name,
+         'className': contextElement.getEnclosingClass().name});
+    compiler.reportMessage(
+        compiler.spanFromElement(contextElement),
+        contextMessage.error(),
+        Diagnostic.INFO);
+  }
+
+  void checkValidOverride(Element member, Element superMember) {
+    if (superMember == null) return;
+    if (member.modifiers.isStatic()) {
+      reportErrorWithContext(
+          member, MessageKind.NO_STATIC_OVERRIDE,
+          superMember, MessageKind.NO_STATIC_OVERRIDE_CONT);
+    } else {
+      FunctionElement superFunction = superMember.asFunctionElement();
+      FunctionElement function = member.asFunctionElement();
+      if (superFunction == null || superFunction.isAccessor()) {
+        // Field or accessor in super.
+        if (function != null && !function.isAccessor()) {
+          // But a plain method in this class.
+          reportErrorWithContext(
+              member, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD,
+              superMember, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT);
+        }
+      } else {
+        // Instance method in super.
+        if (function == null || function.isAccessor()) {
+          // But a field (or accessor) in this class.
+          reportErrorWithContext(
+              member, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD,
+              superMember, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT);
+        } else {
+          // Both are plain instance methods.
+          if (superFunction.requiredParameterCount(compiler) !=
+              function.requiredParameterCount(compiler)) {
+          reportErrorWithContext(
+              member,
+              MessageKind.BAD_ARITY_OVERRIDE,
+              superMember,
+              MessageKind.BAD_ARITY_OVERRIDE_CONT);
+          }
+          // TODO(ahe): Check optional parameters.
+        }
+      }
+    }
+  }
+
+  FunctionSignature resolveSignature(FunctionElement element) {
+    return compiler.withCurrentElement(element, () {
+      FunctionExpression node =
+          compiler.parser.measure(() => element.parseNode(compiler));
+      return measure(() => SignatureResolver.analyze(
+          compiler, node.parameters, node.returnType, element));
+    });
+  }
+
+  FunctionSignature resolveFunctionExpression(Element element,
+                                              FunctionExpression node) {
+    return measure(() => SignatureResolver.analyze(
+      compiler, node.parameters, node.returnType, element));
+  }
+
+  void resolveTypedef(TypedefElement element) {
+    if (element.isResolved || element.isBeingResolved) return;
+    element.isBeingResolved = true;
+    return compiler.withCurrentElement(element, () {
+      measure(() {
+        Typedef node =
+          compiler.parser.measure(() => element.parseNode(compiler));
+        TypedefResolverVisitor visitor =
+          new TypedefResolverVisitor(compiler, element);
+        visitor.visit(node);
+
+        element.isBeingResolved = false;
+        element.isResolved = true;
+      });
+    });
+  }
+
+  FunctionType computeFunctionType(Element element,
+                                   FunctionSignature signature) {
+    var parameterTypes = new LinkBuilder<DartType>();
+    for (Element parameter in signature.requiredParameters) {
+       parameterTypes.addLast(parameter.computeType(compiler));
+    }
+    var optionalParameterTypes = const Link<DartType>();
+    var namedParameters = const Link<SourceString>();
+    var namedParameterTypes = const Link<DartType>();
+    if (signature.optionalParametersAreNamed) {
+      var namedParametersBuilder = new LinkBuilder<SourceString>();
+      var namedParameterTypesBuilder = new LinkBuilder<DartType>();
+      for (Element parameter in signature.orderedOptionalParameters) {
+        namedParametersBuilder.addLast(parameter.name);
+        namedParameterTypesBuilder.addLast(parameter.computeType(compiler));
+      }
+      namedParameters = namedParametersBuilder.toLink();
+      namedParameterTypes = namedParameterTypesBuilder.toLink();
+    } else {
+      var optionalParameterTypesBuilder = new LinkBuilder<DartType>();
+      for (Element parameter in signature.optionalParameters) {
+        optionalParameterTypesBuilder.addLast(parameter.computeType(compiler));
+      }
+      optionalParameterTypes = optionalParameterTypesBuilder.toLink();
+    }
+    return new FunctionType(element,
+        signature.returnType,
+        parameterTypes.toLink(),
+        optionalParameterTypes,
+        namedParameters,
+        namedParameterTypes);
+  }
+
+  void resolveMetadataAnnotation(PartialMetadataAnnotation annotation) {
+    compiler.withCurrentElement(annotation.annotatedElement, () => measure(() {
+      assert(annotation.resolutionState == STATE_NOT_STARTED);
+      annotation.resolutionState = STATE_STARTED;
+
+      Node node = annotation.parseNode(compiler);
+      ResolverVisitor visitor =
+          visitorFor(annotation.annotatedElement.enclosingElement);
+      node.accept(visitor);
+      annotation.value = compiler.metadataHandler.compileNodeWithDefinitions(
+          node, visitor.mapping, isConst: true);
+
+      annotation.resolutionState = STATE_DONE;
+    }));
+  }
+
+  error(Node node, MessageKind kind, [arguments = const {}]) {
+    ResolutionError message = new ResolutionError(kind, arguments);
+    compiler.reportError(node, message);
+  }
+}
+
+class InitializerResolver {
+  final ResolverVisitor visitor;
+  final Map<SourceString, Node> initialized;
+  Link<Node> initializers;
+  bool hasSuper;
+
+  InitializerResolver(this.visitor)
+    : initialized = new Map<SourceString, Node>(), hasSuper = false;
+
+  error(Node node, MessageKind kind, [arguments = const {}]) {
+    visitor.error(node, kind, arguments);
+  }
+
+  warning(Node node, MessageKind kind, [arguments = const {}]) {
+    visitor.warning(node, kind, arguments);
+  }
+
+  bool isFieldInitializer(SendSet node) {
+    if (node.selector.asIdentifier() == null) return false;
+    if (node.receiver == null) return true;
+    if (node.receiver.asIdentifier() == null) return false;
+    return node.receiver.asIdentifier().isThis();
+  }
+
+  void checkForDuplicateInitializers(SourceString name, Node init) {
+    if (initialized.containsKey(name)) {
+      error(init, MessageKind.DUPLICATE_INITIALIZER, {'fieldName': name});
+      warning(initialized[name], MessageKind.ALREADY_INITIALIZED,
+              {'fieldName': name});
+    }
+    initialized[name] = init;
+  }
+
+  void resolveFieldInitializer(FunctionElement constructor, SendSet init) {
+    // init is of the form [this.]field = value.
+    final Node selector = init.selector;
+    final SourceString name = selector.asIdentifier().source;
+    // Lookup target field.
+    Element target;
+    if (isFieldInitializer(init)) {
+      target = constructor.getEnclosingClass().lookupLocalMember(name);
+      if (target == null) {
+        error(selector, MessageKind.CANNOT_RESOLVE, {'name': name});
+      } else if (target.kind != ElementKind.FIELD) {
+        error(selector, MessageKind.NOT_A_FIELD, {'fieldName': name});
+      } else if (!target.isInstanceMember()) {
+        error(selector, MessageKind.INIT_STATIC_FIELD, {'fieldName': name});
+      }
+    } else {
+      error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER);
+    }
+    visitor.useElement(init, target);
+    visitor.world.registerStaticUse(target);
+    checkForDuplicateInitializers(name, init);
+    // Resolve initializing value.
+    visitor.visitInStaticContext(init.arguments.head);
+  }
+
+  ClassElement getSuperOrThisLookupTarget(FunctionElement constructor,
+                                          bool isSuperCall,
+                                          Node diagnosticNode) {
+    ClassElement lookupTarget = constructor.getEnclosingClass();
+    if (isSuperCall) {
+      // Calculate correct lookup target and constructor name.
+      if (identical(lookupTarget, visitor.compiler.objectClass)) {
+        error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT);
+      } else {
+        return lookupTarget.supertype.element;
+      }
+    }
+    return lookupTarget;
+  }
+
+  Element resolveSuperOrThisForSend(FunctionElement constructor,
+                                    FunctionExpression functionNode,
+                                    Send call) {
+    // Resolve the selector and the arguments.
+    ResolverTask resolver = visitor.compiler.resolver;
+    visitor.inStaticContext(() {
+      visitor.resolveSelector(call);
+      visitor.resolveArguments(call.argumentsNode);
+    });
+    Selector selector = visitor.mapping.getSelector(call);
+    bool isSuperCall = Initializers.isSuperConstructorCall(call);
+
+    ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor,
+                                                           isSuperCall,
+                                                           call);
+    Selector constructorSelector =
+        visitor.getRedirectingThisOrSuperConstructorSelector(call);
+    FunctionElement calledConstructor =
+        lookupTarget.lookupConstructor(constructorSelector);
+
+    final bool isImplicitSuperCall = false;
+    final SourceString className = lookupTarget.name;
+    verifyThatConstructorMatchesCall(calledConstructor,
+                                     selector,
+                                     isImplicitSuperCall,
+                                     call,
+                                     className,
+                                     constructorSelector);
+
+    visitor.useElement(call, calledConstructor);
+    visitor.world.registerStaticUse(calledConstructor);
+    return calledConstructor;
+  }
+
+  void resolveImplicitSuperConstructorSend(FunctionElement constructor,
+                                           FunctionExpression functionNode) {
+    // If the class has a super resolve the implicit super call.
+    ClassElement classElement = constructor.getEnclosingClass();
+    ClassElement superClass = classElement.superclass;
+    if (classElement != visitor.compiler.objectClass) {
+      assert(superClass != null);
+      assert(superClass.resolutionState == STATE_DONE);
+      SourceString constructorName = const SourceString('');
+      Selector callToMatch = new Selector.call(
+          constructorName,
+          classElement.getLibrary(),
+          0);
+
+      final bool isSuperCall = true;
+      ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor,
+                                                             isSuperCall,
+                                                             functionNode);
+      Selector constructorSelector = new Selector.callDefaultConstructor(
+          visitor.enclosingElement.getLibrary());
+      Element calledConstructor = lookupTarget.lookupConstructor(
+          constructorSelector);
+
+      final SourceString className = lookupTarget.name;
+      final bool isImplicitSuperCall = true;
+      verifyThatConstructorMatchesCall(calledConstructor,
+                                       callToMatch,
+                                       isImplicitSuperCall,
+                                       functionNode,
+                                       className,
+                                       constructorSelector);
+
+      visitor.world.registerStaticUse(calledConstructor);
+    }
+  }
+
+  void verifyThatConstructorMatchesCall(
+      FunctionElement lookedupConstructor,
+      Selector call,
+      bool isImplicitSuperCall,
+      Node diagnosticNode,
+      SourceString className,
+      Selector constructorSelector) {
+    if (lookedupConstructor == null
+        || !lookedupConstructor.isGenerativeConstructor()) {
+      var fullConstructorName =
+          visitor.compiler.resolver.constructorNameForDiagnostics(
+              className,
+              constructorSelector.name);
+      MessageKind kind = isImplicitSuperCall
+          ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT
+          : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR;
+      error(diagnosticNode, kind, {'constructorName': fullConstructorName});
+    } else {
+      if (!call.applies(lookedupConstructor, visitor.compiler)) {
+        MessageKind kind = isImplicitSuperCall
+                           ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT
+                           : MessageKind.NO_MATCHING_CONSTRUCTOR;
+        error(diagnosticNode, kind);
+      }
+    }
+  }
+
+  FunctionElement resolveRedirection(FunctionElement constructor,
+                                     FunctionExpression functionNode) {
+    if (functionNode.initializers == null) return null;
+    Link<Node> link = functionNode.initializers.nodes;
+    if (!link.isEmpty && Initializers.isConstructorRedirect(link.head)) {
+      return resolveSuperOrThisForSend(constructor, functionNode, link.head);
+    }
+    return null;
+  }
+
+  /**
+   * Resolve all initializers of this constructor. In the case of a redirecting
+   * constructor, the resolved constructor's function element is returned.
+   */
+  FunctionElement resolveInitializers(FunctionElement constructor,
+                                      FunctionExpression functionNode) {
+    // Keep track of all "this.param" parameters specified for constructor so
+    // that we can ensure that fields are initialized only once.
+    FunctionSignature functionParameters =
+        constructor.computeSignature(visitor.compiler);
+    functionParameters.forEachParameter((Element element) {
+      if (identical(element.kind, ElementKind.FIELD_PARAMETER)) {
+        checkForDuplicateInitializers(element.name,
+                                      element.parseNode(visitor.compiler));
+      }
+    });
+
+    if (functionNode.initializers == null) {
+      initializers = const Link<Node>();
+    } else {
+      initializers = functionNode.initializers.nodes;
+    }
+    FunctionElement result;
+    bool resolvedSuper = false;
+    for (Link<Node> link = initializers;
+         !link.isEmpty;
+         link = link.tail) {
+      if (link.head.asSendSet() != null) {
+        final SendSet init = link.head.asSendSet();
+        resolveFieldInitializer(constructor, init);
+      } else if (link.head.asSend() != null) {
+        final Send call = link.head.asSend();
+        if (Initializers.isSuperConstructorCall(call)) {
+          if (resolvedSuper) {
+            error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER);
+          }
+          resolveSuperOrThisForSend(constructor, functionNode, call);
+          resolvedSuper = true;
+        } else if (Initializers.isConstructorRedirect(call)) {
+          // Check that there is no body (Language specification 7.5.1).
+          if (functionNode.hasBody()) {
+            error(functionNode, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_BODY);
+          }
+          // Check that there are no other initializers.
+          if (!initializers.tail.isEmpty) {
+            error(call, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER);
+          }
+          return resolveSuperOrThisForSend(constructor, functionNode, call);
+        } else {
+          visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED);
+          return null;
+        }
+      } else {
+        error(link.head, MessageKind.INVALID_INITIALIZER);
+      }
+    }
+    if (!resolvedSuper) {
+      resolveImplicitSuperConstructorSend(constructor, functionNode);
+    }
+    return null;  // If there was no redirection always return null.
+  }
+}
+
+class CommonResolverVisitor<R> extends Visitor<R> {
+  final Compiler compiler;
+
+  CommonResolverVisitor(Compiler this.compiler);
+
+  R visitNode(Node node) {
+    cancel(node,
+           'internal error: Unhandled node: ${node.getObjectDescription()}');
+  }
+
+  R visitEmptyStatement(Node node) => null;
+
+  /** Convenience method for visiting nodes that may be null. */
+  R visit(Node node) => (node == null) ? null : node.accept(this);
+
+  void error(Node node, MessageKind kind, [Map arguments = const {}]) {
+    ResolutionError message  = new ResolutionError(kind, arguments);
+    compiler.reportError(node, message);
+  }
+
+  void warning(Node node, MessageKind kind, [Map arguments = const {}]) {
+    ResolutionWarning message  = new ResolutionWarning(kind, arguments);
+    compiler.reportWarning(node, message);
+  }
+
+  void cancel(Node node, String message) {
+    compiler.cancel(message, node: node);
+  }
+
+  void internalError(Node node, String message) {
+    compiler.internalError(message, node: node);
+  }
+
+  void unimplemented(Node node, String message) {
+    compiler.unimplemented(message, node: node);
+  }
+}
+
+abstract class LabelScope {
+  LabelScope get outer;
+  LabelElement lookup(String label);
+}
+
+class LabeledStatementLabelScope implements LabelScope {
+  final LabelScope outer;
+  final Map<String, LabelElement> labels;
+  LabeledStatementLabelScope(this.outer, this.labels);
+  LabelElement lookup(String labelName) {
+    LabelElement label = labels[labelName];
+    if (label != null) return label;
+    return outer.lookup(labelName);
+  }
+}
+
+class SwitchLabelScope implements LabelScope {
+  final LabelScope outer;
+  final Map<String, LabelElement> caseLabels;
+
+  SwitchLabelScope(this.outer, this.caseLabels);
+
+  LabelElement lookup(String labelName) {
+    LabelElement result = caseLabels[labelName];
+    if (result != null) return result;
+    return outer.lookup(labelName);
+  }
+}
+
+class EmptyLabelScope implements LabelScope {
+  const EmptyLabelScope();
+  LabelElement lookup(String label) => null;
+  LabelScope get outer {
+    throw 'internal error: empty label scope has no outer';
+  }
+}
+
+class StatementScope {
+  LabelScope labels;
+  Link<TargetElement> breakTargetStack;
+  Link<TargetElement> continueTargetStack;
+  // Used to provide different numbers to statements if one is inside the other.
+  // Can be used to make otherwise duplicate labels unique.
+  int nestingLevel = 0;
+
+  StatementScope()
+      : labels = const EmptyLabelScope(),
+        breakTargetStack = const Link<TargetElement>(),
+        continueTargetStack = const Link<TargetElement>();
+
+  LabelElement lookupLabel(String label) {
+    return labels.lookup(label);
+  }
+
+  TargetElement currentBreakTarget() =>
+    breakTargetStack.isEmpty ? null : breakTargetStack.head;
+
+  TargetElement currentContinueTarget() =>
+    continueTargetStack.isEmpty ? null : continueTargetStack.head;
+
+  void enterLabelScope(Map<String, LabelElement> elements) {
+    labels = new LabeledStatementLabelScope(labels, elements);
+    nestingLevel++;
+  }
+
+  void exitLabelScope() {
+    nestingLevel--;
+    labels = labels.outer;
+  }
+
+  void enterLoop(TargetElement element) {
+    breakTargetStack = breakTargetStack.prepend(element);
+    continueTargetStack = continueTargetStack.prepend(element);
+    nestingLevel++;
+  }
+
+  void exitLoop() {
+    nestingLevel--;
+    breakTargetStack = breakTargetStack.tail;
+    continueTargetStack = continueTargetStack.tail;
+  }
+
+  void enterSwitch(TargetElement breakElement,
+                   Map<String, LabelElement> continueElements) {
+    breakTargetStack = breakTargetStack.prepend(breakElement);
+    labels = new SwitchLabelScope(labels, continueElements);
+    nestingLevel++;
+  }
+
+  void exitSwitch() {
+    nestingLevel--;
+    breakTargetStack = breakTargetStack.tail;
+    labels = labels.outer;
+  }
+}
+
+class TypeResolver {
+  final Compiler compiler;
+
+  TypeResolver(this.compiler);
+
+  Element resolveTypeName(Scope scope,
+                          SourceString prefixName,
+                          Identifier typeName) {
+    if (prefixName != null) {
+      Element e = scope.lookup(prefixName);
+      if (e != null) {
+        if (identical(e.kind, ElementKind.PREFIX)) {
+          // The receiver is a prefix. Lookup in the imported members.
+          PrefixElement prefix = e;
+          return prefix.lookupLocalMember(typeName.source);
+        } else if (identical(e.kind, ElementKind.CLASS)) {
+          // TODO(johnniwinther): Remove this case.
+          // The receiver is the class part of a named constructor.
+          return e;
+        }
+      } else {
+        // The caller creates the ErroneousElement for the MalformedType.
+        return null;
+      }
+    } else {
+      String stringValue = typeName.source.stringValue;
+      if (identical(stringValue, 'void')) {
+        return compiler.types.voidType.element;
+      } else if (identical(stringValue, 'Dynamic')) {
+        // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+        compiler.onDeprecatedFeature(typeName, 'Dynamic');
+        return compiler.dynamicClass;
+      } else if (identical(stringValue, 'dynamic')) {
+        return compiler.dynamicClass;
+      } else {
+        return scope.lookup(typeName.source);
+      }
+    }
+  }
+
+  // TODO(johnniwinther): Change  [onFailure] and [whenResolved] to use boolean
+  // flags instead of closures.
+  DartType resolveTypeAnnotation(
+      TypeAnnotation node,
+      Scope scope,
+      Element enclosingElement,
+      {onFailure(Node node, MessageKind kind, [Map arguments]),
+       whenResolved(Node node, DartType type)}) {
+    if (onFailure == null) {
+      onFailure = (n, k, [arguments]) {};
+    }
+    if (whenResolved == null) {
+      whenResolved = (n, t) {};
+    }
+    if (scope == null) {
+      compiler.internalError('resolveTypeAnnotation: no scope specified');
+    }
+    return resolveTypeAnnotationInContext(scope, node, enclosingElement,
+        onFailure, whenResolved);
+  }
+
+  DartType resolveTypeAnnotationInContext(Scope scope, TypeAnnotation node,
+                                          Element enclosingElement,
+                                          onFailure, whenResolved) {
+    Identifier typeName;
+    SourceString prefixName;
+    Send send = node.typeName.asSend();
+    if (send != null) {
+      // The type name is of the form [: prefix . identifier :].
+      prefixName = send.receiver.asIdentifier().source;
+      typeName = send.selector.asIdentifier();
+    } else {
+      typeName = node.typeName.asIdentifier();
+    }
+
+    Element element = resolveTypeName(scope, prefixName, typeName);
+    DartType type;
+
+    DartType reportFailureAndCreateType(MessageKind messageKind,
+                                        Map messageArguments) {
+      onFailure(node, messageKind, messageArguments);
+      var erroneousElement = new ErroneousElementX(
+          messageKind, messageArguments, typeName.source, enclosingElement);
+      var arguments = new LinkBuilder<DartType>();
+      resolveTypeArguments(
+          node, null, enclosingElement,
+          scope, onFailure, whenResolved, arguments);
+      return new MalformedType(erroneousElement, null, arguments.toLink());
+    }
+
+    DartType checkNoTypeArguments(DartType type) {
+      var arguments = new LinkBuilder<DartType>();
+      bool hashTypeArgumentMismatch = resolveTypeArguments(
+          node, const Link<DartType>(), enclosingElement,
+          scope, onFailure, whenResolved, arguments);
+      if (hashTypeArgumentMismatch) {
+        type = new MalformedType(
+            new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH,
+                {'type': node}, typeName.source, enclosingElement),
+                type, arguments.toLink());
+      }
+      return type;
+    }
+
+    if (element == null) {
+      type = reportFailureAndCreateType(
+          MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node.typeName});
+    } else if (element.isAmbiguous()) {
+      AmbiguousElement ambiguous = element;
+      type = reportFailureAndCreateType(
+          ambiguous.messageKind, ambiguous.messageArguments);
+    } else if (!element.impliesType()) {
+      type = reportFailureAndCreateType(
+          MessageKind.NOT_A_TYPE, {'node': node.typeName});
+    } else {
+      if (identical(element, compiler.types.voidType.element) ||
+          identical(element, compiler.types.dynamicType.element)) {
+        type = checkNoTypeArguments(element.computeType(compiler));
+      } else if (element.isClass()) {
+        ClassElement cls = element;
+        compiler.resolver._ensureClassWillBeResolved(cls);
+        element.computeType(compiler);
+        var arguments = new LinkBuilder<DartType>();
+        bool hashTypeArgumentMismatch = resolveTypeArguments(
+            node, cls.typeVariables, enclosingElement,
+            scope, onFailure, whenResolved, arguments);
+        if (hashTypeArgumentMismatch) {
+          type = new MalformedType(
+              new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH,
+                  {'type': node}, typeName.source, enclosingElement),
+              new InterfaceType(cls.declaration, arguments.toLink()));
+        } else {
+          if (arguments.isEmpty) {
+            type = cls.rawType;
+          } else {
+            type = new InterfaceType(cls.declaration, arguments.toLink());
+          }
+        }
+      } else if (element.isTypedef()) {
+        TypedefElement typdef = element;
+        // TODO(ahe): Should be [ensureResolved].
+        compiler.resolveTypedef(typdef);
+        var arguments = new LinkBuilder<DartType>();
+        bool hashTypeArgumentMismatch = resolveTypeArguments(
+            node, typdef.typeVariables, enclosingElement,
+            scope, onFailure, whenResolved, arguments);
+        if (hashTypeArgumentMismatch) {
+          type = new MalformedType(
+              new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH,
+                  {'type': node}, typeName.source, enclosingElement),
+              new TypedefType(typdef, arguments.toLink()));
+        } else {
+          if (arguments.isEmpty) {
+            type = typdef.rawType;
+          } else {
+           type = new TypedefType(typdef, arguments.toLink());
+          }
+        }
+      } else if (element.isTypeVariable()) {
+        if (enclosingElement.isInStaticMember()) {
+          compiler.reportWarning(node,
+              MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER.message(
+                  {'typeVariableName': node}));
+          type = new MalformedType(
+              new ErroneousElementX(
+                  MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER,
+                  {'typeVariableName': node},
+                  typeName.source, enclosingElement),
+                  element.computeType(compiler));
+        } else {
+          type = element.computeType(compiler);
+        }
+        type = checkNoTypeArguments(type);
+      } else {
+        compiler.cancel("unexpected element kind ${element.kind}",
+                        node: node);
+      }
+    }
+    whenResolved(node, type);
+    return type;
+  }
+
+  /**
+   * Resolves the type arguments of [node] and adds these to [arguments].
+   *
+   * Returns [: true :] if the number of type arguments did not match the
+   * number of type variables.
+   */
+  bool resolveTypeArguments(
+      TypeAnnotation node,
+      Link<DartType> typeVariables,
+      Element enclosingElement,
+      Scope scope,
+      onFailure, whenResolved,
+      LinkBuilder<DartType> arguments) {
+    if (node.typeArguments == null) {
+      return false;
+    }
+    bool typeArgumentCountMismatch = false;
+    for (Link<Node> typeArguments = node.typeArguments.nodes;
+         !typeArguments.isEmpty;
+         typeArguments = typeArguments.tail) {
+      if (typeVariables != null && typeVariables.isEmpty) {
+        onFailure(typeArguments.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
+        typeArgumentCountMismatch = true;
+      }
+      DartType argType = resolveTypeAnnotationInContext(scope,
+                                                        typeArguments.head,
+                                                        enclosingElement,
+                                                        onFailure,
+                                                        whenResolved);
+      arguments.addLast(argType);
+      if (typeVariables != null && !typeVariables.isEmpty) {
+        typeVariables = typeVariables.tail;
+      }
+    }
+    if (typeVariables != null && !typeVariables.isEmpty) {
+      onFailure(node.typeArguments, MessageKind.MISSING_TYPE_ARGUMENT);
+      typeArgumentCountMismatch = true;
+    }
+    return typeArgumentCountMismatch;
+  }
+}
+
+/**
+ * Core implementation of resolution.
+ *
+ * Do not subclass or instantiate this class outside this library
+ * except for testing.
+ */
+class ResolverVisitor extends CommonResolverVisitor<Element> {
+  final TreeElementMapping mapping;
+  Element enclosingElement;
+  final TypeResolver typeResolver;
+  bool inInstanceContext;
+  bool inCheckContext;
+  bool inCatchBlock;
+  Scope scope;
+  ClassElement currentClass;
+  ExpressionStatement currentExpressionStatement;
+  bool typeRequired = false;
+  StatementScope statementScope;
+  int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION
+      | ElementCategory.IMPLIES_TYPE;
+
+  ResolverVisitor(Compiler compiler, Element element, this.mapping)
+    : this.enclosingElement = element,
+      // When the element is a field, we are actually resolving its
+      // initial value, which should not have access to instance
+      // fields.
+      inInstanceContext = (element.isInstanceMember() && !element.isField())
+          || element.isGenerativeConstructor(),
+      this.currentClass = element.isMember() ? element.getEnclosingClass()
+                                             : null,
+      this.statementScope = new StatementScope(),
+      typeResolver = new TypeResolver(compiler),
+      scope = element.buildScope(),
+      inCheckContext = compiler.enableTypeAssertions,
+      inCatchBlock = false,
+      super(compiler);
+
+  ResolutionEnqueuer get world => compiler.enqueuer.resolution;
+
+  Element lookup(Node node, SourceString name) {
+    Element result = scope.lookup(name);
+    if (!Elements.isUnresolved(result)) {
+      if (!inInstanceContext && result.isInstanceMember()) {
+        compiler.reportErrorCode(
+            node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name});
+        return new ErroneousElementX(MessageKind.NO_INSTANCE_AVAILABLE,
+                                     {'name': name},
+                                     name, enclosingElement);
+      } else if (result.isAmbiguous()) {
+        AmbiguousElement ambiguous = result;
+        compiler.reportErrorCode(
+            node, ambiguous.messageKind, ambiguous.messageArguments);
+        return new ErroneousElementX(ambiguous.messageKind,
+                                     ambiguous.messageArguments,
+                                     name, enclosingElement);
+      }
+    }
+    return result;
+  }
+
+  // Create, or reuse an already created, statement element for a statement.
+  TargetElement getOrCreateTargetElement(Node statement) {
+    TargetElement element = mapping[statement];
+    if (element == null) {
+      element = new TargetElementX(statement,
+                                   statementScope.nestingLevel,
+                                   enclosingElement);
+      mapping[statement] = element;
+    }
+    return element;
+  }
+
+  doInCheckContext(action()) {
+    bool wasInCheckContext = inCheckContext;
+    inCheckContext = true;
+    var result = action();
+    inCheckContext = wasInCheckContext;
+    return result;
+  }
+
+  inStaticContext(action()) {
+    bool wasInstanceContext = inInstanceContext;
+    inInstanceContext = false;
+    var result = action();
+    inInstanceContext = wasInstanceContext;
+    return result;
+  }
+
+  visitInStaticContext(Node node) {
+    inStaticContext(() => visit(node));
+  }
+
+  ErroneousElement warnAndCreateErroneousElement(Node node,
+                                                 SourceString name,
+                                                 MessageKind kind,
+                                                 [Map arguments = const {}]) {
+    ResolutionWarning warning = new ResolutionWarning(kind, arguments);
+    compiler.reportWarning(node, warning);
+    return new ErroneousElementX(kind, arguments, name, enclosingElement);
+  }
+
+  Element visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      if (!inInstanceContext) {
+        error(node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node});
+      }
+      return null;
+    } else if (node.isSuper()) {
+      if (!inInstanceContext) error(node, MessageKind.NO_SUPER_IN_STATIC);
+      if ((ElementCategory.SUPER & allowedCategory) == 0) {
+        error(node, MessageKind.INVALID_USE_OF_SUPER);
+      }
+      return null;
+    } else {
+      Element element = lookup(node, node.source);
+      if (element == null) {
+        if (!inInstanceContext) {
+          element = warnAndCreateErroneousElement(node, node.source,
+                                                  MessageKind.CANNOT_RESOLVE,
+                                                  {'name': node});
+        }
+      } else if (element.isErroneous()) {
+        // Use the erroneous element.
+      } else {
+        if ((element.kind.category & allowedCategory) == 0) {
+          // TODO(ahe): Improve error message. Need UX input.
+          error(node, MessageKind.GENERIC,
+                {'text': "is not an expression $element"});
+        }
+      }
+      if (!Elements.isUnresolved(element)
+          && element.kind == ElementKind.CLASS) {
+        ClassElement classElement = element;
+        classElement.ensureResolved(compiler);
+      }
+      return useElement(node, element);
+    }
+  }
+
+  Element visitTypeAnnotation(TypeAnnotation node) {
+    DartType type = resolveTypeAnnotation(node);
+    if (type != null) {
+      if (inCheckContext) {
+        compiler.enqueuer.resolution.registerIsCheck(type);
+      }
+      return type.element;
+    }
+    return null;
+  }
+
+  Element defineElement(Node node, Element element,
+                        {bool doAddToScope: true}) {
+    compiler.ensure(element != null);
+    mapping[node] = element;
+    if (doAddToScope) {
+      Element existing = scope.add(element);
+      if (existing != element) {
+        error(node, MessageKind.DUPLICATE_DEFINITION, {'name': node});
+      }
+    }
+    return element;
+  }
+
+  Element useElement(Node node, Element element) {
+    if (element == null) return null;
+    return mapping[node] = element;
+  }
+
+  DartType useType(TypeAnnotation annotation, DartType type) {
+    if (type != null) {
+      mapping.setType(annotation, type);
+      useElement(annotation, type.element);
+    }
+    return type;
+  }
+
+  bool isNamedConstructor(Send node) => node.receiver != null;
+
+  Selector getRedirectingThisOrSuperConstructorSelector(Send node) {
+    if (isNamedConstructor(node)) {
+      SourceString constructorName = node.selector.asIdentifier().source;
+      return new Selector.callConstructor(
+          constructorName,
+          enclosingElement.getLibrary());
+    } else {
+      return new Selector.callDefaultConstructor(
+          enclosingElement.getLibrary());
+    }
+  }
+
+  FunctionElement resolveConstructorRedirection(FunctionElement constructor) {
+    FunctionExpression node = constructor.parseNode(compiler);
+
+    // A synthetic constructor does not have a node.
+    if (node == null) return null;
+    if (node.initializers == null) return null;
+    Link<Node> initializers = node.initializers.nodes;
+    if (!initializers.isEmpty &&
+        Initializers.isConstructorRedirect(initializers.head)) {
+      Selector selector =
+          getRedirectingThisOrSuperConstructorSelector(initializers.head);
+      final ClassElement classElement = constructor.getEnclosingClass();
+      return classElement.lookupConstructor(selector);
+    }
+    return null;
+  }
+
+  void setupFunction(FunctionExpression node, FunctionElement function) {
+    scope = new MethodScope(scope, function);
+
+    // Put the parameters in scope.
+    FunctionSignature functionParameters =
+        function.computeSignature(compiler);
+    Link<Node> parameterNodes = (node.parameters == null)
+        ? const Link<Node>() : node.parameters.nodes;
+    functionParameters.forEachParameter((Element element) {
+      if (element == functionParameters.optionalParameters.head) {
+        NodeList nodes = parameterNodes.head;
+        parameterNodes = nodes.nodes;
+      }
+      VariableDefinitions variableDefinitions = parameterNodes.head;
+      Node parameterNode = variableDefinitions.definitions.nodes.head;
+      initializerDo(parameterNode, (n) => n.accept(this));
+      // Field parameters (this.x) are not visible inside the constructor. The
+      // fields they reference are visible, but must be resolved independently.
+      if (element.kind == ElementKind.FIELD_PARAMETER) {
+        useElement(parameterNode, element);
+      } else {
+        defineElement(variableDefinitions.definitions.nodes.head, element);
+      }
+      parameterNodes = parameterNodes.tail;
+    });
+  }
+
+  visitCascade(Cascade node) {
+    visit(node.expression);
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visit(node.expression);
+  }
+
+  Element visitClassNode(ClassNode node) {
+    cancel(node, "shouldn't be called");
+  }
+
+  visitIn(Node node, Scope nestedScope) {
+    Scope oldScope = scope;
+    scope = nestedScope;
+    Element element = visit(node);
+    scope = oldScope;
+    return element;
+  }
+
+  /**
+   * Introduces new default targets for break and continue
+   * before visiting the body of the loop
+   */
+  visitLoopBodyIn(Node loop, Node body, Scope bodyScope) {
+    TargetElement element = getOrCreateTargetElement(loop);
+    statementScope.enterLoop(element);
+    visitIn(body, bodyScope);
+    statementScope.exitLoop();
+    if (!element.isTarget) {
+      mapping.remove(loop);
+    }
+  }
+
+  visitBlock(Block node) {
+    visitIn(node.statements, new BlockScope(scope));
+  }
+
+  visitDoWhile(DoWhile node) {
+    visitLoopBodyIn(node, node.body, new BlockScope(scope));
+    visit(node.condition);
+  }
+
+  visitEmptyStatement(EmptyStatement node) { }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    ExpressionStatement oldExpressionStatement = currentExpressionStatement;
+    currentExpressionStatement = node;
+    visit(node.expression);
+    currentExpressionStatement = oldExpressionStatement;
+  }
+
+  visitFor(For node) {
+    Scope blockScope = new BlockScope(scope);
+    visitIn(node.initializer, blockScope);
+    visitIn(node.condition, blockScope);
+    visitIn(node.update, blockScope);
+    visitLoopBodyIn(node, node.body, blockScope);
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    assert(node.function.name != null);
+    visit(node.function);
+    FunctionElement functionElement = mapping[node.function];
+    // TODO(floitsch): this might lead to two errors complaining about
+    // shadowing.
+    defineElement(node, functionElement);
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    visit(node.returnType);
+    SourceString name;
+    if (node.name == null) {
+      name = const SourceString("");
+    } else {
+      name = node.name.asIdentifier().source;
+    }
+
+    FunctionElement function = new FunctionElementX.node(
+        name, node, ElementKind.FUNCTION, Modifiers.EMPTY,
+        enclosingElement);
+    Scope oldScope = scope; // The scope is modified by [setupFunction].
+    setupFunction(node, function);
+    defineElement(node, function, doAddToScope: node.name != null);
+
+    Element previousEnclosingElement = enclosingElement;
+    enclosingElement = function;
+    // Run the body in a fresh statement scope.
+    StatementScope oldStatementScope = statementScope;
+    statementScope = new StatementScope();
+    visit(node.body);
+    statementScope = oldStatementScope;
+
+    scope = oldScope;
+    enclosingElement = previousEnclosingElement;
+
+    world.registerInstantiatedClass(compiler.functionClass);
+  }
+
+  visitIf(If node) {
+    visit(node.condition);
+    visit(node.thenPart);
+    visit(node.elsePart);
+  }
+
+  static bool isLogicalOperator(Identifier op) {
+    String str = op.source.stringValue;
+    return (identical(str, '&&') || str == '||' || str == '!');
+  }
+
+  Element resolveSend(Send node) {
+    Selector selector = resolveSelector(node);
+    if (node.isSuperCall) mapping.superUses.add(node);
+
+    if (node.receiver == null) {
+      // If this send is of the form "assert(expr);", then
+      // this is an assertion.
+      if (selector.isAssert()) {
+        if (selector.argumentCount != 1) {
+          error(node.selector,
+                MessageKind.WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT,
+                {'argumentCount': selector.argumentCount});
+        } else if (selector.namedArgumentCount != 0) {
+          error(node.selector,
+                MessageKind.ASSERT_IS_GIVEN_NAMED_ARGUMENTS,
+                {'argumentCount': selector.namedArgumentCount});
+        }
+        return compiler.assertMethod;
+      }
+
+      return node.selector.accept(this);
+    }
+
+    var oldCategory = allowedCategory;
+    allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER;
+    Element resolvedReceiver = visit(node.receiver);
+    allowedCategory = oldCategory;
+
+    Element target;
+    SourceString name = node.selector.asIdentifier().source;
+    if (identical(name.stringValue, 'this')) {
+      error(node.selector, MessageKind.GENERIC,
+            {'text': "expected an identifier"});
+    } else if (node.isSuperCall) {
+      if (node.isOperator) {
+        if (isUserDefinableOperator(name.stringValue)) {
+          name = selector.name;
+        } else {
+          error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, {'name': name});
+        }
+      }
+      if (!inInstanceContext) {
+        error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name});
+        return null;
+      }
+      if (currentClass.supertype == null) {
+        // This is just to guard against internal errors, so no need
+        // for a real error message.
+        error(node.receiver, MessageKind.GENERIC,
+              {'text': "Object has no superclass"});
+      }
+      // TODO(johnniwinther): Ensure correct behavior if currentClass is a
+      // patch.
+      target = currentClass.lookupSuperMember(name);
+      // [target] may be null which means invoking noSuchMethod on
+      // super.
+    } else if (Elements.isUnresolved(resolvedReceiver)) {
+      return null;
+    } else if (identical(resolvedReceiver.kind, ElementKind.CLASS)) {
+      ClassElement receiverClass = resolvedReceiver;
+      receiverClass.ensureResolved(compiler);
+      if (node.isOperator) {
+        // When the resolved receiver is a class, we can have two cases:
+        //  1) a static send: C.foo, or
+        //  2) an operator send, where the receiver is a class literal: 'C + 1'.
+        // The following code that looks up the selector on the resolved
+        // receiver will treat the second as the invocation of a static operator
+        // if the resolved receiver is not null.
+        return null;
+      }
+      target = receiverClass.lookupLocalMember(name);
+      if (target == null) {
+        // TODO(johnniwinther): With the simplified [TreeElements] invariant,
+        // try to resolve injected elements if [currentClass] is in the patch
+        // library of [receiverClass].
+
+        // TODO(karlklose): this should be reported by the caller of
+        // [resolveSend] to select better warning messages for getters and
+        // setters.
+        return warnAndCreateErroneousElement(node, name,
+                                             MessageKind.METHOD_NOT_FOUND,
+                                             {'className': receiverClass.name,
+                                              'methodName': name});
+      } else if (target.isInstanceMember()) {
+        error(node, MessageKind.MEMBER_NOT_STATIC,
+              {'className': receiverClass.name,
+               'memberName': name});
+      }
+    } else if (identical(resolvedReceiver.kind, ElementKind.PREFIX)) {
+      PrefixElement prefix = resolvedReceiver;
+      target = prefix.lookupLocalMember(name);
+      if (Elements.isUnresolved(target)) {
+        return warnAndCreateErroneousElement(
+            node, name, MessageKind.NO_SUCH_LIBRARY_MEMBER,
+            {'libraryName': prefix.name, 'memberName': name});
+      } else if (target.kind == ElementKind.CLASS) {
+        ClassElement classElement = target;
+        classElement.ensureResolved(compiler);
+      }
+    }
+    return target;
+  }
+
+  DartType resolveTypeTest(Node argument) {
+    TypeAnnotation node = argument.asTypeAnnotation();
+    if (node == null) {
+      // node is of the form !Type.
+      node = argument.asSend().receiver.asTypeAnnotation();
+      if (node == null) compiler.cancel("malformed send");
+    }
+    return resolveTypeRequired(node);
+  }
+
+  static Selector computeSendSelector(Send node, LibraryElement library) {
+    // First determine if this is part of an assignment.
+    bool isSet = node.asSendSet() != null;
+
+    if (node.isIndex) {
+      return isSet ? new Selector.indexSet() : new Selector.index();
+    }
+
+    if (node.isOperator) {
+      SourceString source = node.selector.asOperator().source;
+      String string = source.stringValue;
+      if (identical(string, '!') ||
+          identical(string, '&&') || identical(string, '||') ||
+          identical(string, 'is') || identical(string, 'as') ||
+          identical(string, '===') || identical(string, '!==') ||
+          identical(string, '?') ||
+          identical(string, '>>>')) {
+        return null;
+      }
+      if (!isUserDefinableOperator(source.stringValue)) {
+        source = Elements.mapToUserOperator(source);
+      }
+      return node.arguments.isEmpty
+          ? new Selector.unaryOperator(source)
+          : new Selector.binaryOperator(source);
+    }
+
+    Identifier identifier = node.selector.asIdentifier();
+    if (node.isPropertyAccess) {
+      assert(!isSet);
+      return new Selector.getter(identifier.source, library);
+    } else if (isSet) {
+      return new Selector.setter(identifier.source, library);
+    }
+
+    // Compute the arity and the list of named arguments.
+    int arity = 0;
+    List<SourceString> named = <SourceString>[];
+    for (Link<Node> link = node.argumentsNode.nodes;
+        !link.isEmpty;
+        link = link.tail) {
+      Expression argument = link.head;
+      NamedArgument namedArgument = argument.asNamedArgument();
+      if (namedArgument != null) {
+        named.add(namedArgument.name.source);
+      }
+      arity++;
+    }
+
+    // If we're invoking a closure, we do not have an identifier.
+    return (identifier == null)
+        ? new Selector.callClosure(arity, named)
+        : new Selector.call(identifier.source, library, arity, named);
+  }
+
+  Selector resolveSelector(Send node) {
+    LibraryElement library = enclosingElement.getLibrary();
+    Selector selector = computeSendSelector(node, library);
+    if (selector != null) mapping.setSelector(node, selector);
+    return selector;
+  }
+
+  void resolveArguments(NodeList list) {
+    if (list == null) return;
+    List<SourceString> seenNamedArguments = <SourceString>[];
+    for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) {
+      Expression argument = link.head;
+      visit(argument);
+      NamedArgument namedArgument = argument.asNamedArgument();
+      if (namedArgument != null) {
+        SourceString source = namedArgument.name.source;
+        if (seenNamedArguments.contains(source)) {
+          error(argument, MessageKind.DUPLICATE_DEFINITION,
+                {'name': source});
+        }
+        seenNamedArguments.add(source);
+      } else if (!seenNamedArguments.isEmpty) {
+        error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED);
+      }
+    }
+  }
+
+  visitSend(Send node) {
+    Element target = resolveSend(node);
+    if (!Elements.isUnresolved(target)
+        && target.kind == ElementKind.ABSTRACT_FIELD) {
+      AbstractFieldElement field = target;
+      target = field.getter;
+      if (target == null && !inInstanceContext) {
+        target =
+            warnAndCreateErroneousElement(node.selector, field.name,
+                                          MessageKind.CANNOT_RESOLVE_GETTER);
+      }
+    }
+
+    bool resolvedArguments = false;
+    if (node.isOperator) {
+      String operatorString = node.selector.asOperator().source.stringValue;
+      if (identical(operatorString, 'is') || identical(operatorString, 'as')) {
+        assert(node.arguments.tail.isEmpty);
+        DartType type = resolveTypeTest(node.arguments.head);
+        if (type != null) {
+          compiler.enqueuer.resolution.registerIsCheck(type);
+        }
+        resolvedArguments = true;
+      } else if (identical(operatorString, '?')) {
+        Element parameter = mapping[node.receiver];
+        if (parameter == null
+            || !identical(parameter.kind, ElementKind.PARAMETER)) {
+          error(node.receiver, MessageKind.PARAMETER_NAME_EXPECTED);
+        } else {
+          mapping.checkedParameters.add(parameter);
+        }
+      }
+    }
+
+    if (!resolvedArguments) {
+      resolveArguments(node.argumentsNode);
+    }
+
+    // If the selector is null, it means that we will not be generating
+    // code for this as a send.
+    Selector selector = mapping.getSelector(node);
+    if (selector == null) return;
+
+    if (node.isCall) {
+      if (Elements.isUnresolved(target) ||
+          target.isGetter() ||
+          Elements.isClosureSend(node, target)) {
+        // If we don't know what we're calling or if we are calling a getter,
+        // we need to register that fact that we may be calling a closure
+        // with the same arguments.
+        Selector call = new Selector.callClosureFrom(selector);
+        world.registerDynamicInvocation(call.name, call);
+      } else if (target.impliesType()) {
+        // We call 'call()' on a Type instance returned from the reference to a
+        // class or typedef literal. We do not need to register this call as a
+        // dynamic invocation, because we statically know what the target is.
+      } else if (!selector.applies(target, compiler)) {
+        warnArgumentMismatch(node, target);
+      }
+
+      if (target != null &&
+          target.isForeign(compiler) &&
+          selector.name == const SourceString('JS')) {
+        world.registerJsCall(node, this);
+      }
+    }
+
+    // TODO(ngeoffray): Warn if target is null and the send is
+    // unqualified.
+    useElement(node, target);
+    registerSend(selector, target);
+    if (node.isPropertyAccess) {
+      // It might be the closurization of a method.
+      world.registerInstantiatedClass(compiler.functionClass);
+    }
+    return node.isPropertyAccess ? target : null;
+  }
+
+  void warnArgumentMismatch(Send node, Element target) {
+    // TODO(karlklose): we can be more precise about the reason of the
+    // mismatch.
+    warning(node.argumentsNode, MessageKind.INVALID_ARGUMENTS,
+            {'methodName': target.name});
+  }
+
+  /// Callback for native enqueuer to parse a type.  Returns [:null:] on error.
+  DartType resolveTypeFromString(String typeName) {
+    Element element = scope.lookup(new SourceString(typeName));
+    if (element == null) return null;
+    if (element is! ClassElement) return null;
+    element.ensureResolved(compiler);
+    return element.computeType(compiler);
+  }
+
+  visitSendSet(SendSet node) {
+    Element target = resolveSend(node);
+    Element setter = target;
+    Element getter = target;
+    SourceString operatorName = node.assignmentOperator.source;
+    String source = operatorName.stringValue;
+    bool isComplex = !identical(source, '=');
+    if (!Elements.isUnresolved(target)
+        && target.kind == ElementKind.ABSTRACT_FIELD) {
+      AbstractFieldElement field = target;
+      setter = field.setter;
+      getter = field.getter;
+      if (setter == null && !inInstanceContext) {
+        setter =
+            warnAndCreateErroneousElement(node.selector, field.name,
+                                          MessageKind.CANNOT_RESOLVE_SETTER);
+      }
+      if (isComplex && getter == null && !inInstanceContext) {
+        getter =
+            warnAndCreateErroneousElement(node.selector, field.name,
+                                          MessageKind.CANNOT_RESOLVE_GETTER);
+      }
+    }
+
+    visit(node.argumentsNode);
+
+    // TODO(ngeoffray): Check if the target can be assigned.
+    // TODO(ngeoffray): Warn if target is null and the send is
+    // unqualified.
+
+    Selector selector = mapping.getSelector(node);
+    if (isComplex) {
+      if (selector.isSetter()) {
+        // TODO(kasperl): We're registering the getter selector for
+        // compound assignments on the AST selector node. In the code
+        // generator, we then fetch it from there when generating the
+        // getter for a SendSet node.
+        Selector getterSelector = new Selector.getterFrom(selector);
+        registerSend(getterSelector, getter);
+        mapping.setSelector(node.selector, getterSelector);
+        useElement(node.selector, getter);
+      } else {
+        // TODO(kasperl): If [getter] is resolved, it will actually
+        // refer to the []= operator which isn't the one we want to
+        // register here. We should consider using some notion of
+        // abstract indexable element that we can resolve to so we can
+        // distinguish the two.
+        assert(selector.isIndexSet());
+        registerSend(new Selector.index(), null);
+      }
+
+      // Make sure we include the + and - operators if we are using
+      // the ++ and -- ones.  Also, if op= form is used, include op itself.
+      void registerBinaryOperator(SourceString name) {
+        Selector binop = new Selector.binaryOperator(name);
+        world.registerDynamicInvocation(binop.name, binop);
+      }
+      if (identical(source, '++')) registerBinaryOperator(const SourceString('+'));
+      if (identical(source, '--')) registerBinaryOperator(const SourceString('-'));
+      if (source.endsWith('=')) {
+        registerBinaryOperator(Elements.mapToUserOperator(operatorName));
+      }
+    }
+
+    registerSend(selector, setter);
+    return useElement(node, setter);
+  }
+
+  void registerSend(Selector selector, Element target) {
+    if (target == null || target.isInstanceMember()) {
+      if (selector.isGetter()) {
+        world.registerDynamicGetter(selector.name, selector);
+      } else if (selector.isSetter()) {
+        world.registerDynamicSetter(selector.name, selector);
+      } else {
+        world.registerDynamicInvocation(selector.name, selector);
+      }
+    } else if (Elements.isStaticOrTopLevel(target)) {
+      // TODO(kasperl): It seems like we're not supposed to register
+      // the use of classes. Wouldn't it be simpler if we just did?
+      if (!target.isClass()) {
+        // [target] might be the implementation element and only declaration
+        // elements may be registered.
+        world.registerStaticUse(target.declaration);
+      }
+    }
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    world.registerInstantiatedClass(compiler.intClass);
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    world.registerInstantiatedClass(compiler.doubleClass);
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    world.registerInstantiatedClass(compiler.boolClass);
+  }
+
+  visitLiteralString(LiteralString node) {
+    world.registerInstantiatedClass(compiler.stringClass);
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    world.registerInstantiatedClass(compiler.nullClass);
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    world.registerInstantiatedClass(compiler.stringClass);
+    node.visitChildren(this);
+  }
+
+  visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      visit(link.head);
+    }
+  }
+
+  visitOperator(Operator node) {
+    unimplemented(node, 'operator');
+  }
+
+  visitReturn(Return node) {
+    if (node.isRedirectingFactoryBody) {
+      handleRedirectingFactoryBody(node);
+    } else {
+      visit(node.expression);
+    }
+  }
+
+  void handleRedirectingFactoryBody(Return node) {
+    if (!enclosingElement.isFactoryConstructor()) {
+      compiler.reportErrorCode(
+          node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY);
+      compiler.reportErrorCode(
+          enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD);
+    }
+    Element redirectionTarget = resolveRedirectingFactory(node);
+    var type = mapping.getType(node.expression);
+    if (type is InterfaceType && !type.isRaw) {
+      unimplemented(node.expression, 'type arguments on redirecting factory');
+    }
+    useElement(node.expression, redirectionTarget);
+    FunctionElement constructor = enclosingElement;
+    if (constructor.modifiers.isConst() &&
+        !redirectionTarget.modifiers.isConst()) {
+      error(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST);
+    }
+    constructor.defaultImplementation = redirectionTarget;
+    if (Elements.isUnresolved(redirectionTarget)) return;
+
+    // TODO(ahe): Check that this doesn't lead to a cycle.  For now,
+    // just make sure that the redirection target isn't itself a
+    // redirecting factory.
+    { // This entire block is temporary code per the above TODO.
+      FunctionElement targetImplementation = redirectionTarget.implementation;
+      FunctionExpression function = targetImplementation.parseNode(compiler);
+      if (function.body != null && function.body.asReturn() != null
+          && function.body.asReturn().isRedirectingFactoryBody) {
+        unimplemented(node.expression, 'redirecing to redirecting factory');
+      }
+    }
+    world.registerStaticUse(redirectionTarget);
+    world.registerInstantiatedClass(
+        redirectionTarget.enclosingElement.declaration);
+  }
+
+  visitThrow(Throw node) {
+    if (!inCatchBlock && node.expression == null) {
+      error(node, MessageKind.THROW_WITHOUT_EXPRESSION);
+    }
+    visit(node.expression);
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    VariableDefinitionsVisitor visitor =
+        new VariableDefinitionsVisitor(compiler, node, this,
+                                       ElementKind.VARIABLE);
+    // Ensure that we set the type of the [VariableListElement] since it depends
+    // on the current scope. If the current scope is a [MethodScope] or
+    // [BlockScope] it will not be available for the
+    // [VariableListElement.computeType] method.
+    if (node.type != null) {
+      visitor.variables.type = resolveTypeAnnotation(node.type);
+    } else {
+      visitor.variables.type = compiler.types.dynamicType;
+    }
+    visitor.visit(node.definitions);
+  }
+
+  visitWhile(While node) {
+    visit(node.condition);
+    visitLoopBodyIn(node, node.body, new BlockScope(scope));
+  }
+
+  visitParenthesizedExpression(ParenthesizedExpression node) {
+    visit(node.expression);
+  }
+
+  visitNewExpression(NewExpression node) {
+    Node selector = node.send.selector;
+    FunctionElement constructor = resolveConstructor(node);
+    resolveSelector(node.send);
+    resolveArguments(node.send.argumentsNode);
+    useElement(node.send, constructor);
+    if (Elements.isUnresolved(constructor)) return constructor;
+    // TODO(karlklose): handle optional arguments.
+    if (node.send.argumentCount() != constructor.parameterCount(compiler)) {
+      // TODO(ngeoffray): resolution error with wrong number of
+      // parameters. We cannot do this rigth now because of the
+      // List constructor.
+    }
+    // [constructor] might be the implementation element and only declaration
+    // elements may be registered.
+    world.registerStaticUse(constructor.declaration);
+    compiler.withCurrentElement(constructor, () {
+      FunctionExpression tree = constructor.parseNode(compiler);
+      compiler.resolver.resolveConstructorImplementation(constructor, tree);
+    });
+    // [constructor.defaultImplementation] might be the implementation element
+    // and only declaration elements may be registered.
+    world.registerStaticUse(constructor.defaultImplementation.declaration);
+    ClassElement cls = constructor.defaultImplementation.getEnclosingClass();
+    // [cls] might be the implementation element and only declaration elements
+    // may be registered.
+    world.registerInstantiatedClass(cls.declaration);
+    // [cls] might be the declaration element and we want to include injected
+    // members.
+    cls.implementation.forEachInstanceField(
+        (ClassElement enclosingClass, Element member) {
+          world.addToWorkList(member);
+        },
+        includeBackendMembers: false,
+        includeSuperMembers: true);
+    return null;
+  }
+
+  /**
+   * Try to resolve the constructor that is referred to by [node].
+   * Note: this function may return an ErroneousFunctionElement instead of
+   * [null], if there is no corresponding constructor, class or library.
+   */
+  FunctionElement resolveConstructor(NewExpression node) {
+    return node.accept(new ConstructorResolver(compiler, this));
+  }
+
+  FunctionElement resolveRedirectingFactory(Return node) {
+    return node.accept(new ConstructorResolver(compiler, this));
+  }
+
+  DartType resolveTypeRequired(TypeAnnotation node) {
+    bool old = typeRequired;
+    typeRequired = true;
+    DartType result = resolveTypeAnnotation(node);
+    typeRequired = old;
+    return result;
+  }
+
+  void analyzeTypeArgument(DartType annotation, DartType argument) {
+    if (argument == null) return;
+    if (argument.element.isTypeVariable()) {
+      // Register a dependency between the class where the type
+      // variable is, and the annotation. If the annotation requires
+      // runtime type information, then the class of the type variable
+      // does too.
+      compiler.world.registerRtiDependency(
+          annotation.element,
+          argument.element.enclosingElement);
+    } else if (argument is InterfaceType) {
+      InterfaceType type = argument;
+      type.typeArguments.forEach((DartType argument) {
+        analyzeTypeArgument(type, argument);
+      });
+    }
+  }
+
+  DartType resolveTypeAnnotation(TypeAnnotation node) {
+    Function report = typeRequired ? error : warning;
+    DartType type = typeResolver.resolveTypeAnnotation(
+        node, scope, enclosingElement,
+        onFailure: report, whenResolved: useType);
+    if (type == null) return null;
+    if (inCheckContext) {
+      compiler.enqueuer.resolution.registerIsCheck(type);
+    }
+    if (typeRequired || inCheckContext) {
+      if (type is InterfaceType) {
+        InterfaceType itf = type;
+        itf.typeArguments.forEach((DartType argument) {
+          analyzeTypeArgument(type, argument);
+        });
+      }
+      // TODO(ngeoffray): Also handle cases like:
+      // 1) a is T
+      // 2) T a (in checked mode).
+    }
+    return type;
+  }
+
+  visitModifiers(Modifiers node) {
+    // TODO(ngeoffray): Implement this.
+    unimplemented(node, 'modifiers');
+  }
+
+  visitLiteralList(LiteralList node) {
+    world.registerInstantiatedClass(compiler.listClass);
+    NodeList arguments = node.typeArguments;
+    if (arguments != null) {
+      Link<Node> nodes = arguments.nodes;
+      if (nodes.isEmpty) {
+        error(arguments, MessageKind.MISSING_TYPE_ARGUMENT);
+      } else {
+        resolveTypeRequired(nodes.head);
+        for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) {
+          error(nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
+          resolveTypeRequired(nodes.head);
+        }
+      }
+    }
+    visit(node.elements);
+  }
+
+  visitConditional(Conditional node) {
+    node.visitChildren(this);
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    world.registerInstantiatedClass(compiler.stringClass);
+    node.visitChildren(this);
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    registerImplicitInvocation(const SourceString('toString'), 0);
+    node.visitChildren(this);
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    TargetElement target;
+    if (node.target == null) {
+      target = statementScope.currentBreakTarget();
+      if (target == null) {
+        error(node, MessageKind.NO_BREAK_TARGET);
+        return;
+      }
+      target.isBreakTarget = true;
+    } else {
+      String labelName = node.target.source.slowToString();
+      LabelElement label = statementScope.lookupLabel(labelName);
+      if (label == null) {
+        error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName});
+        return;
+      }
+      target = label.target;
+      if (!target.statement.isValidBreakTarget()) {
+        error(node.target, MessageKind.INVALID_BREAK);
+        return;
+      }
+      label.setBreakTarget();
+      mapping[node.target] = label;
+    }
+    if (mapping[node] != null) {
+      // TODO(ahe): I'm not sure why this node already has an element
+      // that is different from target.  I will talk to Lasse and
+      // figure out what is going on.
+      mapping.remove(node);
+    }
+    mapping[node] = target;
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    TargetElement target;
+    if (node.target == null) {
+      target = statementScope.currentContinueTarget();
+      if (target == null) {
+        error(node, MessageKind.NO_CONTINUE_TARGET);
+        return;
+      }
+      target.isContinueTarget = true;
+    } else {
+      String labelName = node.target.source.slowToString();
+      LabelElement label = statementScope.lookupLabel(labelName);
+      if (label == null) {
+        error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName});
+        return;
+      }
+      target = label.target;
+      if (!target.statement.isValidContinueTarget()) {
+        error(node.target, MessageKind.INVALID_CONTINUE);
+      }
+      // TODO(lrn): Handle continues to switch cases.
+      if (target.statement is SwitchCase) {
+        unimplemented(node, "continue to switch case");
+      }
+      label.setContinueTarget();
+      mapping[node.target] = label;
+    }
+    mapping[node] = target;
+  }
+
+  registerImplicitInvocation(SourceString name, int arity) {
+    Selector selector = new Selector.call(name, null, arity);
+    world.registerDynamicInvocation(name, selector);
+  }
+
+  registerImplicitFieldGet(SourceString name) {
+    Selector selector = new Selector.getter(name, null);
+    world.registerDynamicGetter(name, selector);
+  }
+
+  visitForIn(ForIn node) {
+    for (final name in const [
+        const SourceString('iterator'),
+        const SourceString('current')]) {
+      registerImplicitFieldGet(name);
+    }
+    registerImplicitInvocation(const SourceString('moveNext'), 0);
+    visit(node.expression);
+    Scope blockScope = new BlockScope(scope);
+    Node declaration = node.declaredIdentifier;
+    visitIn(declaration, blockScope);
+    visitLoopBodyIn(node, node.body, blockScope);
+
+    // TODO(lrn): Also allow a single identifier.
+    if ((declaration is !Send || declaration.asSend().selector is !Identifier
+        || declaration.asSend().receiver != null)
+        && (declaration is !VariableDefinitions ||
+        !declaration.asVariableDefinitions().definitions.nodes.tail.isEmpty))
+    {
+      // The variable declaration is either not an identifier, not a
+      // declaration, or it's declaring more than one variable.
+      error(node.declaredIdentifier, MessageKind.INVALID_FOR_IN);
+    }
+  }
+
+  visitLabel(Label node) {
+    // Labels are handled by their containing statements/cases.
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    Statement body = node.statement;
+    TargetElement targetElement = getOrCreateTargetElement(body);
+    Map<String, LabelElement> labelElements = <String, LabelElement>{};
+    for (Label label in node.labels) {
+      String labelName = label.slowToString();
+      if (labelElements.containsKey(labelName)) continue;
+      LabelElement element = targetElement.addLabel(label, labelName);
+      labelElements[labelName] = element;
+    }
+    statementScope.enterLabelScope(labelElements);
+    visit(node.statement);
+    statementScope.exitLabelScope();
+    labelElements.forEach((String labelName, LabelElement element) {
+      if (element.isTarget) {
+        mapping[element.label] = element;
+      } else {
+        warning(element.label, MessageKind.UNUSED_LABEL,
+                {'labelName': labelName});
+      }
+    });
+    if (!targetElement.isTarget && identical(mapping[body], targetElement)) {
+      // If the body is itself a break or continue for another target, it
+      // might have updated its mapping to the target it actually does target.
+      mapping.remove(body);
+    }
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    world.registerInstantiatedClass(compiler.mapClass);
+    node.visitChildren(this);
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    node.visitChildren(this);
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visit(node.expression);
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    node.expression.accept(this);
+
+    TargetElement breakElement = getOrCreateTargetElement(node);
+    Map<String, LabelElement> continueLabels = <String, LabelElement>{};
+    Link<Node> cases = node.cases.nodes;
+    while (!cases.isEmpty) {
+      SwitchCase switchCase = cases.head;
+      for (Node labelOrCase in switchCase.labelsAndCases) {
+        if (labelOrCase is! Label) continue;
+        Label label = labelOrCase;
+        String labelName = label.slowToString();
+
+        LabelElement existingElement = continueLabels[labelName];
+        if (existingElement != null) {
+          // It's an error if the same label occurs twice in the same switch.
+          warning(label, MessageKind.DUPLICATE_LABEL, {'labelName': labelName});
+          error(existingElement.label, MessageKind.EXISTING_LABEL,
+                {'labelName': labelName});
+        } else {
+          // It's only a warning if it shadows another label.
+          existingElement = statementScope.lookupLabel(labelName);
+          if (existingElement != null) {
+            warning(label, MessageKind.DUPLICATE_LABEL,
+                    {'labelName': labelName});
+            warning(existingElement.label,
+                    MessageKind.EXISTING_LABEL, {'labelName': labelName});
+          }
+        }
+
+        TargetElement targetElement =
+            new TargetElementX(switchCase,
+                               statementScope.nestingLevel,
+                               enclosingElement);
+        if (mapping[switchCase] != null) {
+          // TODO(ahe): Talk to Lasse about this.
+          mapping.remove(switchCase);
+        }
+        mapping[switchCase] = targetElement;
+
+        LabelElement labelElement =
+            new LabelElementX(label, labelName,
+                              targetElement, enclosingElement);
+        mapping[label] = labelElement;
+        continueLabels[labelName] = labelElement;
+      }
+      cases = cases.tail;
+      // Test that only the last case, if any, is a default case.
+      if (switchCase.defaultKeyword != null && !cases.isEmpty) {
+        error(switchCase, MessageKind.INVALID_CASE_DEFAULT);
+      }
+    }
+
+    statementScope.enterSwitch(breakElement, continueLabels);
+    node.cases.accept(this);
+    statementScope.exitSwitch();
+
+    // Clean-up unused labels.
+    continueLabels.forEach((String key, LabelElement label) {
+      if (!label.isContinueTarget) {
+        TargetElement targetElement = label.target;
+        SwitchCase switchCase = targetElement.statement;
+        mapping.remove(switchCase);
+        mapping.remove(label.label);
+      }
+    });
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    node.labelsAndCases.accept(this);
+    visitIn(node.statements, new BlockScope(scope));
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    visit(node.expression);
+  }
+
+  visitTryStatement(TryStatement node) {
+    visit(node.tryBlock);
+    if (node.catchBlocks.isEmpty && node.finallyBlock == null) {
+      // TODO(ngeoffray): The precise location is
+      // node.getEndtoken.next. Adjust when issue #1581 is fixed.
+      error(node, MessageKind.NO_CATCH_NOR_FINALLY);
+    }
+    visit(node.catchBlocks);
+    visit(node.finallyBlock);
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    // Check that if catch part is present, then
+    // it has one or two formal parameters.
+    if (node.formals != null) {
+      if (node.formals.isEmpty) {
+        error(node, MessageKind.EMPTY_CATCH_DECLARATION);
+      }
+      if (!node.formals.nodes.tail.isEmpty &&
+          !node.formals.nodes.tail.tail.isEmpty) {
+        for (Node extra in node.formals.nodes.tail.tail) {
+          error(extra, MessageKind.EXTRA_CATCH_DECLARATION);
+        }
+      }
+
+      // Check that the formals aren't optional and that they have no
+      // modifiers or type.
+      for (Link<Node> link = node.formals.nodes;
+           !link.isEmpty;
+           link = link.tail) {
+        // If the formal parameter is a node list, it means that it is a
+        // sequence of optional parameters.
+        NodeList nodeList = link.head.asNodeList();
+        if (nodeList != null) {
+          error(nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH);
+        } else {
+        VariableDefinitions declaration = link.head;
+          for (Node modifier in declaration.modifiers.nodes) {
+            error(modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH);
+          }
+          TypeAnnotation type = declaration.type;
+          if (type != null) {
+            error(type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH);
+          }
+        }
+      }
+    }
+
+    Scope blockScope = new BlockScope(scope);
+    var wasTypeRequired = typeRequired;
+    typeRequired = true;
+    doInCheckContext(() => visitIn(node.type, blockScope));
+    typeRequired = wasTypeRequired;
+    visitIn(node.formals, blockScope);
+    var oldInCatchBlock = inCatchBlock;
+    inCatchBlock = true;
+    visitIn(node.block, blockScope);
+    inCatchBlock = oldInCatchBlock;
+  }
+
+  visitTypedef(Typedef node) {
+    unimplemented(node, 'typedef');
+  }
+}
+
+class TypeDefinitionVisitor extends CommonResolverVisitor<DartType> {
+  Scope scope;
+  TypeDeclarationElement element;
+  TypeResolver typeResolver;
+
+  TypeDefinitionVisitor(Compiler compiler, TypeDeclarationElement element)
+      : this.element = element,
+        scope = Scope.buildEnclosingScope(element),
+        typeResolver = new TypeResolver(compiler),
+        super(compiler);
+
+  void resolveTypeVariableBounds(NodeList node) {
+    if (node == null) return;
+
+    var nameSet = new Set<SourceString>();
+    // Resolve the bounds of type variables.
+    Link<DartType> typeLink = element.typeVariables;
+    Link<Node> nodeLink = node.nodes;
+    while (!nodeLink.isEmpty) {
+      TypeVariableType typeVariable = typeLink.head;
+      SourceString typeName = typeVariable.name;
+      TypeVariable typeNode = nodeLink.head;
+      if (nameSet.contains(typeName)) {
+        error(typeNode, MessageKind.DUPLICATE_TYPE_VARIABLE_NAME,
+              {'typeVariableName': typeName});
+      }
+      nameSet.add(typeName);
+
+      TypeVariableElement variableElement = typeVariable.element;
+      if (typeNode.bound != null) {
+        DartType boundType = typeResolver.resolveTypeAnnotation(
+            typeNode.bound, scope, element, onFailure: warning);
+        if (boundType != null && boundType.element == variableElement) {
+          // TODO(johnniwinther): Check for more general cycles, like
+          // [: <A extends B, B extends C, C extends B> :].
+          warning(node, MessageKind.CYCLIC_TYPE_VARIABLE,
+                  {'typeVariableName': variableElement.name});
+        } else if (boundType != null) {
+          variableElement.bound = boundType;
+        } else {
+          // TODO(johnniwinther): Should be an erroneous type.
+          variableElement.bound = compiler.objectClass.computeType(compiler);
+        }
+      } else {
+        variableElement.bound = compiler.objectClass.computeType(compiler);
+      }
+      nodeLink = nodeLink.tail;
+      typeLink = typeLink.tail;
+    }
+    assert(typeLink.isEmpty);
+  }
+}
+
+class TypedefResolverVisitor extends TypeDefinitionVisitor {
+  TypedefElement get element => super.element;
+
+  TypedefResolverVisitor(Compiler compiler, TypedefElement typedefElement)
+      : super(compiler, typedefElement);
+
+  visitTypedef(Typedef node) {
+    TypedefType type = element.computeType(compiler);
+    scope = new TypeDeclarationScope(scope, element);
+    resolveTypeVariableBounds(node.typeParameters);
+
+    element.functionSignature = SignatureResolver.analyze(
+        compiler, node.formals, node.returnType, element);
+
+    element.alias = compiler.computeFunctionType(
+        element, element.functionSignature);
+
+    // TODO(johnniwinther): Check for cyclic references in the typedef alias.
+  }
+}
+
+/**
+ * The implementation of [ResolverTask.resolveClass].
+ *
+ * This visitor has to be extra careful as it is building the basic
+ * element information, and cannot safely look at other elements as
+ * this may lead to cycles.
+ *
+ * This visitor can assume that the supertypes have already been
+ * resolved, but it cannot call [ResolverTask.resolveClass] directly
+ * or indirectly (through [ClassElement.ensureResolved]) for any other
+ * types.
+ */
+class ClassResolverVisitor extends TypeDefinitionVisitor {
+  ClassElement get element => super.element;
+
+  ClassResolverVisitor(Compiler compiler, ClassElement classElement)
+    : super(compiler, classElement);
+
+  DartType visitClassNode(ClassNode node) {
+    compiler.ensure(element != null);
+    compiler.ensure(element.resolutionState == STATE_STARTED);
+
+    InterfaceType type = element.computeType(compiler);
+    scope = new TypeDeclarationScope(scope, element);
+    // TODO(ahe): It is not safe to call resolveTypeVariableBounds yet.
+    // As a side-effect, this may get us back here trying to
+    // resolve this class again.
+    resolveTypeVariableBounds(node.typeParameters);
+
+    // Setup the supertype for the element.
+    assert(element.supertype == null);
+    if (node.superclass != null) {
+      MixinApplication superMixin = node.superclass.asMixinApplication();
+      if (superMixin != null) {
+        DartType supertype = resolveSupertype(element, superMixin.superclass);
+        Link<Node> link = superMixin.mixins.nodes;
+        while (!link.isEmpty) {
+          supertype = applyMixin(supertype, visit(link.head));
+          link = link.tail;
+        }
+        element.supertype = supertype;
+      } else {
+        element.supertype = resolveSupertype(element, node.superclass);
+      }
+    }
+
+    // If the super type isn't specified, we make it Object.
+    final objectElement = compiler.objectClass;
+    if (!identical(element, objectElement) && element.supertype == null) {
+      if (objectElement == null) {
+        compiler.internalError("Internal error: cannot resolve Object",
+                               node: node);
+      } else {
+        objectElement.ensureResolved(compiler);
+      }
+      element.supertype = objectElement.computeType(compiler);
+    }
+
+    assert(element.interfaces == null);
+    element.interfaces = resolveInterfaces(node.interfaces, node.superclass);
+    calculateAllSupertypes(element);
+
+    if (node.defaultClause != null) {
+      element.defaultClass = visit(node.defaultClause);
+    }
+    element.addDefaultConstructorIfNeeded(compiler);
+    return element.computeType(compiler);
+  }
+
+  DartType visitNamedMixinApplication(NamedMixinApplication node) {
+    compiler.ensure(element != null);
+    compiler.ensure(element.resolutionState == STATE_STARTED);
+
+    InterfaceType type = element.computeType(compiler);
+    scope = new TypeDeclarationScope(scope, element);
+    resolveTypeVariableBounds(node.typeParameters);
+
+    // Generate anonymous mixin application elements for the
+    // intermediate mixin applications (excluding the last).
+    DartType supertype = resolveSupertype(element, node.superclass);
+    Link<Node> link = node.mixins.nodes;
+    while (!link.tail.isEmpty) {
+      supertype = applyMixin(supertype, visit(link.head));
+      link = link.tail;
+    }
+    doApplyMixinTo(element, supertype, visit(link.head));
+    return element.computeType(compiler);
+  }
+
+  DartType applyMixin(DartType supertype, DartType mixinType) {
+    String superName = supertype.name.slowToString();
+    String mixinName = mixinType.name.slowToString();
+    ClassElement mixinApplication = new MixinApplicationElementX(
+        new SourceString("${superName}_${mixinName}"),
+        element.getCompilationUnit(),
+        compiler.getNextFreeClassId(),
+        element.parseNode(compiler),
+        Modifiers.EMPTY);  // TODO(kasperl): Should this be abstract?
+    doApplyMixinTo(mixinApplication, supertype, mixinType);
+    mixinApplication.resolutionState = STATE_DONE;
+    mixinApplication.supertypeLoadState = STATE_DONE;
+    return mixinApplication.computeType(compiler);
+  }
+
+  void doApplyMixinTo(MixinApplicationElement mixinApplication,
+                      DartType supertype,
+                      DartType mixinType) {
+    assert(mixinApplication.supertype == null);
+    mixinApplication.supertype = supertype;
+
+    // Named mixin application may have an 'implements' clause.
+    NamedMixinApplication namedMixinApplication =
+        mixinApplication.parseNode(compiler).asNamedMixinApplication();
+    Link<DartType> interfaces = (namedMixinApplication != null)
+        ? resolveInterfaces(namedMixinApplication.interfaces,
+                            namedMixinApplication.superclass)
+        : const Link<DartType>();
+
+    // The class that is the result of a mixin application implements
+    // the interface of the class that was mixed in so always prepend
+    // that to the interface list.
+    interfaces = interfaces.prepend(mixinType);
+    assert(mixinApplication.interfaces == null);
+    mixinApplication.interfaces = interfaces;
+
+    assert(mixinApplication.mixin == null);
+    mixinApplication.mixin = resolveMixinFor(mixinApplication, mixinType);
+    mixinApplication.addDefaultConstructorIfNeeded(compiler);
+    calculateAllSupertypes(mixinApplication);
+  }
+
+  ClassElement resolveMixinFor(MixinApplicationElement mixinApplication,
+                               DartType mixinType) {
+    ClassElement mixin = mixinType.element;
+    mixin.ensureResolved(compiler);
+
+    // Check for cycles in the mixin chain.
+    ClassElement previous = mixinApplication;  // For better error messages.
+    ClassElement current = mixin;
+    while (current != null && current.isMixinApplication) {
+      MixinApplicationElement currentMixinApplication = current;
+      if (currentMixinApplication == mixinApplication) {
+        compiler.reportErrorCode(
+            mixinApplication, MessageKind.ILLEGAL_MIXIN_CYCLE,
+            {'mixinName1': current.name, 'mixinName2': previous.name});
+        // We have found a cycle in the mixin chain. Return null as
+        // the mixin for this application to avoid getting into
+        // infinite recursion when traversing members.
+        return null;
+      }
+      previous = current;
+      current = currentMixinApplication.mixin;
+    }
+    compiler.world.registerMixinUse(mixinApplication, mixin);
+    return mixin;
+  }
+
+  // TODO(johnniwinther): Remove when default class is no longer supported.
+  DartType visitTypeAnnotation(TypeAnnotation node) {
+    return visit(node.typeName);
+  }
+
+  // TODO(johnniwinther): Remove when default class is no longer supported.
+  DartType visitIdentifier(Identifier node) {
+    Element element = scope.lookup(node.source);
+    if (element == null) {
+      error(node, MessageKind.CANNOT_RESOLVE_TYPE,  {'typeName': node});
+      return null;
+    } else if (!element.impliesType() && !element.isTypeVariable()) {
+      error(node, MessageKind.NOT_A_TYPE, {'node': node});
+      return null;
+    } else {
+      if (element.isTypeVariable()) {
+        TypeVariableElement variableElement = element;
+        return variableElement.type;
+      } else if (element.isTypedef()) {
+        compiler.unimplemented('visitIdentifier for typedefs', node: node);
+      } else {
+        // TODO(ngeoffray): Use type variables.
+        return element.computeType(compiler);
+      }
+    }
+    return null;
+  }
+
+  // TODO(johnniwinther): Remove when default class is no longer supported.
+  DartType visitSend(Send node) {
+    Identifier prefix = node.receiver.asIdentifier();
+    if (prefix == null) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return null;
+    }
+    Element element = scope.lookup(prefix.source);
+    if (element == null || !identical(element.kind, ElementKind.PREFIX)) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return null;
+    }
+    PrefixElement prefixElement = element;
+    Identifier selector = node.selector.asIdentifier();
+    var e = prefixElement.lookupLocalMember(selector.source);
+    if (e == null || !e.impliesType()) {
+      error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE,
+            {'typeName': node.selector});
+      return null;
+    }
+    return e.computeType(compiler);
+  }
+
+  DartType resolveSupertype(ClassElement cls, TypeAnnotation superclass) {
+    DartType supertype = typeResolver.resolveTypeAnnotation(
+        superclass, scope, cls, onFailure: error);
+    if (supertype != null) {
+      if (identical(supertype.kind, TypeKind.MALFORMED_TYPE)) {
+        // Error has already been reported.
+        return null;
+      } else if (!identical(supertype.kind, TypeKind.INTERFACE)) {
+        // TODO(johnniwinther): Handle dynamic.
+        error(superclass.typeName, MessageKind.CLASS_NAME_EXPECTED);
+        return null;
+      } else if (isBlackListed(supertype)) {
+        error(superclass, MessageKind.CANNOT_EXTEND, {'type': supertype});
+        return null;
+      }
+    }
+    return supertype;
+  }
+
+  Link<DartType> resolveInterfaces(NodeList interfaces, Node superclass) {
+    Link<DartType> result = const Link<DartType>();
+    if (interfaces == null) return result;
+    for (Link<Node> link = interfaces.nodes; !link.isEmpty; link = link.tail) {
+      DartType interfaceType = typeResolver.resolveTypeAnnotation(
+          link.head, scope, element, onFailure: error);
+      if (interfaceType != null) {
+        if (identical(interfaceType.kind, TypeKind.MALFORMED_TYPE)) {
+          // Error has already been reported.
+        } else if (!identical(interfaceType.kind, TypeKind.INTERFACE)) {
+          // TODO(johnniwinther): Handle dynamic.
+          TypeAnnotation typeAnnotation = link.head;
+          error(typeAnnotation.typeName, MessageKind.CLASS_NAME_EXPECTED);
+        } else {
+          if (interfaceType == element.supertype) {
+            compiler.reportErrorCode(
+                superclass,
+                MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS,
+                {'type': interfaceType});
+            compiler.reportErrorCode(
+                link.head,
+                MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS,
+                {'type': interfaceType});
+          }
+          if (result.contains(interfaceType)) {
+            compiler.reportErrorCode(
+                link.head,
+                MessageKind.DUPLICATE_IMPLEMENTS,
+                {'type': interfaceType});
+          }
+          result = result.prepend(interfaceType);
+          if (isBlackListed(interfaceType)) {
+            error(link.head, MessageKind.CANNOT_IMPLEMENT,
+                  {'type': interfaceType});
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  void calculateAllSupertypes(ClassElement cls) {
+    // TODO(karlklose): Check if type arguments match, if a class
+    // element occurs more than once in the supertypes.
+    if (cls.allSupertypes != null) return;
+    final DartType supertype = cls.supertype;
+    if (supertype != null) {
+      var allSupertypes = new LinkBuilder<DartType>();
+      addAllSupertypes(allSupertypes, supertype);
+      for (Link<DartType> interfaces = cls.interfaces;
+           !interfaces.isEmpty;
+           interfaces = interfaces.tail) {
+        addAllSupertypes(allSupertypes, interfaces.head);
+      }
+      cls.allSupertypes = allSupertypes.toLink();
+    } else {
+      assert(identical(cls, compiler.objectClass));
+      cls.allSupertypes = const Link<DartType>();
+    }
+ }
+
+  /**
+   * Adds [type] and all supertypes of [type] to [builder] while substituting
+   * type variables.
+   */
+  void addAllSupertypes(LinkBuilder<DartType> builder, InterfaceType type) {
+    builder.addLast(type);
+    Link<DartType> typeArguments = type.typeArguments;
+    ClassElement classElement = type.element;
+    Link<DartType> typeVariables = classElement.typeVariables;
+    Link<DartType> supertypes = classElement.allSupertypes;
+    assert(invariant(element, supertypes != null,
+        message: "Supertypes not computed on $classElement "
+                 "during resolution of $element"));
+    while (!supertypes.isEmpty) {
+      DartType supertype = supertypes.head;
+      builder.addLast(supertype.subst(typeArguments, typeVariables));
+      supertypes = supertypes.tail;
+    }
+  }
+
+  isBlackListed(DartType type) {
+    LibraryElement lib = element.getLibrary();
+    return
+      !identical(lib, compiler.coreLibrary) &&
+      !identical(lib, compiler.jsHelperLibrary) &&
+      !identical(lib, compiler.interceptorsLibrary) &&
+      (identical(type.element, compiler.dynamicClass) ||
+       identical(type.element, compiler.boolClass) ||
+       identical(type.element, compiler.numClass) ||
+       identical(type.element, compiler.intClass) ||
+       identical(type.element, compiler.doubleClass) ||
+       identical(type.element, compiler.stringClass) ||
+       identical(type.element, compiler.nullClass) ||
+       identical(type.element, compiler.functionClass));
+  }
+}
+
+class ClassSupertypeResolver extends CommonResolverVisitor {
+  Scope context;
+  ClassElement classElement;
+
+  ClassSupertypeResolver(Compiler compiler, ClassElement cls)
+    : context = Scope.buildEnclosingScope(cls),
+      this.classElement = cls,
+      super(compiler);
+
+  void loadSupertype(ClassElement element, Node from) {
+    compiler.resolver.loadSupertypes(element, from);
+    element.ensureResolved(compiler);
+  }
+
+  void visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      link.head.accept(this);
+    }
+  }
+
+  void visitClassNode(ClassNode node) {
+    if (node.superclass == null) {
+      if (!identical(classElement, compiler.objectClass)) {
+        loadSupertype(compiler.objectClass, node);
+      }
+    } else {
+      node.superclass.accept(this);
+    }
+    visitNodeList(node.interfaces);
+  }
+
+  void visitMixinApplication(MixinApplication node) {
+    node.superclass.accept(this);
+    visitNodeList(node.mixins);
+  }
+
+  void visitTypeAnnotation(TypeAnnotation node) {
+    node.typeName.accept(this);
+  }
+
+  void visitIdentifier(Identifier node) {
+    Element element = context.lookup(node.source);
+    if (element == null) {
+      error(node, MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node});
+    } else if (!element.impliesType()) {
+      error(node, MessageKind.NOT_A_TYPE, {'node': node});
+    } else {
+      if (element.isClass()) {
+        loadSupertype(element, node);
+      } else {
+        compiler.reportErrorCode(node, MessageKind.CLASS_NAME_EXPECTED);
+      }
+    }
+  }
+
+  void visitSend(Send node) {
+    Identifier prefix = node.receiver.asIdentifier();
+    if (prefix == null) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return;
+    }
+    Element element = context.lookup(prefix.source);
+    if (element == null || !identical(element.kind, ElementKind.PREFIX)) {
+      error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver});
+      return;
+    }
+    PrefixElement prefixElement = element;
+    Identifier selector = node.selector.asIdentifier();
+    var e = prefixElement.lookupLocalMember(selector.source);
+    if (e == null || !e.impliesType()) {
+      error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE,
+            {'typeName': node.selector});
+      return;
+    }
+    loadSupertype(e, node);
+  }
+}
+
+class VariableDefinitionsVisitor extends CommonResolverVisitor<SourceString> {
+  VariableDefinitions definitions;
+  ResolverVisitor resolver;
+  ElementKind kind;
+  VariableListElement variables;
+
+  VariableDefinitionsVisitor(Compiler compiler,
+                             this.definitions, this.resolver, this.kind)
+      : super(compiler) {
+    variables = new VariableListElementX.node(
+        definitions, ElementKind.VARIABLE_LIST, resolver.enclosingElement);
+  }
+
+  SourceString visitSendSet(SendSet node) {
+    assert(node.arguments.tail.isEmpty); // Sanity check
+    resolver.visit(node.arguments.head);
+    return visit(node.selector);
+  }
+
+  SourceString visitIdentifier(Identifier node) {
+    // The variable is initialized to null.
+    resolver.world.registerInstantiatedClass(compiler.nullClass);
+    return node.source;
+  }
+
+  visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      SourceString name = visit(link.head);
+      VariableElement element =
+          new VariableElementX(name, variables, kind, link.head);
+      resolver.defineElement(link.head, element);
+    }
+  }
+}
+
+/**
+ * [SignatureResolver] resolves function signatures.
+ */
+class SignatureResolver extends CommonResolverVisitor<Element> {
+  final Element enclosingElement;
+  Link<Element> optionalParameters = const Link<Element>();
+  int optionalParameterCount = 0;
+  bool optionalParametersAreNamed = false;
+  VariableDefinitions currentDefinitions;
+
+  SignatureResolver(Compiler compiler, this.enclosingElement) : super(compiler);
+
+  Element visitNodeList(NodeList node) {
+    // This must be a list of optional arguments.
+    String value = node.beginToken.stringValue;
+    if ((!identical(value, '[')) && (!identical(value, '{'))) {
+      internalError(node, "expected optional parameters");
+    }
+    optionalParametersAreNamed = (identical(value, '{'));
+    LinkBuilder<Element> elements = analyzeNodes(node.nodes);
+    optionalParameterCount = elements.length;
+    optionalParameters = elements.toLink();
+    return null;
+  }
+
+  Element visitVariableDefinitions(VariableDefinitions node) {
+    Link<Node> definitions = node.definitions.nodes;
+    if (definitions.isEmpty) {
+      cancel(node, 'internal error: no parameter definition');
+      return null;
+    }
+    if (!definitions.tail.isEmpty) {
+      cancel(definitions.tail.head, 'internal error: extra definition');
+      return null;
+    }
+    Node definition = definitions.head;
+    if (definition is NodeList) {
+      cancel(node, 'optional parameters are not implemented');
+    }
+
+    if (currentDefinitions != null) {
+      cancel(node, 'function type parameters not supported');
+    }
+    currentDefinitions = node;
+    Element element = definition.accept(this);
+    currentDefinitions = null;
+    return element;
+  }
+
+  Element visitIdentifier(Identifier node) {
+    Element variables = new VariableListElementX.node(currentDefinitions,
+        ElementKind.VARIABLE_LIST, enclosingElement);
+    // Ensure a parameter is not typed 'void'.
+    variables.computeType(compiler);
+    return new VariableElementX(node.source, variables,
+        ElementKind.PARAMETER, node);
+  }
+
+  SourceString getParameterName(Send node) {
+    var identifier = node.selector.asIdentifier();
+    if (identifier != null) {
+      // Normal parameter: [:Type name:].
+      return identifier.source;
+    } else {
+      // Function type parameter: [:void name(DartType arg):].
+      var functionExpression = node.selector.asFunctionExpression();
+      if (functionExpression != null &&
+          functionExpression.name.asIdentifier() != null) {
+        return functionExpression.name.asIdentifier().source;
+      } else {
+        cancel(node,
+            'internal error: unimplemented receiver on parameter send');
+      }
+    }
+  }
+
+  // The only valid [Send] can be in constructors and must be of the form
+  // [:this.x:] (where [:x:] represents an instance field).
+  FieldParameterElement visitSend(Send node) {
+    FieldParameterElement element;
+    if (node.receiver.asIdentifier() == null ||
+        !node.receiver.asIdentifier().isThis()) {
+      error(node, MessageKind.INVALID_PARAMETER);
+    } else if (!identical(enclosingElement.kind,
+                          ElementKind.GENERATIVE_CONSTRUCTOR)) {
+      error(node, MessageKind.FIELD_PARAMETER_NOT_ALLOWED);
+    } else {
+      SourceString name = getParameterName(node);
+      Element fieldElement = currentClass.lookupLocalMember(name);
+      if (fieldElement == null ||
+          !identical(fieldElement.kind, ElementKind.FIELD)) {
+        error(node, MessageKind.NOT_A_FIELD, {'fieldName': name});
+      } else if (!fieldElement.isInstanceMember()) {
+        error(node, MessageKind.NOT_INSTANCE_FIELD, {'fieldName': name});
+      }
+      Element variables = new VariableListElementX.node(currentDefinitions,
+          ElementKind.VARIABLE_LIST, enclosingElement);
+      element = new FieldParameterElementX(name, fieldElement, variables, node);
+    }
+    return element;
+  }
+
+  Element visitSendSet(SendSet node) {
+    Element element;
+    if (node.receiver != null) {
+      element = visitSend(node);
+    } else if (node.selector.asIdentifier() != null) {
+      Element variables = new VariableListElementX.node(currentDefinitions,
+          ElementKind.VARIABLE_LIST, enclosingElement);
+      element = new VariableElementX(node.selector.asIdentifier().source,
+          variables, ElementKind.PARAMETER, node);
+    }
+    // Visit the value. The compile time constant handler will
+    // make sure it's a compile time constant.
+    resolveExpression(node.arguments.head);
+    return element;
+  }
+
+  Element visitFunctionExpression(FunctionExpression node) {
+    // This is a function typed parameter.
+    // TODO(ahe): Resolve the function type.
+    return visit(node.name);
+  }
+
+  LinkBuilder<Element> analyzeNodes(Link<Node> link) {
+    LinkBuilder<Element> elements = new LinkBuilder<Element>();
+    for (; !link.isEmpty; link = link.tail) {
+      Element element = link.head.accept(this);
+      if (element != null) {
+        elements.addLast(element);
+      } else {
+        // If parameter is null, the current node should be the last,
+        // and a list of optional named parameters.
+        if (!link.tail.isEmpty || (link.head is !NodeList)) {
+          internalError(link.head, "expected optional parameters");
+        }
+      }
+    }
+    return elements;
+  }
+
+  /**
+   * Resolves formal parameters and return type to a [FunctionSignature].
+   */
+  static FunctionSignature analyze(Compiler compiler,
+                                   NodeList formalParameters,
+                                   Node returnNode,
+                                   Element element) {
+    SignatureResolver visitor = new SignatureResolver(compiler, element);
+    Link<Element> parameters = const Link<Element>();
+    int requiredParameterCount = 0;
+    if (formalParameters == null) {
+      if (!element.isGetter()) {
+        compiler.reportErrorCode(element, MessageKind.MISSING_FORMALS);
+      }
+    } else {
+      if (element.isGetter()) {
+        if (!identical(formalParameters.getEndToken().next.stringValue,
+                       // TODO(ahe): Remove the check for native keyword.
+                       'native')) {
+          if (compiler.rejectDeprecatedFeatures &&
+              // TODO(ahe): Remove isPlatformLibrary check.
+              !element.getLibrary().isPlatformLibrary) {
+            compiler.reportErrorCode(formalParameters,
+                                     MessageKind.EXTRA_FORMALS);
+          } else {
+            compiler.onDeprecatedFeature(formalParameters, 'getter parameters');
+          }
+        }
+      }
+      LinkBuilder<Element> parametersBuilder =
+        visitor.analyzeNodes(formalParameters.nodes);
+      requiredParameterCount  = parametersBuilder.length;
+      parameters = parametersBuilder.toLink();
+    }
+    DartType returnType = compiler.resolveReturnType(element, returnNode);
+    if (element.isSetter() && (requiredParameterCount != 1 ||
+                               visitor.optionalParameterCount != 0)) {
+      // If there are no formal parameters, we already reported an error above.
+      if (formalParameters != null) {
+        compiler.reportErrorCode(formalParameters,
+                                 MessageKind.ILLEGAL_SETTER_FORMALS);
+      }
+    }
+    if (element.isGetter() && (requiredParameterCount != 0
+                               || visitor.optionalParameterCount != 0)) {
+      compiler.reportErrorCode(formalParameters, MessageKind.EXTRA_FORMALS);
+    }
+    return new FunctionSignatureX(parameters,
+                                  visitor.optionalParameters,
+                                  requiredParameterCount,
+                                  visitor.optionalParameterCount,
+                                  visitor.optionalParametersAreNamed,
+                                  returnType);
+  }
+
+  // TODO(ahe): This is temporary.
+  void resolveExpression(Node node) {
+    if (node == null) return;
+    node.accept(new ResolverVisitor(compiler, enclosingElement,
+                                    new TreeElementMapping(enclosingElement)));
+  }
+
+  // TODO(ahe): This is temporary.
+  ClassElement get currentClass {
+    return enclosingElement.isMember()
+      ? enclosingElement.getEnclosingClass() : null;
+  }
+}
+
+class ConstructorResolver extends CommonResolverVisitor<Element> {
+  final ResolverVisitor resolver;
+  bool inConstContext = false;
+  DartType type;
+
+  ConstructorResolver(Compiler compiler, this.resolver) : super(compiler);
+
+  visitNode(Node node) {
+    throw 'not supported';
+  }
+
+  failOrReturnErroneousElement(Element enclosing, Node diagnosticNode,
+                               SourceString targetName, MessageKind kind,
+                               Map arguments) {
+    if (inConstContext) {
+      error(diagnosticNode, kind, arguments);
+    } else {
+      ResolutionWarning warning  = new ResolutionWarning(kind, arguments);
+      compiler.reportWarning(diagnosticNode, warning);
+      return new ErroneousElementX(kind, arguments, targetName, enclosing);
+    }
+  }
+
+  Selector createConstructorSelector(SourceString constructorName) {
+    return constructorName == const SourceString('')
+        ? new Selector.callDefaultConstructor(
+            resolver.enclosingElement.getLibrary())
+        : new Selector.callConstructor(
+            constructorName,
+            resolver.enclosingElement.getLibrary());
+  }
+
+  // TODO(ngeoffray): method named lookup should not report errors.
+  FunctionElement lookupConstructor(ClassElement cls,
+                                    Node diagnosticNode,
+                                    SourceString constructorName) {
+    cls.ensureResolved(compiler);
+    Selector selector = createConstructorSelector(constructorName);
+    Element result = cls.lookupConstructor(selector);
+    if (result == null) {
+      String fullConstructorName =
+          resolver.compiler.resolver.constructorNameForDiagnostics(
+              cls.name,
+              constructorName);
+      return failOrReturnErroneousElement(
+          cls,
+          diagnosticNode,
+          new SourceString(fullConstructorName),
+          MessageKind.CANNOT_FIND_CONSTRUCTOR,
+          {'constructorName': fullConstructorName});
+    } else if (inConstContext && !result.modifiers.isConst()) {
+      error(diagnosticNode, MessageKind.CONSTRUCTOR_IS_NOT_CONST);
+    }
+    return result;
+  }
+
+  visitNewExpression(NewExpression node) {
+    inConstContext = node.isConst();
+    Node selector = node.send.selector;
+    Element e = visit(selector);
+    return finishConstructorReference(e, node.send.selector, node);
+  }
+
+  /// Finishes resolution of a constructor reference and records the
+  /// type of the constructed instance on [expression].
+  FunctionElement finishConstructorReference(Element e,
+                                             Node diagnosticNode,
+                                             Node expression) {
+    // Find the unnamed constructor if the reference resolved to a
+    // class.
+    if (!Elements.isUnresolved(e) && e.isClass()) {
+      ClassElement cls = e;
+      cls.ensureResolved(compiler);
+      if (cls.isInterface() && (cls.defaultClass == null)) {
+        // TODO(ahe): Remove this check and error message when we
+        // don't have interfaces anymore.
+        error(diagnosticNode,
+              MessageKind.CANNOT_INSTANTIATE_INTERFACE,
+              {'interfaceName': cls.name});
+      }
+      // The unnamed constructor may not exist, so [e] may become unresolved.
+      e = lookupConstructor(cls, diagnosticNode, const SourceString(''));
+    }
+    if (type == null) {
+      if (Elements.isUnresolved(e)) {
+        type = compiler.dynamicClass.computeType(compiler);
+      } else {
+        type = e.getEnclosingClass().computeType(compiler).asRaw();
+      }
+    }
+    resolver.mapping.setType(expression, type);
+    return e;
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    assert(invariant(node, type == null));
+    type = resolver.resolveTypeRequired(node);
+    return resolver.mapping[node];
+  }
+
+  visitSend(Send node) {
+    Element e = visit(node.receiver);
+    if (Elements.isUnresolved(e)) return e;
+    Identifier name = node.selector.asIdentifier();
+    if (name == null) internalError(node.selector, 'unexpected node');
+
+    if (identical(e.kind, ElementKind.CLASS)) {
+      ClassElement cls = e;
+      cls.ensureResolved(compiler);
+      if (cls.isInterface() && (cls.defaultClass == null)) {
+        error(node.receiver,
+              MessageKind.CANNOT_INSTANTIATE_INTERFACE,
+              {'interfaceName': cls.name});
+      }
+      return lookupConstructor(cls, name, name.source);
+    } else if (identical(e.kind, ElementKind.PREFIX)) {
+      PrefixElement prefix = e;
+      e = prefix.lookupLocalMember(name.source);
+      if (e == null) {
+        return failOrReturnErroneousElement(resolver.enclosingElement, name,
+                                            name.source,
+                                            MessageKind.CANNOT_RESOLVE,
+                                            {'name': name});
+      } else if (!identical(e.kind, ElementKind.CLASS)) {
+        error(node, MessageKind.NOT_A_TYPE, {'node': name});
+      }
+    } else {
+      internalError(node.receiver, 'unexpected element $e');
+    }
+    return e;
+  }
+
+  Element visitIdentifier(Identifier node) {
+    SourceString name = node.source;
+    Element e = resolver.lookup(node, name);
+    // TODO(johnniwinther): Change errors to warnings, cf. 11.11.1.
+    if (e == null) {
+      return failOrReturnErroneousElement(resolver.enclosingElement, node, name,
+                                          MessageKind.CANNOT_RESOLVE,
+                                          {'name': name});
+    } else if (e.isErroneous()) {
+      return e;
+    } else if (identical(e.kind, ElementKind.TYPEDEF)) {
+      error(node, MessageKind.CANNOT_INSTANTIATE_TYPEDEF,
+            {'typedefName': name});
+    } else if (identical(e.kind, ElementKind.TYPE_VARIABLE)) {
+      error(node, MessageKind.CANNOT_INSTANTIATE_TYPE_VARIABLE,
+            {'typeVariableName': name});
+    } else if (!identical(e.kind, ElementKind.CLASS)
+        && !identical(e.kind, ElementKind.PREFIX)) {
+      error(node, MessageKind.NOT_A_TYPE, {'node': name});
+    }
+    return e;
+  }
+
+  /// Assumed to be called by [resolveRedirectingFactory].
+  Element visitReturn(Return node) {
+    Node expression = node.expression;
+    return finishConstructorReference(visit(expression),
+                                      expression, expression);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart
new file mode 100644
index 0000000..130486c
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart
@@ -0,0 +1,30 @@
+// 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.
+
+library resolution;
+
+import 'dart:collection' show Queue, LinkedHashMap;
+
+import '../dart2jslib.dart' hide Diagnostic;
+import '../dart_types.dart';
+import '../../compiler.dart' show Diagnostic;
+import '../tree/tree.dart';
+import '../elements/elements.dart';
+import '../elements/modelx.dart'
+    show FunctionElementX,
+         ErroneousElementX,
+         VariableElementX,
+         FieldParameterElementX,
+         VariableListElementX,
+         FunctionSignatureX,
+         LabelElementX,
+         TargetElementX,
+         MixinApplicationElementX;
+import '../util/util.dart';
+import '../scanner/scannerlib.dart' show PartialMetadataAnnotation;
+
+import 'secret_tree_element.dart' show getTreeElement, setTreeElement;
+
+part 'members.dart';
+part 'scope.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart
new file mode 100644
index 0000000..92e7840
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart
@@ -0,0 +1,163 @@
+// 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.
+
+part of resolution;
+
+abstract class Scope {
+  /**
+   * Adds [element] to this scope. This operation is only allowed on mutable
+   * scopes such as [MethodScope] and [BlockScope].
+   */
+  Element add(Element element);
+
+  /**
+   * Looks up the [Element] for [name] in this scope.
+   */
+  Element lookup(SourceString name);
+
+  static Scope buildEnclosingScope(Element element) {
+    return element.enclosingElement != null
+        ? element.enclosingElement.buildScope() : element.buildScope();
+  }
+}
+
+abstract class NestedScope extends Scope {
+  final Scope parent;
+
+  NestedScope(this.parent);
+
+  Element lookup(SourceString name) {
+    Element result = localLookup(name);
+    if (result != null) return result;
+    return parent.lookup(name);
+  }
+
+  Element localLookup(SourceString name);
+
+  static Scope buildEnclosingScope(Element element) {
+    return element.enclosingElement != null
+        ? element.enclosingElement.buildScope() : element.buildScope();
+  }
+}
+
+/**
+ * [TypeDeclarationScope] defines the outer scope of a type declaration in
+ * which the declared type variables and the entities in the enclosing scope are
+ * available but where declared and inherited members are not available. This
+ * scope is only used for class/interface declarations during resolution of the
+ * class hierarchy. In all other cases [ClassScope] is used.
+ */
+class TypeDeclarationScope extends NestedScope {
+  final TypeDeclarationElement element;
+
+  TypeDeclarationScope(parent, this.element)
+      : super(parent) {
+    assert(parent != null);
+  }
+
+  Element add(Element newElement) {
+    throw "Cannot add element to TypeDeclarationScope";
+  }
+
+  Element lookupTypeVariable(SourceString name) {
+    Link<DartType> typeVariableLink = element.typeVariables;
+    while (!typeVariableLink.isEmpty) {
+      TypeVariableType typeVariable = typeVariableLink.head;
+      if (typeVariable.name == name) {
+        return typeVariable.element;
+      }
+      typeVariableLink = typeVariableLink.tail;
+    }
+    return null;
+  }
+
+  Element localLookup(SourceString name) => lookupTypeVariable(name);
+
+  String toString() =>
+      'TypeDeclarationScope($element)';
+}
+
+abstract class MutableScope extends NestedScope {
+  final Map<SourceString, Element> elements;
+
+  MutableScope(Scope parent)
+      : super(parent),
+        this.elements = new Map<SourceString, Element>() {
+    assert(parent != null);
+  }
+
+  Element add(Element newElement) {
+    if (elements.containsKey(newElement.name)) {
+      return elements[newElement.name];
+    }
+    elements[newElement.name] = newElement;
+    return newElement;
+  }
+
+  Element localLookup(SourceString name) => elements[name];
+}
+
+class MethodScope extends MutableScope {
+  final Element element;
+
+  MethodScope(Scope parent, this.element)
+      : super(parent);
+
+  String toString() => 'MethodScope($element${elements.keys.toList()})';
+}
+
+class BlockScope extends MutableScope {
+  BlockScope(Scope parent) : super(parent);
+
+  String toString() => 'BlockScope(${elements.keys.toList()})';
+}
+
+/**
+ * [ClassScope] defines the inner scope of a class/interface declaration in
+ * which declared members, declared type variables, entities in the enclosing
+ * scope and inherited members are available, in the given order.
+ */
+class ClassScope extends TypeDeclarationScope {
+  ClassElement get element => super.element;
+
+  ClassScope(Scope parentScope, ClassElement element)
+      : super(parentScope, element)  {
+    assert(parent != null);
+  }
+
+  Element localLookup(SourceString name) {
+    Element result = element.lookupLocalMember(name);
+    if (result != null) return result;
+    return super.localLookup(name);
+  }
+
+  Element lookup(SourceString name) {
+    Element result = localLookup(name);
+    if (result != null) return result;
+    result = parent.lookup(name);
+    if (result != null) return result;
+    return element.lookupSuperMember(name);
+  }
+
+  Element add(Element newElement) {
+    throw "Cannot add an element in a class scope";
+  }
+
+  String toString() => 'ClassScope($element)';
+}
+
+class LibraryScope implements Scope {
+  final LibraryElement library;
+
+  LibraryScope(LibraryElement this.library);
+
+  Element localLookup(SourceString name) => library.find(name);
+  Element lookup(SourceString name) => localLookup(name);
+
+  Element add(Element newElement) {
+    throw "Cannot add an element to a library scope";
+  }
+
+  String toString() => 'LibraryScope($library)';
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart
new file mode 100644
index 0000000..e8ee0f1
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart
@@ -0,0 +1,46 @@
+// 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.
+
+/**
+ * Encapsulates the field [TreeElementMixin._element].
+ *
+ * This library is an implementation detail of dart2js, and should not
+ * be imported except by resolution and tree node libraries, or for
+ * testing.
+ *
+ * We have taken great care to ensure AST nodes can be cached between
+ * compiler instances.  Part of this is requires that we always access
+ * resolution results through [TreeElements].
+ *
+ * So please, do not add additional elements to this library, and do
+ * not import it.
+ */
+library secret_tree_element;
+
+/**
+ * The superclass of all AST nodes.
+ */
+abstract class TreeElementMixin {
+  // Deliberately using [Object] here to thwart code completion.
+  // You're not really supposed to access this field anyways.
+  Object _element;
+}
+
+/**
+ * Do not call this method directly.  Instead, use an instance of
+ * [TreeElements].
+ *
+ * Using [Object] as return type to thwart code completion.
+ */
+Object getTreeElement(TreeElementMixin node) {
+  return node._element;
+}
+
+/**
+ * Do not call this method directly.  Instead, use an instance of
+ * [TreeElements].
+ */
+void setTreeElement(TreeElementMixin node, Object value) {
+  node._element = value;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart
new file mode 100644
index 0000000..3bfadb2
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart
@@ -0,0 +1,67 @@
+// 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.
+
+part of dart2js;
+
+abstract class ResolvedVisitor<R> extends Visitor<R> {
+  TreeElements elements;
+
+  ResolvedVisitor(this.elements);
+
+  R visitSend(Send node) {
+    if (node.isSuperCall) {
+      return visitSuperSend(node);
+    } else if (node.isOperator) {
+      return visitOperatorSend(node);
+    } else if (node.isPropertyAccess) {
+      Element element = elements[node];
+      if (!Elements.isUnresolved(element) && element.impliesType()) {
+        // A reference to a class literal, typedef or type variable.
+        return visitTypeReferenceSend(node);
+      } else {
+        return visitGetterSend(node);
+      }
+    } else if (Elements.isClosureSend(node, elements[node])) {
+      return visitClosureSend(node);
+    } else {
+      Element element = elements[node];
+      if (Elements.isUnresolved(element)) {
+        if (element == null) {
+          // Example: f() with 'f' unbound.
+          // This can only happen inside an instance method.
+          return visitDynamicSend(node);
+        } else {
+          return visitStaticSend(node);
+        }
+      } else if (element.impliesType()) {
+        // A reference to a class literal, typedef or type variable.
+        return visitTypeReferenceSend(node);
+      } else if (element.isInstanceMember()) {
+        // Example: f() with 'f' bound to instance method.
+        return visitDynamicSend(node);
+      } else if (!element.isInstanceMember()) {
+        // Example: A.f() or f() with 'f' bound to a static function.
+        // Also includes new A() or new A.named() which is treated like a
+        // static call to a factory.
+        return visitStaticSend(node);
+      } else {
+        internalError("Cannot generate code for send", node: node);
+      }
+    }
+  }
+
+  R visitSuperSend(Send node);
+  R visitOperatorSend(Send node);
+  R visitGetterSend(Send node);
+  R visitClosureSend(Send node);
+  R visitDynamicSend(Send node);
+  R visitStaticSend(Send node);
+  R visitTypeReferenceSend(Send node);
+
+  void internalError(String reason, {Node node});
+
+  R visitNode(Node node) {
+    internalError("Unhandled node", node: node);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart
new file mode 100644
index 0000000..01f8e9e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2011, 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.
+
+part of scanner_implementation;
+
+abstract
+class ArrayBasedScanner<S extends SourceString> extends AbstractScanner<S> {
+  int get charOffset => byteOffset + extraCharOffset;
+  final Token tokens;
+  Token tail;
+  int tokenStart;
+  int byteOffset;
+  final bool includeComments;
+
+  /** Since the input is UTF8, some characters are represented by more
+   * than one byte. [extraCharOffset] tracks the difference. */
+  int extraCharOffset;
+  Link<BeginGroupToken> groupingStack = const Link<BeginGroupToken>();
+
+  ArrayBasedScanner(this.includeComments)
+    : this.extraCharOffset = 0,
+      this.tokenStart = -1,
+      this.byteOffset = -1,
+      this.tokens = new Token(EOF_INFO, -1) {
+    this.tail = this.tokens;
+  }
+
+  int advance() {
+    int next = nextByte();
+    return next;
+  }
+
+  int select(int choice, PrecedenceInfo yes, PrecedenceInfo no) {
+    int next = advance();
+    if (identical(next, choice)) {
+      appendPrecedenceToken(yes);
+      return advance();
+    } else {
+      appendPrecedenceToken(no);
+      return next;
+    }
+  }
+
+  void appendPrecedenceToken(PrecedenceInfo info) {
+    tail.next = new Token(info, tokenStart);
+    tail = tail.next;
+  }
+
+  void appendStringToken(PrecedenceInfo info, String value) {
+    tail.next = new StringToken(info, value, tokenStart);
+    tail = tail.next;
+  }
+
+  void appendKeywordToken(Keyword keyword) {
+    String syntax = keyword.syntax;
+
+    // Type parameters and arguments cannot contain 'this' or 'super'.
+    if (identical(syntax, 'this') || identical(syntax, 'super')) discardOpenLt();
+    tail.next = new KeywordToken(keyword, tokenStart);
+    tail = tail.next;
+  }
+
+  void appendEofToken() {
+    tail.next = new Token(EOF_INFO, charOffset);
+    tail = tail.next;
+    // EOF points to itself so there's always infinite look-ahead.
+    tail.next = tail;
+    discardOpenLt();
+    while (!groupingStack.isEmpty) {
+      unmatchedBeginGroup(groupingStack.head);
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void beginToken() {
+    tokenStart = charOffset;
+  }
+
+  Token firstToken() {
+    return tokens.next;
+  }
+
+  Token previousToken() {
+    return tail;
+  }
+
+  void addToCharOffset(int offset) {
+    extraCharOffset += offset;
+  }
+
+  void appendWhiteSpace(int next) {
+    // Do nothing, we don't collect white space.
+  }
+
+  void appendBeginGroup(PrecedenceInfo info, String value) {
+    Token token = new BeginGroupToken(info, value, tokenStart);
+    tail.next = token;
+    tail = tail.next;
+    if (!identical(info.kind, LT_TOKEN)) discardOpenLt();
+    groupingStack = groupingStack.prepend(token);
+  }
+
+  int appendEndGroup(PrecedenceInfo info, String value, int openKind) {
+    assert(!identical(openKind, LT_TOKEN));
+    appendStringToken(info, value);
+    discardOpenLt();
+    if (groupingStack.isEmpty) {
+      return advance();
+    }
+    BeginGroupToken begin = groupingStack.head;
+    if (!identical(begin.kind, openKind)) {
+      if (!identical(openKind, OPEN_CURLY_BRACKET_TOKEN) ||
+          !identical(begin.kind, STRING_INTERPOLATION_TOKEN)) {
+        // Not ending string interpolation.
+        return error(new SourceString('Unmatched ${begin.stringValue}'));
+      }
+      // We're ending an interpolated expression.
+      begin.endGroup = tail;
+      groupingStack = groupingStack.tail;
+      // Using "start-of-text" to signal that we're back in string
+      // scanning mode.
+      return $STX;
+    }
+    begin.endGroup = tail;
+    groupingStack = groupingStack.tail;
+    return advance();
+  }
+
+  void appendGt(PrecedenceInfo info, String value) {
+    appendStringToken(info, value);
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack.head.endGroup = tail;
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void appendGtGt(PrecedenceInfo info, String value) {
+    appendStringToken(info, value);
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack.head.endGroup = tail;
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void appendGtGtGt(PrecedenceInfo info, String value) {
+    appendStringToken(info, value);
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+    if (groupingStack.isEmpty) return;
+    if (identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack.head.endGroup = tail;
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void appendComment() {
+    if (!includeComments) return;
+    SourceString value = utf8String(tokenStart, -1);
+    appendByteStringToken(COMMENT_INFO, value);
+  }
+
+  void discardOpenLt() {
+    while (!groupingStack.isEmpty
+        && identical(groupingStack.head.kind, LT_TOKEN)) {
+      groupingStack = groupingStack.tail;
+    }
+  }
+
+  void unmatchedBeginGroup(BeginGroupToken begin);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart
new file mode 100644
index 0000000..4cf9eeb
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2011, 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.
+
+part of parser;
+
+/**
+ * Scanner that reads from a byte array and creates tokens that points
+ * to the same array.
+ */
+class ByteArrayScanner extends ArrayBasedScanner<ByteString> {
+  final List<int> bytes;
+
+  ByteArrayScanner(List<int> this.bytes, [bool includeComments = false])
+    : super(includeComments);
+
+  int nextByte() => byteAt(++byteOffset);
+
+  int peek() => byteAt(byteOffset + 1);
+
+  int byteAt(int index) => bytes[index];
+
+  AsciiString asciiString(int start, int offset) {
+    return AsciiString.of(bytes, start, byteOffset - start + offset);
+  }
+
+  Utf8String utf8String(int start, int offset) {
+    return Utf8String.of(bytes, start, byteOffset - start + offset + 1);
+  }
+
+  void appendByteStringToken(PrecedenceInfo info, ByteString value) {
+    tail.next = new ByteStringToken(info, value, tokenStart);
+    tail = tail.next;
+  }
+
+  // This method should be equivalent to the one in super. However,
+  // this is a *HOT* method and Dart VM performs better if it is easy
+  // to inline.
+  int advance() => bytes[++byteOffset];
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart
new file mode 100644
index 0000000..4d79898
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2011, 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.
+
+/**
+ * An abstract string representation.
+ */
+abstract class ByteString extends Iterable<int> implements SourceString {
+  final List<int> bytes;
+  final int offset;
+  final int length;
+  int _hashCode;
+
+  ByteString(List<int> this.bytes, int this.offset, int this.length);
+
+  String get charset;
+
+  String slowToString() => new String.fromCharCodes(
+      new Utf8Decoder(bytes, offset, length).decodeRest());
+
+  String toString() => "ByteString(${slowToString()})";
+
+  bool operator ==(other) {
+    throw "should be overridden in subclass";
+  }
+
+  Iterator<int> get iterator => new Utf8Decoder(bytes, offset, length);
+
+  int get hashCode {
+    if (_hashCode == null) {
+      _hashCode = computeHashCode();
+    }
+    return _hashCode;
+  }
+
+  int computeHashCode() {
+    int code = 1;
+    int end = offset + length;
+    for (int i = offset; i < end; i++) {
+      code += 19 * code + bytes[i];
+    }
+    return code;
+  }
+
+  printOn(StringBuffer sb) {
+    sb.add(slowToString());
+  }
+
+  bool get isEmpty => length == 0;
+  bool isPrivate() => !isEmpty && identical(bytes[offset], $_);
+
+  String get stringValue => null;
+}
+
+/**
+ * A string that consists purely of 7bit ASCII characters.
+ */
+class AsciiString extends ByteString {
+  final String charset = "ASCII";
+
+  AsciiString(List<int> bytes, int offset, int length)
+    : super(bytes, offset, length);
+
+  static AsciiString of(List<int> bytes, int offset, int length) {
+    AsciiString string = new AsciiString(bytes, offset, length);
+    return string;
+  }
+
+  Iterator<int> get iterator => new AsciiStringIterator(bytes);
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    return new AsciiString(bytes, offset + initial,
+                           length - initial - terminal);
+  }
+
+
+  static AsciiString fromString(String string) {
+    List<int> bytes = string.charCodes;
+    return AsciiString.of(bytes, 0, bytes.length);
+  }
+}
+
+
+class AsciiStringIterator implements Iterator<int> {
+  final List<int> bytes;
+  int offset;
+  final int end;
+  int _current;
+
+  AsciiStringIterator(List<int> bytes)
+      : this.bytes = bytes, offset = 0, end = bytes.length;
+  AsciiStringIterator.range(List<int> bytes, int from, int length)
+      : this.bytes = bytes, offset = from, end = from + length;
+
+  int get current => _current;
+  bool moveNext() {
+    if (offset < end) {
+      _current = bytes[offset++];
+      return true;
+    }
+    _current = null;
+    return false;
+  }
+}
+
+
+/**
+ * A string that consists of characters that can be encoded as UTF-8.
+ */
+class Utf8String extends ByteString {
+  final String charset = "UTF8";
+
+  Utf8String(List<int> bytes, int offset, int length)
+    : super(bytes, offset, length);
+
+  static Utf8String of(List<int> bytes, int offset, int length) {
+    return new Utf8String(bytes, offset, length);
+  }
+
+  static Utf8String fromString(String string) {
+    throw "not implemented yet";
+  }
+
+  Iterator<int> get iterator => new Utf8Decoder(bytes, 0, length);
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    assert((){
+      // Only allow dropping ASCII characters, to guarantee that
+      // the resulting Utf8String is still valid.
+      for (int i = 0; i < initial; i++) {
+        if (bytes[offset + i] >= 0x80) return false;
+      }
+      for (int i = 0; i < terminal; i++) {
+        if (bytes[offset + length - terminal + i] >= 0x80) return false;
+      }
+      return true;
+    });
+    // TODO(lrn): Check that first and last bytes use the same type of quotes.
+    return new Utf8String(bytes, offset + initial,
+                          length - initial - terminal);
+  }
+}
+
+/**
+ * A ByteString-valued token.
+ */
+class ByteStringToken extends Token {
+  final ByteString value;
+
+  ByteStringToken(PrecedenceInfo info, ByteString this.value, int charOffset)
+    : super(info, charOffset);
+
+  String toString() => value.toString();
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart
new file mode 100644
index 0000000..1e359aa
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart
@@ -0,0 +1,197 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+class ClassElementParser extends PartialParser {
+  ClassElementParser(Listener listener) : super(listener);
+
+  Token parseClassBody(Token token) => fullParseClassBody(token);
+}
+
+class PartialClassElement extends ClassElementX {
+  final Token beginToken;
+  final Token endToken;
+  ClassNode cachedNode;
+
+  PartialClassElement(SourceString name,
+                      Token this.beginToken,
+                      Token this.endToken,
+                      Element enclosing,
+                      int id)
+      : super(name, enclosing, id, STATE_NOT_STARTED);
+
+  void set supertypeLoadState(int state) {
+    assert(state == supertypeLoadState + 1);
+    assert(state <= STATE_DONE);
+    super.supertypeLoadState = state;
+  }
+
+  void set resolutionState(int state) {
+    assert(state == resolutionState + 1);
+    assert(state <= STATE_DONE);
+    super.resolutionState = state;
+  }
+
+  ClassNode parseNode(Compiler compiler) {
+    if (cachedNode != null) return cachedNode;
+    compiler.withCurrentElement(this, () {
+      compiler.parser.measure(() {
+        MemberListener listener = new MemberListener(compiler, this);
+        Parser parser = new ClassElementParser(listener);
+        Token token = parser.parseTopLevelDeclaration(beginToken);
+        assert(identical(token, endToken.next));
+        cachedNode = listener.popNode();
+        assert(listener.nodes.isEmpty);
+      });
+      compiler.patchParser.measure(() {
+        if (isPatched) {
+          // TODO(lrn): Perhaps extract functionality so it doesn't
+          // need compiler.
+          compiler.patchParser.parsePatchClassNode(patch);
+        }
+      });
+    });
+    return cachedNode;
+  }
+
+  Token position() => beginToken;
+
+  // TODO(johnniwinther): Ensure that modifiers are always available.
+  Modifiers get modifiers =>
+      cachedNode != null ? cachedNode.modifiers : Modifiers.EMPTY;
+
+  bool isInterface() => identical(beginToken.stringValue, "interface");
+}
+
+class MemberListener extends NodeListener {
+  final ClassElement enclosingElement;
+
+  MemberListener(DiagnosticListener listener,
+                 Element enclosingElement)
+      : this.enclosingElement = enclosingElement,
+        super(listener, enclosingElement.getCompilationUnit());
+
+  bool isConstructorName(Node nameNode) {
+    if (enclosingElement == null ||
+        enclosingElement.kind != ElementKind.CLASS) {
+      return false;
+    }
+    SourceString name;
+    if (nameNode.asIdentifier() != null) {
+      name = nameNode.asIdentifier().source;
+    } else {
+      Send send = nameNode.asSend();
+      name = send.receiver.asIdentifier().source;
+    }
+    return enclosingElement.name == name;
+  }
+
+  SourceString getMethodNameHack(Node methodName) {
+    Send send = methodName.asSend();
+    if (send == null) return methodName.asIdentifier().source;
+    Identifier receiver = send.receiver.asIdentifier();
+    Identifier selector = send.selector.asIdentifier();
+    Operator operator = selector.asOperator();
+    if (operator != null) {
+      assert(identical(receiver.source.stringValue, 'operator'));
+      // TODO(ahe): It is a hack to compare to ')', but it beats
+      // parsing the node.
+      bool isUnary = identical(operator.token.next.next.stringValue, ')');
+      return Elements.constructOperatorName(operator.source, isUnary);
+    } else {
+      if (receiver == null) {
+        listener.cancel('library prefix in named factory constructor not '
+                        'implemented', node: send.receiver);
+      }
+      if (receiver.source != enclosingElement.name) {
+        listener.onDeprecatedFeature(receiver, 'interface factories');
+      }
+      return Elements.constructConstructorName(receiver.source,
+                                               selector.source);
+    }
+  }
+
+  void endMethod(Token getOrSet, Token beginToken, Token endToken) {
+    super.endMethod(getOrSet, beginToken, endToken);
+    FunctionExpression method = popNode();
+    pushNode(null);
+    bool isConstructor = isConstructorName(method.name);
+    SourceString name = getMethodNameHack(method.name);
+    ElementKind kind = ElementKind.FUNCTION;
+    if (isConstructor) {
+      if (getOrSet != null) {
+        recoverableError('illegal modifier', token: getOrSet);
+      }
+      kind = ElementKind.GENERATIVE_CONSTRUCTOR;
+    } else if (getOrSet != null) {
+      kind = (identical(getOrSet.stringValue, 'get'))
+             ? ElementKind.GETTER : ElementKind.SETTER;
+    }
+    Element memberElement =
+        new PartialFunctionElement(name, beginToken, getOrSet, endToken,
+                                   kind, method.modifiers, enclosingElement);
+    addMember(memberElement);
+  }
+
+  void endFactoryMethod(Token beginToken, Token endToken) {
+    super.endFactoryMethod(beginToken, endToken);
+    FunctionExpression method = popNode();
+    pushNode(null);
+    SourceString name = getMethodNameHack(method.name);
+    Identifier singleIdentifierName = method.name.asIdentifier();
+    if (singleIdentifierName != null && singleIdentifierName.source == name) {
+      if (name != enclosingElement.name) {
+        listener.onDeprecatedFeature(method.name, 'interface factories');
+      }
+    }
+    ElementKind kind = ElementKind.FUNCTION;
+    Element memberElement =
+        new PartialFunctionElement(name, beginToken, null, endToken,
+                                   kind, method.modifiers, enclosingElement);
+    addMember(memberElement);
+  }
+
+  void endFields(int count, Token beginToken, Token endToken) {
+    super.endFields(count, beginToken, endToken);
+    VariableDefinitions variableDefinitions = popNode();
+    Modifiers modifiers = variableDefinitions.modifiers;
+    pushNode(null);
+    void buildFieldElement(SourceString name, Element fields) {
+      Element element =
+          new VariableElementX(name, fields, ElementKind.FIELD, null);
+      addMember(element);
+    }
+    buildFieldElements(modifiers, variableDefinitions.definitions,
+                       enclosingElement,
+                       buildFieldElement, beginToken, endToken);
+  }
+
+  void endInitializer(Token assignmentOperator) {
+    pushNode(null); // Super expects an expression, but
+                    // ClassElementParser just skips expressions.
+    super.endInitializer(assignmentOperator);
+  }
+
+  void endInitializers(int count, Token beginToken, Token endToken) {
+    pushNode(null);
+  }
+
+  void addMember(Element memberElement) {
+    for (Link link = metadata; !link.isEmpty; link = link.tail) {
+      memberElement.addMetadata(link.head);
+    }
+    metadata = const Link<MetadataAnnotation>();
+    enclosingElement.addMember(memberElement, listener);
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    popNode(); // Discard arguments.
+    if (periodBeforeName != null) {
+      popNode(); // Discard name.
+    }
+    popNode(); // Discard node (Send or Identifier).
+    pushMetadata(new PartialMetadataAnnotation(beginToken, endToken));
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart
new file mode 100644
index 0000000..5d3c9a8
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart
@@ -0,0 +1,235 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+/**
+ * A keyword in the Dart programming language.
+ */
+class Keyword extends Iterable<int> implements SourceString {
+  static const List<Keyword> values = const <Keyword> [
+      const Keyword("assert"),
+      const Keyword("break"),
+      const Keyword("case"),
+      const Keyword("catch"),
+      const Keyword("class"),
+      const Keyword("const"),
+      const Keyword("continue"),
+      const Keyword("default"),
+      const Keyword("do"),
+      const Keyword("else"),
+      const Keyword("extends"),
+      const Keyword("false"),
+      const Keyword("final"),
+      const Keyword("finally"),
+      const Keyword("for"),
+      const Keyword("if"),
+      const Keyword("in"),
+      const Keyword("new"),
+      const Keyword("null"),
+      const Keyword("return"),
+      const Keyword("super"),
+      const Keyword("switch"),
+      const Keyword("this"),
+      const Keyword("throw"),
+      const Keyword("true"),
+      const Keyword("try"),
+      const Keyword("var"),
+      const Keyword("void"),
+      const Keyword("while"),
+      const Keyword("with"),
+
+      // TODO(ahe): Don't think this is a reserved word.
+      // See: http://dartbug.com/5579
+      const Keyword("is", info: IS_INFO),
+
+      const Keyword("abstract", isBuiltIn: true),
+      const Keyword("as", info: AS_INFO, isBuiltIn: true),
+      const Keyword("dynamic", isBuiltIn: true),
+      const Keyword("export", isBuiltIn: true),
+      const Keyword("external", isBuiltIn: true),
+      const Keyword("factory", isBuiltIn: true),
+      const Keyword("get", isBuiltIn: true),
+      const Keyword("implements", isBuiltIn: true),
+      const Keyword("import", isBuiltIn: true),
+      const Keyword("interface", isBuiltIn: true),
+      const Keyword("library", isBuiltIn: true),
+      const Keyword("operator", isBuiltIn: true),
+      const Keyword("part", isBuiltIn: true),
+      const Keyword("set", isBuiltIn: true),
+      const Keyword("static", isBuiltIn: true),
+      const Keyword("typedef", isBuiltIn: true),
+
+      const Keyword("hide", isPseudo: true),
+      const Keyword("native", isPseudo: true),
+      const Keyword("of", isPseudo: true),
+      const Keyword("on", isPseudo: true),
+      const Keyword("show", isPseudo: true),
+      const Keyword("source", isPseudo: true) ];
+
+  // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+  static const DYNAMIC_DEPRECATED = const Keyword("Dynamic", isBuiltIn: true);
+
+  final String syntax;
+  final bool isPseudo;
+  final bool isBuiltIn;
+  final PrecedenceInfo info;
+
+  static Map<String, Keyword> _keywords;
+  static Map<String, Keyword> get keywords {
+    if (_keywords == null) {
+      _keywords = computeKeywordMap();
+    }
+    return _keywords;
+  }
+
+  const Keyword(String this.syntax,
+                {bool this.isPseudo: false,
+                 bool this.isBuiltIn: false,
+                 PrecedenceInfo this.info: KEYWORD_INFO});
+
+  static Map<String, Keyword> computeKeywordMap() {
+    Map<String, Keyword> result = new LinkedHashMap<String, Keyword>();
+    for (Keyword keyword in values) {
+      result[keyword.syntax] = keyword;
+    }
+    return result;
+  }
+
+  int get hashCode => syntax.hashCode;
+
+  bool operator ==(other) {
+    return other is SourceString && toString() == other.slowToString();
+  }
+
+  Iterator<int> get iterator => new StringCodeIterator(syntax);
+
+  void printOn(StringBuffer sb) {
+    sb.add(syntax);
+  }
+
+  String toString() => syntax;
+  String slowToString() => syntax;
+  String get stringValue => syntax;
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    // TODO(lrn): consider remodelling to avoid having this method in keywords.
+    return this;
+  }
+
+  bool get isEmpty => false;
+  bool isPrivate() => false;
+}
+
+/**
+ * Abstract state in a state machine for scanning keywords.
+ */
+abstract class KeywordState {
+  bool isLeaf();
+  KeywordState next(int c);
+  Keyword get keyword;
+
+  static KeywordState _KEYWORD_STATE;
+  static KeywordState get KEYWORD_STATE {
+    if (_KEYWORD_STATE == null) {
+      List<String> strings =
+          new List<String>.fixedLength(Keyword.values.length);
+      for (int i = 0; i < Keyword.values.length; i++) {
+        strings[i] = Keyword.values[i].syntax;
+      }
+      strings.sort((a,b) => a.compareTo(b));
+      _KEYWORD_STATE = computeKeywordStateTable(0, strings, 0, strings.length);
+    }
+    return _KEYWORD_STATE;
+  }
+
+  static KeywordState computeKeywordStateTable(int start, List<String> strings,
+                                               int offset, int length) {
+    List<KeywordState> result = new List<KeywordState>.fixedLength(26);
+    assert(length != 0);
+    int chunk = 0;
+    int chunkStart = -1;
+    bool isLeaf = false;
+    for (int i = offset; i < offset + length; i++) {
+      if (strings[i].length == start) {
+        isLeaf = true;
+      }
+      if (strings[i].length > start) {
+        int c = strings[i].charCodeAt(start);
+        if (chunk != c) {
+          if (chunkStart != -1) {
+            assert(result[chunk - $a] == null);
+            result[chunk - $a] = computeKeywordStateTable(start + 1, strings,
+                                                          chunkStart,
+                                                          i - chunkStart);
+          }
+          chunkStart = i;
+          chunk = c;
+        }
+      }
+    }
+    if (chunkStart != -1) {
+      assert(result[chunk - $a] == null);
+      result[chunk - $a] =
+        computeKeywordStateTable(start + 1, strings, chunkStart,
+                                 offset + length - chunkStart);
+    } else {
+      assert(length == 1);
+      return new LeafKeywordState(strings[offset]);
+    }
+    if (isLeaf) {
+      return new ArrayKeywordState(result, strings[offset]);
+    } else {
+      return new ArrayKeywordState(result, null);
+    }
+  }
+}
+
+/**
+ * A state with multiple outgoing transitions.
+ */
+class ArrayKeywordState extends KeywordState {
+  final List<KeywordState> table;
+  final Keyword keyword;
+
+  ArrayKeywordState(List<KeywordState> this.table, String syntax)
+    : keyword = (syntax == null) ? null : Keyword.keywords[syntax];
+
+  bool isLeaf() => false;
+
+  KeywordState next(int c) => table[c - $a];
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.add("[");
+    if (keyword != null) {
+      sb.add("*");
+      sb.add(keyword);
+      sb.add(" ");
+    }
+    List<KeywordState> foo = table;
+    for (int i = 0; i < foo.length; i++) {
+      if (foo[i] != null) {
+        sb.add("${new String.fromCharCodes([i + $a])}: ${foo[i]}; ");
+      }
+    }
+    sb.add("]");
+    return sb.toString();
+  }
+}
+
+/**
+ * A state that has no outgoing transitions.
+ */
+class LeafKeywordState extends KeywordState {
+  final Keyword keyword;
+
+  LeafKeywordState(String syntax) : keyword = Keyword.keywords[syntax];
+
+  bool isLeaf() => true;
+
+  KeywordState next(int c) => null;
+
+  String toString() => keyword.syntax;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart
new file mode 100644
index 0000000..e535f62
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart
@@ -0,0 +1,2064 @@
+// 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.
+
+part of scanner;
+
+const bool VERBOSE = false;
+
+/**
+ * A parser event listener that does nothing except throw exceptions
+ * on parser errors.
+ */
+class Listener {
+  void beginArgumentDefinitionTest(Token token) {
+  }
+
+  void endArgumentDefinitionTest(Token beginToken, Token endToken) {
+  }
+
+  void beginArguments(Token token) {
+  }
+
+  void endArguments(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginBlock(Token token) {
+  }
+
+  void endBlock(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginCascade(Token token) {
+  }
+
+  void endCascade() {
+  }
+
+  void beginClassBody(Token token) {
+  }
+
+  void endClassBody(int memberCount, Token beginToken, Token endToken) {
+  }
+
+  void beginClassDeclaration(Token token) {
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+  }
+
+  void beginCombinators(Token token) {
+  }
+
+  void endCombinators(int count) {
+  }
+
+  void beginCompilationUnit(Token token) {
+  }
+
+  void endCompilationUnit(int count, Token token) {
+  }
+
+  void beginConstructorReference(Token start) {
+  }
+
+  void endConstructorReference(Token start, Token periodBeforeName,
+                               Token endToken) {
+  }
+
+  void beginDoWhileStatement(Token token) {
+  }
+
+  void endDoWhileStatement(Token doKeyword, Token whileKeyword,
+                           Token endToken) {
+  }
+
+  void beginExport(Token token) {
+  }
+
+  void endExport(Token exportKeyword, Token semicolon) {
+  }
+
+  void beginExpressionStatement(Token token) {
+  }
+
+  void endExpressionStatement(Token token) {
+  }
+
+  void beginDefaultClause(Token token) {
+  }
+
+  void handleNoDefaultClause(Token token) {
+  }
+
+  void endDefaultClause(Token defaultKeyword) {
+  }
+
+  void beginFactoryMethod(Token token) {
+  }
+
+  void endFactoryMethod(Token beginToken, Token endToken) {
+  }
+
+  void beginFormalParameter(Token token) {
+  }
+
+  void endFormalParameter(Token thisKeyword) {
+  }
+
+  void handleNoFormalParameters(Token token) {
+  }
+
+  void beginFormalParameters(Token token) {
+  }
+
+  void endFormalParameters(int count, Token beginToken, Token endToken) {
+  }
+
+  void endFields(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginForStatement(Token token) {
+  }
+
+  void endForStatement(int updateExpressionCount,
+                       Token beginToken, Token endToken) {
+  }
+
+  void endForIn(Token beginToken, Token inKeyword, Token endToken) {
+  }
+
+  void beginFunction(Token token) {
+  }
+
+  void endFunction(Token getOrSet, Token endToken) {
+  }
+
+  void beginFunctionDeclaration(Token token) {
+  }
+
+  void endFunctionDeclaration(Token token) {
+  }
+
+  void beginFunctionBody(Token token) {
+  }
+
+  void endFunctionBody(int count, Token beginToken, Token endToken) {
+  }
+
+  void handleNoFunctionBody(Token token) {
+  }
+
+  void beginFunctionName(Token token) {
+  }
+
+  void endFunctionName(Token token) {
+  }
+
+  void beginFunctionTypeAlias(Token token) {
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+  }
+
+  void beginMixinApplication(Token token) {
+  }
+
+  void endMixinApplication() {
+  }
+
+  void beginNamedMixinApplication(Token token) {
+  }
+
+  void endNamedMixinApplication(Token typedefKeyword,
+                                Token implementsKeyword,
+                                Token endToken) {
+  }
+
+  void beginHide(Token hideKeyword) {
+  }
+
+  void endHide(Token hideKeyword) {
+  }
+
+  void beginIdentifierList(Token token) {
+  }
+
+  void endIdentifierList(int count) {
+  }
+
+  void beginTypeList(Token token) {
+  }
+
+  void endTypeList(int count) {
+  }
+
+  void beginIfStatement(Token token) {
+  }
+
+  void endIfStatement(Token ifToken, Token elseToken) {
+  }
+
+  void beginImport(Token importKeyword) {
+  }
+
+  void endImport(Token importKeyword, Token asKeyword, Token semicolon) {
+  }
+
+  void beginInitializedIdentifier(Token token) {
+  }
+
+  void endInitializedIdentifier() {
+  }
+
+  void beginInitializer(Token token) {
+  }
+
+  void endInitializer(Token assignmentOperator) {
+  }
+
+  void beginInitializers(Token token) {
+  }
+
+  void endInitializers(int count, Token beginToken, Token endToken) {
+  }
+
+  void handleNoInitializers() {
+  }
+
+  void beginInterface(Token token) {
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+  }
+
+  void handleLabel(Token token) {
+  }
+
+  void beginLabeledStatement(Token token, int labelCount) {
+  }
+
+  void endLabeledStatement(int labelCount) {
+  }
+
+  void beginLibraryName(Token token) {
+  }
+
+  void endLibraryName(Token libraryKeyword, Token semicolon) {
+  }
+
+  void beginLiteralMapEntry(Token token) {
+  }
+
+  void endLiteralMapEntry(Token colon, Token endToken) {
+  }
+
+  void beginLiteralString(Token token) {
+  }
+
+  void endLiteralString(int interpolationCount) {
+  }
+
+  void handleStringJuxtaposition(int literalCount) {
+  }
+
+  void beginMember(Token token) {
+  }
+
+  void endMethod(Token getOrSet, Token beginToken, Token endToken) {
+  }
+
+  void beginMetadata(Token token) {
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+  }
+
+  void beginOptionalFormalParameters(Token token) {
+  }
+
+  void endOptionalFormalParameters(int count,
+                                   Token beginToken, Token endToken) {
+  }
+
+  void beginPart(Token token) {
+  }
+
+  void endPart(Token partKeyword, Token semicolon) {
+  }
+
+  void beginPartOf(Token token) {
+  }
+
+  void endPartOf(Token partKeyword, Token semicolon) {
+  }
+
+  void beginRedirectingFactoryBody(Token token) {
+  }
+
+  void endRedirectingFactoryBody(Token beginToken, Token endToken) {
+  }
+
+  void beginReturnStatement(Token token) {
+  }
+
+  void endReturnStatement(bool hasExpression,
+                          Token beginToken, Token endToken) {
+  }
+
+  void beginScriptTag(Token token) {
+  }
+
+  void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) {
+  }
+
+  void beginSend(Token token) {
+  }
+
+  void endSend(Token token) {
+  }
+
+  void beginShow(Token showKeyword) {
+  }
+
+  void endShow(Token showKeyword) {
+  }
+
+  void beginSwitchStatement(Token token) {
+  }
+
+  void endSwitchStatement(Token switchKeyword, Token endToken) {
+  }
+
+  void beginSwitchBlock(Token token) {
+  }
+
+  void endSwitchBlock(int caseCount, Token beginToken, Token endToken) {
+  }
+
+  void beginThrowStatement(Token token) {
+  }
+
+  void endThrowStatement(Token throwToken, Token endToken) {
+  }
+
+  void endRethrowStatement(Token throwToken, Token endToken) {
+  }
+
+  void endTopLevelDeclaration(Token token) {
+  }
+
+  void beginTopLevelMember(Token token) {
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+  }
+
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+  }
+
+  void beginTryStatement(Token token) {
+  }
+
+  void handleCaseMatch(Token caseKeyword, Token colon) {
+  }
+
+  void handleCatchBlock(Token onKeyword, Token catchKeyword) {
+  }
+
+  void handleFinallyBlock(Token finallyKeyword) {
+  }
+
+  void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
+  }
+
+  void endType(Token beginToken, Token endToken) {
+  }
+
+  void beginTypeArguments(Token token) {
+  }
+
+  void endTypeArguments(int count, Token beginToken, Token endToken) {
+  }
+
+  void handleNoTypeArguments(Token token) {
+  }
+
+  void beginTypeVariable(Token token) {
+  }
+
+  void endTypeVariable(Token token) {
+  }
+
+  void beginTypeVariables(Token token) {
+  }
+
+  void endTypeVariables(int count, Token beginToken, Token endToken) {
+  }
+
+  void beginUnamedFunction(Token token) {
+  }
+
+  void endUnamedFunction(Token token) {
+  }
+
+  void beginVariablesDeclaration(Token token) {
+  }
+
+  void endVariablesDeclaration(int count, Token endToken) {
+  }
+
+  void beginWhileStatement(Token token) {
+  }
+
+  void endWhileStatement(Token whileKeyword, Token endToken) {
+  }
+
+  void handleAsOperator(Token operathor, Token endToken) {
+    // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed.
+  }
+
+  void handleAssignmentExpression(Token token) {
+  }
+
+  void handleBinaryExpression(Token token) {
+  }
+
+  void handleConditionalExpression(Token question, Token colon) {
+  }
+
+  void handleConstExpression(Token token) {
+  }
+
+  void handleFunctionTypedFormalParameter(Token token) {
+  }
+
+  void handleIdentifier(Token token) {
+  }
+
+  void handleIndexedExpression(Token openCurlyBracket,
+                               Token closeCurlyBracket) {
+  }
+
+  void handleIsOperator(Token operathor, Token not, Token endToken) {
+    // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed.
+  }
+
+  void handleLiteralBool(Token token) {
+  }
+
+  void handleBreakStatement(bool hasTarget,
+                            Token breakKeyword, Token endToken) {
+  }
+
+  void handleContinueStatement(bool hasTarget,
+                               Token continueKeyword, Token endToken) {
+  }
+
+  void handleEmptyStatement(Token token) {
+  }
+
+  void handleAssertStatement(Token assertKeyword, Token semicolonToken) {
+  }
+
+  /** Called with either the token containing a double literal, or
+    * an immediately preceding "unary plus" token.
+    */
+  void handleLiteralDouble(Token token) {
+  }
+
+  /** Called with either the token containing an integer literal,
+    * or an immediately preceding "unary plus" token.
+    */
+  void handleLiteralInt(Token token) {
+  }
+
+  void handleLiteralList(int count, Token beginToken, Token constKeyword,
+                         Token endToken) {
+  }
+
+  void handleLiteralMap(int count, Token beginToken, Token constKeyword,
+                        Token endToken) {
+  }
+
+  void handleLiteralNull(Token token) {
+  }
+
+  void handleModifier(Token token) {
+  }
+
+  void handleModifiers(int count) {
+  }
+
+  void handleNamedArgument(Token colon) {
+  }
+
+  void handleNewExpression(Token token) {
+  }
+
+  void handleNoArguments(Token token) {
+  }
+
+  void handleNoExpression(Token token) {
+  }
+
+  void handleNoType(Token token) {
+  }
+
+  void handleNoTypeVariables(Token token) {
+  }
+
+  void handleOperatorName(Token operatorKeyword, Token token) {
+  }
+
+  void handleParenthesizedExpression(BeginGroupToken token) {
+  }
+
+  void handleQualified(Token period) {
+  }
+
+  void handleStringPart(Token token) {
+  }
+
+  void handleSuperExpression(Token token) {
+  }
+
+  void handleSwitchCase(int labelCount, int expressionCount,
+                        Token defaultKeyword, int statementCount,
+                        Token firstToken, Token endToken) {
+  }
+
+  void handleThisExpression(Token token) {
+  }
+
+  void handleUnaryPostfixAssignmentExpression(Token token) {
+  }
+
+  void handleUnaryPrefixExpression(Token token) {
+  }
+
+  void handleUnaryPrefixAssignmentExpression(Token token) {
+  }
+
+  void handleValuedFormalParameter(Token equals, Token token) {
+  }
+
+  void handleVoidKeyword(Token token) {
+  }
+
+  Token expected(String string, Token token) {
+    error("expected '$string', but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  void expectedIdentifier(Token token) {
+    error("expected identifier, but got '${token.slowToString()}'", token);
+  }
+
+  Token expectedType(Token token) {
+    error("expected a type, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedExpression(Token token) {
+    error("expected an expression, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token unexpected(Token token) {
+    error("unexpected token '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedBlockToSkip(Token token) {
+    error("expected a block, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedFunctionBody(Token token) {
+    error("expected a function body, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBody(Token token) {
+    error("expected a class body, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBodyToSkip(Token token) {
+    error("expected a class body, but got '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  Link<Token> expectedDeclaration(Token token) {
+    error("expected a declaration, but got '${token.slowToString()}'", token);
+    return const Link<Token>();
+  }
+
+  Token unmatched(Token token) {
+    error("unmatched '${token.slowToString()}'", token);
+    return skipToEof(token);
+  }
+
+  skipToEof(Token token) {
+    while (!identical(token.info, EOF_INFO)) {
+      token = token.next;
+    }
+    return token;
+  }
+
+  void recoverableError(String message, {Token token, Node node}) {
+    if (token == null && node != null) {
+      token = node.getBeginToken();
+    }
+    error(message, token);
+  }
+
+  void error(String message, Token token) {
+    throw new ParserError("$message @ ${token.charOffset}");
+  }
+}
+
+class ParserError {
+  final String reason;
+  ParserError(this.reason);
+  toString() => reason;
+}
+
+typedef int IdGenerator();
+
+/**
+ * A parser event listener designed to work with [PartialParser]. It
+ * builds elements representing the top-level declarations found in
+ * the parsed compilation unit and records them in
+ * [compilationUnitElement].
+ */
+class ElementListener extends Listener {
+  final IdGenerator idGenerator;
+  final DiagnosticListener listener;
+  final CompilationUnitElement compilationUnitElement;
+  final StringValidator stringValidator;
+  Link<StringQuoting> interpolationScope;
+
+  Link<Node> nodes = const Link<Node>();
+
+  Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>();
+
+  ElementListener(DiagnosticListener listener,
+                  this.compilationUnitElement,
+                  this.idGenerator)
+      : this.listener = listener,
+        stringValidator = new StringValidator(listener),
+        interpolationScope = const Link<StringQuoting>();
+
+  void pushQuoting(StringQuoting quoting) {
+    interpolationScope = interpolationScope.prepend(quoting);
+  }
+
+  StringQuoting popQuoting() {
+    StringQuoting result = interpolationScope.head;
+    interpolationScope = interpolationScope.tail;
+    return result;
+  }
+
+  StringNode popLiteralString() {
+    StringNode node = popNode();
+    // TODO(lrn): Handle interpolations in script tags.
+    if (node.isInterpolation) {
+      listener.cancel("String interpolation not supported in library tags",
+                      node: node);
+      return null;
+    }
+    return node;
+  }
+
+  bool allowLibraryTags() {
+    // Library tags are only allowed in the library file itself, not
+    // in sourced files.
+    LibraryElement library = compilationUnitElement.getLibrary();
+    return !compilationUnitElement.hasMembers
+      && library.entryCompilationUnit == compilationUnitElement;
+  }
+
+  void endLibraryName(Token libraryKeyword, Token semicolon) {
+    Expression name = popNode();
+    addLibraryTag(new LibraryName(libraryKeyword, name,
+                                  popMetadata(compilationUnitElement)));
+  }
+
+  void endImport(Token importKeyword, Token asKeyword, Token semicolon) {
+    NodeList combinators = popNode();
+    Identifier prefix;
+    if (asKeyword != null) {
+      prefix = popNode();
+    }
+    StringNode uri = popLiteralString();
+    addLibraryTag(new Import(importKeyword, uri, prefix, combinators,
+                             popMetadata(compilationUnitElement)));
+  }
+
+  void endExport(Token exportKeyword, Token semicolon) {
+    NodeList combinators = popNode();
+    StringNode uri = popNode();
+    addLibraryTag(new Export(exportKeyword, uri, combinators,
+                             popMetadata(compilationUnitElement)));
+  }
+
+  void endCombinators(int count) {
+    if (0 == count) {
+      pushNode(null);
+    } else {
+      pushNode(makeNodeList(count, null, null, " "));
+    }
+  }
+
+  void endHide(Token hideKeyword) => pushCombinator(hideKeyword);
+
+  void endShow(Token showKeyword) => pushCombinator(showKeyword);
+
+  void pushCombinator(Token keywordToken) {
+    NodeList identifiers = popNode();
+    pushNode(new Combinator(identifiers, keywordToken));
+  }
+
+  void endIdentifierList(int count) {
+    pushNode(makeNodeList(count, null, null, ","));
+  }
+
+  void endTypeList(int count) {
+    pushNode(makeNodeList(count, null, null, ","));
+  }
+
+  void endPart(Token partKeyword, Token semicolon) {
+    StringNode uri = popLiteralString();
+    addLibraryTag(new Part(partKeyword, uri,
+                           popMetadata(compilationUnitElement)));
+  }
+
+  void endPartOf(Token partKeyword, Token semicolon) {
+    Expression name = popNode();
+    addPartOfTag(new PartOf(partKeyword, name,
+                            popMetadata(compilationUnitElement)));
+  }
+
+  void addPartOfTag(PartOf tag) {
+    compilationUnitElement.setPartOf(tag, listener);
+  }
+
+  void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) {
+    LiteralString prefix = null;
+    Identifier argumentName = null;
+    if (hasPrefix) {
+      prefix = popLiteralString();
+      argumentName = popNode();
+    }
+    LiteralString firstArgument = popLiteralString();
+    Identifier tag = popNode();
+    ScriptTag scriptTag = new ScriptTag(tag, firstArgument, argumentName,
+                                        prefix, beginToken, endToken);
+    if (const SourceString('import') == tag.source ||
+        const SourceString('source') == tag.source ||
+        const SourceString('library') == tag.source) {
+      addScriptTag(scriptTag);
+    } else {
+      recoverableError('unknown tag: ${tag.source.slowToString()}', node: tag);
+    }
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    if (periodBeforeName != null) {
+      popNode(); // Discard name.
+    }
+    popNode(); // Discard node (Send or Identifier).
+    pushMetadata(new PartialMetadataAnnotation(beginToken, endToken));
+  }
+
+  void endTopLevelDeclaration(Token token) {
+    if (!metadata.isEmpty) {
+      recoverableError('Error: Metadata not supported here.',
+                       token: metadata.head.beginToken);
+      metadata = const Link<MetadataAnnotation>();
+    }
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+    SourceString nativeTagInfo = native.checkForNativeClass(this);
+    NodeList interfaces =
+        makeNodeList(interfacesCount, implementsKeyword, null, ",");
+    Node supertype = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    int id = idGenerator();
+    ClassElement element = new PartialClassElement(
+        name.source, beginToken, endToken, compilationUnitElement, id);
+    element.nativeTagInfo = nativeTagInfo;
+    pushElement(element);
+    rejectBuiltInIdentifier(name);
+  }
+
+  void rejectBuiltInIdentifier(Identifier name) {
+    if (name.source is Keyword) {
+      Keyword keyword = name.source;
+      if (!keyword.isPseudo) {
+        recoverableError('illegal name ${keyword.syntax}', node: name);
+      }
+    }
+  }
+
+  void endDefaultClause(Token defaultKeyword) {
+    NodeList typeParameters = popNode();
+    Node name = popNode();
+    pushNode(new TypeAnnotation(name, typeParameters));
+  }
+
+  void handleNoDefaultClause(Token token) {
+    pushNode(null);
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+    // TODO(ahe): Record the defaultClause.
+    Node defaultClause = popNode();
+    NodeList supertypes =
+        makeNodeList(supertypeCount, extendsKeyword, null, ",");
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    int id = idGenerator();
+    pushElement(new PartialClassElement(
+        name.source, interfaceKeyword, endToken, compilationUnitElement, id));
+    rejectBuiltInIdentifier(name);
+    listener.onDeprecatedFeature(interfaceKeyword, 'interface declarations');
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+    NodeList typeVariables = popNode(); // TOOD(karlklose): do not throw away.
+    Identifier name = popNode();
+    TypeAnnotation returnType = popNode();
+    pushElement(new PartialTypedefElement(name.source, compilationUnitElement,
+                                          typedefKeyword));
+    rejectBuiltInIdentifier(name);
+  }
+
+  void endNamedMixinApplication(Token typedefKeyword,
+                                Token implementsKeyword,
+                                Token endToken) {
+    NodeList interfaces = (implementsKeyword != null) ? popNode() : null;
+    MixinApplication mixinApplication = popNode();
+    Modifiers modifiers = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    NamedMixinApplication namedMixinApplication = new NamedMixinApplication(
+        name, typeParameters, modifiers, mixinApplication, interfaces,
+        typedefKeyword, endToken);
+
+    int id = idGenerator();
+    Element enclosing = compilationUnitElement;
+    pushElement(new MixinApplicationElementX(name.source, enclosing, id,
+                                             namedMixinApplication,
+                                             modifiers));
+    rejectBuiltInIdentifier(name);
+  }
+
+  void endMixinApplication() {
+    NodeList mixins = popNode();
+    TypeAnnotation superclass = popNode();
+    pushNode(new MixinApplication(superclass, mixins));
+  }
+
+  void handleVoidKeyword(Token token) {
+    pushNode(new TypeAnnotation(new Identifier(token), null));
+  }
+
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+    Identifier name = popNode();
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    ElementKind kind;
+    if (getOrSet == null) {
+      kind = ElementKind.FUNCTION;
+    } else if (identical(getOrSet.stringValue, 'get')) {
+      kind = ElementKind.GETTER;
+    } else if (identical(getOrSet.stringValue, 'set')) {
+      kind = ElementKind.SETTER;
+    }
+    pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet,
+                                           endToken, kind,
+                                           modifiers, compilationUnitElement));
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+    void buildFieldElement(SourceString name, Element fields) {
+      pushElement(new VariableElementX(name, fields, ElementKind.FIELD, null));
+    }
+    NodeList variables = makeNodeList(count, null, null, ",");
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    buildFieldElements(modifiers, variables, compilationUnitElement,
+                       buildFieldElement,
+                       beginToken, endToken);
+  }
+
+  void buildFieldElements(Modifiers modifiers,
+                          NodeList variables,
+                          Element enclosingElement,
+                          void buildFieldElement(SourceString name,
+                                                 Element fields),
+                          Token beginToken, Token endToken) {
+    Element fields = new PartialFieldListElement(beginToken,
+                                                 endToken,
+                                                 modifiers,
+                                                 enclosingElement);
+    for (Link<Node> variableNodes = variables.nodes;
+         !variableNodes.isEmpty;
+         variableNodes = variableNodes.tail) {
+      Expression initializedIdentifier = variableNodes.head;
+      Identifier identifier = initializedIdentifier.asIdentifier();
+      if (identifier == null) {
+        identifier = initializedIdentifier.asSendSet().selector.asIdentifier();
+      }
+      SourceString name = identifier.source;
+      buildFieldElement(name, fields);
+    }
+  }
+
+  void handleIdentifier(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleQualified(Token period) {
+    Identifier last = popNode();
+    Expression first = popNode();
+    pushNode(new Send(first, last));
+  }
+
+  void handleNoType(Token token) {
+    pushNode(null);
+  }
+
+  void endTypeVariable(Token token) {
+    TypeAnnotation bound = popNode();
+    Identifier name = popNode();
+    pushNode(new TypeVariable(name, bound));
+  }
+
+  void endTypeVariables(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ','));
+  }
+
+  void handleNoTypeVariables(token) {
+    pushNode(null);
+  }
+
+  void endTypeArguments(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ','));
+  }
+
+  void handleNoTypeArguments(Token token) {
+    pushNode(null);
+  }
+
+  void endType(Token beginToken, Token endToken) {
+    NodeList typeArguments = popNode();
+    Expression typeName = popNode();
+    pushNode(new TypeAnnotation(typeName, typeArguments));
+  }
+
+  void handleParenthesizedExpression(BeginGroupToken token) {
+    Expression expression = popNode();
+    pushNode(new ParenthesizedExpression(expression, token));
+  }
+
+  void handleModifier(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleModifiers(int count) {
+    if (count == 0) {
+      pushNode(Modifiers.EMPTY);
+    } else {
+      NodeList modifierNodes = makeNodeList(count, null, null, ' ');
+      pushNode(new Modifiers(modifierNodes));
+    }
+  }
+
+  Token expected(String string, Token token) {
+    listener.cancel("expected '$string', but got '${token.slowToString()}'",
+                    token: token);
+    return skipToEof(token);
+  }
+
+  void expectedIdentifier(Token token) {
+    listener.cancel("expected identifier, but got '${token.slowToString()}'",
+                    token: token);
+    pushNode(null);
+  }
+
+  Token expectedType(Token token) {
+    listener.cancel("expected a type, but got '${token.slowToString()}'",
+                    token: token);
+    pushNode(null);
+    return skipToEof(token);
+  }
+
+  Token expectedExpression(Token token) {
+    listener.cancel("expected an expression, but got '${token.slowToString()}'",
+                    token: token);
+    pushNode(null);
+    return skipToEof(token);
+  }
+
+  Token unexpected(Token token) {
+    String message = "unexpected token '${token.slowToString()}'";
+    if (token.info == BAD_INPUT_INFO) {
+      message = token.stringValue;
+    }
+    listener.cancel(message, token: token);
+    return skipToEof(token);
+  }
+
+  Token expectedBlockToSkip(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeBlockToSkip(this, token);
+    } else {
+      return unexpected(token);
+    }
+  }
+
+  Token expectedFunctionBody(Token token) {
+    String printString = token.slowToString();
+    listener.cancel("expected a function body, but got '$printString'",
+                    token: token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBody(Token token) {
+    listener.cancel("expected a class body, but got '${token.slowToString()}'",
+                    token: token);
+    return skipToEof(token);
+  }
+
+  Token expectedClassBodyToSkip(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeClassBodyToSkip(this, token);
+    } else {
+      return unexpected(token);
+    }
+  }
+
+  Link<Token> expectedDeclaration(Token token) {
+    listener.cancel("expected a declaration, but got '${token.slowToString()}'",
+                    token: token);
+    return const Link<Token>();
+  }
+
+  Token unmatched(Token token) {
+    listener.cancel("unmatched '${token.slowToString()}'", token: token);
+    return skipToEof(token);
+  }
+
+  void recoverableError(String message, {Token token, Node node}) {
+    listener.cancel(message, token: token, node: node);
+  }
+
+  void pushElement(Element element) {
+    popMetadata(element);
+    compilationUnitElement.addMember(element, listener);
+  }
+
+  Link<MetadataAnnotation> popMetadata(Element element) {
+    var result = metadata;
+    for (Link link = metadata; !link.isEmpty; link = link.tail) {
+      element.addMetadata(link.head);
+    }
+    metadata = const Link<MetadataAnnotation>();
+    return result;
+  }
+
+  void pushMetadata(MetadataAnnotation annotation) {
+    metadata = metadata.prepend(annotation);
+  }
+
+  // TODO(ahe): Remove this method.
+  void addScriptTag(ScriptTag tag) {
+    listener.onDeprecatedFeature(tag, '# tags');
+    addLibraryTag(tag.toLibraryTag());
+  }
+
+  void addLibraryTag(LibraryTag tag) {
+    if (!allowLibraryTags()) {
+      recoverableError('library tags not allowed here', node: tag);
+    }
+    compilationUnitElement.getImplementationLibrary().addTag(tag, listener);
+  }
+
+  void pushNode(Node node) {
+    nodes = nodes.prepend(node);
+    if (VERBOSE) log("push $nodes");
+  }
+
+  Node popNode() {
+    assert(!nodes.isEmpty);
+    Node node = nodes.head;
+    nodes = nodes.tail;
+    if (VERBOSE) log("pop $nodes");
+    return node;
+  }
+
+  Node peekNode() {
+    assert(!nodes.isEmpty);
+    Node node = nodes.head;
+    if (VERBOSE) log("peek $node");
+    return node;
+  }
+
+  void log(message) {
+    print(message);
+  }
+
+  NodeList makeNodeList(int count, Token beginToken, Token endToken,
+                        String delimiter) {
+    Link<Node> poppedNodes = const Link<Node>();
+    for (; count > 0; --count) {
+      // This effectively reverses the order of nodes so they end up
+      // in correct (source) order.
+      poppedNodes = poppedNodes.prepend(popNode());
+    }
+    SourceString sourceDelimiter =
+        (delimiter == null) ? null : new SourceString(delimiter);
+    return new NodeList(beginToken, poppedNodes, endToken, sourceDelimiter);
+  }
+
+  void beginLiteralString(Token token) {
+    SourceString source = token.value;
+    StringQuoting quoting = StringValidator.quotingFromString(source);
+    pushQuoting(quoting);
+    // Just wrap the token for now. At the end of the interpolation,
+    // when we know how many there are, go back and validate the tokens.
+    pushNode(new LiteralString(token, null));
+  }
+
+  void handleStringPart(Token token) {
+    // Just push an unvalidated token now, and replace it when we know the
+    // end of the interpolation.
+    pushNode(new LiteralString(token, null));
+  }
+
+  void endLiteralString(int count) {
+    StringQuoting quoting = popQuoting();
+
+    Link<StringInterpolationPart> parts =
+        const Link<StringInterpolationPart>();
+    // Parts of the string interpolation are popped in reverse order,
+    // starting with the last literal string part.
+    bool isLast = true;
+    for (int i = 0; i < count; i++) {
+      LiteralString string = popNode();
+      DartString validation =
+          stringValidator.validateInterpolationPart(string.token, quoting,
+                                                    isFirst: false,
+                                                    isLast: isLast);
+      // Replace the unvalidated LiteralString with a new LiteralString
+      // object that has the validation result included.
+      string = new LiteralString(string.token, validation);
+      Expression expression = popNode();
+      parts = parts.prepend(new StringInterpolationPart(expression, string));
+      isLast = false;
+    }
+
+    LiteralString string = popNode();
+    DartString validation =
+        stringValidator.validateInterpolationPart(string.token, quoting,
+                                                  isFirst: true,
+                                                  isLast: isLast);
+    string = new LiteralString(string.token, validation);
+    if (isLast) {
+      pushNode(string);
+    } else {
+      NodeList partNodes =
+          new NodeList(null, parts, null, const SourceString(""));
+      pushNode(new StringInterpolation(string, partNodes));
+    }
+  }
+
+  void handleStringJuxtaposition(int stringCount) {
+    assert(stringCount != 0);
+    Expression accumulator = popNode();
+    stringCount--;
+    while (stringCount > 0) {
+      Expression expression = popNode();
+      accumulator = new StringJuxtaposition(expression, accumulator);
+      stringCount--;
+    }
+    pushNode(accumulator);
+  }
+}
+
+class NodeListener extends ElementListener {
+  NodeListener(DiagnosticListener listener, CompilationUnitElement element)
+    : super(listener, element, null);
+
+  void addLibraryTag(LibraryTag tag) {
+    pushNode(tag);
+  }
+
+  void addPartOfTag(PartOf tag) {
+    pushNode(tag);
+  }
+
+  void endArgumentDefinitionTest(Token beginToken, Token endToken) {
+    pushNode(new Send.prefix(popNode(), new Operator(beginToken)));
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+    NodeList body = popNode();
+    NodeList interfaces =
+        makeNodeList(interfacesCount, implementsKeyword, null, ",");
+    Node supertype = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new ClassNode(modifiers, name, typeParameters, supertype,
+                           interfaces, null, beginToken, extendsKeyword, body,
+                           endToken));
+  }
+
+  void endCompilationUnit(int count, Token token) {
+    pushNode(makeNodeList(count, null, null, '\n'));
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+    NodeList formals = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    TypeAnnotation returnType = popNode();
+    pushNode(new Typedef(returnType, name, typeParameters, formals,
+                         typedefKeyword, endToken));
+  }
+
+  void endNamedMixinApplication(Token typedefKeyword,
+                                Token implementsKeyword,
+                                Token endToken) {
+    NodeList interfaces = (implementsKeyword != null) ? popNode() : null;
+    Node mixinApplication = popNode();
+    Modifiers modifiers = popNode();
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    pushNode(new NamedMixinApplication(name, typeParameters,
+                                       modifiers, mixinApplication,
+                                       interfaces,
+                                       typedefKeyword, endToken));
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+    NodeList body = popNode();
+    TypeAnnotation defaultClause = popNode();
+    NodeList supertypes = makeNodeList(supertypeCount, extendsKeyword,
+                                       null, ',');
+    NodeList typeParameters = popNode();
+    Identifier name = popNode();
+    pushNode(new ClassNode(Modifiers.EMPTY, name, typeParameters, null,
+                           supertypes, defaultClause, interfaceKeyword, null,
+                           body, endToken));
+  }
+
+  void endClassBody(int memberCount, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(memberCount, beginToken, endToken, null));
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+    NodeList variables = makeNodeList(count, null, endToken, ",");
+    Modifiers modifiers = popNode();
+    pushNode(new VariableDefinitions(null, modifiers, variables));
+  }
+
+  void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
+    Statement body = popNode();
+    NodeList formalParameters = popNode();
+    Identifier name = popNode();
+    Modifiers modifiers = popNode();
+    ElementKind kind;
+    if (getOrSet == null) {
+      kind = ElementKind.FUNCTION;
+    } else if (identical(getOrSet.stringValue, 'get')) {
+      kind = ElementKind.GETTER;
+    } else if (identical(getOrSet.stringValue, 'set')) {
+      kind = ElementKind.SETTER;
+    }
+    pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet,
+                                           endToken, kind,
+                                           modifiers, compilationUnitElement));
+  }
+
+  void endFormalParameter(Token thisKeyword) {
+    Expression name = popNode();
+    if (thisKeyword != null) {
+      Identifier thisIdentifier = new Identifier(thisKeyword);
+      if (name.asSend() == null) {
+        name = new Send(thisIdentifier, name);
+      } else {
+        name = name.asSend().copyWithReceiver(thisIdentifier);
+      }
+    }
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(
+        new VariableDefinitions(type, modifiers, new NodeList.singleton(name)));
+  }
+
+  void endFormalParameters(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ","));
+  }
+
+  void handleNoFormalParameters(Token token) {
+    pushNode(null);
+  }
+
+  void endArguments(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ","));
+  }
+
+  void handleNoArguments(Token token) {
+    pushNode(null);
+  }
+
+  void endConstructorReference(Token start, Token periodBeforeName,
+                               Token endToken) {
+    Identifier name = null;
+    if (periodBeforeName != null) {
+      name = popNode();
+    }
+    NodeList typeArguments = popNode();
+    Node classReference = popNode();
+    if (typeArguments != null) {
+      classReference = new TypeAnnotation(classReference, typeArguments);
+    } else {
+      Identifier identifier = classReference.asIdentifier();
+      Send send = classReference.asSend();
+      if (identifier != null) {
+        // TODO(ahe): Should be:
+        // classReference = new Send(null, identifier);
+        classReference = identifier;
+      } else if (send != null) {
+        classReference = send;
+      } else {
+        internalError(node: classReference);
+      }
+    }
+    Node constructor = classReference;
+    if (name != null) {
+      // Either typeName<args>.name or x.y.name.
+      constructor = new Send(classReference, name);
+    }
+    pushNode(constructor);
+  }
+
+  void endRedirectingFactoryBody(Token beginToken,
+                                 Token endToken) {
+    pushNode(new Return(beginToken, endToken, popNode()));
+  }
+
+  void endReturnStatement(bool hasExpression,
+                          Token beginToken, Token endToken) {
+    Expression expression = hasExpression ? popNode() : null;
+    pushNode(new Return(beginToken, endToken, expression));
+  }
+
+  void endExpressionStatement(Token token) {
+    pushNode(new ExpressionStatement(popNode(), token));
+  }
+
+  void handleOnError(Token token, var errorInformation) {
+    listener.cancel("internal error: '${token.value}': ${errorInformation}",
+                    token: token);
+  }
+
+  Token expectedFunctionBody(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeFunctionBody(this, token);
+    } else {
+      listener.cancel(
+          "expected a function body, but got '${token.slowToString()}'",
+          token: token);
+      return skipToEof(token);
+    }
+  }
+
+  Token expectedClassBody(Token token) {
+    if (identical(token.stringValue, 'native')) {
+      return native.handleNativeClassBody(this, token);
+    } else {
+      listener.cancel(
+          "expected a class body, but got '${token.slowToString()}'",
+          token: token);
+      return skipToEof(token);
+    }
+  }
+
+  void handleLiteralInt(Token token) {
+    pushNode(new LiteralInt(token, (t, e) => handleOnError(t, e)));
+  }
+
+  void handleLiteralDouble(Token token) {
+    pushNode(new LiteralDouble(token, (t, e) => handleOnError(t, e)));
+  }
+
+  void handleLiteralBool(Token token) {
+    pushNode(new LiteralBool(token, (t, e) => handleOnError(t, e)));
+  }
+
+  void handleLiteralNull(Token token) {
+    pushNode(new LiteralNull(token));
+  }
+
+  void handleBinaryExpression(Token token) {
+    Node argument = popNode();
+    Node receiver = popNode();
+    String tokenString = token.stringValue;
+    if (identical(tokenString, '.') || identical(tokenString, '..')) {
+      Send argumentSend = argument.asSend();
+      if (argumentSend == null) {
+        // TODO(ahe): The parser should diagnose this problem, not
+        // this listener.
+        listener.cancel('Syntax error: Expected an identifier.',
+                        node: argument);
+      }
+      if (argumentSend.receiver != null) internalError(node: argument);
+      if (argument is SendSet) internalError(node: argument);
+      pushNode(argument.asSend().copyWithReceiver(receiver));
+    } else {
+      NodeList arguments = new NodeList.singleton(argument);
+      pushNode(new Send(receiver, new Operator(token), arguments));
+    }
+    if (identical(tokenString, '===') || identical(tokenString, '!==')) {
+      listener.onDeprecatedFeature(token, tokenString);
+    }
+  }
+
+  void beginCascade(Token token) {
+    pushNode(new CascadeReceiver(popNode(), token));
+  }
+
+  void endCascade() {
+    pushNode(new Cascade(popNode()));
+  }
+
+  void handleAsOperator(Token operathor, Token endToken) {
+    TypeAnnotation type = popNode();
+    Expression expression = popNode();
+    NodeList arguments = new NodeList.singleton(type);
+    pushNode(new Send(expression, new Operator(operathor), arguments));
+  }
+
+  void handleAssignmentExpression(Token token) {
+    Node arg = popNode();
+    Node node = popNode();
+    Send send = node.asSend();
+    if (send == null || !(send.isPropertyAccess || send.isIndex)) {
+      reportNotAssignable(node);
+    }
+    if (send.asSendSet() != null) internalError(node: send);
+    NodeList arguments;
+    if (send.isIndex) {
+      Link<Node> link = const Link<Node>().prepend(arg);
+      link = link.prepend(send.arguments.head);
+      arguments = new NodeList(null, link);
+    } else {
+      arguments = new NodeList.singleton(arg);
+    }
+    Operator op = new Operator(token);
+    pushNode(new SendSet(send.receiver, send.selector, op, arguments));
+  }
+
+  void reportNotAssignable(Node node) {
+    // TODO(ahe): The parser should diagnose this problem, not this
+    // listener.
+    listener.cancel('Syntax error: Not assignable.', node: node);
+  }
+
+  void handleConditionalExpression(Token question, Token colon) {
+    Node elseExpression = popNode();
+    Node thenExpression = popNode();
+    Node condition = popNode();
+    pushNode(new Conditional(
+        condition, thenExpression, elseExpression, question, colon));
+  }
+
+  void endSend(Token token) {
+    NodeList arguments = popNode();
+    Node selector = popNode();
+    // TODO(ahe): Handle receiver.
+    pushNode(new Send(null, selector, arguments));
+  }
+
+  void endFunctionBody(int count, Token beginToken, Token endToken) {
+    if (count == 0 && beginToken == null) {
+      pushNode(new EmptyStatement(endToken));
+    } else {
+      pushNode(new Block(makeNodeList(count, beginToken, endToken, null)));
+    }
+  }
+
+  void handleNoFunctionBody(Token token) {
+    pushNode(null);
+  }
+
+  void endFunction(Token getOrSet, Token endToken) {
+    Statement body = popNode();
+    NodeList initializers = popNode();
+    NodeList formals = popNode();
+    // The name can be an identifier or a send in case of named constructors.
+    Expression name = popNode();
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new FunctionExpression(name, formals, body, type,
+                                    modifiers, initializers, getOrSet));
+  }
+
+  void endFunctionDeclaration(Token endToken) {
+    pushNode(new FunctionDeclaration(popNode()));
+  }
+
+  void endVariablesDeclaration(int count, Token endToken) {
+    // TODO(ahe): Pick one name for this concept, either
+    // VariablesDeclaration or VariableDefinitions.
+    NodeList variables = makeNodeList(count, null, endToken, ",");
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new VariableDefinitions(type, modifiers, variables));
+  }
+
+  void endInitializer(Token assignmentOperator) {
+    Expression initializer = popNode();
+    NodeList arguments = new NodeList.singleton(initializer);
+    Expression name = popNode();
+    Operator op = new Operator(assignmentOperator);
+    pushNode(new SendSet(null, name, op, arguments));
+  }
+
+  void endIfStatement(Token ifToken, Token elseToken) {
+    Statement elsePart = (elseToken == null) ? null : popNode();
+    Statement thenPart = popNode();
+    ParenthesizedExpression condition = popNode();
+    pushNode(new If(condition, thenPart, elsePart, ifToken, elseToken));
+  }
+
+  void endForStatement(int updateExpressionCount,
+                       Token beginToken, Token endToken) {
+    Statement body = popNode();
+    NodeList updates = makeNodeList(updateExpressionCount, null, null, ',');
+    Statement condition = popNode();
+    Node initializer = popNode();
+    pushNode(new For(initializer, condition, updates, body, beginToken));
+  }
+
+  void handleNoExpression(Token token) {
+    pushNode(null);
+  }
+
+  void endDoWhileStatement(Token doKeyword, Token whileKeyword,
+                           Token endToken) {
+    Expression condition = popNode();
+    Statement body = popNode();
+    pushNode(new DoWhile(body, condition, doKeyword, whileKeyword, endToken));
+  }
+
+  void endWhileStatement(Token whileKeyword, Token endToken) {
+    Statement body = popNode();
+    Expression condition = popNode();
+    pushNode(new While(condition, body, whileKeyword));
+  }
+
+  void endBlock(int count, Token beginToken, Token endToken) {
+    pushNode(new Block(makeNodeList(count, beginToken, endToken, null)));
+  }
+
+  void endThrowStatement(Token throwToken, Token endToken) {
+    Expression expression = popNode();
+    pushNode(new Throw(expression, throwToken, endToken));
+  }
+
+  void endRethrowStatement(Token throwToken, Token endToken) {
+    pushNode(new Throw(null, throwToken, endToken));
+  }
+
+  void handleUnaryPrefixExpression(Token token) {
+    pushNode(new Send.prefix(popNode(), new Operator(token)));
+  }
+
+  void handleSuperExpression(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleThisExpression(Token token) {
+    pushNode(new Identifier(token));
+  }
+
+  void handleUnaryAssignmentExpression(Token token, bool isPrefix) {
+    Node node = popNode();
+    Send send = node.asSend();
+    if (send == null) {
+      reportNotAssignable(node);
+    }
+    if (!(send.isPropertyAccess || send.isIndex)) {
+      reportNotAssignable(node);
+    }
+    if (send.asSendSet() != null) internalError(node: send);
+    Node argument = null;
+    if (send.isIndex) argument = send.arguments.head;
+    Operator op = new Operator(token);
+
+    if (isPrefix) {
+      pushNode(new SendSet.prefix(send.receiver, send.selector, op, argument));
+    } else {
+      pushNode(new SendSet.postfix(send.receiver, send.selector, op, argument));
+    }
+  }
+
+  void handleUnaryPostfixAssignmentExpression(Token token) {
+    handleUnaryAssignmentExpression(token, false);
+  }
+
+  void handleUnaryPrefixAssignmentExpression(Token token) {
+    handleUnaryAssignmentExpression(token, true);
+  }
+
+  void endInitializers(int count, Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, null, ','));
+  }
+
+  void handleNoInitializers() {
+    pushNode(null);
+  }
+
+  void endFields(int count, Token beginToken, Token endToken) {
+    NodeList variables = makeNodeList(count, null, endToken, ",");
+    TypeAnnotation type = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new VariableDefinitions(type, modifiers, variables));
+  }
+
+  void endMethod(Token getOrSet, Token beginToken, Token endToken) {
+    Statement body = popNode();
+    NodeList initializers = popNode();
+    NodeList formalParameters = popNode();
+    Expression name = popNode();
+    TypeAnnotation returnType = popNode();
+    Modifiers modifiers = popNode();
+    pushNode(new FunctionExpression(name, formalParameters, body, returnType,
+                                    modifiers, initializers, getOrSet));
+  }
+
+  void handleLiteralMap(int count, Token beginToken, Token constKeyword,
+                        Token endToken) {
+    NodeList entries = makeNodeList(count, beginToken, endToken, ',');
+    NodeList typeArguments = popNode();
+    pushNode(new LiteralMap(typeArguments, entries, constKeyword));
+  }
+
+  void endLiteralMapEntry(Token colon, Token endToken) {
+    Expression value = popNode();
+    Expression key = popNode();
+    if (key.asStringNode() == null) {
+      recoverableError('expected a string', node: key);
+    }
+    pushNode(new LiteralMapEntry(key, colon, value));
+  }
+
+  void handleLiteralList(int count, Token beginToken, Token constKeyword,
+                         Token endToken) {
+    NodeList elements = makeNodeList(count, beginToken, endToken, ',');
+    pushNode(new LiteralList(popNode(), elements, constKeyword));
+  }
+
+  void handleIndexedExpression(Token openSquareBracket,
+                               Token closeSquareBracket) {
+    NodeList arguments =
+        makeNodeList(1, openSquareBracket, closeSquareBracket, null);
+    Node receiver = popNode();
+    Token token =
+      new StringToken(INDEX_INFO, '[]', openSquareBracket.charOffset);
+    Node selector = new Operator(token);
+    pushNode(new Send(receiver, selector, arguments));
+  }
+
+  void handleNewExpression(Token token) {
+    NodeList arguments = popNode();
+    Node name = popNode();
+    pushNode(new NewExpression(token, new Send(null, name, arguments)));
+  }
+
+  void handleConstExpression(Token token) {
+    // [token] carries the 'const' information.
+    handleNewExpression(token);
+  }
+
+  void handleOperatorName(Token operatorKeyword, Token token) {
+    Operator op = new Operator(token);
+    pushNode(new Send(new Identifier(operatorKeyword), op, null));
+  }
+
+  void handleNamedArgument(Token colon) {
+    Expression expression = popNode();
+    Identifier name = popNode();
+    pushNode(new NamedArgument(name, colon, expression));
+  }
+
+  void endOptionalFormalParameters(int count,
+                                   Token beginToken, Token endToken) {
+    pushNode(makeNodeList(count, beginToken, endToken, ','));
+  }
+
+  void handleFunctionTypedFormalParameter(Token endToken) {
+    NodeList formals = popNode();
+    Identifier name = popNode();
+    TypeAnnotation returnType = popNode();
+    pushNode(null); // Signal "no type" to endFormalParameter.
+    pushNode(new FunctionExpression(name, formals, null, returnType,
+                                    Modifiers.EMPTY, null, null));
+  }
+
+  void handleValuedFormalParameter(Token equals, Token token) {
+    Expression defaultValue = popNode();
+    Expression parameterName = popNode();
+    pushNode(new SendSet(null, parameterName, new Operator(equals),
+                         new NodeList.singleton(defaultValue)));
+  }
+
+  void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) {
+    Block finallyBlock = null;
+    if (finallyKeyword != null) {
+      finallyBlock = popNode();
+    }
+    NodeList catchBlocks = makeNodeList(catchCount, null, null, null);
+    Block tryBlock = popNode();
+    pushNode(new TryStatement(tryBlock, catchBlocks, finallyBlock,
+                              tryKeyword, finallyKeyword));
+  }
+
+  void handleCaseMatch(Token caseKeyword, Token colon) {
+    pushNode(new CaseMatch(caseKeyword, popNode(), colon));
+  }
+
+  void handleCatchBlock(Token onKeyword, Token catchKeyword) {
+    Block block = popNode();
+    NodeList formals = catchKeyword != null? popNode(): null;
+    TypeAnnotation type = onKeyword != null ? popNode() : null;
+    pushNode(new CatchBlock(type, formals, block, onKeyword, catchKeyword));
+  }
+
+  void endSwitchStatement(Token switchKeyword, Token endToken) {
+    NodeList cases = popNode();
+    ParenthesizedExpression expression = popNode();
+    pushNode(new SwitchStatement(expression, cases, switchKeyword));
+  }
+
+  void endSwitchBlock(int caseCount, Token beginToken, Token endToken) {
+    Link<Node> caseNodes = const Link<Node>();
+    while (caseCount > 0) {
+      SwitchCase switchCase = popNode();
+      caseNodes = caseNodes.prepend(switchCase);
+      caseCount--;
+    }
+    pushNode(new NodeList(beginToken, caseNodes, endToken, null));
+  }
+
+  void handleSwitchCase(int labelCount, int caseCount,
+                        Token defaultKeyword, int statementCount,
+                        Token firstToken, Token endToken) {
+    NodeList statements = makeNodeList(statementCount, null, null, null);
+    NodeList labelsAndCases =
+        makeNodeList(labelCount + caseCount, null, null, null);
+    pushNode(new SwitchCase(labelsAndCases, defaultKeyword, statements,
+                            firstToken));
+  }
+
+  void handleBreakStatement(bool hasTarget,
+                            Token breakKeyword, Token endToken) {
+    Identifier target = null;
+    if (hasTarget) {
+      target = popNode();
+    }
+    pushNode(new BreakStatement(target, breakKeyword, endToken));
+  }
+
+  void handleContinueStatement(bool hasTarget,
+                               Token continueKeyword, Token endToken) {
+    Identifier target = null;
+    if (hasTarget) {
+      target = popNode();
+    }
+    pushNode(new ContinueStatement(target, continueKeyword, endToken));
+  }
+
+  void handleEmptyStatement(Token token) {
+    pushNode(new EmptyStatement(token));
+  }
+
+  void endFactoryMethod(Token beginToken, Token endToken) {
+    Statement body = popNode();
+    NodeList formals = popNode();
+    Node name = popNode();
+
+    // TODO(ahe): Move this parsing to the parser.
+    int modifierCount = 0;
+    Token modifier = beginToken;
+    if (modifier.stringValue == "external") {
+      handleModifier(modifier);
+      modifierCount++;
+      modifier = modifier.next;
+    }
+    if (modifier.stringValue == "const") {
+      handleModifier(modifier);
+      modifierCount++;
+      modifier = modifier.next;
+    }
+    assert(modifier.stringValue == "factory");
+    handleModifier(modifier);
+    modifierCount++;
+    handleModifiers(modifierCount);
+    Modifiers modifiers = popNode();
+
+    pushNode(new FunctionExpression(name, formals, body, null,
+                                    modifiers, null, null));
+  }
+
+  void endForIn(Token beginToken, Token inKeyword, Token endToken) {
+    Statement body = popNode();
+    Expression expression = popNode();
+    Node declaredIdentifier = popNode();
+    pushNode(new ForIn(declaredIdentifier, expression, body,
+                                beginToken, inKeyword));
+  }
+
+  void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) {
+    NodeList arguments = popNode();
+    if (arguments == null) {
+      // This is a constant expression.
+      Identifier name;
+      if (periodBeforeName != null) {
+        name = popNode();
+      }
+      NodeList typeArguments = popNode();
+      Node receiver = popNode();
+      if (typeArguments != null) {
+        receiver = new TypeAnnotation(receiver, typeArguments);
+        recoverableError('Error: type arguments are not allowed here',
+                         node: typeArguments);
+      } else {
+        Identifier identifier = receiver.asIdentifier();
+        Send send = receiver.asSend();
+        if (identifier != null) {
+          receiver = new Send(null, identifier);
+        } else if (send == null) {
+          internalError(node: receiver);
+        }
+      }
+      Send send = receiver;
+      if (name != null) {
+        send = new Send(receiver, name);
+      }
+      pushNode(send);
+    } else {
+      // This is a const constructor call.
+      endConstructorReference(beginToken, periodBeforeName, endToken);
+      Node constructor = popNode();
+      pushNode(new NewExpression(beginToken,
+                                 new Send(null, constructor, arguments)));
+    }
+  }
+
+  void handleAssertStatement(Token assertKeyword, Token semicolonToken) {
+    NodeList arguments = popNode();
+    Node selector = new Identifier(assertKeyword);
+    Node send = new Send(null, selector, arguments);
+    pushNode(new ExpressionStatement(send, semicolonToken));
+  }
+
+  void endUnamedFunction(Token token) {
+    Statement body = popNode();
+    NodeList formals = popNode();
+    pushNode(new FunctionExpression(null, formals, body, null,
+                                    Modifiers.EMPTY, null, null));
+  }
+
+  void handleIsOperator(Token operathor, Token not, Token endToken) {
+    TypeAnnotation type = popNode();
+    Expression expression = popNode();
+    Node argument;
+    if (not != null) {
+      argument = new Send.prefix(type, new Operator(not));
+    } else {
+      argument = type;
+    }
+
+    NodeList arguments = new NodeList.singleton(argument);
+    pushNode(new Send(expression, new Operator(operathor), arguments));
+  }
+
+  void handleLabel(Token colon) {
+    Identifier name = popNode();
+    pushNode(new Label(name, colon));
+  }
+
+  void endLabeledStatement(int labelCount) {
+    Statement statement = popNode();
+    NodeList labels = makeNodeList(labelCount, null, null, null);
+    pushNode(new LabeledStatement(labels, statement));
+  }
+
+  void log(message) {
+    listener.log(message);
+  }
+
+  void internalError({Token token, Node node}) {
+    // TODO(ahe): This should call listener.internalError.
+    Spannable spannable = (token == null) ? node : token;
+    throw new SpannableAssertionFailure(spannable, 'internal error in parser');
+  }
+}
+
+class PartialFunctionElement extends FunctionElementX {
+  final Token beginToken;
+  final Token getOrSet;
+  final Token endToken;
+
+  PartialFunctionElement(SourceString name,
+                         Token this.beginToken,
+                         Token this.getOrSet,
+                         Token this.endToken,
+                         ElementKind kind,
+                         Modifiers modifiers,
+                         Element enclosing)
+    : super(name, kind, modifiers, enclosing);
+
+  FunctionExpression parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    parseFunction(Parser p) {
+      if (isMember() && modifiers.isFactory()) {
+        p.parseFactoryMethod(beginToken);
+      } else {
+        p.parseFunction(beginToken, getOrSet);
+      }
+    }
+    cachedNode = parse(listener, getCompilationUnit(), parseFunction);
+    return cachedNode;
+  }
+
+  Token position() {
+    return findMyName(beginToken);
+  }
+
+  PartialFunctionElement cloneTo(Element enclosing,
+                                 DiagnosticListener listener) {
+    if (patch != null) {
+      listener.cancel("Cloning a patched function.", element: this);
+    }
+    PartialFunctionElement result = new PartialFunctionElement(
+        name, beginToken, getOrSet, endToken, kind, modifiers, enclosing);
+    return result;
+  }
+}
+
+class PartialFieldListElement extends VariableListElementX {
+  final Token beginToken;
+  final Token endToken;
+
+  PartialFieldListElement(Token this.beginToken,
+                          Token this.endToken,
+                          Modifiers modifiers,
+                          Element enclosing)
+    : super(ElementKind.VARIABLE_LIST, modifiers, enclosing);
+
+  VariableDefinitions parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = parse(listener,
+                       getCompilationUnit(),
+                       (p) => p.parseVariablesDeclaration(beginToken));
+    if (!cachedNode.modifiers.isVar() &&
+        !cachedNode.modifiers.isFinal() &&
+        !cachedNode.modifiers.isConst() &&
+        cachedNode.type == null) {
+      listener.cancel('A field declaration must start with var, final, '
+                      'const, or a type annotation.',
+                      node: cachedNode);
+    }
+    return cachedNode;
+  }
+
+  Token position() => beginToken; // findMyName doesn't work. I'm nameless.
+
+  PartialFieldListElement cloneTo(Element enclosing,
+                                  DiagnosticListener listener) {
+    PartialFieldListElement result = new PartialFieldListElement(
+        beginToken, endToken, modifiers, enclosing);
+    return result;
+  }
+}
+
+class PartialTypedefElement extends TypedefElementX {
+  final Token token;
+
+  PartialTypedefElement(SourceString name, Element enclosing, this.token)
+      : super(name, enclosing);
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = parse(listener,
+                       getCompilationUnit(),
+                       (p) => p.parseTopLevelDeclaration(token));
+    return cachedNode;
+  }
+
+  position() => findMyName(token);
+
+  PartialTypedefElement cloneTo(Element enclosing,
+                                DiagnosticListener listener) {
+    PartialTypedefElement result =
+        new PartialTypedefElement(name, enclosing, token);
+    return result;
+  }
+}
+
+/// A [MetadataAnnotation] which is constructed on demand.
+class PartialMetadataAnnotation extends MetadataAnnotationX {
+  final Token beginToken;
+  final Token tokenAfterEndToken;
+  Expression cachedNode;
+  Constant value;
+
+  PartialMetadataAnnotation(this.beginToken, this.tokenAfterEndToken);
+
+  Token get endToken {
+    Token token = beginToken;
+    while (token.kind != EOF_TOKEN) {
+      if (identical(token.next, tokenAfterEndToken)) return token;
+      token = token.next;
+    }
+  }
+
+  Node parseNode(DiagnosticListener listener) {
+    if (cachedNode != null) return cachedNode;
+    cachedNode = parse(listener,
+                       annotatedElement.getCompilationUnit(),
+                       (p) => p.parseMetadata(beginToken));
+    return cachedNode;
+  }
+}
+
+Node parse(DiagnosticListener diagnosticListener,
+           CompilationUnitElement element,
+           doParse(Parser parser)) {
+  NodeListener listener = new NodeListener(diagnosticListener, element);
+  doParse(new Parser(listener));
+  Node node = listener.popNode();
+  assert(listener.nodes.isEmpty);
+  return node;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart
new file mode 100644
index 0000000..5a85e00
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart
@@ -0,0 +1,2231 @@
+// 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.
+
+part of scanner;
+
+/**
+ * An event generating parser of Dart programs. This parser expects
+ * all tokens in a linked list (aka a token stream).
+ *
+ * The class [Scanner] is used to generate a token stream. See the
+ * file scanner.dart.
+ *
+ * Subclasses of the class [Listener] are used to listen to events.
+ *
+ * Most methods of this class belong in one of two major categories:
+ * parse metods and peek methods. Parse methods all have the prefix
+ * parse, and peek methods all have the prefix peek.
+ *
+ * Parse methods generate events (by calling methods on [listener])
+ * and return the next token to parse. Peek methods do not generate
+ * events (except for errors) and may return null.
+ *
+ * Parse methods are generally named parseGrammarProductionSuffix. The
+ * suffix can be one of "opt", or "star". "opt" means zero or one
+ * matches, "star" means zero or more matches. For example,
+ * [parseMetadataStar] corresponds to this grammar snippet: [:
+ * metadata* :], and [parseTypeOpt] corresponds to: [: type? :].
+ */
+class Parser {
+  final Listener listener;
+  bool mayParseFunctionExpressions = true;
+
+  Parser(this.listener);
+
+  Token parseUnit(Token token) {
+    listener.beginCompilationUnit(token);
+    int count = 0;
+    while (!identical(token.kind, EOF_TOKEN)) {
+      token = parseTopLevelDeclaration(token);
+      listener.endTopLevelDeclaration(token);
+      count++;
+    }
+    listener.endCompilationUnit(count, token);
+    return token;
+  }
+
+  Token parseTopLevelDeclaration(Token token) {
+    token = parseMetadataStar(token);
+    final String value = token.stringValue;
+    if (identical(value, 'interface')) {
+      return parseInterface(token);
+    } else if ((identical(value, 'abstract')) || (identical(value, 'class'))) {
+      return parseClass(token);
+    } else if (identical(value, 'typedef')) {
+      return parseTypedef(token);
+    } else if (identical(value, '#')) {
+      return parseScriptTags(token);
+    } else if (identical(value, 'library')) {
+      return parseLibraryName(token);
+    } else if (identical(value, 'import')) {
+      return parseImport(token);
+    } else if (identical(value, 'export')) {
+      return parseExport(token);
+    } else if (identical(value, 'part')) {
+      return parsePartOrPartOf(token);
+    } else {
+      return parseTopLevelMember(token);
+    }
+  }
+
+  /// library qualified ';'
+  Token parseLibraryName(Token token) {
+    Token libraryKeyword = token;
+    listener.beginLibraryName(libraryKeyword);
+    assert(optional('library', token));
+    token = parseQualified(token.next);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endLibraryName(libraryKeyword, semicolon);
+    return token;
+  }
+
+  /// import uri (as identifier)? combinator* ';'
+  Token parseImport(Token token) {
+    Token importKeyword = token;
+    listener.beginImport(importKeyword);
+    assert(optional('import', token));
+    token = parseLiteralStringOrRecoverExpression(token.next);
+    Token asKeyword;
+    if (optional('as', token)) {
+      asKeyword = token;
+      token = parseIdentifier(token.next);
+    }
+    token = parseCombinators(token);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endImport(importKeyword, asKeyword, semicolon);
+    return token;
+  }
+
+  /// export uri combinator* ';'
+  Token parseExport(Token token) {
+    Token exportKeyword = token;
+    listener.beginExport(exportKeyword);
+    assert(optional('export', token));
+    token = parseLiteralStringOrRecoverExpression(token.next);
+    token = parseCombinators(token);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endExport(exportKeyword, semicolon);
+    return token;
+  }
+
+  Token parseCombinators(Token token) {
+    listener.beginCombinators(token);
+    int count = 0;
+    while (true) {
+      String value = token.stringValue;
+      if (identical('hide', value)) {
+        token = parseHide(token);
+      } else if (identical('show', value)) {
+        token = parseShow(token);
+      } else {
+        listener.endCombinators(count);
+        return token;
+      }
+      count++;
+    }
+  }
+
+  /// hide identifierList
+  Token parseHide(Token token) {
+    Token hideKeyword = token;
+    listener.beginHide(hideKeyword);
+    assert(optional('hide', token));
+    token = parseIdentifierList(token.next);
+    listener.endHide(hideKeyword);
+    return token;
+  }
+
+  /// show identifierList
+  Token parseShow(Token token) {
+    Token showKeyword = token;
+    listener.beginShow(showKeyword);
+    assert(optional('show', token));
+    token = parseIdentifierList(token.next);
+    listener.endShow(showKeyword);
+    return token;
+  }
+
+  /// identifier (, identifier)*
+  Token parseIdentifierList(Token token) {
+    listener.beginIdentifierList(token);
+    token = parseIdentifier(token);
+    int count = 1;
+    while (optional(',', token)) {
+      token = parseIdentifier(token.next);
+      count++;
+    }
+    listener.endIdentifierList(count);
+    return token;
+  }
+
+  /// type (, type)*
+  Token parseTypeList(Token token) {
+    listener.beginTypeList(token);
+    token = parseType(token);
+    int count = 1;
+    while (optional(',', token)) {
+      token = parseType(token.next);
+      count++;
+    }
+    listener.endTypeList(count);
+    return token;
+  }
+
+  Token parsePartOrPartOf(Token token) {
+    assert(optional('part', token));
+    if (optional('of', token.next)) {
+      return parsePartOf(token);
+    } else {
+      return parsePart(token);
+    }
+  }
+
+  Token parsePart(Token token) {
+    Token partKeyword = token;
+    listener.beginPart(token);
+    assert(optional('part', token));
+    token = parseLiteralStringOrRecoverExpression(token.next);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endPart(partKeyword, semicolon);
+    return token;
+  }
+
+  Token parsePartOf(Token token) {
+    listener.beginPartOf(token);
+    assert(optional('part', token));
+    assert(optional('of', token.next));
+    Token partKeyword = token;
+    token = parseQualified(token.next.next);
+    Token semicolon = token;
+    token = expect(';', token);
+    listener.endPartOf(partKeyword, semicolon);
+    return token;
+  }
+
+  Token parseMetadataStar(Token token) {
+    while (optional('@', token)) {
+      token = parseMetadata(token);
+    }
+    return token;
+  }
+
+  /**
+   * Parse
+   * [: '@' qualified (‘.’ identifier)? (arguments)? :]
+   */
+  Token parseMetadata(Token token) {
+    listener.beginMetadata(token);
+    Token atToken = token;
+    assert(optional('@', token));
+    token = parseIdentifier(token.next);
+    token = parseQualifiedRestOpt(token);
+    token = parseTypeArgumentsOpt(token);
+    Token period = null;
+    if (optional('.', token)) {
+      period = token;
+      token = parseIdentifier(token.next);
+    }
+    token = parseArgumentsOpt(token);
+    listener.endMetadata(atToken, period, token);
+    return token;
+  }
+
+  Token parseInterface(Token token) {
+    Token interfaceKeyword = token;
+    listener.beginInterface(token);
+    token = parseIdentifier(token.next);
+    token = parseTypeVariablesOpt(token);
+    int supertypeCount = 0;
+    Token extendsKeyword = null;
+    if (optional('extends', token)) {
+      extendsKeyword = token;
+      do {
+        token = parseType(token.next);
+        ++supertypeCount;
+      } while (optional(',', token));
+    }
+    token = parseDefaultClauseOpt(token);
+    token = parseInterfaceBody(token);
+    listener.endInterface(supertypeCount, interfaceKeyword,
+                          extendsKeyword, token);
+    return token.next;
+  }
+
+  Token parseInterfaceBody(Token token) {
+    return parseClassBody(token);
+  }
+
+  Token parseTypedef(Token token) {
+    Token typedefKeyword = token;
+    if (optional('=', peekAfterType(token.next))) {
+      listener.beginNamedMixinApplication(token);
+      token = parseIdentifier(token.next);
+      token = parseTypeVariablesOpt(token);
+      token = expect('=', token);
+      token = parseModifiers(token);
+      token = parseMixinApplication(token);
+      Token implementsKeyword = null;
+      if (optional('implements', token)) {
+        implementsKeyword = token;
+        token = parseTypeList(token.next);
+      }
+      listener.endNamedMixinApplication(
+          typedefKeyword, implementsKeyword, token);
+    } else {
+      listener.beginFunctionTypeAlias(token);
+      token = parseReturnTypeOpt(token.next);
+      token = parseIdentifier(token);
+      token = parseTypeVariablesOpt(token);
+      token = parseFormalParameters(token);
+      listener.endFunctionTypeAlias(typedefKeyword, token);
+    }
+    return expect(';', token);
+  }
+
+  Token parseMixinApplication(Token token) {
+    listener.beginMixinApplication(token);
+    token = parseType(token);
+    token = expect('with', token);
+    token = parseTypeList(token);
+    listener.endMixinApplication();
+    return token;
+  }
+
+  Token parseReturnTypeOpt(Token token) {
+    if (identical(token.stringValue, 'void')) {
+      listener.handleVoidKeyword(token);
+      return token.next;
+    } else {
+      return parseTypeOpt(token);
+    }
+  }
+
+  Token parseFormalParametersOpt(Token token) {
+    if (optional('(', token)) {
+      return parseFormalParameters(token);
+    } else {
+      listener.handleNoFormalParameters(token);
+      return token;
+    }
+  }
+
+  Token parseFormalParameters(Token token) {
+    Token begin = token;
+    listener.beginFormalParameters(begin);
+    expect('(', token);
+    int parameterCount = 0;
+    if (optional(')', token.next)) {
+      listener.endFormalParameters(parameterCount, begin, token.next);
+      return token.next.next;
+    }
+    do {
+      ++parameterCount;
+      token = token.next;
+      String value = token.stringValue;
+      if (identical(value, '[')) {
+        token = parseOptionalFormalParameters(token, false);
+        break;
+      } else if (identical(value, '{')) {
+        token = parseOptionalFormalParameters(token, true);
+        break;
+      }
+      token = parseFormalParameter(token);
+    } while (optional(',', token));
+    listener.endFormalParameters(parameterCount, begin, token);
+    return expect(')', token);
+  }
+
+  Token parseFormalParameter(Token token) {
+    listener.beginFormalParameter(token);
+    token = parseModifiers(token);
+    // TODO(ahe): Validate that there are formal parameters if void.
+    token = parseReturnTypeOpt(token);
+    Token thisKeyword = null;
+    if (optional('this', token)) {
+      thisKeyword = token;
+      // TODO(ahe): Validate field initializers are only used in
+      // constructors, and not for function-typed arguments.
+      token = expect('.', token.next);
+    }
+    token = parseIdentifier(token);
+    if (optional('(', token)) {
+      token = parseFormalParameters(token);
+      listener.handleFunctionTypedFormalParameter(token);
+    }
+    String value = token.stringValue;
+    if ((identical('=', value)) || (identical(':', value))) {
+      // TODO(ahe): Validate that these are only used for optional parameters.
+      Token equal = token;
+      token = parseExpression(token.next);
+      listener.handleValuedFormalParameter(equal, token);
+    }
+    listener.endFormalParameter(thisKeyword);
+    return token;
+  }
+
+  Token parseOptionalFormalParameters(Token token, bool isNamed) {
+    Token begin = token;
+    listener.beginOptionalFormalParameters(begin);
+    assert((isNamed && optional('{', token)) || optional('[', token));
+    int parameterCount = 0;
+    do {
+      token = token.next;
+      token = parseFormalParameter(token);
+      ++parameterCount;
+    } while (optional(',', token));
+    listener.endOptionalFormalParameters(parameterCount, begin, token);
+    if (isNamed) {
+      return expect('}', token);
+    } else {
+      return expect(']', token);
+    }
+  }
+
+  Token parseTypeOpt(Token token) {
+    String value = token.stringValue;
+    if (!identical(value, 'this')) {
+      Token peek = peekAfterExpectedType(token);
+      if (peek.isIdentifier() || optional('this', peek)) {
+        return parseType(token);
+      }
+    }
+    listener.handleNoType(token);
+    return token;
+  }
+
+  bool isValidTypeReference(Token token) {
+    final kind = token.kind;
+    if (identical(kind, IDENTIFIER_TOKEN)) return true;
+    if (identical(kind, KEYWORD_TOKEN)) {
+      Keyword keyword = token.value;
+      String value = keyword.stringValue;
+      // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+      return keyword.isPseudo
+          || (identical(value, 'dynamic'))
+          || (identical(value, 'Dynamic'))
+          || (identical(value, 'void'));
+    }
+    return false;
+  }
+
+  Token parseDefaultClauseOpt(Token token) {
+    if (isDefaultKeyword(token)) {
+      // TODO(ahe): Remove support for 'factory' in this position.
+      Token defaultKeyword = token;
+      listener.beginDefaultClause(defaultKeyword);
+      token = parseIdentifier(token.next);
+      token = parseQualifiedRestOpt(token);
+      token = parseTypeVariablesOpt(token);
+      listener.endDefaultClause(defaultKeyword);
+    } else {
+      listener.handleNoDefaultClause(token);
+    }
+    return token;
+  }
+
+  Token parseQualified(Token token) {
+    token = parseIdentifier(token);
+    while (optional('.', token)) {
+      token = parseQualifiedRest(token);
+    }
+    return token;
+  }
+
+  Token parseQualifiedRestOpt(Token token) {
+    if (optional('.', token)) {
+      return parseQualifiedRest(token);
+    } else {
+      return token;
+    }
+  }
+
+  Token parseQualifiedRest(Token token) {
+    assert(optional('.', token));
+    Token period = token;
+    token = parseIdentifier(token.next);
+    listener.handleQualified(period);
+    return token;
+  }
+
+  bool isDefaultKeyword(Token token) {
+    String value = token.stringValue;
+    if (identical(value, 'default')) return true;
+    if (identical(value, 'factory')) {
+      listener.recoverableError("expected 'default'", token: token);
+      return true;
+    }
+    return false;
+  }
+
+  Token skipBlock(Token token) {
+    if (!optional('{', token)) {
+      return listener.expectedBlockToSkip(token);
+    }
+    BeginGroupToken beginGroupToken = token;
+    Token endGroup = beginGroupToken.endGroup;
+    if (endGroup == null) {
+      return listener.unmatched(beginGroupToken);
+    } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) {
+      return listener.unmatched(beginGroupToken);
+    }
+    return beginGroupToken.endGroup;
+  }
+
+  Token parseClass(Token token) {
+    Token begin = token;
+    listener.beginClassDeclaration(token);
+    int modifierCount = 0;
+    if (optional('abstract', token)) {
+      listener.handleModifier(token);
+      modifierCount++;
+      token = token.next;
+    }
+    listener.handleModifiers(modifierCount);
+    token = parseIdentifier(token.next);
+    token = parseTypeVariablesOpt(token);
+    Token extendsKeyword;
+    if (optional('extends', token)) {
+      extendsKeyword = token;
+      if (optional('with', peekAfterType(token.next))) {
+        token = parseMixinApplication(token.next);
+      } else {
+        token = parseType(token.next);
+      }
+    } else {
+      extendsKeyword = null;
+      listener.handleNoType(token);
+    }
+    Token implementsKeyword;
+    int interfacesCount = 0;
+    if (optional('implements', token)) {
+      implementsKeyword = token;
+      do {
+        token = parseType(token.next);
+        ++interfacesCount;
+      } while (optional(',', token));
+    }
+    token = parseClassBody(token);
+    listener.endClassDeclaration(interfacesCount, begin, extendsKeyword,
+                                 implementsKeyword, token);
+    return token.next;
+  }
+
+  Token parseStringPart(Token token) {
+    if (identical(token.kind, STRING_TOKEN)) {
+      listener.handleStringPart(token);
+      return token.next;
+    } else {
+      return listener.expected('string', token);
+    }
+  }
+
+  Token parseIdentifier(Token token) {
+    if (token.isIdentifier()) {
+      listener.handleIdentifier(token);
+    } else {
+      listener.expectedIdentifier(token);
+    }
+    return token.next;
+  }
+
+  Token expect(String string, Token token) {
+    if (!identical(string, token.stringValue)) {
+      return listener.expected(string, token);
+    }
+    return token.next;
+  }
+
+  Token parseTypeVariable(Token token) {
+    listener.beginTypeVariable(token);
+    token = parseIdentifier(token);
+    if (optional('extends', token)) {
+      token = parseType(token.next);
+    } else {
+      listener.handleNoType(token);
+    }
+    listener.endTypeVariable(token);
+    return token;
+  }
+
+  /**
+   * Returns true if the stringValue of the [token] is [value].
+   */
+  bool optional(String value, Token token) {
+      return identical(value, token.stringValue);
+  }
+
+  /**
+   * Returns true if the stringValue of the [token] is either [value1],
+   * [value2], [value3], or [value4].
+   */
+  bool isOneOf4(Token token,
+                String value1, String value2, String value3, String value4) {
+    String stringValue = token.stringValue;
+    return identical(value1, stringValue) ||
+           identical(value2, stringValue) ||
+           identical(value3, stringValue) ||
+           identical(value4, stringValue);
+  }
+
+  bool notEofOrValue(String value, Token token) {
+    return !identical(token.kind, EOF_TOKEN) &&
+           !identical(value, token.stringValue);
+  }
+
+  Token parseType(Token token) {
+    Token begin = token;
+    if (isValidTypeReference(token)) {
+      token = parseIdentifier(token);
+      token = parseQualifiedRestOpt(token);
+    } else {
+      token = listener.expectedType(token);
+    }
+    token = parseTypeArgumentsOpt(token);
+    listener.endType(begin, token);
+    return token;
+  }
+
+  Token parseTypeArgumentsOpt(Token token) {
+    return parseStuff(token,
+                      (t) => listener.beginTypeArguments(t),
+                      (t) => parseType(t),
+                      (c, bt, et) => listener.endTypeArguments(c, bt, et),
+                      (t) => listener.handleNoTypeArguments(t));
+  }
+
+  Token parseTypeVariablesOpt(Token token) {
+    return parseStuff(token,
+                      (t) => listener.beginTypeVariables(t),
+                      (t) => parseTypeVariable(t),
+                      (c, bt, et) => listener.endTypeVariables(c, bt, et),
+                      (t) => listener.handleNoTypeVariables(t));
+  }
+
+  // TODO(ahe): Clean this up.
+  Token parseStuff(Token token, Function beginStuff, Function stuffParser,
+                   Function endStuff, Function handleNoStuff) {
+    if (optional('<', token)) {
+      Token begin = token;
+      beginStuff(begin);
+      int count = 0;
+      do {
+        token = stuffParser(token.next);
+        ++count;
+      } while (optional(',', token));
+      Token next = token.next;
+      if (identical(token.stringValue, '>>')) {
+        token = new Token(GT_INFO, token.charOffset);
+        token.next = new Token(GT_INFO, token.charOffset + 1);
+        token.next.next = next;
+      } else if (identical(token.stringValue, '>>>')) {
+        token = new Token(GT_INFO, token.charOffset);
+        token.next = new Token(GT_GT_INFO, token.charOffset + 1);
+        token.next.next = next;
+      }
+      endStuff(count, begin, token);
+      return expect('>', token);
+    }
+    handleNoStuff(token);
+    return token;
+  }
+
+  Token parseTopLevelMember(Token token) {
+    Token start = token;
+    listener.beginTopLevelMember(token);
+
+    Link<Token> identifiers = findMemberName(token);
+    if (identifiers.isEmpty) {
+      return listener.unexpected(start);
+    }
+    Token name = identifiers.head;
+    identifiers = identifiers.tail;
+    Token getOrSet;
+    if (!identifiers.isEmpty) {
+      String value = identifiers.head.stringValue;
+      if ((identical(value, 'get')) || (identical(value, 'set'))) {
+        getOrSet = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    Token type;
+    if (!identifiers.isEmpty) {
+      if (isValidTypeReference(identifiers.head)) {
+        type = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    parseModifierList(identifiers.reverse());
+    if (type == null) {
+      listener.handleNoType(token);
+    } else {
+      parseReturnTypeOpt(type);
+    }
+    token = parseIdentifier(name);
+
+    bool isField;
+    while (true) {
+      // Loop to allow the listener to rewrite the token stream for
+      // error handling.
+      final String value = token.stringValue;
+      if ((identical(value, '(')) || (identical(value, '{'))
+          || (identical(value, '=>'))) {
+        isField = false;
+        break;
+      } else if ((identical(value, '=')) || (identical(value, ','))) {
+        isField = true;
+        break;
+      } else if (identical(value, ';')) {
+        if (getOrSet != null) {
+          // If we found a "get" keyword, this must be an abstract
+          // getter.
+          isField = (!identical(getOrSet.stringValue, 'get'));
+          // TODO(ahe): This feels like a hack.
+        } else {
+          isField = true;
+        }
+        break;
+      } else {
+        token = listener.unexpected(token);
+        if (identical(token.kind, EOF_TOKEN)) {
+          // TODO(ahe): This is a hack. It would be better to tell the
+          // listener more explicitly that it must pop an identifier.
+          listener.endTopLevelFields(1, start, token);
+          return token;
+        }
+      }
+    }
+    if (isField) {
+      int fieldCount = 1;
+      token = parseVariableInitializerOpt(token);
+      while (optional(',', token)) {
+        token = parseIdentifier(token.next);
+        token = parseVariableInitializerOpt(token);
+        ++fieldCount;
+      }
+      expectSemicolon(token);
+      listener.endTopLevelFields(fieldCount, start, token);
+    } else {
+      token = parseFormalParametersOpt(token);
+      token = parseFunctionBody(token, false);
+      listener.endTopLevelMethod(start, getOrSet, token);
+    }
+    return token.next;
+  }
+
+  Link<Token> findMemberName(Token token) {
+    Token start = token;
+    Link<Token> identifiers = const Link<Token>();
+    while (!identical(token.kind, EOF_TOKEN)) {
+      String value = token.stringValue;
+      if ((identical(value, '(')) || (identical(value, '{'))
+          || (identical(value, '=>'))) {
+        // A method.
+        return identifiers;
+      } else if ((identical(value, '=')) || (identical(value, ';'))
+          || (identical(value, ','))) {
+        // A field or abstract getter.
+        return identifiers;
+      }
+      identifiers = identifiers.prepend(token);
+      if (isValidTypeReference(token)) {
+        // type ...
+        if (optional('.', token.next)) {
+          // type '.' ...
+          if (token.next.next.isIdentifier()) {
+            // type '.' identifier
+            token = token.next.next;
+          }
+        }
+        if (optional('<', token.next)) {
+          if (token.next is BeginGroupToken) {
+            BeginGroupToken beginGroup = token.next;
+            token = beginGroup.endGroup;
+          }
+        }
+      }
+      token = token.next;
+    }
+    return listener.expectedDeclaration(start);
+  }
+
+  Token parseVariableInitializerOpt(Token token) {
+    if (optional('=', token)) {
+      Token assignment = token;
+      listener.beginInitializer(token);
+      token = parseExpression(token.next);
+      listener.endInitializer(assignment);
+    }
+    return token;
+  }
+
+  Token parseInitializersOpt(Token token) {
+    if (optional(':', token)) {
+      return parseInitializers(token);
+    } else {
+      listener.handleNoInitializers();
+      return token;
+    }
+  }
+
+  Token parseInitializers(Token token) {
+    Token begin = token;
+    listener.beginInitializers(begin);
+    expect(':', token);
+    int count = 0;
+    bool old = mayParseFunctionExpressions;
+    mayParseFunctionExpressions = false;
+    do {
+      token = parseExpression(token.next);
+      ++count;
+    } while (optional(',', token));
+    mayParseFunctionExpressions = old;
+    listener.endInitializers(count, begin, token);
+    return token;
+  }
+
+  Token parseScriptTags(Token token) {
+    Token begin = token;
+    listener.beginScriptTag(token);
+    token = parseIdentifier(token.next);
+    token = expect('(', token);
+    token = parseLiteralStringOrRecoverExpression(token);
+    bool hasPrefix = false;
+    if (optional(',', token)) {
+      hasPrefix = true;
+      token = parseIdentifier(token.next);
+      token = expect(':', token);
+      token = parseLiteralStringOrRecoverExpression(token);
+    }
+    token = expect(')', token);
+    listener.endScriptTag(hasPrefix, begin, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseLiteralStringOrRecoverExpression(Token token) {
+    if (identical(token.kind, STRING_TOKEN)) {
+      return parseLiteralString(token);
+    } else {
+      listener.recoverableError("unexpected", token: token);
+      return parseExpression(token);
+    }
+  }
+
+  Token expectSemicolon(Token token) {
+    return expect(';', token);
+  }
+
+  bool isModifier(Token token) {
+    final String value = token.stringValue;
+    return (identical('final', value)) ||
+           (identical('var', value)) ||
+           (identical('const', value)) ||
+           (identical('abstract', value)) ||
+           (identical('static', value)) ||
+           (identical('external', value));
+  }
+
+  Token parseModifier(Token token) {
+    assert(isModifier(token));
+    listener.handleModifier(token);
+    return token.next;
+  }
+
+  void parseModifierList(Link<Token> tokens) {
+    int count = 0;
+    for (; !tokens.isEmpty; tokens = tokens.tail) {
+      Token token = tokens.head;
+      if (isModifier(token)) {
+        parseModifier(token);
+      } else {
+        listener.unexpected(token);
+      }
+      count++;
+    }
+    listener.handleModifiers(count);
+  }
+
+  Token parseModifiers(Token token) {
+    int count = 0;
+    while (identical(token.kind, KEYWORD_TOKEN)) {
+      if (!isModifier(token))
+        break;
+      token = parseModifier(token);
+      count++;
+    }
+    listener.handleModifiers(count);
+    return token;
+  }
+
+  Token peekAfterType(Token token) {
+    // TODO(ahe): Also handle var?
+    // We are looking at "identifier ...".
+    Token peek = token.next;
+    if (identical(peek.kind, PERIOD_TOKEN)) {
+      if (peek.next.isIdentifier()) {
+        // Look past a library prefix.
+        peek = peek.next.next;
+      }
+    }
+    // We are looking at "qualified ...".
+    if (identical(peek.kind, LT_TOKEN)) {
+      // Possibly generic type.
+      // We are looking at "qualified '<'".
+      BeginGroupToken beginGroupToken = peek;
+      Token gtToken = beginGroupToken.endGroup;
+      if (gtToken != null) {
+        // We are looking at "qualified '<' ... '>' ...".
+        return gtToken.next;
+      }
+    }
+    return peek;
+  }
+
+  /**
+   * Returns the token after the type which is expected to begin at [token].
+   * If [token] is not the start of a type, [Listener.unexpectedType] is called.
+   */
+  Token peekAfterExpectedType(Token token) {
+    if (!identical('void', token.stringValue) && !token.isIdentifier()) {
+      return listener.expectedType(token);
+    }
+    return peekAfterType(token);
+  }
+
+  Token parseClassBody(Token token) {
+    Token begin = token;
+    listener.beginClassBody(token);
+    if (!optional('{', token)) {
+      token = listener.expectedClassBody(token);
+    }
+    token = token.next;
+    int count = 0;
+    while (notEofOrValue('}', token)) {
+      token = parseMember(token);
+      ++count;
+    }
+    expect('}', token);
+    listener.endClassBody(count, begin, token);
+    return token;
+  }
+
+  bool isGetOrSet(Token token) {
+    final String value = token.stringValue;
+    return (identical(value, 'get')) || (identical(value, 'set'));
+  }
+
+  bool isFactoryDeclaration(Token token) {
+    if (optional('external', token)) token = token.next;
+    if (optional('const', token)) token = token.next;
+    return optional('factory', token);
+  }
+
+  Token parseMember(Token token) {
+    token = parseMetadataStar(token);
+    String value = token.stringValue;
+    if (isFactoryDeclaration(token)) {
+      return parseFactoryMethod(token);
+    }
+    Token start = token;
+    listener.beginMember(token);
+
+    Link<Token> identifiers = findMemberName(token);
+    if (identifiers.isEmpty) {
+      return listener.unexpected(start);
+    }
+    Token name = identifiers.head;
+    identifiers = identifiers.tail;
+    if (!identifiers.isEmpty) {
+      if (optional('operator', identifiers.head)) {
+        name = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    Token getOrSet;
+    if (!identifiers.isEmpty) {
+      if (isGetOrSet(identifiers.head)) {
+        getOrSet = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    Token type;
+    if (!identifiers.isEmpty) {
+      if (isValidTypeReference(identifiers.head)) {
+        type = identifiers.head;
+        identifiers = identifiers.tail;
+      }
+    }
+    parseModifierList(identifiers.reverse());
+    if (type == null) {
+      listener.handleNoType(token);
+    } else {
+      parseReturnTypeOpt(type);
+    }
+
+    if (optional('operator', name)) {
+      token = parseOperatorName(name);
+    } else {
+      token = parseIdentifier(name);
+    }
+    bool isField;
+    while (true) {
+      // Loop to allow the listener to rewrite the token stream for
+      // error handling.
+      final String value = token.stringValue;
+      if ((identical(value, '(')) || (identical(value, '.'))
+          || (identical(value, '{')) || (identical(value, '=>'))) {
+        isField = false;
+        break;
+      } else if (identical(value, ';')) {
+        if (getOrSet != null) {
+          // If we found a "get" keyword, this must be an abstract
+          // getter.
+          isField = (!identical(getOrSet.stringValue, 'get'));
+          // TODO(ahe): This feels like a hack.
+        } else {
+          isField = true;
+        }
+        break;
+      } else if ((identical(value, '=')) || (identical(value, ','))) {
+        isField = true;
+        break;
+      } else {
+        token = listener.unexpected(token);
+        if (identical(token.kind, EOF_TOKEN)) {
+          // TODO(ahe): This is a hack, see parseTopLevelMember.
+          listener.endFields(1, start, token);
+          return token;
+        }
+      }
+    }
+    if (isField) {
+      int fieldCount = 1;
+      token = parseVariableInitializerOpt(token);
+      if (getOrSet != null) {
+        listener.recoverableError("unexpected", token: getOrSet);
+      }
+      while (optional(',', token)) {
+        // TODO(ahe): Count these.
+        token = parseIdentifier(token.next);
+        token = parseVariableInitializerOpt(token);
+        ++fieldCount;
+      }
+      expectSemicolon(token);
+      listener.endFields(fieldCount, start, token);
+    } else {
+      token = parseQualifiedRestOpt(token);
+      token = parseFormalParametersOpt(token);
+      token = parseInitializersOpt(token);
+      if (optional('=', token)) {
+        token = parseRedirectingFactoryBody(token);
+      } else {
+        token = parseFunctionBody(token, false);
+      }
+      listener.endMethod(getOrSet, start, token);
+    }
+    return token.next;
+  }
+
+  Token parseFactoryMethod(Token token) {
+    assert(isFactoryDeclaration(token));
+    Token start = token;
+    if (identical(token.stringValue, 'external')) token = token.next;
+    Token constKeyword = null;
+    if (optional('const', token)) {
+      constKeyword = token;
+      token = token.next;
+    }
+    Token factoryKeyword = token;
+    listener.beginFactoryMethod(factoryKeyword);
+    token = token.next; // Skip 'factory'.
+    token = parseConstructorReference(token);
+    token = parseFormalParameters(token);
+    if (optional('=', token)) {
+      token = parseRedirectingFactoryBody(token);
+    } else {
+      token = parseFunctionBody(token, false);
+    }
+    listener.endFactoryMethod(start, token);
+    return token.next;
+  }
+
+  Token parseOperatorName(Token token) {
+    assert(optional('operator', token));
+    if (isUserDefinableOperator(token.next.stringValue)) {
+      Token operator = token;
+      token = token.next;
+      listener.handleOperatorName(operator, token);
+      return token.next;
+    } else {
+      return parseIdentifier(token);
+    }
+  }
+
+  Token parseFunction(Token token, Token getOrSet) {
+    listener.beginFunction(token);
+    token = parseModifiers(token);
+    if (identical(getOrSet, token)) token = token.next;
+    if (optional('operator', token)) {
+      listener.handleNoType(token);
+      listener.beginFunctionName(token);
+      token = parseOperatorName(token);
+    } else {
+      token = parseReturnTypeOpt(token);
+      if (identical(getOrSet, token)) token = token.next;
+      listener.beginFunctionName(token);
+      if (optional('operator', token)) {
+        token = parseOperatorName(token);
+      } else {
+        token = parseIdentifier(token);
+      }
+    }
+    token = parseQualifiedRestOpt(token);
+    listener.endFunctionName(token);
+    token = parseFormalParametersOpt(token);
+    token = parseInitializersOpt(token);
+    if (optional('=', token)) {
+      token = parseRedirectingFactoryBody(token);
+    } else {
+      token = parseFunctionBody(token, false);
+    }
+    listener.endFunction(getOrSet, token);
+    return token.next;
+  }
+
+  Token parseUnamedFunction(Token token) {
+    listener.beginUnamedFunction(token);
+    token = parseFormalParameters(token);
+    bool isBlock = optional('{', token);
+    token = parseFunctionBody(token, true);
+    listener.endUnamedFunction(token);
+    return isBlock ? token.next : token;
+  }
+
+  Token parseFunctionDeclaration(Token token) {
+    listener.beginFunctionDeclaration(token);
+    token = parseFunction(token, null);
+    listener.endFunctionDeclaration(token);
+    return token;
+  }
+
+  Token parseFunctionExpression(Token token) {
+    listener.beginFunction(token);
+    listener.handleModifiers(0);
+    token = parseReturnTypeOpt(token);
+    listener.beginFunctionName(token);
+    token = parseIdentifier(token);
+    listener.endFunctionName(token);
+    token = parseFormalParameters(token);
+    listener.handleNoInitializers();
+    bool isBlock = optional('{', token);
+    token = parseFunctionBody(token, true);
+    listener.endFunction(null, token);
+    return isBlock ? token.next : token;
+  }
+
+  Token parseConstructorReference(Token token) {
+    Token start = token;
+    listener.beginConstructorReference(start);
+    token = parseIdentifier(token);
+    token = parseQualifiedRestOpt(token);
+    token = parseTypeArgumentsOpt(token);
+    Token period = null;
+    if (optional('.', token)) {
+      period = token;
+      token = parseIdentifier(token.next);
+    }
+    listener.endConstructorReference(start, period, token);
+    return token;
+  }
+
+  Token parseRedirectingFactoryBody(Token token) {
+    listener.beginRedirectingFactoryBody(token);
+    assert(optional('=', token));
+    Token equals = token;
+    token = parseConstructorReference(token.next);
+    Token semicolon = token;
+    expectSemicolon(token);
+    listener.endRedirectingFactoryBody(equals, semicolon);
+    return token;
+  }
+
+  Token parseFunctionBody(Token token, bool isExpression) {
+    if (optional(';', token)) {
+      listener.endFunctionBody(0, null, token);
+      return token;
+    } else if (optional('=>', token)) {
+      Token begin = token;
+      token = parseExpression(token.next);
+      if (!isExpression) {
+        expectSemicolon(token);
+        listener.endReturnStatement(true, begin, token);
+      } else {
+        listener.endReturnStatement(true, begin, null);
+      }
+      return token;
+    }
+    Token begin = token;
+    int statementCount = 0;
+    if (!optional('{', token)) {
+      return listener.expectedFunctionBody(token);
+    }
+
+    listener.beginFunctionBody(begin);
+    token = token.next;
+    while (notEofOrValue('}', token)) {
+      token = parseStatement(token);
+      ++statementCount;
+    }
+    listener.endFunctionBody(statementCount, begin, token);
+    expect('}', token);
+    return token;
+  }
+
+  Token parseStatement(Token token) {
+    final value = token.stringValue;
+    if (identical(token.kind, IDENTIFIER_TOKEN)) {
+      return parseExpressionStatementOrDeclaration(token);
+    } else if (identical(value, '{')) {
+      return parseBlock(token);
+    } else if (identical(value, 'return')) {
+      return parseReturnStatement(token);
+    } else if (identical(value, 'var') || identical(value, 'final')) {
+      return parseVariablesDeclaration(token);
+    } else if (identical(value, 'if')) {
+      return parseIfStatement(token);
+    } else if (identical(value, 'for')) {
+      return parseForStatement(token);
+    } else if (identical(value, 'throw')) {
+      return parseThrowStatement(token);
+    } else if (identical(value, 'void')) {
+      return parseExpressionStatementOrDeclaration(token);
+    } else if (identical(value, 'while')) {
+      return parseWhileStatement(token);
+    } else if (identical(value, 'do')) {
+      return parseDoWhileStatement(token);
+    } else if (identical(value, 'try')) {
+      return parseTryStatement(token);
+    } else if (identical(value, 'switch')) {
+      return parseSwitchStatement(token);
+    } else if (identical(value, 'break')) {
+      return parseBreakStatement(token);
+    } else if (identical(value, 'continue')) {
+      return parseContinueStatement(token);
+    } else if (identical(value, 'assert')) {
+      return parseAssertStatement(token);
+    } else if (identical(value, ';')) {
+      return parseEmptyStatement(token);
+    } else if (identical(value, 'const')) {
+      return parseExpressionStatementOrConstDeclaration(token);
+    } else if (token.isIdentifier()) {
+      return parseExpressionStatementOrDeclaration(token);
+    } else {
+      return parseExpressionStatement(token);
+    }
+  }
+
+  Token parseReturnStatement(Token token) {
+    Token begin = token;
+    listener.beginReturnStatement(begin);
+    assert(identical('return', token.stringValue));
+    token = token.next;
+    if (optional(';', token)) {
+      listener.endReturnStatement(false, begin, token);
+    } else {
+      token = parseExpression(token);
+      listener.endReturnStatement(true, begin, token);
+    }
+    return expectSemicolon(token);
+  }
+
+  Token peekIdentifierAfterType(Token token) {
+    Token peek = peekAfterType(token);
+    if (peek != null && peek.isIdentifier()) {
+      // We are looking at "type identifier".
+      return peek;
+    } else {
+      return null;
+    }
+  }
+
+  Token peekIdentifierAfterOptionalType(Token token) {
+    Token peek = peekIdentifierAfterType(token);
+    if (peek != null) {
+      // We are looking at "type identifier".
+      return peek;
+    } else if (token.isIdentifier()) {
+      // We are looking at "identifier".
+      return token;
+    } else {
+      return null;
+    }
+  }
+
+  Token parseExpressionStatementOrDeclaration(Token token) {
+    assert(token.isIdentifier() || identical(token.stringValue, 'void'));
+    Token identifier = peekIdentifierAfterType(token);
+    if (identifier != null) {
+      assert(identifier.isIdentifier());
+      Token afterId = identifier.next;
+      int afterIdKind = afterId.kind;
+      if (identical(afterIdKind, EQ_TOKEN) ||
+          identical(afterIdKind, SEMICOLON_TOKEN) ||
+          identical(afterIdKind, COMMA_TOKEN)) {
+        // We are looking at "type identifier" followed by '=', ';', ','.
+        return parseVariablesDeclaration(token);
+      } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) {
+        // We are looking at "type identifier '('".
+        BeginGroupToken beginParen = afterId;
+        Token endParen = beginParen.endGroup;
+        Token afterParens = endParen.next;
+        if (optional('{', afterParens) || optional('=>', afterParens)) {
+          // We are looking at "type identifier '(' ... ')'" followed
+          // by '=>' or '{'.
+          return parseFunctionDeclaration(token);
+        }
+      }
+      // Fall-through to expression statement.
+    } else {
+      if (optional(':', token.next)) {
+        return parseLabeledStatement(token);
+      } else if (optional('(', token.next)) {
+        BeginGroupToken begin = token.next;
+        String afterParens = begin.endGroup.next.stringValue;
+        if (identical(afterParens, '{') || identical(afterParens, '=>')) {
+          return parseFunctionDeclaration(token);
+        }
+      }
+    }
+    return parseExpressionStatement(token);
+  }
+
+  Token parseExpressionStatementOrConstDeclaration(Token token) {
+    assert(identical(token.stringValue, 'const'));
+    if (isModifier(token.next)) {
+      return parseVariablesDeclaration(token);
+    }
+    Token identifier = peekIdentifierAfterOptionalType(token.next);
+    if (identifier != null) {
+      assert(identifier.isIdentifier());
+      Token afterId = identifier.next;
+      int afterIdKind = afterId.kind;
+      if (identical(afterIdKind, EQ_TOKEN) ||
+          identical(afterIdKind, SEMICOLON_TOKEN) ||
+          identical(afterIdKind, COMMA_TOKEN)) {
+        // We are looking at "const type identifier" followed by '=', ';', or
+        // ','.
+        return parseVariablesDeclaration(token);
+      }
+      // Fall-through to expression statement.
+    }
+    return parseExpressionStatement(token);
+  }
+
+  Token parseLabel(Token token) {
+    token = parseIdentifier(token);
+    Token colon = token;
+    token = expect(':', token);
+    listener.handleLabel(colon);
+    return token;
+  }
+
+  Token parseLabeledStatement(Token token) {
+    int labelCount = 0;
+    do {
+      token = parseLabel(token);
+      labelCount++;
+    } while (token.isIdentifier() && optional(':', token.next));
+    listener.beginLabeledStatement(token, labelCount);
+    token = parseStatement(token);
+    listener.endLabeledStatement(labelCount);
+    return token;
+  }
+
+  Token parseExpressionStatement(Token token) {
+    listener.beginExpressionStatement(token);
+    token = parseExpression(token);
+    listener.endExpressionStatement(token);
+    return expectSemicolon(token);
+  }
+
+  Token parseExpression(Token token) {
+    return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true);
+  }
+
+  Token parseExpressionWithoutCascade(Token token) {
+    return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false);
+  }
+
+  Token parseConditionalExpressionRest(Token token) {
+    assert(optional('?', token));
+    Token question = token;
+    token = parseExpressionWithoutCascade(token.next);
+    Token colon = token;
+    token = expect(':', token);
+    token = parseExpressionWithoutCascade(token);
+    listener.handleConditionalExpression(question, colon);
+    return token;
+  }
+
+  Token parsePrecedenceExpression(Token token, int precedence,
+                                  bool allowCascades) {
+    assert(precedence >= 1);
+    assert(precedence <= POSTFIX_PRECEDENCE);
+    token = parseUnaryExpression(token, allowCascades);
+    PrecedenceInfo info = token.info;
+    int tokenLevel = info.precedence;
+    for (int level = tokenLevel; level >= precedence; --level) {
+      while (identical(tokenLevel, level)) {
+        Token operator = token;
+        if (identical(tokenLevel, CASCADE_PRECEDENCE)) {
+          if (!allowCascades) {
+            return token;
+          }
+          token = parseCascadeExpression(token);
+        } else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) {
+          // Right associative, so we recurse at the same precedence
+          // level.
+          token = parsePrecedenceExpression(token.next, level, allowCascades);
+          listener.handleAssignmentExpression(operator);
+        } else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) {
+          if (identical(info, PERIOD_INFO)) {
+            // Left associative, so we recurse at the next higher
+            // precedence level. However, POSTFIX_PRECEDENCE is the
+            // highest level, so we just call parseUnaryExpression
+            // directly.
+            token = parseUnaryExpression(token.next, allowCascades);
+            listener.handleBinaryExpression(operator);
+          } else if ((identical(info, OPEN_PAREN_INFO)) ||
+                     (identical(info, OPEN_SQUARE_BRACKET_INFO))) {
+            token = parseArgumentOrIndexStar(token);
+          } else if ((identical(info, PLUS_PLUS_INFO)) ||
+                     (identical(info, MINUS_MINUS_INFO))) {
+            listener.handleUnaryPostfixAssignmentExpression(token);
+            token = token.next;
+          } else {
+            token = listener.unexpected(token);
+          }
+        } else if (identical(info, IS_INFO)) {
+          token = parseIsOperatorRest(token);
+        } else if (identical(info, AS_INFO)) {
+          token = parseAsOperatorRest(token);
+        } else if (identical(info, QUESTION_INFO)) {
+          token = parseConditionalExpressionRest(token);
+        } else {
+          // Left associative, so we recurse at the next higher
+          // precedence level.
+          token = parsePrecedenceExpression(token.next, level + 1,
+                                            allowCascades);
+          listener.handleBinaryExpression(operator);
+        }
+        info = token.info;
+        tokenLevel = info.precedence;
+      }
+    }
+    return token;
+  }
+
+  Token parseCascadeExpression(Token token) {
+    listener.beginCascade(token);
+    assert(optional('..', token));
+    Token cascadeOperator = token;
+    token = token.next;
+    if (optional('[', token)) {
+      token = parseArgumentOrIndexStar(token);
+    } else if (token.isIdentifier()) {
+      token = parseSend(token);
+      listener.handleBinaryExpression(cascadeOperator);
+    } else {
+      return listener.unexpected(token);
+    }
+    Token mark;
+    do {
+      mark = token;
+      if (optional('.', token)) {
+        Token period = token;
+        token = parseSend(token.next);
+        listener.handleBinaryExpression(period);
+      }
+      token = parseArgumentOrIndexStar(token);
+    } while (!identical(mark, token));
+
+    if (identical(token.info.precedence, ASSIGNMENT_PRECEDENCE)) {
+      Token assignment = token;
+      token = parseExpressionWithoutCascade(token.next);
+      listener.handleAssignmentExpression(assignment);
+    }
+    listener.endCascade();
+    return token;
+  }
+
+  Token parseUnaryExpression(Token token, bool allowCascades) {
+    String value = token.stringValue;
+    // Prefix:
+    if (identical(value, '+')) {
+      // Dart only allows "prefix plus" as an initial part of a
+      // decimal literal. We scan it as a separate token and let
+      // the parser listener combine it with the digits.
+      Token next = token.next;
+      if (identical(next.charOffset, token.charOffset + 1)) {
+        if (identical(next.kind, INT_TOKEN)) {
+          listener.handleLiteralInt(token);
+          return next.next;
+        }
+        if (identical(next.kind, DOUBLE_TOKEN)) {
+          listener.handleLiteralDouble(token);
+          return next.next;
+        }
+      }
+      listener.recoverableError("Unexpected token '+'", token: token);
+      return parsePrecedenceExpression(next, POSTFIX_PRECEDENCE,
+                                       allowCascades);
+    } else if ((identical(value, '!')) ||
+               (identical(value, '-')) ||
+               (identical(value, '~'))) {
+      Token operator = token;
+      // Right associative, so we recurse at the same precedence
+      // level.
+      token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE,
+                                        allowCascades);
+      listener.handleUnaryPrefixExpression(operator);
+    } else if ((identical(value, '++')) || identical(value, '--')) {
+      // TODO(ahe): Validate this is used correctly.
+      Token operator = token;
+      // Right associative, so we recurse at the same precedence
+      // level.
+      token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE,
+                                        allowCascades);
+      listener.handleUnaryPrefixAssignmentExpression(operator);
+    } else {
+      token = parsePrimary(token);
+    }
+    return token;
+  }
+
+  Token parseArgumentOrIndexStar(Token token) {
+    while (true) {
+      if (optional('[', token)) {
+        Token openSquareBracket = token;
+        bool old = mayParseFunctionExpressions;
+        mayParseFunctionExpressions = true;
+        token = parseExpression(token.next);
+        mayParseFunctionExpressions = old;
+        listener.handleIndexedExpression(openSquareBracket, token);
+        token = expect(']', token);
+      } else if (optional('(', token)) {
+        token = parseArguments(token);
+        listener.endSend(token);
+      } else {
+        break;
+      }
+    }
+    return token;
+  }
+
+  Token parsePrimary(Token token) {
+    final kind = token.kind;
+    if (identical(kind, IDENTIFIER_TOKEN)) {
+      return parseSendOrFunctionLiteral(token);
+    } else if (identical(kind, INT_TOKEN)
+        || identical(kind, HEXADECIMAL_TOKEN)) {
+      return parseLiteralInt(token);
+    } else if (identical(kind, DOUBLE_TOKEN)) {
+      return parseLiteralDouble(token);
+    } else if (identical(kind, STRING_TOKEN)) {
+      return parseLiteralString(token);
+    } else if (identical(kind, KEYWORD_TOKEN)) {
+      final value = token.stringValue;
+      if ((identical(value, 'true')) || (identical(value, 'false'))) {
+        return parseLiteralBool(token);
+      } else if (identical(value, 'null')) {
+        return parseLiteralNull(token);
+      } else if (identical(value, 'this')) {
+        return parseThisExpression(token);
+      } else if (identical(value, 'super')) {
+        return parseSuperExpression(token);
+      } else if (identical(value, 'new')) {
+        return parseNewExpression(token);
+      } else if (identical(value, 'const')) {
+        return parseConstExpression(token);
+      } else if (identical(value, 'void')) {
+        return parseFunctionExpression(token);
+      } else if (token.isIdentifier()) {
+        return parseSendOrFunctionLiteral(token);
+      } else {
+        return listener.expectedExpression(token);
+      }
+    } else if (identical(kind, OPEN_PAREN_TOKEN)) {
+      return parseParenthesizedExpressionOrFunctionLiteral(token);
+    } else if ((identical(kind, LT_TOKEN)) ||
+               (identical(kind, OPEN_SQUARE_BRACKET_TOKEN)) ||
+               (identical(kind, OPEN_CURLY_BRACKET_TOKEN)) ||
+               identical(token.stringValue, '[]')) {
+      return parseLiteralListOrMap(token);
+    } else if (identical(kind, QUESTION_TOKEN)) {
+      return parseArgumentDefinitionTest(token);
+    } else {
+      return listener.expectedExpression(token);
+    }
+  }
+
+  Token parseArgumentDefinitionTest(Token token) {
+    Token questionToken = token;
+    listener.beginArgumentDefinitionTest(questionToken);
+    assert(optional('?', token));
+    token = parseIdentifier(token.next);
+    listener.endArgumentDefinitionTest(questionToken, token);
+    return token;
+  }
+
+  Token parseParenthesizedExpressionOrFunctionLiteral(Token token) {
+    BeginGroupToken beginGroup = token;
+    int kind = beginGroup.endGroup.next.kind;
+    if (mayParseFunctionExpressions &&
+        (identical(kind, FUNCTION_TOKEN)
+            || identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
+      return parseUnamedFunction(token);
+    } else {
+      bool old = mayParseFunctionExpressions;
+      mayParseFunctionExpressions = true;
+      token = parseParenthesizedExpression(token);
+      mayParseFunctionExpressions = old;
+      return token;
+    }
+  }
+
+  Token parseParenthesizedExpression(Token token) {
+    var begin = token;
+    token = expect('(', token);
+    token = parseExpression(token);
+    if (!identical(begin.endGroup, token)) {
+      listener.unexpected(token);
+      token = begin.endGroup;
+    }
+    listener.handleParenthesizedExpression(begin);
+    return expect(')', token);
+  }
+
+  Token parseThisExpression(Token token) {
+    listener.handleThisExpression(token);
+    token = token.next;
+    if (optional('(', token)) {
+      // Constructor forwarding.
+      token = parseArguments(token);
+      listener.endSend(token);
+    }
+    return token;
+  }
+
+  Token parseSuperExpression(Token token) {
+    listener.handleSuperExpression(token);
+    token = token.next;
+    if (optional('(', token)) {
+      // Super constructor.
+      token = parseArguments(token);
+      listener.endSend(token);
+    }
+    return token;
+  }
+
+  Token parseLiteralListOrMap(Token token) {
+    Token constKeyword = null;
+    if (optional('const', token)) {
+      constKeyword = token;
+      token = token.next;
+    }
+    token = parseTypeArgumentsOpt(token);
+    Token beginToken = token;
+    int count = 0;
+    if (optional('{', token)) {
+      bool old = mayParseFunctionExpressions;
+      mayParseFunctionExpressions = true;
+      do {
+        if (optional('}', token.next)) {
+          token = token.next;
+          break;
+        }
+        token = parseMapLiteralEntry(token.next);
+        ++count;
+      } while (optional(',', token));
+      mayParseFunctionExpressions = old;
+      listener.handleLiteralMap(count, beginToken, constKeyword, token);
+      return expect('}', token);
+    } else if (optional('[', token)) {
+      bool old = mayParseFunctionExpressions;
+      mayParseFunctionExpressions = true;
+      do {
+        if (optional(']', token.next)) {
+          token = token.next;
+          break;
+        }
+        token = parseExpression(token.next);
+        ++count;
+      } while (optional(',', token));
+      mayParseFunctionExpressions = old;
+      listener.handleLiteralList(count, beginToken, constKeyword, token);
+      return expect(']', token);
+    } else if (optional('[]', token)) {
+      listener.handleLiteralList(0, token, constKeyword, token);
+      return token.next;
+    } else {
+      listener.unexpected(token);
+    }
+  }
+
+  Token parseMapLiteralEntry(Token token) {
+    listener.beginLiteralMapEntry(token);
+    // Assume the listener rejects non-string keys.
+    token = parseExpression(token);
+    Token colon = token;
+    token = expect(':', token);
+    token = parseExpression(token);
+    listener.endLiteralMapEntry(colon, token);
+    return token;
+  }
+
+  Token parseSendOrFunctionLiteral(Token token) {
+    if (!mayParseFunctionExpressions) return parseSend(token);
+    Token peek = peekAfterExpectedType(token);
+    if (identical(peek.kind, IDENTIFIER_TOKEN) && isFunctionDeclaration(peek.next)) {
+      return parseFunctionExpression(token);
+    } else if (isFunctionDeclaration(token.next)) {
+      return parseFunctionExpression(token);
+    } else {
+      return parseSend(token);
+    }
+  }
+
+  bool isFunctionDeclaration(Token token) {
+    if (optional('(', token)) {
+      BeginGroupToken begin = token;
+      String afterParens = begin.endGroup.next.stringValue;
+      if (identical(afterParens, '{') || identical(afterParens, '=>')) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Token parseRequiredArguments(Token token) {
+    if (optional('(', token)) {
+      token = parseArguments(token);
+    } else {
+      listener.handleNoArguments(token);
+      token = listener.unexpected(token);
+    }
+    return token;
+  }
+
+  Token parseNewExpression(Token token) {
+    Token newKeyword = token;
+    token = expect('new', token);
+    token = parseConstructorReference(token);
+    token = parseRequiredArguments(token);
+    listener.handleNewExpression(newKeyword);
+    return token;
+  }
+
+  Token parseConstExpression(Token token) {
+    Token constKeyword = token;
+    token = expect('const', token);
+    final String value = token.stringValue;
+    if ((identical(value, '<')) ||
+        (identical(value, '[')) ||
+        (identical(value, '[]')) ||
+        (identical(value, '{'))) {
+      return parseLiteralListOrMap(constKeyword);
+    }
+    token = parseConstructorReference(token);
+    token = parseRequiredArguments(token);
+    listener.handleConstExpression(constKeyword);
+    return token;
+  }
+
+  Token parseLiteralInt(Token token) {
+    listener.handleLiteralInt(token);
+    return token.next;
+  }
+
+  Token parseLiteralDouble(Token token) {
+    listener.handleLiteralDouble(token);
+    return token.next;
+  }
+
+  Token parseLiteralString(Token token) {
+    token = parseSingleLiteralString(token);
+    int count = 1;
+    while (identical(token.kind, STRING_TOKEN)) {
+      token = parseSingleLiteralString(token);
+      count++;
+    }
+    if (count > 1) {
+      listener.handleStringJuxtaposition(count);
+    }
+    return token;
+  }
+
+  /**
+   * Only called when [:token.kind === STRING_TOKEN:].
+   */
+  Token parseSingleLiteralString(Token token) {
+    listener.beginLiteralString(token);
+    // Parsing the prefix, for instance 'x of 'x${id}y${id}z'
+    token = token.next;
+    int interpolationCount = 0;
+    var kind = token.kind;
+    while (kind != EOF_TOKEN) {
+      if (identical(kind, STRING_INTERPOLATION_TOKEN)) {
+        // Parsing ${expression}.
+        token = token.next;
+        token = parseExpression(token);
+        token = expect('}', token);
+      } else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) {
+        // Parsing $identifier.
+        token = token.next;
+        token = parseExpression(token);
+      } else {
+        break;
+      }
+      ++interpolationCount;
+      // Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z'
+      token = parseStringPart(token);
+      kind = token.kind;
+    }
+    listener.endLiteralString(interpolationCount);
+    return token;
+  }
+
+  Token parseLiteralBool(Token token) {
+    listener.handleLiteralBool(token);
+    return token.next;
+  }
+
+  Token parseLiteralNull(Token token) {
+    listener.handleLiteralNull(token);
+    return token.next;
+  }
+
+  Token parseSend(Token token) {
+    listener.beginSend(token);
+    token = parseIdentifier(token);
+    token = parseArgumentsOpt(token);
+    listener.endSend(token);
+    return token;
+  }
+
+  Token parseArgumentsOpt(Token token) {
+    if (!optional('(', token)) {
+      listener.handleNoArguments(token);
+      return token;
+    } else {
+      return parseArguments(token);
+    }
+  }
+
+  Token parseArguments(Token token) {
+    Token begin = token;
+    listener.beginArguments(begin);
+    assert(identical('(', token.stringValue));
+    int argumentCount = 0;
+    if (optional(')', token.next)) {
+      listener.endArguments(argumentCount, begin, token.next);
+      return token.next.next;
+    }
+    bool old = mayParseFunctionExpressions;
+    mayParseFunctionExpressions = true;
+    do {
+      Token colon = null;
+      if (optional(':', token.next.next)) {
+        token = parseIdentifier(token.next);
+        colon = token;
+      }
+      token = parseExpression(token.next);
+      if (colon != null) listener.handleNamedArgument(colon);
+      ++argumentCount;
+    } while (optional(',', token));
+    mayParseFunctionExpressions = old;
+    listener.endArguments(argumentCount, begin, token);
+    return expect(')', token);
+  }
+
+  Token parseIsOperatorRest(Token token) {
+    assert(optional('is', token));
+    Token operator = token;
+    Token not = null;
+    if (optional('!', token.next)) {
+      token = token.next;
+      not = token;
+    }
+    token = parseType(token.next);
+    listener.handleIsOperator(operator, not, token);
+    String value = token.stringValue;
+    if (identical(value, 'is') || identical(value, 'as')) {
+      // The is- and as-operators cannot be chained, but they can take part of
+      // expressions like: foo is Foo || foo is Bar.
+      listener.unexpected(token);
+    }
+    return token;
+  }
+
+  Token parseAsOperatorRest(Token token) {
+    assert(optional('as', token));
+    Token operator = token;
+    token = parseType(token.next);
+    listener.handleAsOperator(operator, token);
+    String value = token.stringValue;
+    if (identical(value, 'is') || identical(value, 'as')) {
+      // The is- and as-operators cannot be chained.
+      listener.unexpected(token);
+    }
+    return token;
+  }
+
+  Token parseVariablesDeclaration(Token token) {
+    return parseVariablesDeclarationMaybeSemicolon(token, true);
+  }
+
+  Token parseVariablesDeclarationNoSemicolon(Token token) {
+    return parseVariablesDeclarationMaybeSemicolon(token, false);
+  }
+
+  Token parseVariablesDeclarationMaybeSemicolon(Token token,
+                                                bool endWithSemicolon) {
+    int count = 1;
+    listener.beginVariablesDeclaration(token);
+    token = parseModifiers(token);
+    token = parseTypeOpt(token);
+    token = parseOptionallyInitializedIdentifier(token);
+    while (optional(',', token)) {
+      token = parseOptionallyInitializedIdentifier(token.next);
+      ++count;
+    }
+    if (endWithSemicolon) {
+      Token semicolon = token;
+      token = expectSemicolon(semicolon);
+      listener.endVariablesDeclaration(count, semicolon);
+      return token;
+    } else {
+      listener.endVariablesDeclaration(count, null);
+      return token;
+    }
+  }
+
+  Token parseOptionallyInitializedIdentifier(Token token) {
+    listener.beginInitializedIdentifier(token);
+    token = parseIdentifier(token);
+    token = parseVariableInitializerOpt(token);
+    listener.endInitializedIdentifier();
+    return token;
+  }
+
+  Token parseIfStatement(Token token) {
+    Token ifToken = token;
+    listener.beginIfStatement(ifToken);
+    token = expect('if', token);
+    token = parseParenthesizedExpression(token);
+    token = parseStatement(token);
+    Token elseToken = null;
+    if (optional('else', token)) {
+      elseToken = token;
+      token = parseStatement(token.next);
+    }
+    listener.endIfStatement(ifToken, elseToken);
+    return token;
+  }
+
+  Token parseForStatement(Token token) {
+    Token forToken = token;
+    listener.beginForStatement(forToken);
+    token = expect('for', token);
+    token = expect('(', token);
+    token = parseVariablesDeclarationOrExpressionOpt(token);
+    if (optional('in', token)) {
+      return parseForInRest(forToken, token);
+    } else {
+      return parseForRest(forToken, token);
+    }
+  }
+
+  Token parseVariablesDeclarationOrExpressionOpt(Token token) {
+    final String value = token.stringValue;
+    if (identical(value, ';')) {
+      listener.handleNoExpression(token);
+      return token;
+    } else if ((identical(value, 'var')) || (identical(value, 'final'))) {
+      return parseVariablesDeclarationNoSemicolon(token);
+    }
+    Token identifier = peekIdentifierAfterType(token);
+    if (identifier != null) {
+      assert(identifier.isIdentifier());
+      if (isOneOf4(identifier.next, '=', ';', ',', 'in')) {
+        return parseVariablesDeclarationNoSemicolon(token);
+      }
+    }
+    return parseExpression(token);
+  }
+
+  Token parseForRest(Token forToken, Token token) {
+    token = expectSemicolon(token);
+    if (optional(';', token)) {
+      token = parseEmptyStatement(token);
+    } else {
+      token = parseExpressionStatement(token);
+    }
+    int expressionCount = 0;
+    while (true) {
+      if (optional(')', token)) break;
+      token = parseExpression(token);
+      ++expressionCount;
+      if (optional(',', token)) {
+        token = token.next;
+      } else {
+        break;
+      }
+    }
+    token = expect(')', token);
+    token = parseStatement(token);
+    listener.endForStatement(expressionCount, forToken, token);
+    return token;
+  }
+
+  Token parseForInRest(Token forToken, Token token) {
+    assert(optional('in', token));
+    Token inKeyword = token;
+    token = parseExpression(token.next);
+    token = expect(')', token);
+    token = parseStatement(token);
+    listener.endForIn(forToken, inKeyword, token);
+    return token;
+  }
+
+  Token parseWhileStatement(Token token) {
+    Token whileToken = token;
+    listener.beginWhileStatement(whileToken);
+    token = expect('while', token);
+    token = parseParenthesizedExpression(token);
+    token = parseStatement(token);
+    listener.endWhileStatement(whileToken, token);
+    return token;
+  }
+
+  Token parseDoWhileStatement(Token token) {
+    Token doToken = token;
+    listener.beginDoWhileStatement(doToken);
+    token = expect('do', token);
+    token = parseStatement(token);
+    Token whileToken = token;
+    token = expect('while', token);
+    token = parseParenthesizedExpression(token);
+    listener.endDoWhileStatement(doToken, whileToken, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseBlock(Token token) {
+    Token begin = token;
+    listener.beginBlock(begin);
+    int statementCount = 0;
+    token = expect('{', token);
+    while (notEofOrValue('}', token)) {
+      token = parseStatement(token);
+      ++statementCount;
+    }
+    listener.endBlock(statementCount, begin, token);
+    return expect('}', token);
+  }
+
+  Token parseThrowStatement(Token token) {
+    Token throwToken = token;
+    listener.beginThrowStatement(throwToken);
+    token = expect('throw', token);
+    if (optional(';', token)) {
+      listener.endRethrowStatement(throwToken, token);
+      return token.next;
+    } else {
+      token = parseExpression(token);
+      listener.endThrowStatement(throwToken, token);
+      return expectSemicolon(token);
+    }
+  }
+
+  Token parseTryStatement(Token token) {
+    assert(optional('try', token));
+    Token tryKeyword = token;
+    listener.beginTryStatement(tryKeyword);
+    token = parseBlock(token.next);
+    int catchCount = 0;
+
+    String value = token.stringValue;
+    while (identical(value, 'catch') || identical(value, 'on')) {
+      var onKeyword = null;
+      if (identical(value, 'on')) {
+        // on qualified catchPart?
+        onKeyword = token;
+        token = parseType(token.next);
+        value = token.stringValue;
+      }
+      Token catchKeyword = null;
+      if (identical(value, 'catch')) {
+        catchKeyword = token;
+        // TODO(ahe): Validate the "parameters".
+        token = parseFormalParameters(token.next);
+      }
+      token = parseBlock(token);
+      ++catchCount;
+      listener.handleCatchBlock(onKeyword, catchKeyword);
+      value = token.stringValue; // while condition
+    }
+
+    Token finallyKeyword = null;
+    if (optional('finally', token)) {
+      finallyKeyword = token;
+      token = parseBlock(token.next);
+      listener.handleFinallyBlock(finallyKeyword);
+    }
+    listener.endTryStatement(catchCount, tryKeyword, finallyKeyword);
+    return token;
+  }
+
+  Token parseSwitchStatement(Token token) {
+    assert(optional('switch', token));
+    Token switchKeyword = token;
+    listener.beginSwitchStatement(switchKeyword);
+    token = parseParenthesizedExpression(token.next);
+    token = parseSwitchBlock(token);
+    listener.endSwitchStatement(switchKeyword, token);
+    return token.next;
+  }
+
+  Token parseSwitchBlock(Token token) {
+    Token begin = token;
+    listener.beginSwitchBlock(begin);
+    token = expect('{', token);
+    int caseCount = 0;
+    while (!identical(token.kind, EOF_TOKEN)) {
+      if (optional('}', token)) {
+        break;
+      }
+      token = parseSwitchCase(token);
+      ++caseCount;
+    }
+    listener.endSwitchBlock(caseCount, begin, token);
+    expect('}', token);
+    return token;
+  }
+
+  /**
+   * Peek after the following labels (if any). The following token
+   * is used to determine if the labels belong to a statement or a
+   * switch case.
+   */
+  Token peekPastLabels(Token token) {
+    while (token.isIdentifier() && optional(':', token.next)) {
+      token = token.next.next;
+    }
+    return token;
+  }
+
+  /**
+   * Parse a group of labels, cases and possibly a default keyword and
+   * the statements that they select.
+   */
+  Token parseSwitchCase(Token token) {
+    Token begin = token;
+    Token defaultKeyword = null;
+    int expressionCount = 0;
+    int labelCount = 0;
+    Token peek = peekPastLabels(token);
+    while (true) {
+      // Loop until we find something that can't be part of a switch case.
+      String value = peek.stringValue;
+      if (identical(value, 'default')) {
+        while (!identical(token, peek)) {
+          token = parseLabel(token);
+          labelCount++;
+        }
+        defaultKeyword = token;
+        token = expect(':', token.next);
+        peek = token;
+        break;
+      } else if (identical(value, 'case')) {
+        while (!identical(token, peek)) {
+          token = parseLabel(token);
+          labelCount++;
+        }
+        Token caseKeyword = token;
+        token = parseExpression(token.next);
+        Token colonToken = token;
+        token = expect(':', token);
+        listener.handleCaseMatch(caseKeyword, colonToken);
+        expressionCount++;
+        peek = peekPastLabels(token);
+      } else {
+        if (expressionCount == 0) {
+          listener.expected("case", token);
+        }
+        break;
+      }
+    }
+    // Finally zero or more statements.
+    int statementCount = 0;
+    while (!identical(token.kind, EOF_TOKEN)) {
+      String value = peek.stringValue;
+      if ((identical(value, 'case')) ||
+          (identical(value, 'default')) ||
+          ((identical(value, '}')) && (identical(token, peek)))) {
+        // A label just before "}" will be handled as a statement error.
+        break;
+      } else {
+        token = parseStatement(token);
+      }
+      statementCount++;
+      peek = peekPastLabels(token);
+    }
+    listener.handleSwitchCase(labelCount, expressionCount, defaultKeyword,
+                              statementCount, begin, token);
+    return token;
+  }
+
+  Token parseBreakStatement(Token token) {
+    assert(optional('break', token));
+    Token breakKeyword = token;
+    token = token.next;
+    bool hasTarget = false;
+    if (token.isIdentifier()) {
+      token = parseIdentifier(token);
+      hasTarget = true;
+    }
+    listener.handleBreakStatement(hasTarget, breakKeyword, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseAssertStatement(Token token) {
+    Token assertKeyword = token;
+    token = expect('assert', token);
+    expect('(', token);
+    token = parseArguments(token);
+    listener.handleAssertStatement(assertKeyword, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseContinueStatement(Token token) {
+    assert(optional('continue', token));
+    Token continueKeyword = token;
+    token = token.next;
+    bool hasTarget = false;
+    if (token.isIdentifier()) {
+      token = parseIdentifier(token);
+      hasTarget = true;
+    }
+    listener.handleContinueStatement(hasTarget, continueKeyword, token);
+    return expectSemicolon(token);
+  }
+
+  Token parseEmptyStatement(Token token) {
+    listener.handleEmptyStatement(token);
+    return expectSemicolon(token);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart
new file mode 100644
index 0000000..eca572b
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+class ParserTask extends CompilerTask {
+  ParserTask(Compiler compiler) : super(compiler);
+  String get name => 'Parser';
+
+  Node parse(Element element) {
+    return measure(() => element.parseNode(compiler));
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart
new file mode 100644
index 0000000..02f409a
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+class PartialParser extends Parser {
+  PartialParser(Listener listener) : super(listener);
+
+  Token parseClassBody(Token token) => skipClassBody(token);
+
+  Token fullParseClassBody(Token token) => super.parseClassBody(token);
+
+  Token parseExpression(Token token) => skipExpression(token);
+
+  Token parseArgumentsOpt(Token token) {
+    // This method is overridden for two reasons:
+    // 1. Avoid generating events for arguments.
+    // 2. Avoid calling skip expression for each argument (which doesn't work).
+    if (optional('(', token)) {
+      BeginGroupToken begin = token;
+      return begin.endGroup.next;
+    } else {
+      return token;
+    }
+  }
+
+  Token skipExpression(Token token) {
+    while (true) {
+      final kind = token.kind;
+      final value = token.stringValue;
+      if ((identical(kind, EOF_TOKEN)) ||
+          (identical(value, ';')) ||
+          (identical(value, ',')) ||
+          (identical(value, ']')))
+        return token;
+      if (identical(value, '=')) {
+        var nextValue = token.next.stringValue;
+        if (identical(nextValue, 'const')) {
+          token = token.next;
+          nextValue = token.next.stringValue;
+        }
+        if (identical(nextValue, '{')) {
+          // Handle cases like this:
+          // class Foo {
+          //   var map;
+          //   Foo() : map = {};
+          // }
+          BeginGroupToken begin = token.next;
+          token = (begin.endGroup != null) ? begin.endGroup : token;
+          token = token.next;
+          continue;
+        }
+        if (identical(nextValue, '<')) {
+          // Handle cases like this:
+          // class Foo {
+          //   var map;
+          //   Foo() : map = <String, Foo>{};
+          // }
+          BeginGroupToken begin = token.next;
+          token = (begin.endGroup != null) ? begin.endGroup : token;
+          token = token.next;
+          if (identical(token.stringValue, '{')) {
+            begin = token;
+            token = (begin.endGroup != null) ? begin.endGroup : token;
+            token = token.next;
+          }
+          continue;
+        }
+      }
+      if (!mayParseFunctionExpressions && identical(value, '{')) return token;
+      if (token is BeginGroupToken) {
+        BeginGroupToken begin = token;
+        token = (begin.endGroup != null) ? begin.endGroup : token;
+      }
+      token = token.next;
+    }
+  }
+
+  Token skipClassBody(Token token) {
+    if (!optional('{', token)) {
+      return listener.expectedClassBodyToSkip(token);
+    }
+    BeginGroupToken beginGroupToken = token;
+    Token endGroup = beginGroupToken.endGroup;
+    if (endGroup == null) {
+      return listener.unmatched(beginGroupToken);
+    } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) {
+      return listener.unmatched(beginGroupToken);
+    }
+    return endGroup;
+  }
+
+  Token parseFunctionBody(Token token, bool isExpression) {
+    assert(!isExpression);
+    String value = token.stringValue;
+    if (identical(value, ';')) {
+      // No body.
+    } else if (identical(value, '=>')) {
+      token = parseExpression(token.next);
+      expectSemicolon(token);
+    } else if (value == '=') {
+      token = parseRedirectingFactoryBody(token);
+      expectSemicolon(token);
+    } else {
+      token = skipBlock(token);
+    }
+    // There is no "skipped function body event", so we use
+    // handleNoFunctionBody instead.
+    listener.handleNoFunctionBody(token);
+    return token;
+  }
+
+  Token parseFormalParameters(Token token) => skipFormals(token);
+
+  Token skipFormals(Token token) {
+    listener.beginOptionalFormalParameters(token);
+    if (!optional('(', token)) {
+      if (optional(';', token)) {
+        listener.recoverableError("expected '('", token: token);
+        return token;
+      }
+      return listener.unexpected(token);
+    }
+    BeginGroupToken beginGroupToken = token;
+    Token endToken = beginGroupToken.endGroup;
+    listener.endFormalParameters(0, token, endToken);
+    return endToken.next;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart
new file mode 100644
index 0000000..123b650
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart
@@ -0,0 +1,875 @@
+// 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.
+
+part of scanner;
+
+abstract class Scanner {
+  Token tokenize();
+}
+
+/**
+ * Common base class for a Dart scanner.
+ */
+abstract class AbstractScanner<T extends SourceString> implements Scanner {
+  int advance();
+  int nextByte();
+
+  /**
+   * Returns the current character or byte depending on the underlying input
+   * kind. For example, [StringScanner] operates on [String] and thus returns
+   * characters (Unicode codepoints represented as int) whereas
+   * [ByteArrayScanner] operates on byte arrays and thus returns bytes.
+   */
+  int peek();
+
+  /**
+   * Appends a fixed token based on whether the current char is [choice] or not.
+   * If the current char is [choice] a fixed token whose kind and content
+   * is determined by [yes] is appended, otherwise a fixed token whose kind
+   * and content is determined by [no] is appended.
+   */
+  int select(int choice, PrecedenceInfo yes, PrecedenceInfo no);
+
+  /**
+   * Appends a fixed token whose kind and content is determined by [info].
+   */
+  void appendPrecedenceToken(PrecedenceInfo info);
+
+  /**
+   * Appends a token whose kind is determined by [info] and content is [value].
+   */
+  void appendStringToken(PrecedenceInfo info, String value);
+
+  /**
+   * Appends a token whose kind is determined by [info] and content is defined
+   * by the SourceString [value].
+   */
+  void appendByteStringToken(PrecedenceInfo info, T value);
+
+  /**
+   * Appends a keyword token whose kind is determined by [keyword].
+   */
+  void appendKeywordToken(Keyword keyword);
+  void appendWhiteSpace(int next);
+  void appendEofToken();
+
+  /**
+   * Creates an ASCII SourceString whose content begins at the source byte
+   * offset [start] and ends at [offset] bytes from the current byte offset of
+   * the scanner. For example, if the current byte offset is 10,
+   * [:asciiString(0,-1):] creates an ASCII SourceString whose content is found
+   * at the [0,9[ byte interval of the source text.
+   */
+  T asciiString(int start, int offset);
+  T utf8String(int start, int offset);
+  Token firstToken();
+  Token previousToken();
+  void beginToken();
+  void addToCharOffset(int offset);
+  int get charOffset;
+  int get byteOffset;
+  void appendBeginGroup(PrecedenceInfo info, String value);
+  int appendEndGroup(PrecedenceInfo info, String value, int openKind);
+  void appendGt(PrecedenceInfo info, String value);
+  void appendGtGt(PrecedenceInfo info, String value);
+  void appendGtGtGt(PrecedenceInfo info, String value);
+  void appendComment();
+
+  /**
+   * We call this method to discard '<' from the "grouping" stack
+   * (maintained by subclasses).
+   *
+   * [PartialParser.skipExpression] relies on the fact that we do not
+   * create groups for stuff like:
+   * [:a = b < c, d = e > f:].
+   *
+   * In other words, this method is called when the scanner recognizes
+   * something which cannot possibly be part of a type
+   * parameter/argument list.
+   */
+  void discardOpenLt();
+
+  // TODO(ahe): Move this class to implementation.
+
+  Token tokenize() {
+    int next = advance();
+    while (!identical(next, $EOF)) {
+      next = bigSwitch(next);
+    }
+    appendEofToken();
+    return firstToken();
+  }
+
+  int bigSwitch(int next) {
+    beginToken();
+    if (identical(next, $SPACE) || identical(next, $TAB)
+        || identical(next, $LF) || identical(next, $CR)) {
+      appendWhiteSpace(next);
+      next = advance();
+      while (identical(next, $SPACE)) {
+        appendWhiteSpace(next);
+        next = advance();
+      }
+      return next;
+    }
+
+    if ($a <= next && next <= $z) {
+      if (identical($r, next)) {
+        return tokenizeRawStringKeywordOrIdentifier(next);
+      }
+      return tokenizeKeywordOrIdentifier(next, true);
+    }
+
+    if (($A <= next && next <= $Z) || identical(next, $_) || identical(next, $$)) {
+      return tokenizeIdentifier(next, byteOffset, true);
+    }
+
+    if (identical(next, $LT)) {
+      return tokenizeLessThan(next);
+    }
+
+    if (identical(next, $GT)) {
+      return tokenizeGreaterThan(next);
+    }
+
+    if (identical(next, $EQ)) {
+      return tokenizeEquals(next);
+    }
+
+    if (identical(next, $BANG)) {
+      return tokenizeExclamation(next);
+    }
+
+    if (identical(next, $PLUS)) {
+      return tokenizePlus(next);
+    }
+
+    if (identical(next, $MINUS)) {
+      return tokenizeMinus(next);
+    }
+
+    if (identical(next, $STAR)) {
+      return tokenizeMultiply(next);
+    }
+
+    if (identical(next, $PERCENT)) {
+      return tokenizePercent(next);
+    }
+
+    if (identical(next, $AMPERSAND)) {
+      return tokenizeAmpersand(next);
+    }
+
+    if (identical(next, $BAR)) {
+      return tokenizeBar(next);
+    }
+
+    if (identical(next, $CARET)) {
+      return tokenizeCaret(next);
+    }
+
+    if (identical(next, $OPEN_SQUARE_BRACKET)) {
+      return tokenizeOpenSquareBracket(next);
+    }
+
+    if (identical(next, $TILDE)) {
+      return tokenizeTilde(next);
+    }
+
+    if (identical(next, $BACKSLASH)) {
+      appendPrecedenceToken(BACKSLASH_INFO);
+      return advance();
+    }
+
+    if (identical(next, $HASH)) {
+      return tokenizeTag(next);
+    }
+
+    if (identical(next, $OPEN_PAREN)) {
+      appendBeginGroup(OPEN_PAREN_INFO, "(");
+      return advance();
+    }
+
+    if (identical(next, $CLOSE_PAREN)) {
+      return appendEndGroup(CLOSE_PAREN_INFO, ")", OPEN_PAREN_TOKEN);
+    }
+
+    if (identical(next, $COMMA)) {
+      appendPrecedenceToken(COMMA_INFO);
+      return advance();
+    }
+
+    if (identical(next, $COLON)) {
+      appendPrecedenceToken(COLON_INFO);
+      return advance();
+    }
+
+    if (identical(next, $SEMICOLON)) {
+      appendPrecedenceToken(SEMICOLON_INFO);
+      // Type parameters and arguments cannot contain semicolon.
+      discardOpenLt();
+      return advance();
+    }
+
+    if (identical(next, $QUESTION)) {
+      appendPrecedenceToken(QUESTION_INFO);
+      return advance();
+    }
+
+    if (identical(next, $CLOSE_SQUARE_BRACKET)) {
+      return appendEndGroup(CLOSE_SQUARE_BRACKET_INFO, "]",
+                            OPEN_SQUARE_BRACKET_TOKEN);
+    }
+
+    if (identical(next, $BACKPING)) {
+      appendPrecedenceToken(BACKPING_INFO);
+      return advance();
+    }
+
+    if (identical(next, $OPEN_CURLY_BRACKET)) {
+      appendBeginGroup(OPEN_CURLY_BRACKET_INFO, "{");
+      return advance();
+    }
+
+    if (identical(next, $CLOSE_CURLY_BRACKET)) {
+      return appendEndGroup(CLOSE_CURLY_BRACKET_INFO, "}",
+                            OPEN_CURLY_BRACKET_TOKEN);
+    }
+
+    if (identical(next, $SLASH)) {
+      return tokenizeSlashOrComment(next);
+    }
+
+    if (identical(next, $AT)) {
+      return tokenizeAt(next);
+    }
+
+    if (identical(next, $DQ) || identical(next, $SQ)) {
+      return tokenizeString(next, byteOffset, false);
+    }
+
+    if (identical(next, $PERIOD)) {
+      return tokenizeDotsOrNumber(next);
+    }
+
+    if (identical(next, $0)) {
+      return tokenizeHexOrNumber(next);
+    }
+
+    // TODO(ahe): Would a range check be faster?
+    if (identical(next, $1) || identical(next, $2) || identical(next, $3)
+        || identical(next, $4) ||  identical(next, $5) || identical(next, $6)
+        || identical(next, $7) || identical(next, $8) || identical(next, $9)) {
+      return tokenizeNumber(next);
+    }
+
+    if (identical(next, $EOF)) {
+      return $EOF;
+    }
+    if (next < 0x1f) {
+      return error(new SourceString("unexpected character $next"));
+    }
+
+    // The following are non-ASCII characters.
+
+    if (identical(next, $NBSP)) {
+      appendWhiteSpace(next);
+      return advance();
+    }
+
+    return tokenizeIdentifier(next, byteOffset, true);
+  }
+
+  int tokenizeTag(int next) {
+    // # or #!.*[\n\r]
+    if (byteOffset == 0) {
+      if (identical(peek(), $BANG)) {
+        do {
+          next = advance();
+        } while (!identical(next, $LF) && !identical(next, $CR) && !identical(next, $EOF));
+        return next;
+      }
+    }
+    appendPrecedenceToken(HASH_INFO);
+    return advance();
+  }
+
+  int tokenizeTilde(int next) {
+    // ~ ~/ ~/=
+    next = advance();
+    if (identical(next, $SLASH)) {
+      return select($EQ, TILDE_SLASH_EQ_INFO, TILDE_SLASH_INFO);
+    } else {
+      appendPrecedenceToken(TILDE_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeOpenSquareBracket(int next) {
+    // [ [] []=
+    next = advance();
+    if (identical(next, $CLOSE_SQUARE_BRACKET)) {
+      Token token = previousToken();
+      if (token is KeywordToken && identical(token.value.stringValue, 'operator')) {
+        return select($EQ, INDEX_EQ_INFO, INDEX_INFO);
+      }
+    }
+    appendBeginGroup(OPEN_SQUARE_BRACKET_INFO, "[");
+    return next;
+  }
+
+  int tokenizeCaret(int next) {
+    // ^ ^=
+    return select($EQ, CARET_EQ_INFO, CARET_INFO);
+  }
+
+  int tokenizeBar(int next) {
+    // | || |=
+    next = advance();
+    if (identical(next, $BAR)) {
+      appendPrecedenceToken(BAR_BAR_INFO);
+      return advance();
+    } else if (identical(next, $EQ)) {
+      appendPrecedenceToken(BAR_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(BAR_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeAmpersand(int next) {
+    // && &= &
+    next = advance();
+    if (identical(next, $AMPERSAND)) {
+      appendPrecedenceToken(AMPERSAND_AMPERSAND_INFO);
+      return advance();
+    } else if (identical(next, $EQ)) {
+      appendPrecedenceToken(AMPERSAND_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(AMPERSAND_INFO);
+      return next;
+    }
+  }
+
+  int tokenizePercent(int next) {
+    // % %=
+    return select($EQ, PERCENT_EQ_INFO, PERCENT_INFO);
+  }
+
+  int tokenizeMultiply(int next) {
+    // * *=
+    return select($EQ, STAR_EQ_INFO, STAR_INFO);
+  }
+
+  int tokenizeMinus(int next) {
+    // - -- -=
+    next = advance();
+    if (identical(next, $MINUS)) {
+      appendPrecedenceToken(MINUS_MINUS_INFO);
+      return advance();
+    } else if (identical(next, $EQ)) {
+      appendPrecedenceToken(MINUS_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(MINUS_INFO);
+      return next;
+    }
+  }
+
+
+  int tokenizePlus(int next) {
+    // + ++ +=
+    next = advance();
+    if (identical($PLUS, next)) {
+      appendPrecedenceToken(PLUS_PLUS_INFO);
+      return advance();
+    } else if (identical($EQ, next)) {
+      appendPrecedenceToken(PLUS_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(PLUS_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeExclamation(int next) {
+    // ! != !==
+    next = advance();
+    if (identical(next, $EQ)) {
+      return select($EQ, BANG_EQ_EQ_INFO, BANG_EQ_INFO);
+    }
+    appendPrecedenceToken(BANG_INFO);
+    return next;
+  }
+
+  int tokenizeEquals(int next) {
+    // = == ===
+
+    // Type parameters and arguments cannot contain any token that
+    // starts with '='.
+    discardOpenLt();
+
+    next = advance();
+    if (identical(next, $EQ)) {
+      return select($EQ, EQ_EQ_EQ_INFO, EQ_EQ_INFO);
+    } else if (identical(next, $GT)) {
+      appendPrecedenceToken(FUNCTION_INFO);
+      return advance();
+    }
+    appendPrecedenceToken(EQ_INFO);
+    return next;
+  }
+
+  int tokenizeGreaterThan(int next) {
+    // > >= >> >>= >>> >>>=
+    next = advance();
+    if (identical($EQ, next)) {
+      appendPrecedenceToken(GT_EQ_INFO);
+      return advance();
+    } else if (identical($GT, next)) {
+      next = advance();
+      if (identical($EQ, next)) {
+        appendPrecedenceToken(GT_GT_EQ_INFO);
+        return advance();
+      } else {
+        appendGtGt(GT_GT_INFO, ">>");
+        return next;
+      }
+    } else {
+      appendGt(GT_INFO, ">");
+      return next;
+    }
+  }
+
+  int tokenizeLessThan(int next) {
+    // < <= << <<=
+    next = advance();
+    if (identical($EQ, next)) {
+      appendPrecedenceToken(LT_EQ_INFO);
+      return advance();
+    } else if (identical($LT, next)) {
+      return select($EQ, LT_LT_EQ_INFO, LT_LT_INFO);
+    } else {
+      appendBeginGroup(LT_INFO, "<");
+      return next;
+    }
+  }
+
+  int tokenizeNumber(int next) {
+    int start = byteOffset;
+    while (true) {
+      next = advance();
+      if ($0 <= next && next <= $9) {
+        continue;
+      } else if (identical(next, $PERIOD)) {
+        return tokenizeFractionPart(advance(), start);
+      } else if (identical(next, $e) || identical(next, $E)
+          || identical(next, $d) || identical(next, $D)) {
+        return tokenizeFractionPart(next, start);
+      } else {
+        appendByteStringToken(INT_INFO, asciiString(start, 0));
+        return next;
+      }
+    }
+  }
+
+  int tokenizeHexOrNumber(int next) {
+    int x = peek();
+    if (identical(x, $x) || identical(x, $X)) {
+      advance();
+      return tokenizeHex(x);
+    }
+    return tokenizeNumber(next);
+  }
+
+  int tokenizeHex(int next) {
+    int start = byteOffset - 1;
+    bool hasDigits = false;
+    while (true) {
+      next = advance();
+      if (($0 <= next && next <= $9)
+          || ($A <= next && next <= $F)
+          || ($a <= next && next <= $f)) {
+        hasDigits = true;
+      } else {
+        if (!hasDigits) {
+          return error(const SourceString("hex digit expected"));
+        }
+        appendByteStringToken(HEXADECIMAL_INFO, asciiString(start, 0));
+        return next;
+      }
+    }
+  }
+
+  int tokenizeDotsOrNumber(int next) {
+    int start = byteOffset;
+    next = advance();
+    if (($0 <= next && next <= $9)) {
+      return tokenizeFractionPart(next, start);
+    } else if (identical($PERIOD, next)) {
+      return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO);
+    } else {
+      appendPrecedenceToken(PERIOD_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeFractionPart(int next, int start) {
+    bool done = false;
+    bool hasDigit = false;
+    LOOP: while (!done) {
+      if ($0 <= next && next <= $9) {
+        hasDigit = true;
+      } else if (identical($e, next) || identical($E, next)) {
+        hasDigit = true;
+        next = tokenizeExponent(advance());
+        done = true;
+        continue LOOP;
+      } else {
+        done = true;
+        continue LOOP;
+      }
+      next = advance();
+    }
+    if (!hasDigit) {
+      appendByteStringToken(INT_INFO, asciiString(start, -1));
+      if (identical($PERIOD, next)) {
+        return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO);
+      }
+      // TODO(ahe): Wrong offset for the period.
+      appendPrecedenceToken(PERIOD_INFO);
+      return bigSwitch(next);
+    }
+    if (identical(next, $d) || identical(next, $D)) {
+      next = advance();
+    }
+    appendByteStringToken(DOUBLE_INFO, asciiString(start, 0));
+    return next;
+  }
+
+  int tokenizeExponent(int next) {
+    if (identical(next, $PLUS) || identical(next, $MINUS)) {
+      next = advance();
+    }
+    bool hasDigits = false;
+    while (true) {
+      if ($0 <= next && next <= $9) {
+        hasDigits = true;
+      } else {
+        if (!hasDigits) {
+          return error(const SourceString("digit expected"));
+        }
+        return next;
+      }
+      next = advance();
+    }
+  }
+
+  int tokenizeSlashOrComment(int next) {
+    next = advance();
+    if (identical($STAR, next)) {
+      return tokenizeMultiLineComment(next);
+    } else if (identical($SLASH, next)) {
+      return tokenizeSingleLineComment(next);
+    } else if (identical($EQ, next)) {
+      appendPrecedenceToken(SLASH_EQ_INFO);
+      return advance();
+    } else {
+      appendPrecedenceToken(SLASH_INFO);
+      return next;
+    }
+  }
+
+  int tokenizeSingleLineComment(int next) {
+    while (true) {
+      next = advance();
+      if (identical($LF, next) || identical($CR, next) || identical($EOF, next)) {
+        appendComment();
+        return next;
+      }
+    }
+  }
+
+  int tokenizeMultiLineComment(int next) {
+    int nesting = 1;
+    next = advance();
+    while (true) {
+      if (identical($EOF, next)) {
+        // TODO(ahe): Report error.
+        return next;
+      } else if (identical($STAR, next)) {
+        next = advance();
+        if (identical($SLASH, next)) {
+          --nesting;
+          if (0 == nesting) {
+            next = advance();
+            appendComment();
+            return next;
+          } else {
+            next = advance();
+          }
+        }
+      } else if (identical($SLASH, next)) {
+        next = advance();
+        if (identical($STAR, next)) {
+          next = advance();
+          ++nesting;
+        }
+      } else {
+        next = advance();
+      }
+    }
+  }
+
+  int tokenizeRawStringKeywordOrIdentifier(int next) {
+    int nextnext = peek();
+    if (identical(nextnext, $DQ) || identical(nextnext, $SQ)) {
+      int start = byteOffset;
+      next = advance();
+      return tokenizeString(next, start, true);
+    }
+    return tokenizeKeywordOrIdentifier(next, true);
+  }
+
+  int tokenizeKeywordOrIdentifier(int next, bool allowDollar) {
+    KeywordState state = KeywordState.KEYWORD_STATE;
+    int start = byteOffset;
+    while (state != null && $a <= next && next <= $z) {
+      state = state.next(next);
+      next = advance();
+    }
+    if (state == null || state.keyword == null) {
+      return tokenizeIdentifier(next, start, allowDollar);
+    }
+    if (($A <= next && next <= $Z) ||
+        ($0 <= next && next <= $9) ||
+        identical(next, $_) ||
+        identical(next, $$)) {
+      return tokenizeIdentifier(next, start, allowDollar);
+    } else if (next < 128) {
+      appendKeywordToken(state.keyword);
+      return next;
+    } else {
+      return tokenizeIdentifier(next, start, allowDollar);
+    }
+  }
+
+  int tokenizeIdentifier(int next, int start, bool allowDollar) {
+    bool isAscii = true;
+
+    // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support.
+    bool isDynamicBuiltIn = false;
+
+    if (identical(next, $D)) {
+      next = advance();
+      if (identical(next, $y)) {
+        next = advance();
+        if (identical(next, $n)) {
+          next = advance();
+          if (identical(next, $a)) {
+            next = advance();
+            if (identical(next, $m)) {
+              next = advance();
+              if (identical(next, $i)) {
+                next = advance();
+                if (identical(next, $c)) {
+                  isDynamicBuiltIn = true;
+                  next = advance();
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    while (true) {
+      if (($a <= next && next <= $z) ||
+          ($A <= next && next <= $Z) ||
+          ($0 <= next && next <= $9) ||
+          identical(next, $_) ||
+          (identical(next, $$) && allowDollar)) {
+        isDynamicBuiltIn = false;
+        next = advance();
+      } else if ((next < 128) || (identical(next, $NBSP))) {
+        // Identifier ends here.
+        if (start == byteOffset) {
+          return error(const SourceString("expected identifier"));
+        } else if (isDynamicBuiltIn) {
+          appendKeywordToken(Keyword.DYNAMIC_DEPRECATED);
+        } else if (isAscii) {
+          appendByteStringToken(IDENTIFIER_INFO, asciiString(start, 0));
+        } else {
+          appendByteStringToken(BAD_INPUT_INFO, utf8String(start, -1));
+        }
+        return next;
+      } else {
+        isDynamicBuiltIn = false;
+        int nonAsciiStart = byteOffset;
+        do {
+          next = nextByte();
+          if (identical(next, $NBSP)) break;
+        } while (next > 127);
+        String string = utf8String(nonAsciiStart, -1).slowToString();
+        isAscii = false;
+        int byteLength = nonAsciiStart - byteOffset;
+        addToCharOffset(string.length - byteLength);
+      }
+    }
+  }
+
+  int tokenizeAt(int next) {
+    int start = byteOffset;
+    next = advance();
+    appendPrecedenceToken(AT_INFO);
+    return next;
+  }
+
+  int tokenizeString(int next, int start, bool raw) {
+    int quoteChar = next;
+    next = advance();
+    if (identical(quoteChar, next)) {
+      next = advance();
+      if (identical(quoteChar, next)) {
+        // Multiline string.
+        return tokenizeMultiLineString(quoteChar, start, raw);
+      } else {
+        // Empty string.
+        appendByteStringToken(STRING_INFO, utf8String(start, -1));
+        return next;
+      }
+    }
+    if (raw) {
+      return tokenizeSingleLineRawString(next, quoteChar, start);
+    } else {
+      return tokenizeSingleLineString(next, quoteChar, start);
+    }
+  }
+
+  static bool isHexDigit(int character) {
+    if ($0 <= character && character <= $9) return true;
+    character |= 0x20;
+    return ($a <= character && character <= $f);
+  }
+
+  int tokenizeSingleLineString(int next, int quoteChar, int start) {
+    while (!identical(next, quoteChar)) {
+      if (identical(next, $BACKSLASH)) {
+        next = advance();
+      } else if (identical(next, $$)) {
+        next = tokenizeStringInterpolation(start);
+        start = byteOffset;
+        continue;
+      }
+      if (next <= $CR
+          && (identical(next, $LF) || identical(next, $CR) || identical(next, $EOF))) {
+        return error(const SourceString("unterminated string literal"));
+      }
+      next = advance();
+    }
+    appendByteStringToken(STRING_INFO, utf8String(start, 0));
+    return advance();
+  }
+
+  int tokenizeStringInterpolation(int start) {
+    appendByteStringToken(STRING_INFO, utf8String(start, -1));
+    beginToken(); // $ starts here.
+    int next = advance();
+    if (identical(next, $OPEN_CURLY_BRACKET)) {
+      return tokenizeInterpolatedExpression(next, start);
+    } else {
+      return tokenizeInterpolatedIdentifier(next, start);
+    }
+  }
+
+  int tokenizeInterpolatedExpression(int next, int start) {
+    appendBeginGroup(STRING_INTERPOLATION_INFO, "\${");
+    beginToken(); // The expression starts here.
+    next = advance();
+    while (!identical(next, $EOF) && !identical(next, $STX)) {
+      next = bigSwitch(next);
+    }
+    if (identical(next, $EOF)) return next;
+    next = advance();
+    beginToken(); // The string interpolation suffix starts here.
+    return next;
+  }
+
+  int tokenizeInterpolatedIdentifier(int next, int start) {
+    appendPrecedenceToken(STRING_INTERPOLATION_IDENTIFIER_INFO);
+    beginToken(); // The identifier starts here.
+    next = tokenizeKeywordOrIdentifier(next, false);
+    beginToken(); // The string interpolation suffix starts here.
+    return next;
+  }
+
+  int tokenizeSingleLineRawString(int next, int quoteChar, int start) {
+    next = advance();
+    while (next != $EOF) {
+      if (identical(next, quoteChar)) {
+        appendByteStringToken(STRING_INFO, utf8String(start, 0));
+        return advance();
+      } else if (identical(next, $LF) || identical(next, $CR)) {
+        return error(const SourceString("unterminated string literal"));
+      }
+      next = advance();
+    }
+    return error(const SourceString("unterminated string literal"));
+  }
+
+  int tokenizeMultiLineRawString(int quoteChar, int start) {
+    int next = advance();
+    outer: while (!identical(next, $EOF)) {
+      while (!identical(next, quoteChar)) {
+        next = advance();
+        if (identical(next, $EOF)) break outer;
+      }
+      next = advance();
+      if (identical(next, quoteChar)) {
+        next = advance();
+        if (identical(next, quoteChar)) {
+          appendByteStringToken(STRING_INFO, utf8String(start, 0));
+          return advance();
+        }
+      }
+    }
+    return error(const SourceString("unterminated string literal"));
+  }
+
+  int tokenizeMultiLineString(int quoteChar, int start, bool raw) {
+    if (raw) return tokenizeMultiLineRawString(quoteChar, start);
+    int next = advance();
+    while (!identical(next, $EOF)) {
+      if (identical(next, $$)) {
+        next = tokenizeStringInterpolation(start);
+        start = byteOffset;
+        continue;
+      }
+      if (identical(next, quoteChar)) {
+        next = advance();
+        if (identical(next, quoteChar)) {
+          next = advance();
+          if (identical(next, quoteChar)) {
+            appendByteStringToken(STRING_INFO, utf8String(start, 0));
+            return advance();
+          }
+        }
+        continue;
+      }
+      if (identical(next, $BACKSLASH)) {
+        next = advance();
+        if (identical(next, $EOF)) break;
+      }
+      next = advance();
+    }
+    return error(const SourceString("unterminated string literal"));
+  }
+
+  int error(SourceString message) {
+    appendByteStringToken(BAD_INPUT_INFO, message);
+    return advance(); // Ensure progress.
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart
new file mode 100644
index 0000000..1bd8327
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2011, 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.
+
+library scanner_implementation;
+
+import 'scannerlib.dart';
+import '../util/util.dart';
+import '../util/characters.dart';
+
+part 'array_based_scanner.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart
new file mode 100644
index 0000000..0738355
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart
@@ -0,0 +1,53 @@
+// 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.
+
+part of scanner;
+
+class ScannerTask extends CompilerTask {
+  ScannerTask(Compiler compiler) : super(compiler);
+  String get name => 'Scanner';
+
+  void scanLibrary(LibraryElement library) {
+    var compilationUnit = library.entryCompilationUnit;
+    var canonicalUri = library.canonicalUri.toString();
+    var resolvedUri = compilationUnit.script.uri.toString();
+    if (canonicalUri == resolvedUri) {
+      compiler.log("scanning library $canonicalUri");
+    } else {
+      compiler.log("scanning library $canonicalUri ($resolvedUri)");
+    }
+    scan(compilationUnit);
+  }
+
+  void scan(CompilationUnitElement compilationUnit) {
+    measure(() {
+      scanElements(compilationUnit);
+    });
+  }
+
+  void scanElements(CompilationUnitElement compilationUnit) {
+    Script script = compilationUnit.script;
+    Token tokens = new StringScanner(script.text,
+        includeComments: compiler.preserveComments).tokenize();
+    if (compiler.preserveComments) {
+      tokens = compiler.processAndStripComments(tokens);
+    }
+    compiler.dietParser.dietParse(compilationUnit, tokens);
+  }
+}
+
+class DietParserTask extends CompilerTask {
+  DietParserTask(Compiler compiler) : super(compiler);
+  final String name = 'Diet Parser';
+
+  dietParse(CompilationUnitElement compilationUnit, Token tokens) {
+    measure(() {
+      Function idGenerator = compiler.getNextFreeClassId;
+      ElementListener listener =
+          new ElementListener(compiler, compilationUnit, idGenerator);
+      PartialParser parser = new PartialParser(listener);
+      parser.parseUnit(tokens);
+    });
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart
new file mode 100644
index 0000000..e3cd591
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2011, 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.
+
+library scanner;
+
+import 'dart:collection' show LinkedHashMap;
+import 'dart:uri';
+
+import 'scanner_implementation.dart';
+import '../elements/elements.dart';
+import '../elements/modelx.dart'
+    show FunctionElementX,
+         TypedefElementX,
+         VariableElementX,
+         VariableListElementX,
+         ClassElementX,
+         MetadataAnnotationX,
+         MixinApplicationElementX;
+import '../dart2jslib.dart';
+import '../native_handler.dart' as native;
+import '../string_validator.dart';
+import '../tree/tree.dart';
+import '../util/characters.dart';
+import '../util/util.dart';
+// TODO(ahe): Rename prefix to 'api' when VM bug is fixed.
+import '../../compiler.dart' as api_s;
+
+part 'class_element_parser.dart';
+part 'keyword.dart';
+part 'listener.dart';
+part 'parser.dart';
+part 'parser_task.dart';
+part 'partial_parser.dart';
+part 'scanner.dart';
+part 'scanner_task.dart';
+part 'string_scanner.dart';
+part 'token.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart
new file mode 100644
index 0000000..0fa3489
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+/**
+ * Scanner that reads from a String and creates tokens that points to
+ * substrings.
+ */
+class StringScanner extends ArrayBasedScanner<SourceString> {
+  final String string;
+
+  StringScanner(String this.string, {bool includeComments: false})
+    : super(includeComments);
+
+  int nextByte() => charAt(++byteOffset);
+
+  int peek() => charAt(byteOffset + 1);
+
+  int charAt(index)
+      => (string.length > index) ? string.charCodeAt(index) : $EOF;
+
+  SourceString asciiString(int start, int offset) {
+    return new SubstringWrapper(string, start, byteOffset + offset);
+  }
+
+  SourceString utf8String(int start, int offset) {
+    return new SubstringWrapper(string, start, byteOffset + offset + 1);
+  }
+
+  void appendByteStringToken(PrecedenceInfo info, SourceString value) {
+    // assert(kind != $a || keywords.get(value) == null);
+    tail.next = new StringToken.fromSource(info, value, tokenStart);
+    tail = tail.next;
+  }
+
+  void unmatchedBeginGroup(BeginGroupToken begin) {
+    SourceString error = new SourceString('unmatched "${begin.stringValue}"');
+    Token close =
+        new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset);
+    // We want to ensure that unmatched BeginGroupTokens are reported
+    // as errors. However, the rest of the parser assume the groups
+    // are well-balanced and will never look at the endGroup
+    // token. This is a nice property that allows us to skip quickly
+    // over correct code. By inserting an additional error token in
+    // the stream, we can keep ignoring endGroup tokens.
+    Token next =
+        new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset);
+    begin.endGroup = close;
+    close.next = next;
+    next.next = begin.next;
+  }
+}
+
+class SubstringWrapper extends Iterable<int> implements SourceString {
+  final String internalString;
+  final int begin;
+  final int end;
+  int cashedHash = 0;
+  String cachedSubString;
+
+  SubstringWrapper(String this.internalString,
+                   int this.begin, int this.end);
+
+  int get hashCode {
+    if (0 == cashedHash) {
+      cashedHash = slowToString().hashCode;
+    }
+    return cashedHash;
+  }
+
+  bool operator ==(other) {
+    return other is SourceString && slowToString() == other.slowToString();
+  }
+
+  void printOn(StringBuffer sb) {
+    sb.add(internalString.substring(begin, end));
+  }
+
+  String slowToString() {
+    if (cachedSubString == null) {
+      cachedSubString = internalString.substring(begin, end);
+    }
+    return cachedSubString;
+  }
+
+  String toString() => "SubstringWrapper(${slowToString()})";
+
+  String get stringValue => null;
+
+  Iterator<int> get iterator =>
+      new StringCodeIterator.substring(internalString, begin, end);
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    assert(0 <= initial);
+    assert(0 <= terminal);
+    assert(initial + terminal <= internalString.length);
+    return new SubstringWrapper(internalString,
+                                begin + initial, end - terminal);
+  }
+
+  bool get isEmpty => begin == end;
+
+  bool isPrivate() => !isEmpty && identical(internalString.charCodeAt(begin), $_);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart
new file mode 100644
index 0000000..a236eae
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart
@@ -0,0 +1,530 @@
+// Copyright (c) 2011, 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.
+
+part of scanner;
+
+const int EOF_TOKEN = 0;
+
+const int KEYWORD_TOKEN = $k;
+const int IDENTIFIER_TOKEN = $a;
+const int BAD_INPUT_TOKEN = $X;
+const int DOUBLE_TOKEN = $d;
+const int INT_TOKEN = $i;
+const int HEXADECIMAL_TOKEN = $x;
+const int STRING_TOKEN = $SQ;
+
+const int AMPERSAND_TOKEN = $AMPERSAND;
+const int BACKPING_TOKEN = $BACKPING;
+const int BACKSLASH_TOKEN = $BACKSLASH;
+const int BANG_TOKEN = $BANG;
+const int BAR_TOKEN = $BAR;
+const int COLON_TOKEN = $COLON;
+const int COMMA_TOKEN = $COMMA;
+const int EQ_TOKEN = $EQ;
+const int GT_TOKEN = $GT;
+const int HASH_TOKEN = $HASH;
+const int OPEN_CURLY_BRACKET_TOKEN = $OPEN_CURLY_BRACKET;
+const int OPEN_SQUARE_BRACKET_TOKEN = $OPEN_SQUARE_BRACKET;
+const int OPEN_PAREN_TOKEN = $OPEN_PAREN;
+const int LT_TOKEN = $LT;
+const int MINUS_TOKEN = $MINUS;
+const int PERIOD_TOKEN = $PERIOD;
+const int PLUS_TOKEN = $PLUS;
+const int QUESTION_TOKEN = $QUESTION;
+const int AT_TOKEN = $AT;
+const int CLOSE_CURLY_BRACKET_TOKEN = $CLOSE_CURLY_BRACKET;
+const int CLOSE_SQUARE_BRACKET_TOKEN = $CLOSE_SQUARE_BRACKET;
+const int CLOSE_PAREN_TOKEN = $CLOSE_PAREN;
+const int SEMICOLON_TOKEN = $SEMICOLON;
+const int SLASH_TOKEN = $SLASH;
+const int TILDE_TOKEN = $TILDE;
+const int STAR_TOKEN = $STAR;
+const int PERCENT_TOKEN = $PERCENT;
+const int CARET_TOKEN = $CARET;
+
+const int STRING_INTERPOLATION_TOKEN = 128;
+const int LT_EQ_TOKEN = STRING_INTERPOLATION_TOKEN + 1;
+const int FUNCTION_TOKEN = LT_EQ_TOKEN + 1;
+const int SLASH_EQ_TOKEN = FUNCTION_TOKEN + 1;
+const int PERIOD_PERIOD_PERIOD_TOKEN = SLASH_EQ_TOKEN + 1;
+const int PERIOD_PERIOD_TOKEN = PERIOD_PERIOD_PERIOD_TOKEN + 1;
+const int EQ_EQ_EQ_TOKEN = PERIOD_PERIOD_TOKEN + 1;
+const int EQ_EQ_TOKEN = EQ_EQ_EQ_TOKEN + 1;
+const int LT_LT_EQ_TOKEN = EQ_EQ_TOKEN + 1;
+const int LT_LT_TOKEN = LT_LT_EQ_TOKEN + 1;
+const int GT_EQ_TOKEN = LT_LT_TOKEN + 1;
+const int GT_GT_EQ_TOKEN = GT_EQ_TOKEN + 1;
+const int INDEX_EQ_TOKEN = GT_GT_EQ_TOKEN + 1;
+const int INDEX_TOKEN = INDEX_EQ_TOKEN + 1;
+const int BANG_EQ_EQ_TOKEN = INDEX_TOKEN + 1;
+const int BANG_EQ_TOKEN = BANG_EQ_EQ_TOKEN + 1;
+const int AMPERSAND_AMPERSAND_TOKEN = BANG_EQ_TOKEN + 1;
+const int AMPERSAND_EQ_TOKEN = AMPERSAND_AMPERSAND_TOKEN + 1;
+const int BAR_BAR_TOKEN = AMPERSAND_EQ_TOKEN + 1;
+const int BAR_EQ_TOKEN = BAR_BAR_TOKEN + 1;
+const int STAR_EQ_TOKEN = BAR_EQ_TOKEN + 1;
+const int PLUS_PLUS_TOKEN = STAR_EQ_TOKEN + 1;
+const int PLUS_EQ_TOKEN = PLUS_PLUS_TOKEN + 1;
+const int MINUS_MINUS_TOKEN = PLUS_EQ_TOKEN + 1;
+const int MINUS_EQ_TOKEN = MINUS_MINUS_TOKEN + 1;
+const int TILDE_SLASH_EQ_TOKEN = MINUS_EQ_TOKEN + 1;
+const int TILDE_SLASH_TOKEN = TILDE_SLASH_EQ_TOKEN + 1;
+const int PERCENT_EQ_TOKEN = TILDE_SLASH_TOKEN + 1;
+const int GT_GT_TOKEN = PERCENT_EQ_TOKEN + 1;
+const int CARET_EQ_TOKEN = GT_GT_TOKEN + 1;
+const int COMMENT_TOKEN = CARET_EQ_TOKEN + 1;
+const int STRING_INTERPOLATION_IDENTIFIER_TOKEN = COMMENT_TOKEN + 1;
+
+// TODO(ahe): Get rid of this.
+const int UNKNOWN_TOKEN = 1024;
+
+/**
+ * A token that doubles as a linked list.
+ */
+class Token implements Spannable {
+  /**
+   * The precedence info for this token. [info] determines the kind and the
+   * precedence level of this token.
+   */
+  final PrecedenceInfo info;
+
+  /**
+   * The character offset of the start of this token within the source text.
+   */
+  final int charOffset;
+
+  /**
+   * The next token in the token stream.
+   */
+  Token next;
+
+  Token(PrecedenceInfo this.info, int this.charOffset);
+
+  get value => info.value;
+
+  /**
+   * Returns the string value for keywords and symbols. For instance 'class' for
+   * the [CLASS] keyword token and '*' for a [Token] based on [STAR_INFO]. For
+   * other tokens, such identifiers, strings, numbers, etc, [stringValue]
+   * returns [:null:].
+   *
+   * [stringValue] should only be used for testing keywords and symbols.
+   */
+  String get stringValue => info.value.stringValue;
+
+  /**
+   * The kind enum of this token as determined by its [info].
+   */
+  int get kind => info.kind;
+
+  /**
+   * The precedence level for this token.
+   */
+  int get precedence => info.precedence;
+
+  bool isIdentifier() => identical(kind, IDENTIFIER_TOKEN);
+
+  /**
+   * Returns a textual representation of this token to be used for debugging
+   * purposes. The resulting string might contain information about the
+   * structure of the token, for example 'StringToken(foo)' for the identifier
+   * token 'foo'. Use [slowToString] for the text actually parsed by the token.
+   */
+  String toString() => info.value.toString();
+
+  /**
+   * The text parsed by this token.
+   */
+  String slowToString() => toString();
+
+  /**
+   * The number of characters parsed by this token.
+   */
+  int get slowCharCount {
+    if (info == BAD_INPUT_INFO) {
+      // This is a token that wraps around an error message. Return 1
+      // instead of the size of the length of the error message.
+      return 1;
+    } else {
+      return slowToString().length;
+    }
+  }
+}
+
+/**
+ * A keyword token.
+ */
+class KeywordToken extends Token {
+  final Keyword value;
+  String get stringValue => value.syntax;
+
+  KeywordToken(Keyword value, int charOffset)
+    : this.value = value, super(value.info, charOffset);
+
+  bool isIdentifier() => value.isPseudo || value.isBuiltIn;
+
+  String toString() => value.syntax;
+}
+
+/**
+ * A String-valued token.
+ */
+class StringToken extends Token {
+  final SourceString value;
+  String get stringValue => value.stringValue;
+
+  StringToken(PrecedenceInfo info, String value, int charOffset)
+    : this.fromSource(info, new SourceString(value), charOffset);
+
+  StringToken.fromSource(PrecedenceInfo info, this.value, int charOffset)
+    : super(info, charOffset);
+
+  String toString() => "StringToken(${value.slowToString()})";
+
+  String slowToString() => value.slowToString();
+}
+
+abstract class SourceString extends Iterable<int> {
+  const factory SourceString(String string) = StringWrapper;
+
+  void printOn(StringBuffer sb);
+
+  /** Gives a [SourceString] that is not including the [initial] first and
+   * [terminal] last characters. This is only intended to be used to remove
+   * quotes from string literals (including an initial '@' for raw strings).
+   */
+  SourceString copyWithoutQuotes(int initial, int terminal);
+
+  String get stringValue;
+
+  String slowToString();
+
+  bool get isEmpty;
+
+  bool isPrivate();
+}
+
+class StringWrapper extends Iterable<int> implements SourceString {
+  final String stringValue;
+
+  const StringWrapper(String this.stringValue);
+
+  int get hashCode => stringValue.hashCode;
+
+  bool operator ==(other) {
+    return other is SourceString && toString() == other.slowToString();
+  }
+
+  Iterator<int> get iterator => new StringCodeIterator(stringValue);
+
+  void printOn(StringBuffer sb) {
+    sb.add(stringValue);
+  }
+
+  String toString() => stringValue;
+
+  String slowToString() => stringValue;
+
+  SourceString copyWithoutQuotes(int initial, int terminal) {
+    assert(0 <= initial);
+    assert(0 <= terminal);
+    assert(initial + terminal <= stringValue.length);
+    return new StringWrapper(
+        stringValue.substring(initial, stringValue.length - terminal));
+  }
+
+  bool get isEmpty => stringValue.isEmpty;
+
+  bool isPrivate() => !isEmpty && identical(stringValue.charCodeAt(0), $_);
+}
+
+class StringCodeIterator implements Iterator<int> {
+  final String string;
+  int index;
+  final int end;
+  int _current;
+
+  StringCodeIterator(String string) :
+    this.string = string, index = 0, end = string.length;
+
+  StringCodeIterator.substring(this.string, this.index, this.end) {
+    assert(0 <= index);
+    assert(index <= end);
+    assert(end <= string.length);
+  }
+
+  int get current => _current;
+
+  bool moveNext() {
+    _current = null;
+    if (index >= end) return false;
+    _current = string.charCodeAt(index++);
+    return true;
+  }
+}
+
+class BeginGroupToken extends StringToken {
+  Token endGroup;
+  BeginGroupToken(PrecedenceInfo info, String value, int charOffset)
+    : super(info, value, charOffset);
+}
+
+bool isUserDefinableOperator(String value) {
+  return
+      isBinaryOperator(value) ||
+      isMinusOperator(value) ||
+      isTernaryOperator(value) ||
+      isUnaryOperator(value);
+}
+
+bool isUnaryOperator(String value) => identical(value, '~');
+
+bool isBinaryOperator(String value) {
+  return
+      (identical(value, '==')) ||
+      (identical(value, '[]')) ||
+      (identical(value, '*')) ||
+      (identical(value, '/')) ||
+      (identical(value, '%')) ||
+      (identical(value, '~/')) ||
+      (identical(value, '+')) ||
+      (identical(value, '<<')) ||
+      (identical(value, '>>')) ||
+      (identical(value, '>=')) ||
+      (identical(value, '>')) ||
+      (identical(value, '<=')) ||
+      (identical(value, '<')) ||
+      (identical(value, '&')) ||
+      (identical(value, '^')) ||
+      (identical(value, '|'));
+}
+
+bool isTernaryOperator(String value) => identical(value, '[]=');
+
+bool isMinusOperator(String value) => identical(value, '-');
+
+class PrecedenceInfo {
+  final SourceString value;
+  final int precedence;
+  final int kind;
+
+  const PrecedenceInfo(this.value, this.precedence, this.kind);
+
+  toString() => 'PrecedenceInfo($value, $precedence, $kind)';
+}
+
+// TODO(ahe): The following are not tokens in Dart.
+const PrecedenceInfo BACKPING_INFO =
+  const PrecedenceInfo(const SourceString('`'), 0, BACKPING_TOKEN);
+const PrecedenceInfo BACKSLASH_INFO =
+  const PrecedenceInfo(const SourceString('\\'), 0, BACKSLASH_TOKEN);
+const PrecedenceInfo PERIOD_PERIOD_PERIOD_INFO =
+  const PrecedenceInfo(const SourceString('...'), 0,
+                       PERIOD_PERIOD_PERIOD_TOKEN);
+
+/**
+ * The cascade operator has the lowest precedence of any operator
+ * except assignment.
+ */
+const int CASCADE_PRECEDENCE = 2;
+const PrecedenceInfo PERIOD_PERIOD_INFO =
+  const PrecedenceInfo(const SourceString('..'), CASCADE_PRECEDENCE,
+                       PERIOD_PERIOD_TOKEN);
+
+const PrecedenceInfo BANG_INFO =
+  const PrecedenceInfo(const SourceString('!'), 0, BANG_TOKEN);
+const PrecedenceInfo COLON_INFO =
+  const PrecedenceInfo(const SourceString(':'), 0, COLON_TOKEN);
+const PrecedenceInfo INDEX_INFO =
+  const PrecedenceInfo(const SourceString('[]'), 0, INDEX_TOKEN);
+const PrecedenceInfo MINUS_MINUS_INFO =
+  const PrecedenceInfo(const SourceString('--'), POSTFIX_PRECEDENCE,
+                       MINUS_MINUS_TOKEN);
+const PrecedenceInfo PLUS_PLUS_INFO =
+  const PrecedenceInfo(const SourceString('++'), POSTFIX_PRECEDENCE,
+                       PLUS_PLUS_TOKEN);
+const PrecedenceInfo TILDE_INFO =
+  const PrecedenceInfo(const SourceString('~'), 0, TILDE_TOKEN);
+
+const PrecedenceInfo FUNCTION_INFO =
+  const PrecedenceInfo(const SourceString('=>'), 0, FUNCTION_TOKEN);
+const PrecedenceInfo HASH_INFO =
+  const PrecedenceInfo(const SourceString('#'), 0, HASH_TOKEN);
+const PrecedenceInfo INDEX_EQ_INFO =
+  const PrecedenceInfo(const SourceString('[]='), 0, INDEX_EQ_TOKEN);
+const PrecedenceInfo SEMICOLON_INFO =
+  const PrecedenceInfo(const SourceString(';'), 0, SEMICOLON_TOKEN);
+const PrecedenceInfo COMMA_INFO =
+  const PrecedenceInfo(const SourceString(','), 0, COMMA_TOKEN);
+
+const PrecedenceInfo AT_INFO =
+  const PrecedenceInfo(const SourceString('@'), 0, AT_TOKEN);
+
+// Assignment operators.
+const int ASSIGNMENT_PRECEDENCE = 1;
+const PrecedenceInfo AMPERSAND_EQ_INFO =
+  const PrecedenceInfo(const SourceString('&='),
+                       ASSIGNMENT_PRECEDENCE, AMPERSAND_EQ_TOKEN);
+const PrecedenceInfo BAR_EQ_INFO =
+  const PrecedenceInfo(const SourceString('|='),
+                       ASSIGNMENT_PRECEDENCE, BAR_EQ_TOKEN);
+const PrecedenceInfo CARET_EQ_INFO =
+  const PrecedenceInfo(const SourceString('^='),
+                       ASSIGNMENT_PRECEDENCE, CARET_EQ_TOKEN);
+const PrecedenceInfo EQ_INFO =
+  const PrecedenceInfo(const SourceString('='),
+                       ASSIGNMENT_PRECEDENCE, EQ_TOKEN);
+const PrecedenceInfo GT_GT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('>>='),
+                       ASSIGNMENT_PRECEDENCE, GT_GT_EQ_TOKEN);
+const PrecedenceInfo LT_LT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('<<='),
+                       ASSIGNMENT_PRECEDENCE, LT_LT_EQ_TOKEN);
+const PrecedenceInfo MINUS_EQ_INFO =
+  const PrecedenceInfo(const SourceString('-='),
+                       ASSIGNMENT_PRECEDENCE, MINUS_EQ_TOKEN);
+const PrecedenceInfo PERCENT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('%='),
+                       ASSIGNMENT_PRECEDENCE, PERCENT_EQ_TOKEN);
+const PrecedenceInfo PLUS_EQ_INFO =
+  const PrecedenceInfo(const SourceString('+='),
+                       ASSIGNMENT_PRECEDENCE, PLUS_EQ_TOKEN);
+const PrecedenceInfo SLASH_EQ_INFO =
+  const PrecedenceInfo(const SourceString('/='),
+                       ASSIGNMENT_PRECEDENCE, SLASH_EQ_TOKEN);
+const PrecedenceInfo STAR_EQ_INFO =
+  const PrecedenceInfo(const SourceString('*='),
+                       ASSIGNMENT_PRECEDENCE, STAR_EQ_TOKEN);
+const PrecedenceInfo TILDE_SLASH_EQ_INFO =
+  const PrecedenceInfo(const SourceString('~/='),
+                       ASSIGNMENT_PRECEDENCE, TILDE_SLASH_EQ_TOKEN);
+
+const PrecedenceInfo QUESTION_INFO =
+  const PrecedenceInfo(const SourceString('?'), 3, QUESTION_TOKEN);
+
+const PrecedenceInfo BAR_BAR_INFO =
+  const PrecedenceInfo(const SourceString('||'), 4, BAR_BAR_TOKEN);
+
+const PrecedenceInfo AMPERSAND_AMPERSAND_INFO =
+  const PrecedenceInfo(const SourceString('&&'), 5, AMPERSAND_AMPERSAND_TOKEN);
+
+const PrecedenceInfo BAR_INFO =
+  const PrecedenceInfo(const SourceString('|'), 6, BAR_TOKEN);
+
+const PrecedenceInfo CARET_INFO =
+  const PrecedenceInfo(const SourceString('^'), 7, CARET_TOKEN);
+
+const PrecedenceInfo AMPERSAND_INFO =
+  const PrecedenceInfo(const SourceString('&'), 8, AMPERSAND_TOKEN);
+
+// Equality operators.
+const PrecedenceInfo BANG_EQ_EQ_INFO =
+  const PrecedenceInfo(const SourceString('!=='), 9, BANG_EQ_EQ_TOKEN);
+const PrecedenceInfo BANG_EQ_INFO =
+  const PrecedenceInfo(const SourceString('!='), 9, BANG_EQ_TOKEN);
+const PrecedenceInfo EQ_EQ_EQ_INFO =
+  const PrecedenceInfo(const SourceString('==='), 9, EQ_EQ_EQ_TOKEN);
+const PrecedenceInfo EQ_EQ_INFO =
+  const PrecedenceInfo(const SourceString('=='), 9, EQ_EQ_TOKEN);
+
+// Relational operators.
+const PrecedenceInfo GT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('>='), 10, GT_EQ_TOKEN);
+const PrecedenceInfo GT_INFO =
+  const PrecedenceInfo(const SourceString('>'), 10, GT_TOKEN);
+const PrecedenceInfo IS_INFO =
+  const PrecedenceInfo(const SourceString('is'), 10, KEYWORD_TOKEN);
+const PrecedenceInfo AS_INFO =
+  const PrecedenceInfo(const SourceString('as'), 10, KEYWORD_TOKEN);
+const PrecedenceInfo LT_EQ_INFO =
+  const PrecedenceInfo(const SourceString('<='), 10, LT_EQ_TOKEN);
+const PrecedenceInfo LT_INFO =
+  const PrecedenceInfo(const SourceString('<'), 10, LT_TOKEN);
+
+// Shift operators.
+const PrecedenceInfo GT_GT_INFO =
+  const PrecedenceInfo(const SourceString('>>'), 11, GT_GT_TOKEN);
+const PrecedenceInfo LT_LT_INFO =
+  const PrecedenceInfo(const SourceString('<<'), 11, LT_LT_TOKEN);
+
+// Additive operators.
+const PrecedenceInfo MINUS_INFO =
+  const PrecedenceInfo(const SourceString('-'), 12, MINUS_TOKEN);
+const PrecedenceInfo PLUS_INFO =
+  const PrecedenceInfo(const SourceString('+'), 12, PLUS_TOKEN);
+
+// Multiplicative operators.
+const PrecedenceInfo PERCENT_INFO =
+  const PrecedenceInfo(const SourceString('%'), 13, PERCENT_TOKEN);
+const PrecedenceInfo SLASH_INFO =
+  const PrecedenceInfo(const SourceString('/'), 13, SLASH_TOKEN);
+const PrecedenceInfo STAR_INFO =
+  const PrecedenceInfo(const SourceString('*'), 13, STAR_TOKEN);
+const PrecedenceInfo TILDE_SLASH_INFO =
+  const PrecedenceInfo(const SourceString('~/'), 13, TILDE_SLASH_TOKEN);
+
+const int POSTFIX_PRECEDENCE = 14;
+const PrecedenceInfo PERIOD_INFO =
+  const PrecedenceInfo(const SourceString('.'), POSTFIX_PRECEDENCE,
+                       PERIOD_TOKEN);
+
+const PrecedenceInfo KEYWORD_INFO =
+  const PrecedenceInfo(const SourceString('keyword'), 0, KEYWORD_TOKEN);
+
+const PrecedenceInfo EOF_INFO =
+  const PrecedenceInfo(const SourceString('EOF'), 0, EOF_TOKEN);
+
+const PrecedenceInfo IDENTIFIER_INFO =
+  const PrecedenceInfo(const SourceString('identifier'), 0, IDENTIFIER_TOKEN);
+
+const PrecedenceInfo BAD_INPUT_INFO =
+  const PrecedenceInfo(const SourceString('malformed input'), 0,
+                       BAD_INPUT_TOKEN);
+
+const PrecedenceInfo OPEN_PAREN_INFO =
+  const PrecedenceInfo(const SourceString('('), POSTFIX_PRECEDENCE,
+                       OPEN_PAREN_TOKEN);
+
+const PrecedenceInfo CLOSE_PAREN_INFO =
+  const PrecedenceInfo(const SourceString(')'), 0, CLOSE_PAREN_TOKEN);
+
+const PrecedenceInfo OPEN_CURLY_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString('{'), 0, OPEN_CURLY_BRACKET_TOKEN);
+
+const PrecedenceInfo CLOSE_CURLY_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString('}'), 0, CLOSE_CURLY_BRACKET_TOKEN);
+
+const PrecedenceInfo INT_INFO =
+  const PrecedenceInfo(const SourceString('int'), 0, INT_TOKEN);
+
+const PrecedenceInfo STRING_INFO =
+  const PrecedenceInfo(const SourceString('string'), 0, STRING_TOKEN);
+
+const PrecedenceInfo OPEN_SQUARE_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString('['), POSTFIX_PRECEDENCE,
+                       OPEN_SQUARE_BRACKET_TOKEN);
+
+const PrecedenceInfo CLOSE_SQUARE_BRACKET_INFO =
+  const PrecedenceInfo(const SourceString(']'), 0, CLOSE_SQUARE_BRACKET_TOKEN);
+
+const PrecedenceInfo DOUBLE_INFO =
+  const PrecedenceInfo(const SourceString('double'), 0, DOUBLE_TOKEN);
+
+const PrecedenceInfo STRING_INTERPOLATION_INFO =
+  const PrecedenceInfo(const SourceString('\${'), 0,
+                       STRING_INTERPOLATION_TOKEN);
+
+const PrecedenceInfo STRING_INTERPOLATION_IDENTIFIER_INFO =
+  const PrecedenceInfo(const SourceString('\$'), 0,
+                       STRING_INTERPOLATION_IDENTIFIER_TOKEN);
+
+const PrecedenceInfo HEXADECIMAL_INFO =
+  const PrecedenceInfo(const SourceString('hexadecimal'), 0, HEXADECIMAL_TOKEN);
+
+const PrecedenceInfo COMMENT_INFO =
+  const PrecedenceInfo(const SourceString('comment'), 0, COMMENT_TOKEN);
+
+// For reporting lexical errors.
+const PrecedenceInfo ERROR_INFO =
+  const PrecedenceInfo(const SourceString('?'), 0, UNKNOWN_TOKEN);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/script.dart b/pkgs/markdown/test/lib/src/compiler/implementation/script.dart
new file mode 100644
index 0000000..b130616
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/script.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2011, 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.
+
+part of dart2js;
+
+class Script {
+  // TODO(kasperl): Once MockFile in tests/compiler/dart2js/parser_helper.dart
+  // implements SourceFile, we should be able to type the [file] field as
+  // such.
+  final file;
+
+  /**
+   * The readable URI from which this script was loaded.
+   *
+   * See [LibraryLoader] for terminology on URIs.
+   */
+  final Uri uri;
+
+  Script(this.uri, this.file);
+
+  String get text => (file == null) ? null : file.text;
+  String get name => (file == null) ? null : file.filename;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart b/pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart
new file mode 100644
index 0000000..14a4175
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart
@@ -0,0 +1,105 @@
+// 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.
+
+library source_file;
+
+import 'dart:math';
+
+import 'colors.dart' as colors;
+
+/**
+ * Represents a file of source code.
+ */
+class SourceFile {
+
+  /** The name of the file. */
+  final String filename;
+
+  /** The text content of the file. */
+  final String text;
+
+  List<int> _lineStarts;
+
+  SourceFile(this.filename, this.text);
+
+  List<int> get lineStarts {
+    if (_lineStarts == null) {
+      var starts = [0];
+      var index = 0;
+      while (index < text.length) {
+        index = text.indexOf('\n', index) + 1;
+        if (index <= 0) break;
+        starts.add(index);
+      }
+      starts.add(text.length + 1);
+      _lineStarts = starts;
+    }
+    return _lineStarts;
+  }
+
+  int getLine(int position) {
+    List<int> starts = lineStarts;
+    if (position < 0 || starts.last <= position) {
+      throw 'bad position #$position in file $filename with '
+            'length ${text.length}.';
+    }
+    int first = 0;
+    int count = starts.length;
+    while (count > 1) {
+      int step = count ~/ 2;
+      int middle = first + step;
+      int lineStart = starts[middle];
+      if (position < lineStart) {
+        count = step;
+      } else {
+        first = middle;
+        count -= step;
+      }
+    }
+    return first;
+  }
+
+  int getColumn(int line, int position) {
+    return position - lineStarts[line];
+  }
+
+  /**
+   * Create a pretty string representation from a character position
+   * in the file.
+   */
+  String getLocationMessage(String message, int start, int end,
+                            bool includeText, String color(String x)) {
+    var line = getLine(start);
+    var column = getColumn(line, start);
+
+    var buf = new StringBuffer(
+        '${filename}:${line + 1}:${column + 1}: $message');
+    if (includeText) {
+      buf.add('\n');
+      var textLine;
+      // +1 for 0-indexing, +1 again to avoid the last line of the file
+      if ((line + 2) < _lineStarts.length) {
+        textLine = text.substring(_lineStarts[line], _lineStarts[line+1]);
+      } else {
+        textLine = '${text.substring(_lineStarts[line])}\n';
+      }
+
+      int toColumn = min(column + (end-start), textLine.length);
+      buf.add(textLine.substring(0, column));
+      buf.add(color(textLine.substring(column, toColumn)));
+      buf.add(textLine.substring(toColumn));
+
+      int i = 0;
+      for (; i < column; i++) {
+        buf.add(' ');
+      }
+
+      for (; i < toColumn; i++) {
+        buf.add(color('^'));
+      }
+    }
+
+    return buf.toString();
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart b/pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart
new file mode 100644
index 0000000..c100694
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart
@@ -0,0 +1,125 @@
+// 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.
+
+library source_file_provider;
+
+import 'dart:async';
+import 'dart:uri';
+import 'dart:io';
+import 'dart:utf';
+
+import '../compiler.dart' as api show Diagnostic;
+import 'dart2js.dart' show AbortLeg;
+import 'colors.dart' as colors;
+import 'source_file.dart';
+import 'filenames.dart';
+import 'util/uri_extras.dart';
+
+String readAll(String filename) {
+  var file = (new File(filename)).openSync(FileMode.READ);
+  var length = file.lengthSync();
+  var buffer = new List<int>.fixedLength(length);
+  var bytes = file.readListSync(buffer, 0, length);
+  file.closeSync();
+  return new String.fromCharCodes(new Utf8Decoder(buffer).decodeRest());
+}
+
+class SourceFileProvider {
+  bool isWindows = (Platform.operatingSystem == 'windows');
+  Uri cwd = getCurrentDirectory();
+  Map<String, SourceFile> sourceFiles = <String, SourceFile>{};
+  int dartCharactersRead = 0;
+
+  Future<String> readStringFromUri(Uri resourceUri) {
+    if (resourceUri.scheme != 'file') {
+      throw new ArgumentError("Unknown scheme in uri '$resourceUri'");
+    }
+    String source;
+    try {
+      source = readAll(uriPathToNative(resourceUri.path));
+    } on FileIOException catch (ex) {
+      throw 'Error: Cannot read "${relativize(cwd, resourceUri, isWindows)}" '
+            '(${ex.osError}).';
+    }
+    dartCharactersRead += source.length;
+    sourceFiles[resourceUri.toString()] =
+      new SourceFile(relativize(cwd, resourceUri, isWindows), source);
+    return new Future.immediate(source);
+  }
+}
+
+void silentDiagnosticHandler(Uri uri, int begin, int end, String message,
+                             api.Diagnostic kind) {
+}
+
+class FormattingDiagnosticHandler {
+  final SourceFileProvider provider;
+  bool showWarnings = true;
+  bool verbose = false;
+  bool isAborting = false;
+  bool enableColors = false;
+  bool throwOnError = false;
+
+  final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal;
+  final int INFO =
+      api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal;
+
+  FormattingDiagnosticHandler(SourceFileProvider this.provider);
+
+  void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) {
+    if (!verbose && identical(kind, api.Diagnostic.VERBOSE_INFO)) return;
+    if (enableColors) {
+      print('${colors.green("info:")} $message');
+    } else {
+      print('info: $message');
+    }
+  }
+
+  void diagnosticHandler(Uri uri, int begin, int end, String message,
+                         api.Diagnostic kind) {
+    // TODO(ahe): Remove this when source map is handled differently.
+    if (identical(kind.name, 'source map')) return;
+
+    if (isAborting) return;
+    isAborting = identical(kind, api.Diagnostic.CRASH);
+    bool fatal = (kind.ordinal & FATAL) != 0;
+    bool isInfo = (kind.ordinal & INFO) != 0;
+    if (isInfo && uri == null && !identical(kind, api.Diagnostic.INFO)) {
+      info(message, kind);
+      return;
+    }
+    var color;
+    if (!enableColors) {
+      color = (x) => x;
+    } else if (identical(kind, api.Diagnostic.ERROR)) {
+      color = colors.red;
+    } else if (identical(kind, api.Diagnostic.WARNING)) {
+      color = colors.magenta;
+    } else if (identical(kind, api.Diagnostic.LINT)) {
+      color = colors.magenta;
+    } else if (identical(kind, api.Diagnostic.CRASH)) {
+      color = colors.red;
+    } else if (identical(kind, api.Diagnostic.INFO)) {
+      color = colors.green;
+    } else {
+      throw 'Unknown kind: $kind (${kind.ordinal})';
+    }
+    if (uri == null) {
+      assert(fatal);
+      print(color(message));
+    } else if (fatal || showWarnings) {
+      SourceFile file = provider.sourceFiles[uri.toString()];
+      if (file == null) {
+        throw '$uri: file is null';
+      }
+      print(file.getLocationMessage(color(message), begin, end, true, color));
+    }
+    if (fatal && throwOnError) {
+      isAborting = true;
+      throw new AbortLeg(message);
+    }
+  }
+}
+
+
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart b/pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart
new file mode 100644
index 0000000..a2cd6fc
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart
@@ -0,0 +1,192 @@
+// 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.
+
+library source_map_builder;
+
+import 'util/util.dart';
+import 'scanner/scannerlib.dart' show Token;
+import 'source_file.dart';
+
+class SourceMapBuilder {
+  static const int VLQ_BASE_SHIFT = 5;
+  static const int VLQ_BASE_MASK = (1 << 5) - 1;
+  static const int VLQ_CONTINUATION_BIT = 1 << 5;
+  static const int VLQ_CONTINUATION_MASK = 1 << 5;
+  static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn'
+                                      'opqrstuvwxyz0123456789+/';
+
+  List<SourceMapEntry> entries;
+
+  Map<String, int> sourceUrlMap;
+  List<String> sourceUrlList;
+  Map<String, int> sourceNameMap;
+  List<String> sourceNameList;
+
+  int previousTargetLine;
+  int previousTargetColumn;
+  int previousSourceUrlIndex;
+  int previousSourceLine;
+  int previousSourceColumn;
+  int previousSourceNameIndex;
+  bool firstEntryInLine;
+
+  SourceMapBuilder() {
+    entries = new List<SourceMapEntry>();
+
+    sourceUrlMap = new Map<String, int>();
+    sourceUrlList = new List<String>();
+    sourceNameMap = new Map<String, int>();
+    sourceNameList = new List<String>();
+
+    previousTargetLine = 0;
+    previousTargetColumn = 0;
+    previousSourceUrlIndex = 0;
+    previousSourceLine = 0;
+    previousSourceColumn = 0;
+    previousSourceNameIndex = 0;
+    firstEntryInLine = true;
+  }
+
+  void addMapping(int targetOffset, SourceFileLocation sourceLocation) {
+    entries.add(new SourceMapEntry(sourceLocation, targetOffset));
+  }
+
+  void printStringListOn(List<String> strings, StringBuffer buffer) {
+    bool first = true;
+    buffer.add('[');
+    for (String string in strings) {
+      if (!first) buffer.add(',');
+      buffer.add('"');
+      writeJsonEscapedCharsOn(string, buffer);
+      buffer.add('"');
+      first = false;
+    }
+    buffer.add(']');
+  }
+
+  String build(SourceFile targetFile) {
+    StringBuffer mappingsBuffer = new StringBuffer();
+    entries.forEach((SourceMapEntry entry) => writeEntry(entry, targetFile,
+                                                         mappingsBuffer));
+    StringBuffer buffer = new StringBuffer();
+    buffer.add('{\n');
+    buffer.add('  "version": 3,\n');
+    buffer.add('  "sourceRoot": "",\n');
+    buffer.add('  "sources": ');
+    printStringListOn(sourceUrlList, buffer);
+    buffer.add(',\n');
+    buffer.add('  "names": ');
+    printStringListOn(sourceNameList, buffer);
+    buffer.add(',\n');
+    buffer.add('  "mappings": "');
+    buffer.add(mappingsBuffer);
+    buffer.add('"\n}\n');
+    return buffer.toString();
+  }
+
+  void writeEntry(SourceMapEntry entry, SourceFile targetFile, StringBuffer output) {
+    int targetLine = targetFile.getLine(entry.targetOffset);
+    int targetColumn = targetFile.getColumn(targetLine, entry.targetOffset);
+
+    if (targetLine > previousTargetLine) {
+      for (int i = previousTargetLine; i < targetLine; ++i) {
+        output.add(';');
+      }
+      previousTargetLine = targetLine;
+      previousTargetColumn = 0;
+      firstEntryInLine = true;
+    }
+
+    if (!firstEntryInLine) {
+      output.add(',');
+    }
+    firstEntryInLine = false;
+
+    encodeVLQ(output, targetColumn - previousTargetColumn);
+    previousTargetColumn = targetColumn;
+
+    if (entry.sourceLocation == null) return;
+
+    String sourceUrl = entry.sourceLocation.getSourceUrl();
+    int sourceLine = entry.sourceLocation.getLine();
+    int sourceColumn = entry.sourceLocation.getColumn();
+    String sourceName = entry.sourceLocation.getSourceName();
+
+    int sourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap);
+    encodeVLQ(output, sourceUrlIndex - previousSourceUrlIndex);
+    previousSourceUrlIndex = sourceUrlIndex;
+
+    encodeVLQ(output, sourceLine - previousSourceLine);
+    previousSourceLine = sourceLine;
+    encodeVLQ(output, sourceColumn - previousSourceColumn);
+    previousSourceColumn = sourceColumn;
+
+    if (sourceName == null) {
+      return;
+    }
+
+    int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap);
+    encodeVLQ(output, sourceNameIndex - previousSourceNameIndex);
+    previousSourceNameIndex = sourceNameIndex;
+  }
+
+  int indexOf(List<String> list, String value, Map<String, int> map) {
+    return map.putIfAbsent(value, () {
+      int index = list.length;
+      map[value] = index;
+      list.add(value);
+      return index;
+    });
+  }
+
+  static void encodeVLQ(StringBuffer output, int value) {
+    int signBit = 0;
+    if (value < 0) {
+      signBit = 1;
+      value = -value;
+    }
+    value = (value << 1) | signBit;
+    do {
+      int digit = value & VLQ_BASE_MASK;
+      value >>= VLQ_BASE_SHIFT;
+      if (value > 0) {
+        digit |= VLQ_CONTINUATION_BIT;
+      }
+      output.add(BASE64_DIGITS[digit]);
+    } while (value > 0);
+  }
+}
+
+class SourceMapEntry {
+  SourceFileLocation sourceLocation;
+  int targetOffset;
+
+  SourceMapEntry(this.sourceLocation, this.targetOffset);
+}
+
+class SourceFileLocation {
+  SourceFile sourceFile;
+  Token token;
+  int line;
+
+  SourceFileLocation(this.sourceFile, this.token) {
+    assert(isValid());
+  }
+
+  String getSourceUrl() => sourceFile.filename;
+
+  int getLine() {
+    if (line == null) line = sourceFile.getLine(token.charOffset);
+    return line;
+  }
+
+  int getColumn() => sourceFile.getColumn(getLine(), token.charOffset);
+
+  String getSourceName() {
+    if (token.isIdentifier()) return token.slowToString();
+    return null;
+  }
+
+  bool isValid() => token.charOffset < sourceFile.text.length;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart
new file mode 100644
index 0000000..0e10ecf
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart
@@ -0,0 +1,630 @@
+// 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.
+
+part of ssa;
+
+class BailoutInfo {
+  int instructionId;
+  int bailoutId;
+  BailoutInfo(this.instructionId, this.bailoutId);
+}
+
+/**
+ * Keeps track of the execution environment for instructions. An
+ * execution environment contains the SSA instructions that are live.
+ */
+class Environment {
+  final Set<HInstruction> lives;
+  final List<HBasicBlock> loopMarkers;
+  Environment() : lives = new Set<HInstruction>(),
+                  loopMarkers = new List<HBasicBlock>();
+  Environment.from(Environment other)
+    : lives = new Set<HInstruction>.from(other.lives),
+      loopMarkers = new List<HBasicBlock>.from(other.loopMarkers);
+
+  void remove(HInstruction instruction) {
+    lives.remove(instruction);
+  }
+
+  void add(HInstruction instruction) {
+    // If the instruction is a check, we add its checked input
+    // instead. This allows sharing the same environment between
+    // different type guards.
+    //
+    // Also, we don't need to add code motion invariant instructions
+    // in the live set (because we generate them at use-site), except
+    // for parameters that are not 'this', which is always passed as
+    // the receiver.
+    if (instruction is HCheck) {
+      add(instruction.checkedInput);
+    } else if (!instruction.isCodeMotionInvariant()
+               || (instruction is HParameterValue && instruction is !HThis)) {
+      lives.add(instruction);
+    } else {
+      for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+        add(instruction.inputs[i]);
+      }
+    }
+  }
+
+  void addAll(Environment other) {
+    lives.addAll(other.lives);
+  }
+
+  bool get isEmpty => lives.isEmpty && loopMarkers.isEmpty;
+}
+
+
+/**
+ * Visits the graph in dominator order and inserts TypeGuards in places where
+ * we consider the guard to be of value.
+ *
+ * Might modify the [types] in an inconsistent way. No further analysis should
+ * rely on them.
+ */
+class SsaTypeGuardInserter extends HGraphVisitor implements OptimizationPhase {
+  final Compiler compiler;
+  final String name = 'SsaTypeGuardInserter';
+  final CodegenWorkItem work;
+  final HTypeMap types;
+  bool calledInLoop = false;
+  bool isRecursiveMethod = false;
+  int stateId = 1;
+
+  SsaTypeGuardInserter(this.compiler, this.work, this.types);
+
+  void visitGraph(HGraph graph) {
+    isRecursiveMethod = graph.isRecursiveMethod;
+    calledInLoop = graph.calledInLoop;
+    work.guards = <HTypeGuard>[];
+    visitDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    block.forEachPhi(visitInstruction);
+
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      // Note that visitInstruction (from the phis and here) might insert an
+      // HTypeGuard instruction. We have to skip those.
+      if (instruction is !HTypeGuard) visitInstruction(instruction);
+      instruction = instruction.next;
+    }
+  }
+
+  // Primitive types that are not null are valuable. These include
+  // indexable arrays.
+  bool typeValuable(HType type) {
+    return type.isPrimitive() && !type.isNull();
+  }
+
+  bool get hasTypeGuards => work.guards.length != 0;
+
+  bool typeGuardWouldBeValuable(HInstruction instruction,
+                                HType speculativeType) {
+    // If the type itself is not valuable, do not generate a guard for it.
+    if (!typeValuable(speculativeType)) return false;
+
+    // Do not insert a type guard if the instruction has a type
+    // annotation that disagrees with the speculated type.
+    Element source = instruction.sourceElement;
+    if (source != null) {
+      DartType sourceType = source.computeType(compiler);
+      DartType speculatedType = speculativeType.computeType(compiler);
+      JavaScriptBackend backend = compiler.backend;
+      if (speculatedType != null) {
+        // Use the num type instead of JSNumber because JSNumber
+        // is not assignment compatible with int and double, but we
+        // still want to generate a type guard.
+        if (speculatedType.element == backend.jsNumberClass) {
+          speculatedType = compiler.numClass.computeType(compiler);
+        }
+        if (!compiler.types.isAssignable(speculatedType, sourceType)) {
+          return false;
+        }
+      }
+    }
+
+    // Insert type guards for recursive methods.
+    if (isRecursiveMethod) return true;
+
+    // Insert type guards if there are uses in loops.
+    bool isNested(HBasicBlock inner, HBasicBlock outer) {
+      if (identical(inner, outer)) return false;
+      if (outer == null) return true;
+      while (inner != null) {
+        if (identical(inner, outer)) return true;
+        inner = inner.parentLoopHeader;
+      }
+      return false;
+    }
+
+    // If the instruction is not in a loop then the header will be null.
+    HBasicBlock currentLoopHeader = instruction.block.enclosingLoopHeader;
+    for (HInstruction user in instruction.usedBy) {
+      HBasicBlock userLoopHeader = user.block.enclosingLoopHeader;
+      if (isNested(userLoopHeader, currentLoopHeader)) return true;
+    }
+
+    bool isIndexOperatorOnIndexablePrimitive(instruction, types) {
+      return instruction is HIndex
+          || (instruction is HInvokeDynamicMethod
+              && instruction.isIndexOperatorOnIndexablePrimitive(types));
+    }
+
+    // To speed up computations on values loaded from arrays, we
+    // insert type guards for builtin array indexing operations in
+    // nested loops. Since this can blow up code size quite
+    // significantly, we only do it if type guards have already been
+    // inserted for this method. The code size price for an additional
+    // type guard is much smaller than the first one that causes the
+    // generation of a bailout method.
+    if (hasTypeGuards
+        && isIndexOperatorOnIndexablePrimitive(instruction, types)) {
+      HBasicBlock loopHeader = instruction.block.enclosingLoopHeader;
+      if (loopHeader != null && loopHeader.parentLoopHeader != null) {
+        return true;
+      }
+    }
+
+    // If the instruction is used by a phi where a guard would be
+    // valuable, put the guard on that instruction.
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HPhi
+          && user.block.id > instruction.id
+          && typeGuardWouldBeValuable(user, speculativeType)) {
+        return true;
+      }
+    }
+
+    // Insert type guards if the method is likely to be called in a
+    // loop.
+    return calledInLoop;
+  }
+
+  bool shouldInsertTypeGuard(HInstruction instruction,
+                             HType speculativeType,
+                             HType computedType) {
+    if (!speculativeType.isUseful()) return false;
+    // If the types agree we don't need to check.
+    if (speculativeType == computedType) return false;
+    // If a bailout check is more expensive than doing the actual operation
+    // don't do it either.
+    return typeGuardWouldBeValuable(instruction, speculativeType);
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    HType speculativeType = types[instruction];
+    HType computedType = instruction.computeTypeFromInputTypes(types, compiler);
+    // Currently the type in [types] is the speculative type each instruction
+    // would like to have. We start by recomputing the type non-speculatively.
+    // If we add a type guard then the guard will expose the speculative type.
+    // If we don't add a type guard then this avoids that subsequent
+    // instructions use the wrong (speculative) type.
+    //
+    // Note that just setting the speculative type of the instruction is not
+    // complete since the type could lead to a phi node which in turn could
+    // change the speculative type. In this case we might miss some guards we
+    // would have liked to insert. Most of the time this should however be
+    // fine, due to dominator-order visiting.
+    types[instruction] = computedType;
+
+    if (shouldInsertTypeGuard(instruction, speculativeType, computedType)) {
+      HInstruction insertionPoint;
+      if (instruction is HPhi) {
+        insertionPoint = instruction.block.first;
+      } else if (instruction is HParameterValue) {
+        // We insert the type guard at the end of the entry block
+        // because if a parameter is live, it must be kept in the live
+        // environment. Not doing so would mean we could visit a
+        // parameter and remove it from the environment before
+        // visiting a type guard.
+        insertionPoint = instruction.block.last;
+      } else {
+        insertionPoint = instruction.next;
+      }
+      // If the previous instruction is also a type guard, then both
+      // guards have the same environment, and can therefore share the
+      // same state id.
+      HBailoutTarget target;
+      int state;
+      if (insertionPoint.previous is HTypeGuard) {
+        HTypeGuard other = insertionPoint.previous;
+        target = other.bailoutTarget;
+      } else {
+        state = stateId++;
+        target = new HBailoutTarget(state);
+        insertionPoint.block.addBefore(insertionPoint, target);
+      }
+      HTypeGuard guard = new HTypeGuard(speculativeType, instruction, target);
+      types[guard] = speculativeType;
+      work.guards.add(guard);
+      instruction.block.rewrite(instruction, guard);
+      insertionPoint.block.addBefore(insertionPoint, guard);
+    }
+  }
+}
+
+/**
+ * Computes the environment for each SSA instruction: visits the graph
+ * in post-dominator order. Removes an instruction from the environment
+ * and adds its inputs to the environment at the instruction's
+ * definition.
+ *
+ * At the end of the computation, insert type guards in the graph.
+ */
+class SsaEnvironmentBuilder extends HBaseVisitor implements OptimizationPhase {
+  final Compiler compiler;
+  final String name = 'SsaEnvironmentBuilder';
+
+  final Map<HBailoutTarget, Environment> capturedEnvironments;
+  final Map<HBasicBlock, Environment> liveInstructions;
+  Environment environment;
+  /**
+   * The set of current loop headers that dominate the current block.
+   */
+  Set<HBasicBlock> loopMarkers;
+
+  SsaEnvironmentBuilder(Compiler this.compiler)
+    : capturedEnvironments = new Map<HBailoutTarget, Environment>(),
+      liveInstructions = new Map<HBasicBlock, Environment>(),
+      loopMarkers = new Set<HBasicBlock>();
+
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+    if (!liveInstructions[graph.entry].isEmpty) {
+      compiler.internalError('Bailout environment computation',
+          node: compiler.currentElement.parseNode(compiler));
+    }
+    updateLoopMarkers();
+    insertCapturedEnvironments();
+  }
+
+  void updateLoopMarkers() {
+    // If the block is a loop header, we need to merge the loop
+    // header's live instructions into every environment that contains
+    // the loop marker.
+    // For example with the following loop (read the example in
+    // reverse):
+    //
+    // while (true) { <-- (4) update the marker with the environment
+    //   use(x);      <-- (3) environment = {x}
+    //   bailout;     <-- (2) has the marker when computed
+    // }              <-- (1) create a loop marker
+    //
+    // The bailout instruction first captures the marker, but it
+    // will be replaced by the live environment at the loop entry,
+    // in this case {x}.
+    capturedEnvironments.forEach((ignoredInstruction, env) {
+      env.loopMarkers.forEach((HBasicBlock header) {
+        env.addAll(liveInstructions[header]);
+      });
+      env.loopMarkers.clear();
+    });
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    environment = new Environment();
+
+    // Add to the environment the live instructions of its successor, as well as
+    // the inputs of the phis of the successor that flow from this block.
+    for (int i = 0; i < block.successors.length; i++) {
+      HBasicBlock successor = block.successors[i];
+      Environment successorEnv = liveInstructions[successor];
+      if (successorEnv != null) {
+        environment.addAll(successorEnv);
+      } else {
+        // If we haven't computed the liveInstructions of that successor, we
+        // know it must be a loop header.
+        assert(successor.isLoopHeader());
+        assert(!block.isLoopHeader());
+        loopMarkers.add(successor);
+      }
+
+      int index = successor.predecessors.indexOf(block);
+      for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) {
+        environment.add(phi.inputs[index]);
+      }
+    }
+
+    if (block.isLoopHeader()) {
+      loopMarkers.remove(block);
+    }
+
+    // If the block is a loop header, we're adding all [loopMarkers]
+    // after removing it from the list of [loopMarkers], because
+    // it will just recompute the loop phis.
+    environment.loopMarkers.addAll(loopMarkers);
+
+    // Iterate over all instructions to remove an instruction from the
+    // environment and add its inputs.
+    HInstruction instruction = block.last;
+    while (instruction != null) {
+      instruction.accept(this);
+      instruction = instruction.previous;
+    }
+
+    // We just remove the phis from the environment. The inputs of the
+    // phis will be put in the environment of the predecessors.
+    for (HPhi phi = block.phis.first; phi != null; phi = phi.next) {
+      environment.remove(phi);
+    }
+
+    // Finally save the liveInstructions of that block.
+    liveInstructions[block] = environment;
+  }
+
+  void visitBailoutTarget(HBailoutTarget target) {
+    visitInstruction(target);
+    capturedEnvironments[target] = new Environment.from(environment);
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    environment.remove(instruction);
+    for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+      environment.add(instruction.inputs[i]);
+    }
+  }
+
+  /**
+   * Stores all live variables in the bailout target and the guards.
+   */
+  void insertCapturedEnvironments() {
+    capturedEnvironments.forEach((HBailoutTarget target, Environment env) {
+      assert(target.inputs.length == 0);
+      target.inputs.addAll(env.lives);
+      // TODO(floitsch): we should add the bailout-target's input variables
+      // as input to the guards only in the optimized version. The
+      // non-optimized version does not use the bailout guards and it is
+      // unnecessary to keep the variables alive until the check.
+      for (HTypeGuard guard in target.usedBy) {
+        // A type-guard initially only has two inputs: the guarded instruction
+        // and the bailout-target. Only after adding the environment is it
+        // allowed to have more inputs.
+        assert(guard.inputs.length == 2);
+        guard.inputs.addAll(env.lives);
+      }
+      for (HInstruction live in env.lives) {
+        live.usedBy.add(target);
+        live.usedBy.addAll(target.usedBy);
+      }
+    });
+  }
+}
+
+/**
+ * Propagates bailout information to blocks that need it. This visitor
+ * is run before codegen, to know which blocks have to deal with
+ * bailouts.
+ */
+class SsaBailoutPropagator extends HBaseVisitor {
+  final Compiler compiler;
+  /**
+   * A list to propagate bailout information to blocks that start a
+   * guarded or labeled list of statements. Currently, these blocks
+   * are:
+   *    - first block of a then branch,
+   *    - first block of an else branch,
+   *    - a loop header,
+   *    - labeled block.
+   */
+  final List<HBasicBlock> blocks;
+
+  /**
+   * The current subgraph we are visiting.
+   */
+  SubGraph subGraph;
+
+  /**
+   * The current block information we are visiting.
+   */
+  HBlockInformation currentBlockInformation;
+
+  /**
+   * Max number of arguments to the bailout (not counting the state).
+   */
+  int bailoutArity;
+  /**
+   * A map from variables to their names.  These are the names in the
+   * unoptimized (bailout) version of the function.  Their names could be
+   * different in the optimized version.
+   */
+  VariableNames variableNames;
+  /**
+   * Maps from the variable names to their positions in the argument list of the
+   * bailout instruction.  Because of the way the variable allocator works,
+   * several variables can end up with the same name (if their live ranges do
+   * not overlap), therefore they can have the same position in the bailout
+   * argument list
+   */
+  Map<String, int> parameterNames;
+
+  /**
+   * If set to true, the graph has either multiple bailouts in
+   * different places, or a bailout inside an if or a loop. For such a
+   * graph, the code generator will emit a generic switch.
+   */
+  bool hasComplexBailoutTargets = false;
+
+  /**
+   * The first type guard in the graph.
+   */
+  HBailoutTarget firstBailoutTarget;
+
+  /**
+   * If set, it is the first block in the graph where we generate
+   * code. Blocks before this one are dead code in the bailout
+   * version.
+   */
+
+  SsaBailoutPropagator(this.compiler, this.variableNames)
+      : blocks = <HBasicBlock>[],
+        bailoutArity = 0,
+        parameterNames = new Map<String, int>();
+
+  void visitGraph(HGraph graph) {
+    subGraph = new SubGraph(graph.entry, graph.exit);
+    visitBasicBlock(graph.entry);
+    if (!blocks.isEmpty) {
+      compiler.internalError('Bailout propagation',
+          node: compiler.currentElement.parseNode(compiler));
+    }
+  }
+
+  /**
+   * Returns true if we can visit the given [blockFlow]. False
+   * otherwise. Currently, try/catch and switch are not in bailout
+   * methods, so this method only deals with loops and labeled blocks.
+   * If [blockFlow] is a labeled block or a loop, we also visit the
+   * continuation of the block flow.
+   */
+  bool handleBlockFlow(HBlockFlow blockFlow) {
+    HBlockInformation body = blockFlow.body;
+
+    // We reach here again when starting to visit a subgraph. Just
+    // return to visiting the block.
+    if (currentBlockInformation == body) return false;
+
+    HBlockInformation oldInformation = currentBlockInformation;
+    if (body is HLabeledBlockInformation) {
+      currentBlockInformation = body;
+      HLabeledBlockInformation info = body;
+      visitStatements(info.body, newFlow: true);
+    } else if (body is HLoopBlockInformation) {
+      currentBlockInformation = body;
+      HLoopBlockInformation info = body;
+      if (info.initializer != null) {
+        visitExpression(info.initializer);
+      }
+      blocks.addLast(info.loopHeader);
+      if (!info.isDoWhile()) {
+        visitExpression(info.condition);
+      }
+      visitStatements(info.body, newFlow: false);
+      if (info.isDoWhile()) {
+        visitExpression(info.condition);
+      }
+      if (info.updates != null) {
+        visitExpression(info.updates);
+      }
+      blocks.removeLast();
+    } else {
+      assert(body is! HTryBlockInformation);
+      assert(body is! HSwitchBlockInformation);
+      // [HIfBlockInformation] is handled by visitIf.
+      return false;
+    }
+
+    currentBlockInformation = oldInformation;
+    if (blockFlow.continuation != null) {
+      visitBasicBlock(blockFlow.continuation);
+    }
+    return true;
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    // Abort traversal if we are leaving the currently active sub-graph.
+    if (!subGraph.contains(block)) return;
+
+    HBlockFlow blockFlow = block.blockFlow;
+    if (blockFlow != null && handleBlockFlow(blockFlow)) return;
+
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      instruction.accept(this);
+      instruction = instruction.next;
+    }
+  }
+
+  void visitExpression(HSubExpressionBlockInformation info) {
+    visitSubGraph(info.subExpression);
+  }
+
+  /**
+   * Visit the statements in [info]. If [newFlow] is true, we add the
+   * first block of [statements] to the list of [blocks].
+   */
+  void visitStatements(HSubGraphBlockInformation info, {bool newFlow}) {
+    SubGraph graph = info.subGraph;
+    if (newFlow) blocks.addLast(graph.start);
+    visitSubGraph(graph);
+    if (newFlow) blocks.removeLast();
+  }
+
+  void visitSubGraph(SubGraph graph) {
+    SubGraph oldSubGraph = subGraph;
+    subGraph = graph;
+    visitBasicBlock(graph.start);
+    subGraph = oldSubGraph;
+  }
+
+  void visitIf(HIf instruction) {
+    int preVisitedBlocks = 0;
+    HIfBlockInformation info = instruction.blockInformation.body;
+    visitStatements(info.thenGraph, newFlow: true);
+    preVisitedBlocks++;
+    visitStatements(info.elseGraph, newFlow: true);
+    preVisitedBlocks++;
+
+    HBasicBlock joinBlock = instruction.joinBlock;
+    if (joinBlock != null
+        && !identical(joinBlock.dominator, instruction.block)) {
+      // The join block is dominated by a block in one of the branches.
+      // The subgraph traversal never reached it, so we visit it here
+      // instead.
+      visitBasicBlock(joinBlock);
+    }
+
+    // Visit all the dominated blocks that are not part of the then or else
+    // branches, and is not the join block.
+    // Depending on how the then/else branches terminate
+    // (e.g., return/throw/break) there can be any number of these.
+    List<HBasicBlock> dominated = instruction.block.dominatedBlocks;
+    int dominatedCount = dominated.length;
+    for (int i = preVisitedBlocks; i < dominatedCount; i++) {
+      HBasicBlock dominatedBlock = dominated[i];
+      visitBasicBlock(dominatedBlock);
+    }
+  }
+
+  void visitGoto(HGoto goto) {
+    HBasicBlock block = goto.block;
+    HBasicBlock successor = block.successors[0];
+    if (identical(successor.dominator, block)) {
+      visitBasicBlock(block.successors[0]);
+    }
+  }
+
+  void visitLoopBranch(HLoopBranch branch) {
+    // For a do-while loop, the body has already been visited.
+    if (!branch.isDoWhile()) {
+      visitBasicBlock(branch.block.dominatedBlocks[0]);
+    }
+  }
+
+  visitBailoutTarget(HBailoutTarget target) {
+    int inputLength = target.inputs.length;
+    for (HInstruction input in target.inputs) {
+      String inputName = variableNames.getName(input);
+      int position = parameterNames[inputName];
+      if (position == null) {
+        position = parameterNames[inputName] = bailoutArity++;
+      }
+    }
+
+    if (blocks.isEmpty) {
+      if (firstBailoutTarget == null) {
+        firstBailoutTarget = target;
+      } else {
+        hasComplexBailoutTargets = true;
+      }
+    } else {
+      hasComplexBailoutTargets = true;
+      blocks.forEach((HBasicBlock block) {
+        block.bailoutTargets.add(target);
+      });
+    }
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart
new file mode 100644
index 0000000..ac19fea
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart
@@ -0,0 +1,5034 @@
+// 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.
+
+part of ssa;
+
+/**
+ * A special element for the extra parameter taken by intercepted
+ * methods. We need to override [Element.computeType] because our
+ * optimizers may look at its declared type.
+ */
+class InterceptedElement extends ElementX {
+  final HType ssaType;
+  InterceptedElement(this.ssaType, Element enclosing)
+      : super(const SourceString('receiver'),
+              ElementKind.PARAMETER,
+              enclosing);
+
+  DartType computeType(Compiler compiler) => ssaType.computeType(compiler);
+}
+
+class SsaBuilderTask extends CompilerTask {
+  final CodeEmitterTask emitter;
+  // Loop tracking information.
+  final Set<FunctionElement> functionsCalledInLoop;
+  final Map<SourceString, Selector> selectorsCalledInLoop;
+  final JavaScriptBackend backend;
+
+  String get name => 'SSA builder';
+
+  SsaBuilderTask(JavaScriptBackend backend)
+    : emitter = backend.emitter,
+      functionsCalledInLoop = new Set<FunctionElement>(),
+      selectorsCalledInLoop = new Map<SourceString, Selector>(),
+      backend = backend,
+      super(backend.compiler);
+
+  HGraph build(CodegenWorkItem work) {
+    return measure(() {
+      Element element = work.element.implementation;
+      HInstruction.idCounter = 0;
+      ConstantSystem constantSystem = compiler.backend.constantSystem;
+      SsaBuilder builder = new SsaBuilder(constantSystem, this, work);
+      HGraph graph;
+      ElementKind kind = element.kind;
+      if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR)) {
+        graph = compileConstructor(builder, work);
+      } else if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY) ||
+                 identical(kind, ElementKind.FUNCTION) ||
+                 identical(kind, ElementKind.GETTER) ||
+                 identical(kind, ElementKind.SETTER)) {
+        graph = builder.buildMethod(element);
+      } else if (identical(kind, ElementKind.FIELD)) {
+        graph = builder.buildLazyInitializer(element);
+      } else {
+        compiler.internalErrorOnElement(element,
+                                        'unexpected element kind $kind');
+      }
+      assert(graph.isValid());
+      if (!identical(kind, ElementKind.FIELD)) {
+        bool inLoop = functionsCalledInLoop.contains(element.declaration);
+        if (!inLoop) {
+          Selector selector = selectorsCalledInLoop[element.name];
+          inLoop = selector != null && selector.applies(element, compiler);
+        }
+        graph.calledInLoop = inLoop;
+
+        // If there is an estimate of the parameter types assume these types
+        // when compiling.
+        // TODO(karlklose,ngeoffray): add a check to make sure that element is
+        // of type FunctionElement.
+        FunctionElement function = element;
+        OptionalParameterTypes defaultValueTypes = null;
+        FunctionSignature signature = function.computeSignature(compiler);
+        if (signature.optionalParameterCount > 0) {
+          defaultValueTypes =
+              new OptionalParameterTypes(signature.optionalParameterCount);
+          int index = 0;
+          signature.forEachOptionalParameter((Element parameter) {
+            Constant defaultValue = builder.compileVariable(parameter);
+            HType type = HGraph.mapConstantTypeToSsaType(defaultValue);
+            defaultValueTypes.update(index, parameter.name, type);
+            index++;
+          });
+        } else {
+          // TODO(ahe): I have disabled type optimizations for
+          // optional arguments as the types are stored in the wrong
+          // order.
+          HTypeList parameterTypes =
+              backend.optimisticParameterTypes(element.declaration,
+                                               defaultValueTypes);
+          if (!parameterTypes.allUnknown) {
+            int i = 0;
+            signature.forEachParameter((Element param) {
+              builder.parameters[param].guaranteedType = parameterTypes[i++];
+            });
+          }
+          backend.registerParameterTypesOptimization(
+              element.declaration, parameterTypes, defaultValueTypes);
+        }
+      }
+
+      if (compiler.tracer.enabled) {
+        String name;
+        if (element.isMember()) {
+          String className = element.getEnclosingClass().name.slowToString();
+          String memberName = element.name.slowToString();
+          name = "$className.$memberName";
+          if (element.isGenerativeConstructorBody()) {
+            name = "$name (body)";
+          }
+        } else {
+          name = "${element.name.slowToString()}";
+        }
+        compiler.tracer.traceCompilation(name, work.compilationContext);
+        compiler.tracer.traceGraph('builder', graph);
+      }
+      return graph;
+    });
+  }
+
+  HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) {
+    // The body of the constructor will be generated in a separate function.
+    final ClassElement classElement = work.element.getEnclosingClass();
+    return builder.buildFactory(classElement.implementation,
+                                work.element.implementation);
+  }
+}
+
+/**
+ * Keeps track of locals (including parameters and phis) when building. The
+ * 'this' reference is treated as parameter and hence handled by this class,
+ * too.
+ */
+class LocalsHandler {
+  /**
+   * The values of locals that can be directly accessed (without redirections
+   * to boxes or closure-fields).
+   *
+   * [directLocals] is iterated, so it is a [LinkedHashMap] to make the
+   * iteration order a function only of insertions and not a function of
+   * e.g. Element hash codes.  I'd prefer to use a SortedMap but some elements
+   * don't have source locations for [Elements.compareByPosition].
+   */
+  LinkedHashMap<Element, HInstruction> directLocals;
+  Map<Element, Element> redirectionMapping;
+  SsaBuilder builder;
+  ClosureClassMap closureData;
+
+  LocalsHandler(this.builder)
+      : directLocals = new LinkedHashMap<Element, HInstruction>(),
+        redirectionMapping = new Map<Element, Element>();
+
+  get typesTask => builder.compiler.typesTask;
+
+  /**
+   * Creates a new [LocalsHandler] based on [other]. We only need to
+   * copy the [directLocals], since the other fields can be shared
+   * throughout the AST visit.
+   */
+  LocalsHandler.from(LocalsHandler other)
+      : directLocals =
+            new LinkedHashMap<Element, HInstruction>.from(other.directLocals),
+        redirectionMapping = other.redirectionMapping,
+        builder = other.builder,
+        closureData = other.closureData;
+
+  /**
+   * Redirects accesses from element [from] to element [to]. The [to] element
+   * must be a boxed variable or a variable that is stored in a closure-field.
+   */
+  void redirectElement(Element from, Element to) {
+    assert(redirectionMapping[from] == null);
+    redirectionMapping[from] = to;
+    assert(isStoredInClosureField(from) || isBoxed(from));
+  }
+
+  HInstruction createBox() {
+    // TODO(floitsch): Clean up this hack. Should we create a box-object by
+    // just creating an empty object literal?
+    HInstruction box = new HForeign(const LiteralDartString("{}"),
+                                    HType.UNKNOWN,
+                                    <HInstruction>[]);
+    builder.add(box);
+    return box;
+  }
+
+  /**
+   * If the scope (function or loop) [node] has captured variables then this
+   * method creates a box and sets up the redirections.
+   */
+  void enterScope(Node node, Element element) {
+    // See if any variable in the top-scope of the function is captured. If yes
+    // we need to create a box-object.
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData != null) {
+      HInstruction box;
+      // The scope has captured variables.
+      if (element != null && element.isGenerativeConstructorBody()) {
+        // The box is passed as a parameter to a generative
+        // constructor body.
+        box = builder.addParameter(scopeData.boxElement);
+      } else {
+        box = createBox();
+      }
+      // Add the box to the known locals.
+      directLocals[scopeData.boxElement] = box;
+      // Make sure that accesses to the boxed locals go into the box. We also
+      // need to make sure that parameters are copied into the box if necessary.
+      scopeData.capturedVariableMapping.forEach((Element from, Element to) {
+        // The [from] can only be a parameter for function-scopes and not
+        // loop scopes.
+        if (from.isParameter() && !element.isGenerativeConstructorBody()) {
+          // Now that the redirection is set up, the update to the local will
+          // write the parameter value into the box.
+          // Store the captured parameter in the box. Get the current value
+          // before we put the redirection in place.
+          // We don't need to update the local for a generative
+          // constructor body, because it receives a box that already
+          // contains the updates as the last parameter.
+          HInstruction instruction = readLocal(from);
+          redirectElement(from, to);
+          updateLocal(from, instruction);
+        } else {
+          redirectElement(from, to);
+        }
+      });
+    }
+  }
+
+  /**
+   * Replaces the current box with a new box and copies over the given list
+   * of elements from the old box into the new box.
+   */
+  void updateCaptureBox(Element boxElement, List<Element> toBeCopiedElements) {
+    // Create a new box and copy over the values from the old box into the
+    // new one.
+    HInstruction oldBox = readLocal(boxElement);
+    HInstruction newBox = createBox();
+    for (Element boxedVariable in toBeCopiedElements) {
+      // [readLocal] uses the [boxElement] to find its box. By replacing it
+      // behind its back we can still get to the old values.
+      updateLocal(boxElement, oldBox);
+      HInstruction oldValue = readLocal(boxedVariable);
+      updateLocal(boxElement, newBox);
+      updateLocal(boxedVariable, oldValue);
+    }
+    updateLocal(boxElement, newBox);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [function] must be an implementation element.
+   */
+  void startFunction(Element element, Expression node) {
+    assert(invariant(node, element.isImplementation));
+    Compiler compiler = builder.compiler;
+    closureData = compiler.closureToClassMapper.computeClosureToClassMapping(
+            element, node, builder.elements);
+
+    if (element is FunctionElement) {
+      FunctionElement functionElement = element;
+      FunctionSignature params = functionElement.computeSignature(compiler);
+      params.orderedForEachParameter((Element parameterElement) {
+        if (element.isGenerativeConstructorBody()) {
+          ClosureScope scopeData = closureData.capturingScopes[node];
+          if (scopeData != null
+              && scopeData.capturedVariableMapping.containsKey(
+                  parameterElement)) {
+            // The parameter will be a field in the box passed as the
+            // last parameter. So no need to have it.
+            return;
+          }
+        }
+        HInstruction parameter = builder.addParameter(parameterElement);
+        builder.parameters[parameterElement] = parameter;
+        directLocals[parameterElement] = parameter;
+        parameter.guaranteedType =
+            builder.mapInferredType(
+                typesTask.getGuaranteedTypeOfElement(parameterElement));
+      });
+    }
+
+    enterScope(node, element);
+
+    // If the freeVariableMapping is not empty, then this function was a
+    // nested closure that captures variables. Redirect the captured
+    // variables to fields in the closure.
+    closureData.freeVariableMapping.forEach((Element from, Element to) {
+      redirectElement(from, to);
+    });
+    if (closureData.isClosure()) {
+      // Inside closure redirect references to itself to [:this:].
+      HThis thisInstruction = new HThis(closureData.thisElement);
+      builder.graph.thisInstruction = thisInstruction;
+      builder.graph.entry.addAtEntry(thisInstruction);
+      updateLocal(closureData.closureElement, thisInstruction);
+    } else if (element.isInstanceMember()
+               || element.isGenerativeConstructor()) {
+      // Once closures have been mapped to classes their instance members might
+      // not have any thisElement if the closure was created inside a static
+      // context.
+      ClassElement cls = element.getEnclosingClass();
+      DartType type = cls.computeType(builder.compiler);
+      HThis thisInstruction = new HThis(closureData.thisElement,
+                                        new HBoundedType.nonNull(type));
+      builder.graph.thisInstruction = thisInstruction;
+      builder.graph.entry.addAtEntry(thisInstruction);
+      directLocals[closureData.thisElement] = thisInstruction;
+    }
+
+    // If this method is an intercepted method, add the extra
+    // parameter to it, that is the actual receiver.
+    ClassElement cls = element.getEnclosingClass();
+    if (builder.backend.isInterceptorClass(cls)) {
+      HType type = HType.UNKNOWN;
+      if (cls == builder.backend.jsArrayClass) {
+        type = HType.READABLE_ARRAY;
+      } else if (cls == builder.backend.jsStringClass) {
+        type = HType.STRING;
+      } else if (cls == builder.backend.jsNumberClass) {
+        type = HType.NUMBER;
+      } else if (cls == builder.backend.jsIntClass) {
+        type = HType.INTEGER;
+      } else if (cls == builder.backend.jsDoubleClass) {
+        type = HType.DOUBLE;
+      } else if (cls == builder.backend.jsNullClass) {
+        type = HType.NULL;
+      } else if (cls == builder.backend.jsBoolClass) {
+        type = HType.BOOLEAN;
+      }
+      Element parameter = new InterceptedElement(type, element);
+      HParameterValue value = new HParameterValue(parameter);
+      builder.graph.entry.addAfter(
+          directLocals[closureData.thisElement], value);
+      directLocals[closureData.thisElement] = value;
+      value.guaranteedType = type;
+    }
+  }
+
+  bool hasValueForDirectLocal(Element element) {
+    assert(element != null);
+    assert(isAccessedDirectly(element));
+    return directLocals[element] != null;
+  }
+
+  /**
+   * Returns true if the local can be accessed directly. Boxed variables or
+   * captured variables that are stored in the closure-field return [false].
+   */
+  bool isAccessedDirectly(Element element) {
+    assert(element != null);
+    return redirectionMapping[element] == null
+        && !closureData.usedVariablesInTry.contains(element);
+  }
+
+  bool isStoredInClosureField(Element element) {
+    assert(element != null);
+    if (isAccessedDirectly(element)) return false;
+    Element redirectTarget = redirectionMapping[element];
+    if (redirectTarget == null) return false;
+    if (redirectTarget.isMember()) {
+      assert(redirectTarget is ClosureFieldElement);
+      return true;
+    }
+    return false;
+  }
+
+  bool isBoxed(Element element) {
+    if (isAccessedDirectly(element)) return false;
+    if (isStoredInClosureField(element)) return false;
+    return redirectionMapping[element] != null;
+  }
+
+  bool isUsedInTry(Element element) {
+    return closureData.usedVariablesInTry.contains(element);
+  }
+
+  /**
+   * Returns an [HInstruction] for the given element. If the element is
+   * boxed or stored in a closure then the method generates code to retrieve
+   * the value.
+   */
+  HInstruction readLocal(Element element) {
+    if (isAccessedDirectly(element)) {
+      if (directLocals[element] == null) {
+        builder.compiler.internalError("Cannot find value $element",
+                                       element: element);
+      }
+      return directLocals[element];
+    } else if (isStoredInClosureField(element)) {
+      Element redirect = redirectionMapping[element];
+      HInstruction receiver = readLocal(closureData.closureElement);
+      HInstruction fieldGet = new HFieldGet(redirect, receiver);
+      builder.add(fieldGet);
+      return fieldGet;
+    } else if (isBoxed(element)) {
+      Element redirect = redirectionMapping[element];
+      // In the function that declares the captured variable the box is
+      // accessed as direct local. Inside the nested closure the box is
+      // accessed through a closure-field.
+      // Calling [readLocal] makes sure we generate the correct code to get
+      // the box.
+      assert(redirect.enclosingElement.isVariable());
+      HInstruction box = readLocal(redirect.enclosingElement);
+      HInstruction lookup = new HFieldGet(redirect, box);
+      builder.add(lookup);
+      return lookup;
+    } else {
+      assert(isUsedInTry(element));
+      HLocalValue local = getLocal(element);
+      HInstruction variable = new HLocalGet(element, local);
+      builder.add(variable);
+      return variable;
+    }
+  }
+
+  HType cachedTypeOfThis;
+
+  HInstruction readThis() {
+    HInstruction res = readLocal(closureData.thisElement);
+    if (res.guaranteedType == null) {
+      if (cachedTypeOfThis == null) {
+        assert(closureData.isClosure());
+        Element element = closureData.thisElement;
+        ClassElement cls = element.enclosingElement.getEnclosingClass();
+        DartType type = cls.computeType(builder.compiler);
+        cachedTypeOfThis = new HBoundedType.nonNull(type);
+      }
+      res.guaranteedType = cachedTypeOfThis;
+    }
+    return res;
+  }
+
+  HLocalValue getLocal(Element element) {
+    // If the element is a parameter, we already have a
+    // HParameterValue for it. We cannot create another one because
+    // it could then have another name than the real parameter. And
+    // the other one would not know it is just a copy of the real
+    // parameter.
+    if (element.isParameter()) return builder.parameters[element];
+
+    return builder.activationVariables.putIfAbsent(element, () {
+      HLocalValue local = new HLocalValue(element);
+      builder.graph.entry.addAtExit(local);
+      return local;
+    });
+  }
+
+  /**
+   * Sets the [element] to [value]. If the element is boxed or stored in a
+   * closure then the method generates code to set the value.
+   */
+  void updateLocal(Element element, HInstruction value) {
+    assert(!isStoredInClosureField(element));
+    if (isAccessedDirectly(element)) {
+      directLocals[element] = value;
+    } else if (isBoxed(element)) {
+      Element redirect = redirectionMapping[element];
+      // The box itself could be captured, or be local. A local variable that
+      // is captured will be boxed, but the box itself will be a local.
+      // Inside the closure the box is stored in a closure-field and cannot
+      // be accessed directly.
+      assert(redirect.enclosingElement.isVariable());
+      HInstruction box = readLocal(redirect.enclosingElement);
+      builder.add(new HFieldSet(redirect, box, value));
+    } else {
+      assert(isUsedInTry(element));
+      HLocalValue local = getLocal(element);
+      builder.add(new HLocalSet(element, local, value));
+    }
+  }
+
+  /**
+   * This function must be called before visiting any children of the loop. In
+   * particular it needs to be called before executing the initializers.
+   *
+   * The [LocalsHandler] will make the boxes and updates at the right moment.
+   * The builder just needs to call [enterLoopBody] and [enterLoopUpdates] (for
+   * [For] loops) at the correct places. For phi-handling [beginLoopHeader] and
+   * [endLoop] must also be called.
+   *
+   * The correct place for the box depends on the given loop. In most cases
+   * the box will be created when entering the loop-body: while, do-while, and
+   * for-in (assuming the call to [:next:] is inside the body) can always be
+   * constructed this way.
+   *
+   * Things are slightly more complicated for [For] loops. If no declared
+   * loop variable is boxed then the loop-body approach works here too. If a
+   * loop-variable is boxed we need to introduce a new box for the
+   * loop-variable before we enter the initializer so that the initializer
+   * writes the values into the box. In any case we need to create the box
+   * before the condition since the condition could box the variable.
+   * Since the first box is created outside the actual loop we have a second
+   * location where a box is created: just before the updates. This is
+   * necessary since updates are considered to be part of the next iteration
+   * (and can again capture variables).
+   *
+   * For example the following Dart code prints 1 3 -- 3 4.
+   *
+   *     var fs = [];
+   *     for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) {
+   *       i++;
+   *     }
+   *     print("--");
+   *     for (var i = 0; i < 2; i++) fs[i]();
+   *
+   * We solve this by emitting the following code (only for [For] loops):
+   *  <Create box>    <== move the first box creation outside the loop.
+   *  <initializer>;
+   *  loop-entry:
+   *    if (!<condition>) goto loop-exit;
+   *    <body>
+   *    <update box>  // create a new box and copy the captured loop-variables.
+   *    <updates>
+   *    goto loop-entry;
+   *  loop-exit:
+   */
+  void startLoop(Node node) {
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    if (scopeData.hasBoxedLoopVariables()) {
+      // If there are boxed loop variables then we set up the box and
+      // redirections already now. This way the initializer can write its
+      // values into the box.
+      // For other loops the box will be created when entering the body.
+      enterScope(node, null);
+    }
+  }
+
+  void beginLoopHeader(Node node, HBasicBlock loopEntry) {
+    // Create a copy because we modify the map while iterating over it.
+    Map<Element, HInstruction> saved =
+        new LinkedHashMap<Element, HInstruction>.from(directLocals);
+
+    // Create phis for all elements in the definitions environment.
+    saved.forEach((Element element, HInstruction instruction) {
+      if (isAccessedDirectly(element)) {
+        // We know 'this' cannot be modified.
+        if (!identical(element, closureData.thisElement)) {
+          HPhi phi = new HPhi.singleInput(element, instruction);
+          loopEntry.addPhi(phi);
+          directLocals[element] = phi;
+        } else {
+          directLocals[element] = instruction;
+        }
+      }
+    });
+  }
+
+  void enterLoopBody(Node node) {
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    // If there are no declared boxed loop variables then we did not create the
+    // box before the initializer and we have to create the box now.
+    if (!scopeData.hasBoxedLoopVariables()) {
+      enterScope(node, null);
+    }
+  }
+
+  void enterLoopUpdates(Loop node) {
+    // If there are declared boxed loop variables then the updates might have
+    // access to the box and we must switch to a new box before executing the
+    // updates.
+    // In all other cases a new box will be created when entering the body of
+    // the next iteration.
+    ClosureScope scopeData = closureData.capturingScopes[node];
+    if (scopeData == null) return;
+    if (scopeData.hasBoxedLoopVariables()) {
+      updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables);
+    }
+  }
+
+  void endLoop(HBasicBlock loopEntry) {
+    // If the loop has an aborting body, we don't update the loop
+    // phis.
+    if (loopEntry.predecessors.length == 1) return;
+    loopEntry.forEachPhi((HPhi phi) {
+      Element element = phi.sourceElement;
+      HInstruction postLoopDefinition = directLocals[element];
+      phi.addInput(postLoopDefinition);
+    });
+  }
+
+  /**
+   * Merge [otherLocals] into this locals handler, creating phi-nodes when
+   * there is a conflict.
+   * If a phi node is necessary, it will use this handler's instruction as the
+   * first input, and the otherLocals instruction as the second.
+   */
+  void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) {
+    // If an element is in one map but not the other we can safely
+    // ignore it. It means that a variable was declared in the
+    // block. Since variable declarations are scoped the declared
+    // variable cannot be alive outside the block. Note: this is only
+    // true for nodes where we do joins.
+    Map<Element, HInstruction> joinedLocals =
+        new LinkedHashMap<Element, HInstruction>();
+    otherLocals.directLocals.forEach((element, instruction) {
+      // We know 'this' cannot be modified.
+      if (identical(element, closureData.thisElement)) {
+        assert(directLocals[element] == instruction);
+        joinedLocals[element] = instruction;
+      } else {
+        HInstruction mine = directLocals[element];
+        if (mine == null) return;
+        if (identical(instruction, mine)) {
+          joinedLocals[element] = instruction;
+        } else {
+          HInstruction phi =
+              new HPhi.manyInputs(element, <HInstruction>[mine, instruction]);
+          joinBlock.addPhi(phi);
+          joinedLocals[element] = phi;
+        }
+      }
+    });
+    directLocals = joinedLocals;
+  }
+
+  /**
+   * The current localsHandler is not used for its values, only for its
+   * declared variables. This is a way to exclude local values from the
+   * result when they are no longer in scope.
+   * Returns the new LocalsHandler to use (may not be [this]).
+   */
+  LocalsHandler mergeMultiple(List<LocalsHandler> locals,
+                              HBasicBlock joinBlock) {
+    assert(locals.length > 0);
+    if (locals.length == 1) return locals[0];
+    Map<Element, HInstruction> joinedLocals =
+        new LinkedHashMap<Element,HInstruction>();
+    HInstruction thisValue = null;
+    directLocals.forEach((Element element, HInstruction instruction) {
+      if (!identical(element, closureData.thisElement)) {
+        HPhi phi = new HPhi.noInputs(element);
+        joinedLocals[element] = phi;
+        joinBlock.addPhi(phi);
+      } else {
+        // We know that "this" never changes, if it's there.
+        // Save it for later. While merging, there is no phi for "this",
+        // so we don't have to special case it in the merge loop.
+        thisValue = instruction;
+      }
+    });
+    for (LocalsHandler local in locals) {
+      local.directLocals.forEach((Element element, HInstruction instruction) {
+        HPhi phi = joinedLocals[element];
+        if (phi != null) {
+          phi.addInput(instruction);
+        }
+      });
+    }
+    if (thisValue != null) {
+      // If there was a "this" for the scope, add it to the new locals.
+      joinedLocals[closureData.thisElement] = thisValue;
+    }
+    directLocals = joinedLocals;
+    return this;
+  }
+}
+
+
+// Represents a single break/continue instruction.
+class JumpHandlerEntry {
+  final HJump jumpInstruction;
+  final LocalsHandler locals;
+  bool isBreak() => jumpInstruction is HBreak;
+  bool isContinue() => jumpInstruction is HContinue;
+  JumpHandlerEntry(this.jumpInstruction, this.locals);
+}
+
+
+abstract class JumpHandler {
+  factory JumpHandler(SsaBuilder builder, TargetElement target) {
+    return new TargetJumpHandler(builder, target);
+  }
+  void generateBreak([LabelElement label]);
+  void generateContinue([LabelElement label]);
+  void forEachBreak(void action(HBreak instruction, LocalsHandler locals));
+  void forEachContinue(void action(HContinue instruction,
+                                   LocalsHandler locals));
+  bool hasAnyContinue();
+  bool hasAnyBreak();
+  void close();
+  final TargetElement target;
+  List<LabelElement> labels();
+}
+
+// Insert break handler used to avoid null checks when a target isn't
+// used as the target of a break, and therefore doesn't need a break
+// handler associated with it.
+class NullJumpHandler implements JumpHandler {
+  final Compiler compiler;
+
+  NullJumpHandler(this.compiler);
+
+  void generateBreak([LabelElement label]) {
+    compiler.internalError('generateBreak should not be called');
+  }
+
+  void generateContinue([LabelElement label]) {
+    compiler.internalError('generateContinue should not be called');
+  }
+
+  void forEachBreak(Function ignored) { }
+  void forEachContinue(Function ignored) { }
+  void close() { }
+  bool hasAnyContinue() => false;
+  bool hasAnyBreak() => false;
+
+  List<LabelElement> labels() => const <LabelElement>[];
+  TargetElement get target => null;
+}
+
+// Records breaks until a target block is available.
+// Breaks are always forward jumps.
+// Continues in loops are implemented as breaks of the body.
+// Continues in switches is currently not handled.
+class TargetJumpHandler implements JumpHandler {
+  final SsaBuilder builder;
+  final TargetElement target;
+  final List<JumpHandlerEntry> jumps;
+
+  TargetJumpHandler(SsaBuilder builder, this.target)
+      : this.builder = builder,
+        jumps = <JumpHandlerEntry>[] {
+    assert(builder.jumpTargets[target] == null);
+    builder.jumpTargets[target] = this;
+  }
+
+  void generateBreak([LabelElement label]) {
+    HInstruction breakInstruction;
+    if (label == null) {
+      breakInstruction = new HBreak(target);
+    } else {
+      breakInstruction = new HBreak.toLabel(label);
+    }
+    LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
+    builder.close(breakInstruction);
+    jumps.add(new JumpHandlerEntry(breakInstruction, locals));
+  }
+
+  void generateContinue([LabelElement label]) {
+    HInstruction continueInstruction;
+    if (label == null) {
+      continueInstruction = new HContinue(target);
+    } else {
+      continueInstruction = new HContinue.toLabel(label);
+    }
+    LocalsHandler locals = new LocalsHandler.from(builder.localsHandler);
+    builder.close(continueInstruction);
+    jumps.add(new JumpHandlerEntry(continueInstruction, locals));
+  }
+
+  void forEachBreak(Function action) {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isBreak()) action(entry.jumpInstruction, entry.locals);
+    }
+  }
+
+  void forEachContinue(Function action) {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isContinue()) action(entry.jumpInstruction, entry.locals);
+    }
+  }
+
+  bool hasAnyContinue() {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isContinue()) return true;
+    }
+    return false;
+  }
+
+  bool hasAnyBreak() {
+    for (JumpHandlerEntry entry in jumps) {
+      if (entry.isBreak()) return true;
+    }
+    return false;
+  }
+
+  void close() {
+    // The mapping from TargetElement to JumpHandler is no longer needed.
+    builder.jumpTargets.remove(target);
+  }
+
+  List<LabelElement> labels() {
+    List<LabelElement> result = null;
+    for (LabelElement element in target.labels) {
+      if (result == null) result = <LabelElement>[];
+      result.add(element);
+    }
+    return (result == null) ? const <LabelElement>[] : result;
+  }
+}
+
+class SsaBuilder extends ResolvedVisitor implements Visitor {
+  final SsaBuilderTask builder;
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final ConstantSystem constantSystem;
+  HGraph graph;
+  LocalsHandler localsHandler;
+  HInstruction rethrowableException;
+  Map<Element, HInstruction> parameters;
+  final RuntimeTypeInformation rti;
+  HParameterValue lastAddedParameter;
+
+  Map<TargetElement, JumpHandler> jumpTargets;
+
+  /**
+   * Variables stored in the current activation. These variables are
+   * being updated in try/catch blocks, and should be
+   * accessed indirectly through [HLocalGet] and [HLocalSet].
+   */
+  Map<Element, HLocalValue> activationVariables;
+
+  // We build the Ssa graph by simulating a stack machine.
+  List<HInstruction> stack;
+
+  // The current block to add instructions to. Might be null, if we are
+  // visiting dead code.
+  HBasicBlock current;
+  // The most recently opened block. Has the same value as [current] while
+  // the block is open, but unlike [current], it isn't cleared when the current
+  // block is closed.
+  HBasicBlock lastOpenedBlock;
+
+  final List<Element> sourceElementStack;
+
+  Element get currentElement => sourceElementStack.last.declaration;
+  Compiler get compiler => builder.compiler;
+  CodeEmitterTask get emitter => builder.emitter;
+
+  SsaBuilder(this.constantSystem, SsaBuilderTask builder, CodegenWorkItem work)
+    : this.builder = builder,
+      this.backend = builder.backend,
+      this.work = work,
+      graph = new HGraph(),
+      stack = new List<HInstruction>(),
+      activationVariables = new Map<Element, HLocalValue>(),
+      jumpTargets = new Map<TargetElement, JumpHandler>(),
+      parameters = new Map<Element, HInstruction>(),
+      sourceElementStack = <Element>[work.element],
+      inliningStack = <InliningState>[],
+      rti = builder.backend.rti,
+      super(work.resolutionTree) {
+    localsHandler = new LocalsHandler(this);
+  }
+
+  static const MAX_INLINING_DEPTH = 3;
+  static const MAX_INLINING_SOURCE_SIZE = 128;
+  List<InliningState> inliningStack;
+  Element returnElement;
+  DartType returnType;
+  bool inTryStatement = false;
+
+  /**
+   * Compiles compile-time constants. Never returns [:null:]. If the
+   * initial value is not a compile-time constants, it reports an
+   * internal error.
+   */
+  Constant compileConstant(VariableElement element) {
+    return compiler.constantHandler.compileConstant(element);
+  }
+
+  Constant compileVariable(VariableElement element) {
+    return compiler.constantHandler.compileVariable(element);
+  }
+
+  bool isLazilyInitialized(VariableElement element) {
+    Constant initialValue = compileVariable(element);
+    return initialValue == null;
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [functionElement] must be an implementation element.
+   */
+  HGraph buildMethod(FunctionElement functionElement) {
+    assert(invariant(functionElement, functionElement.isImplementation));
+    FunctionExpression function = functionElement.parseNode(compiler);
+    assert(function != null);
+    assert(!function.modifiers.isExternal());
+    assert(elements[function] != null);
+    openFunction(functionElement, function);
+    SourceString name = functionElement.name;
+    // If [functionElement] is operator== we explicitely add a null
+    // check at the beginning of the method. This is to avoid having
+    // call sites do the null check.
+    if (name == const SourceString('==')) {
+      handleIf(
+          function,
+          () {
+            HParameterValue parameter = parameters.values.first;
+            push(new HIdentity(
+                parameter, graph.addConstantNull(constantSystem)));
+          },
+          () {
+            HReturn ret = new HReturn(
+                graph.addConstantBool(false, constantSystem));
+            close(ret).addSuccessor(graph.exit);
+          },
+          null);
+    }
+    function.body.accept(this);
+    return closeFunction();
+  }
+
+  HGraph buildLazyInitializer(VariableElement variable) {
+    SendSet node = variable.parseNode(compiler);
+    openFunction(variable, node);
+    Link<Node> link = node.arguments;
+    assert(!link.isEmpty && link.tail.isEmpty);
+    visit(link.head);
+    HInstruction value = pop();
+    value = potentiallyCheckType(value, variable.computeType(compiler));
+    close(new HReturn(value)).addSuccessor(graph.exit);
+    return closeFunction();
+  }
+
+  /**
+   * Returns the constructor body associated with the given constructor or
+   * creates a new constructor body, if none can be found.
+   *
+   * Returns [:null:] if the constructor does not have a body.
+   */
+  ConstructorBodyElement getConstructorBody(FunctionElement constructor) {
+    assert(constructor.isGenerativeConstructor());
+    assert(invariant(constructor, constructor.isImplementation));
+    if (constructor.isSynthesized) return null;
+    FunctionExpression node = constructor.parseNode(compiler);
+    // If we know the body doesn't have any code, we don't generate it.
+    if (!node.hasBody()) return null;
+    if (node.hasEmptyBody()) return null;
+    ClassElement classElement = constructor.getEnclosingClass();
+    ConstructorBodyElement bodyElement;
+    classElement.forEachBackendMember((Element backendMember) {
+      if (backendMember.isGenerativeConstructorBody()) {
+        ConstructorBodyElement body = backendMember;
+        if (body.constructor == constructor) {
+          // TODO(kasperl): Find a way of stopping the iteration
+          // through the backend members.
+          bodyElement = backendMember;
+        }
+      }
+    });
+    if (bodyElement == null) {
+      bodyElement = new ConstructorBodyElementX(constructor);
+      // [:resolveMethodElement:] require the passed element to be a
+      // declaration.
+      TreeElements treeElements =
+          compiler.enqueuer.resolution.getCachedElements(
+              constructor.declaration);
+      classElement.addBackendMember(bodyElement);
+
+      if (constructor.isPatch) {
+        // Create origin body element for patched constructors.
+        bodyElement.origin = new ConstructorBodyElementX(constructor.origin);
+        bodyElement.origin.patch = bodyElement;
+        classElement.origin.addBackendMember(bodyElement.origin);
+      }
+      compiler.enqueuer.codegen.addToWorkList(bodyElement.declaration,
+                                              treeElements);
+    }
+    assert(bodyElement.isGenerativeConstructorBody());
+    return bodyElement;
+  }
+
+  HParameterValue addParameter(Element element) {
+    HParameterValue result = new HParameterValue(element);
+    if (lastAddedParameter == null) {
+      graph.entry.addBefore(graph.entry.first, result);
+    } else {
+      graph.entry.addAfter(lastAddedParameter, result);
+    }
+    lastAddedParameter = result;
+    return result;
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [function] must be an implementation element.
+   */
+  InliningState enterInlinedMethod(PartialFunctionElement function,
+                                   Selector selector,
+                                   Link<Node> arguments,
+                                   Node currentNode) {
+    assert(invariant(function, function.isImplementation));
+
+    // Once we start to compile the arguments we must be sure that we don't
+    // abort.
+    List<HInstruction> compiledArguments = new List<HInstruction>();
+    bool succeeded = addStaticSendArgumentsToList(selector,
+                                                  arguments,
+                                                  function,
+                                                  compiledArguments);
+    assert(succeeded);
+
+    FunctionSignature signature = function.computeSignature(compiler);
+    int index = 0;
+    signature.orderedForEachParameter((Element parameter) {
+      HInstruction argument = compiledArguments[index++];
+      localsHandler.updateLocal(parameter, argument);
+      potentiallyCheckType(argument, parameter.computeType(compiler));
+    });
+
+    if (function.isConstructor()) {
+      ClassElement enclosing = function.getEnclosingClass();
+      if (compiler.world.needsRti(enclosing)) {
+        assert(currentNode is NewExpression);
+        InterfaceType type = elements.getType(currentNode);
+        Link<DartType> typeVariable = enclosing.typeVariables;
+        type.typeArguments.forEach((DartType argument) {
+          HInstruction instruction =
+              analyzeTypeArgument(argument, currentNode);
+          localsHandler.updateLocal(typeVariable.head.element, instruction);
+          typeVariable = typeVariable.tail;
+        });
+        while (!typeVariable.isEmpty) {
+          localsHandler.updateLocal(typeVariable.head.element,
+                                    graph.addConstantNull(constantSystem));
+          typeVariable = typeVariable.tail;
+        }
+      }
+    }
+    InliningState state =
+        new InliningState(function, returnElement, returnType, elements, stack);
+
+    // TODO(kasperl): Bad smell. We shouldn't be constructing elements here.
+    returnElement = new ElementX(const SourceString("result"),
+                                 ElementKind.VARIABLE,
+                                 function);
+    localsHandler.updateLocal(returnElement,
+                              graph.addConstantNull(constantSystem));
+    elements = compiler.enqueuer.resolution.getCachedElements(function);
+    assert(elements != null);
+    returnType = signature.returnType;
+    stack = <HInstruction>[];
+    inliningStack.add(state);
+    return state;
+  }
+
+  void leaveInlinedMethod(InliningState state) {
+    InliningState poppedState = inliningStack.removeLast();
+    assert(state == poppedState);
+    elements = state.oldElements;
+    stack.add(localsHandler.readLocal(returnElement));
+    returnElement = state.oldReturnElement;
+    returnType = state.oldReturnType;
+    assert(stack.length == 1);
+    state.oldStack.add(stack[0]);
+    stack = state.oldStack;
+  }
+
+  /**
+   * Try to inline [element] within the currect context of the
+   * builder. The insertion point is the state of the builder.
+   */
+  bool tryInlineMethod(Element element,
+                       Selector selector,
+                       Link<Node> arguments,
+                       Node currentNode) {
+    if (compiler.disableInlining) return false;
+    // Ensure that [element] is an implementation element.
+    element = element.implementation;
+    // TODO(floitsch): we should be able to inline inside lazy initializers.
+    if (!currentElement.isFunction()) return false;
+    // TODO(floitsch): find a cleaner way to know if the element is a function
+    // containing nodes.
+    // [PartialFunctionElement]s are [FunctionElement]s that have [Node]s.
+    if (element is !PartialFunctionElement) return false;
+    // TODO(ngeoffray): try to inline generative constructors. They
+    // don't have any body, which make it more difficult.
+    if (element.isGenerativeConstructor()) return false;
+    if (inliningStack.length > MAX_INLINING_DEPTH) return false;
+    // Don't inline recursive calls. We use the same elements for the inlined
+    // functions and would thus clobber our local variables.
+    // Use [:element.declaration:] since [work.element] is always a declaration.
+    if (currentElement == element.declaration) return false;
+    for (int i = 0; i < inliningStack.length; i++) {
+      if (inliningStack[i].function == element) return false;
+    }
+    PartialFunctionElement function = element;
+    int sourceSize =
+        function.endToken.charOffset - function.beginToken.charOffset;
+    if (sourceSize > MAX_INLINING_SOURCE_SIZE) return false;
+    if (!selector.applies(function, compiler)) return false;
+    FunctionExpression functionExpression = function.parseNode(compiler);
+    TreeElements newElements =
+        compiler.enqueuer.resolution.getCachedElements(function);
+    if (newElements == null) {
+      compiler.internalError("Element not resolved: $function");
+    }
+    if (!InlineWeeder.canBeInlined(functionExpression, newElements)) {
+      return false;
+    }
+
+    InliningState state = enterInlinedMethod(
+        function, selector, arguments, currentNode);
+    inlinedFrom(element, () {
+      functionExpression.body.accept(this);
+    });
+    leaveInlinedMethod(state);
+    return true;
+  }
+
+  inlinedFrom(Element element, f()) {
+    return compiler.withCurrentElement(element, () {
+      sourceElementStack.add(element);
+      var result = f();
+      sourceElementStack.removeLast();
+      return result;
+    });
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [constructor] and [constructors] must all be implementation
+   * elements.
+   */
+  void inlineSuperOrRedirect(FunctionElement constructor,
+                             Selector selector,
+                             Link<Node> arguments,
+                             List<FunctionElement> constructors,
+                             Map<Element, HInstruction> fieldValues,
+                             FunctionElement inlinedFromElement) {
+    compiler.withCurrentElement(constructor, () {
+      assert(invariant(constructor, constructor.isImplementation));
+      constructors.addLast(constructor);
+
+      List<HInstruction> compiledArguments = new List<HInstruction>();
+      bool succeeded =
+          inlinedFrom(inlinedFromElement,
+                       () => addStaticSendArgumentsToList(selector,
+                                                          arguments,
+                                                          constructor,
+                                                          compiledArguments));
+      if (!succeeded) {
+        // Non-matching super and redirects are compile-time errors and thus
+        // checked by the resolver.
+        compiler.internalError(
+            "Parameters and arguments didn't match for super/redirect call",
+            element: constructor);
+      }
+
+      sourceElementStack.add(constructor.enclosingElement);
+      buildFieldInitializers(constructor.enclosingElement.implementation,
+                             fieldValues);
+      sourceElementStack.removeLast();
+
+      int index = 0;
+      FunctionSignature params = constructor.computeSignature(compiler);
+      params.orderedForEachParameter((Element parameter) {
+        HInstruction argument = compiledArguments[index++];
+        // Because we are inlining the initializer, we must update
+        // what was given as parameter. This will be used in case
+        // there is a parameter check expression in the initializer.
+        parameters[parameter] = argument;
+        localsHandler.updateLocal(parameter, argument);
+        // Don't forget to update the field, if the parameter is of the
+        // form [:this.x:].
+        if (parameter.kind == ElementKind.FIELD_PARAMETER) {
+          FieldParameterElement fieldParameterElement = parameter;
+          fieldValues[fieldParameterElement.fieldElement] = argument;
+        }
+      });
+
+      // Build the initializers in the context of the new constructor.
+      TreeElements oldElements = elements;
+      elements =
+          compiler.enqueuer.resolution.getCachedElements(constructor);
+
+      ClosureClassMap oldClosureData = localsHandler.closureData;
+      Node node = constructor.parseNode(compiler);
+      ClosureClassMap newClosureData =
+          compiler.closureToClassMapper.computeClosureToClassMapping(
+              constructor, node, elements);
+      // The [:this:] element now refers to the one in the new closure
+      // data, that is the [:this:] of the super constructor. We
+      // update the element to refer to the current [:this:].
+      localsHandler.updateLocal(newClosureData.thisElement,
+                                localsHandler.readThis());
+      localsHandler.closureData = newClosureData;
+
+      params.orderedForEachParameter((Element parameterElement) {
+        if (elements.isParameterChecked(parameterElement)) {
+          addParameterCheckInstruction(parameterElement);
+        }
+      });
+      localsHandler.enterScope(node, constructor);
+      buildInitializers(constructor, constructors, fieldValues);
+      localsHandler.closureData = oldClosureData;
+      elements = oldElements;
+    });
+  }
+
+  /**
+   * Run through the initializers and inline all field initializers. Recursively
+   * inlines super initializers.
+   *
+   * The constructors of the inlined initializers is added to [constructors]
+   * with sub constructors having a lower index than super constructors.
+   *
+   * Invariant: The [constructor] and elements in [constructors] must all be
+   * implementation elements.
+   */
+  void buildInitializers(FunctionElement constructor,
+                         List<FunctionElement> constructors,
+                         Map<Element, HInstruction> fieldValues) {
+    assert(invariant(constructor, constructor.isImplementation));
+    FunctionExpression functionNode = constructor.parseNode(compiler);
+
+    bool foundSuperOrRedirect = false;
+
+    if (functionNode.initializers != null) {
+      Link<Node> initializers = functionNode.initializers.nodes;
+      for (Link<Node> link = initializers; !link.isEmpty; link = link.tail) {
+        assert(link.head is Send);
+        if (link.head is !SendSet) {
+          // A super initializer or constructor redirection.
+          Send call = link.head;
+          assert(Initializers.isSuperConstructorCall(call) ||
+                 Initializers.isConstructorRedirect(call));
+          FunctionElement target = elements[call];
+          Selector selector = elements.getSelector(call);
+          Link<Node> arguments = call.arguments;
+          inlineSuperOrRedirect(target, selector, arguments, constructors,
+                                fieldValues, constructor);
+          foundSuperOrRedirect = true;
+        } else {
+          // A field initializer.
+          SendSet init = link.head;
+          Link<Node> arguments = init.arguments;
+          assert(!arguments.isEmpty && arguments.tail.isEmpty);
+          sourceElementStack.add(constructor);
+          visit(arguments.head);
+          sourceElementStack.removeLast();
+          fieldValues[elements[init]] = pop();
+        }
+      }
+    }
+
+    if (!foundSuperOrRedirect) {
+      // No super initializer found. Try to find the default constructor if
+      // the class is not Object.
+      ClassElement enclosingClass = constructor.getEnclosingClass();
+      ClassElement superClass = enclosingClass.superclass;
+      if (!enclosingClass.isObject(compiler)) {
+        assert(superClass != null);
+        assert(superClass.resolutionState == STATE_DONE);
+        Selector selector =
+            new Selector.callDefaultConstructor(enclosingClass.getLibrary());
+        // TODO(johnniwinther): Should we find injected constructors as well?
+        FunctionElement target = superClass.lookupConstructor(selector);
+        if (target == null) {
+          compiler.internalError("no default constructor available");
+        }
+        inlineSuperOrRedirect(target.implementation,
+                              selector,
+                              const Link<Node>(),
+                              constructors,
+                              fieldValues,
+                              constructor);
+      }
+    }
+  }
+
+  /**
+   * Run through the fields of [cls] and add their potential
+   * initializers.
+   *
+   * Invariant: [classElement] must be an implementation element.
+   */
+  void buildFieldInitializers(ClassElement classElement,
+                              Map<Element, HInstruction> fieldValues) {
+    assert(invariant(classElement, classElement.isImplementation));
+    classElement.forEachInstanceField(
+        (ClassElement enclosingClass, Element member) {
+          TreeElements definitions = compiler.analyzeElement(member);
+          Node node = member.parseNode(compiler);
+          SendSet assignment = node.asSendSet();
+          HInstruction value;
+          if (assignment == null) {
+            value = graph.addConstantNull(constantSystem);
+          } else {
+            Node right = assignment.arguments.head;
+            TreeElements savedElements = elements;
+            elements = definitions;
+            right.accept(this);
+            elements = savedElements;
+            value = pop();
+          }
+          fieldValues[member] = value;
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: false);
+  }
+
+
+  /**
+   * Build the factory function corresponding to the constructor
+   * [functionElement]:
+   *  - Initialize fields with the values of the field initializers of the
+   *    current constructor and super constructors or constructors redirected
+   *    to, starting from the current constructor.
+   *  - Call the the constructor bodies, starting from the constructor(s) in the
+   *    super class(es).
+   *
+   * Invariant: Both [classElement] and [functionElement] must be
+   * implementation elements.
+   */
+  HGraph buildFactory(ClassElement classElement,
+                      FunctionElement functionElement) {
+    assert(invariant(classElement, classElement.isImplementation));
+    assert(invariant(functionElement, functionElement.isImplementation));
+    FunctionExpression function = functionElement.parseNode(compiler);
+    // Note that constructors (like any other static function) do not need
+    // to deal with optional arguments. It is the callers job to provide all
+    // arguments as if they were positional.
+
+    // The initializer list could contain closures.
+    openFunction(functionElement, function);
+
+    Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>();
+
+    // Compile the possible initialization code for local fields and
+    // super fields.
+    buildFieldInitializers(classElement, fieldValues);
+
+    // Compile field-parameters such as [:this.x:].
+    FunctionSignature params = functionElement.computeSignature(compiler);
+    params.orderedForEachParameter((Element element) {
+      if (element.kind == ElementKind.FIELD_PARAMETER) {
+        // If the [element] is a field-parameter then
+        // initialize the field element with its value.
+        FieldParameterElement fieldParameterElement = element;
+        HInstruction parameterValue = localsHandler.readLocal(element);
+        fieldValues[fieldParameterElement.fieldElement] = parameterValue;
+      }
+    });
+
+    // Analyze the constructor and all referenced constructors and collect
+    // initializers and constructor bodies.
+    List<FunctionElement> constructors = <FunctionElement>[functionElement];
+    buildInitializers(functionElement, constructors, fieldValues);
+
+    // Call the JavaScript constructor with the fields as argument.
+    List<HInstruction> constructorArguments = <HInstruction>[];
+    classElement.forEachInstanceField(
+        (ClassElement enclosingClass, Element member) {
+          constructorArguments.add(potentiallyCheckType(
+              fieldValues[member], member.computeType(compiler)));
+        },
+        includeBackendMembers: true,
+        includeSuperMembers: true);
+
+    InterfaceType type = classElement.computeType(compiler);
+    HType ssaType = new HBoundedType.exact(type);
+    HForeignNew newObject = new HForeignNew(classElement,
+                                            ssaType,
+                                            constructorArguments);
+    add(newObject);
+
+    // Create the runtime type information, if needed.
+    List<HInstruction> inputs = <HInstruction>[];
+    if (compiler.world.needsRti(classElement)) {
+      classElement.typeVariables.forEach((TypeVariableType typeVariable) {
+        inputs.add(localsHandler.directLocals[typeVariable.element]);
+      });
+      callSetRuntimeTypeInfo(classElement, inputs, newObject);
+    }
+
+    // Generate calls to the constructor bodies.
+    for (int index = constructors.length - 1; index >= 0; index--) {
+      FunctionElement constructor = constructors[index];
+      assert(invariant(functionElement, constructor.isImplementation));
+      ConstructorBodyElement body = getConstructorBody(constructor);
+      if (body == null) continue;
+      List bodyCallInputs = <HInstruction>[];
+      bodyCallInputs.add(newObject);
+      FunctionSignature functionSignature = body.computeSignature(compiler);
+      functionSignature.orderedForEachParameter((parameter) {
+        if (!localsHandler.isBoxed(parameter)) {
+          // The parameter will be a field in the box passed as the
+          // last parameter. So no need to pass it.
+          bodyCallInputs.add(localsHandler.readLocal(parameter));
+        }
+      });
+
+      // If parameters are checked, we pass the already computed
+      // boolean to the constructor body.
+      TreeElements elements =
+          compiler.enqueuer.resolution.getCachedElements(constructor);
+      Node node = constructor.parseNode(compiler);
+      ClosureClassMap parameterClosureData =
+          compiler.closureToClassMapper.getMappingForNestedFunction(node);
+      functionSignature.orderedForEachParameter((parameter) {
+        if (elements.isParameterChecked(parameter)) {
+          Element fieldCheck =
+              parameterClosureData.parametersWithSentinel[parameter];
+          bodyCallInputs.add(localsHandler.readLocal(fieldCheck));
+        }
+      });
+
+      // If there are locals that escape (ie used in closures), we
+      // pass the box to the constructor.
+      ClosureScope scopeData = parameterClosureData.capturingScopes[node];
+      if (scopeData != null) {
+        bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement));
+      }
+
+      // TODO(ahe): The constructor name is statically resolved. See
+      // SsaCodeGenerator.visitInvokeDynamicMethod. Is there a cleaner
+      // way to do this?
+      SourceString name =
+          new SourceString(backend.namer.getName(body.declaration));
+      // TODO(kasperl): This seems fishy. We shouldn't be inventing all
+      // these selectors. Maybe the resolver can do more of the work
+      // for us here?
+      LibraryElement library = body.getLibrary();
+      Selector selector = new Selector.call(
+          name, library, bodyCallInputs.length - 1);
+      HInvokeDynamic invoke =
+          new HInvokeDynamicMethod(selector, bodyCallInputs);
+      invoke.element = body;
+      add(invoke);
+    }
+    close(new HReturn(newObject)).addSuccessor(graph.exit);
+    return closeFunction();
+  }
+
+  void addParameterCheckInstruction(Element element) {
+    HInstruction check;
+    Element checkResultElement =
+        localsHandler.closureData.parametersWithSentinel[element];
+    if (currentElement.isGenerativeConstructorBody()) {
+      // A generative constructor body receives extra parameters that
+      // indicate if a parameter was passed to the factory.
+      check = addParameter(checkResultElement);
+    } else {
+      // This is the code we emit for a parameter that is being checked
+      // on whether it was given at value at the call site:
+      //
+      // foo([a = 42]) {
+      //   if (?a) print('parameter passed $a');
+      // }
+      //
+      // foo([a = 42]) {
+      //   var t1 = identical(a, sentinel);
+      //   if (t1) a = 42;
+      //   if (!t1) print('parameter passed ' + a);
+      // }
+
+      // Fetch the original default value of [element];
+      Constant constant = compileVariable(element);
+      HConstant defaultValue = constant == null
+          ? graph.addConstantNull(constantSystem)
+          : graph.addConstant(constant);
+
+      // Emit the equality check with the sentinel.
+      HConstant sentinel = graph.addConstant(SentinelConstant.SENTINEL);
+      HInstruction operand = parameters[element];
+      check = new HIdentity(sentinel, operand);
+      add(check);
+
+      // If the check succeeds, we must update the parameter with the
+      // default value.
+      handleIf(element.parseNode(compiler),
+               () => stack.add(check),
+               () => localsHandler.updateLocal(element, defaultValue),
+               null);
+
+      // Create the instruction that parameter checks will use.
+      check = new HNot(check);
+      add(check);
+    }
+
+    localsHandler.updateLocal(checkResultElement, check);
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [functionElement] must be the implementation element.
+   */
+  void openFunction(Element element, Expression node) {
+    assert(invariant(element, element.isImplementation));
+    HBasicBlock block = graph.addNewBlock();
+    open(graph.entry);
+
+    localsHandler.startFunction(element, node);
+    close(new HGoto()).addSuccessor(block);
+
+    open(block);
+
+    if (element is FunctionElement) {
+      FunctionElement functionElement = element;
+      FunctionSignature signature = functionElement.computeSignature(compiler);
+      signature.orderedForEachParameter((Element parameterElement) {
+        if (elements.isParameterChecked(parameterElement)) {
+          addParameterCheckInstruction(parameterElement);
+        }
+      });
+
+      // Put the type checks in the first successor of the entry,
+      // because that is where the type guards will also be inserted.
+      // This way we ensure that a type guard will dominate the type
+      // check.
+      signature.orderedForEachParameter((Element parameterElement) {
+        if (element.isGenerativeConstructorBody()) {
+          ClosureScope scopeData =
+              localsHandler.closureData.capturingScopes[node];
+          if (scopeData != null
+              && scopeData.capturedVariableMapping.containsKey(
+                  parameterElement)) {
+            // The parameter will be a field in the box passed as the
+            // last parameter. So no need to have it.
+            return;
+          }
+        }
+        HInstruction newParameter = potentiallyCheckType(
+            localsHandler.directLocals[parameterElement],
+            parameterElement.computeType(compiler));
+        localsHandler.directLocals[parameterElement] = newParameter;
+      });
+
+      returnType = signature.returnType;
+    } else {
+      // Otherwise it is a lazy initializer which does not have parameters.
+      assert(element is VariableElement);
+    }
+
+    // Add the type parameters of the class as parameters of this
+    // method.
+    var enclosing = element.enclosingElement;
+    if (element.isConstructor() && compiler.world.needsRti(enclosing)) {
+      enclosing.typeVariables.forEach((TypeVariableType typeVariable) {
+        HParameterValue param = addParameter(typeVariable.element);
+        localsHandler.directLocals[typeVariable.element] = param;
+      });
+    }
+  }
+
+  HInstruction potentiallyCheckType(
+      HInstruction original, DartType type,
+      { int kind: HTypeConversion.CHECKED_MODE_CHECK }) {
+    if (!compiler.enableTypeAssertions) return original;
+    HInstruction other = original.convertType(compiler, type, kind);
+    if (other != original) add(other);
+    return other;
+  }
+
+  HGraph closeFunction() {
+    // TODO(kasperl): Make this goto an implicit return.
+    if (!isAborted()) close(new HGoto()).addSuccessor(graph.exit);
+    graph.finalize();
+    return graph;
+  }
+
+  HBasicBlock addNewBlock() {
+    HBasicBlock block = graph.addNewBlock();
+    // If adding a new block during building of an expression, it is due to
+    // conditional expressions or short-circuit logical operators.
+    return block;
+  }
+
+  void open(HBasicBlock block) {
+    block.open();
+    current = block;
+    lastOpenedBlock = block;
+  }
+
+  HBasicBlock close(HControlFlow end) {
+    HBasicBlock result = current;
+    current.close(end);
+    current = null;
+    return result;
+  }
+
+  void goto(HBasicBlock from, HBasicBlock to) {
+    from.close(new HGoto());
+    from.addSuccessor(to);
+  }
+
+  bool isAborted() {
+    return current == null;
+  }
+
+  /**
+   * Creates a new block, transitions to it from any current block, and
+   * opens the new block.
+   */
+  HBasicBlock openNewBlock() {
+    HBasicBlock newBlock = addNewBlock();
+    if (!isAborted()) goto(current, newBlock);
+    open(newBlock);
+    return newBlock;
+  }
+
+  void add(HInstruction instruction) {
+    current.add(instruction);
+  }
+
+  void addWithPosition(HInstruction instruction, Node node) {
+    add(attachPosition(instruction, node));
+  }
+
+  void push(HInstruction instruction) {
+    add(instruction);
+    stack.add(instruction);
+  }
+
+  void pushWithPosition(HInstruction instruction, Node node) {
+    push(attachPosition(instruction, node));
+  }
+
+  HInstruction pop() {
+    return stack.removeLast();
+  }
+
+  void dup() {
+    stack.add(stack.last);
+  }
+
+  HInstruction popBoolified() {
+    HInstruction value = pop();
+    if (compiler.enableTypeAssertions) {
+      return potentiallyCheckType(
+          value,
+          compiler.boolClass.computeType(compiler),
+          kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK);
+    }
+    HInstruction result = new HBoolify(value);
+    add(result);
+    return result;
+  }
+
+  HInstruction attachPosition(HInstruction target, Node node) {
+    target.sourcePosition = sourceFileLocationForBeginToken(node);
+    return target;
+  }
+
+  SourceFileLocation sourceFileLocationForBeginToken(Node node) =>
+      sourceFileLocationForToken(node, node.getBeginToken());
+
+  SourceFileLocation sourceFileLocationForEndToken(Node node) =>
+      sourceFileLocationForToken(node, node.getEndToken());
+
+  SourceFileLocation sourceFileLocationForToken(Node node, Token token) {
+    Element element = sourceElementStack.last;
+    // TODO(johnniwinther): remove the 'element.patch' hack.
+    if (element is FunctionElement) {
+      FunctionElement functionElement = element;
+      if (functionElement.patch != null) element = functionElement.patch;
+    }
+    Script script = element.getCompilationUnit().script;
+    SourceFile sourceFile = script.file;
+    SourceFileLocation location = new SourceFileLocation(sourceFile, token);
+    if (!location.isValid()) {
+      throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message(
+          {'offset': token.charOffset,
+           'fileName': sourceFile.filename,
+           'length': sourceFile.text.length});
+    }
+    return location;
+}
+
+  void visit(Node node) {
+    if (node != null) node.accept(this);
+  }
+
+  visitBlock(Block node) {
+    for (Link<Node> link = node.statements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      visit(link.head);
+      if (isAborted()) {
+        // The block has been aborted by a return or a throw.
+        if (!stack.isEmpty) compiler.cancel('non-empty instruction stack');
+        return;
+      }
+    }
+    assert(!current.isClosed());
+    if (!stack.isEmpty) compiler.cancel('non-empty instruction stack');
+  }
+
+  visitClassNode(ClassNode node) {
+    compiler.internalError('visitClassNode should not be called', node: node);
+  }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    visit(node.expression);
+    pop();
+  }
+
+  /**
+   * Creates a new loop-header block. The previous [current] block
+   * is closed with an [HGoto] and replaced by the newly created block.
+   * Also notifies the locals handler that we're entering a loop.
+   */
+  JumpHandler beginLoopHeader(Node node) {
+    assert(!isAborted());
+    HBasicBlock previousBlock = close(new HGoto());
+
+    JumpHandler jumpHandler = createJumpHandler(node);
+    HBasicBlock loopEntry = graph.addNewLoopHeaderBlock(
+        jumpHandler.target,
+        jumpHandler.labels());
+    previousBlock.addSuccessor(loopEntry);
+    open(loopEntry);
+
+    localsHandler.beginLoopHeader(node, loopEntry);
+    return jumpHandler;
+  }
+
+  /**
+   * Ends the loop:
+   * - creates a new block and adds it as successor to the [branchBlock].
+   * - opens the new block (setting as [current]).
+   * - notifies the locals handler that we're exiting a loop.
+   */
+  void endLoop(HBasicBlock loopEntry,
+               HBasicBlock branchBlock,
+               JumpHandler jumpHandler,
+               LocalsHandler savedLocals) {
+    if (branchBlock == null && !jumpHandler.hasAnyBreak()) return;
+
+    HBasicBlock loopExitBlock = addNewBlock();
+    assert(branchBlock == null || branchBlock.successors.length == 1);
+    List<LocalsHandler> breakLocals = <LocalsHandler>[];
+    jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
+      breakInstruction.block.addSuccessor(loopExitBlock);
+      breakLocals.add(locals);
+    });
+    if (branchBlock != null) {
+      branchBlock.addSuccessor(loopExitBlock);
+    }
+    open(loopExitBlock);
+    localsHandler.endLoop(loopEntry);
+    if (!breakLocals.isEmpty) {
+      breakLocals.add(savedLocals);
+      localsHandler = savedLocals.mergeMultiple(breakLocals, loopExitBlock);
+    } else {
+      localsHandler = savedLocals;
+    }
+  }
+
+  HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) {
+    if (statements == null) return null;
+    return new HSubGraphBlockInformation(statements);
+  }
+
+  HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) {
+    if (expression == null) return null;
+    return new HSubExpressionBlockInformation(expression);
+  }
+
+  // For while loops, initializer and update are null.
+  // The condition function must return a boolean result.
+  // None of the functions must leave anything on the stack.
+  void handleLoop(Node loop,
+                  void initialize(),
+                  HInstruction condition(),
+                  void update(),
+                  void body()) {
+    // Generate:
+    //  <initializer>
+    //  loop-entry:
+    //    if (!<condition>) goto loop-exit;
+    //    <body>
+    //    <updates>
+    //    goto loop-entry;
+    //  loop-exit:
+
+    localsHandler.startLoop(loop);
+
+    // The initializer.
+    SubExpression initializerGraph = null;
+    HBasicBlock startBlock;
+    if (initialize != null) {
+      HBasicBlock initializerBlock = openNewBlock();
+      startBlock = initializerBlock;
+      initialize();
+      assert(!isAborted());
+      initializerGraph =
+          new SubExpression(initializerBlock, current);
+    }
+
+    JumpHandler jumpHandler = beginLoopHeader(loop);
+    HLoopInformation loopInfo = current.loopInformation;
+    HBasicBlock conditionBlock = current;
+    if (startBlock == null) startBlock = conditionBlock;
+
+    HInstruction conditionInstruction = condition();
+    HBasicBlock conditionExitBlock =
+        close(new HLoopBranch(conditionInstruction));
+    SubExpression conditionExpression =
+        new SubExpression(conditionBlock, conditionExitBlock);
+
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+
+    // The body.
+    HBasicBlock beginBodyBlock = addNewBlock();
+    conditionExitBlock.addSuccessor(beginBodyBlock);
+    open(beginBodyBlock);
+
+    localsHandler.enterLoopBody(loop);
+    body();
+
+    SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock);
+    HBasicBlock bodyBlock = current;
+    if (current != null) close(new HGoto());
+
+    SubExpression updateGraph;
+
+    // Check that the loop has at least one back-edge.
+    if (jumpHandler.hasAnyContinue() || bodyBlock != null) {
+      // Update.
+      // We create an update block, even when we are in a while loop. There the
+      // update block is the jump-target for continue statements. We could avoid
+      // the creation if there is no continue, but for now we always create it.
+      HBasicBlock updateBlock = addNewBlock();
+
+      List<LocalsHandler> continueLocals = <LocalsHandler>[];
+      jumpHandler.forEachContinue((HContinue instruction,
+                                   LocalsHandler locals) {
+        instruction.block.addSuccessor(updateBlock);
+        continueLocals.add(locals);
+      });
+
+
+      if (bodyBlock != null) {
+        continueLocals.add(localsHandler);
+        bodyBlock.addSuccessor(updateBlock);
+      }
+
+      open(updateBlock);
+      localsHandler =
+          continueLocals[0].mergeMultiple(continueLocals, updateBlock);
+
+      HLabeledBlockInformation labelInfo;
+      List<LabelElement> labels = jumpHandler.labels();
+      TargetElement target = elements[loop];
+      if (!labels.isEmpty) {
+        beginBodyBlock.setBlockFlow(
+            new HLabeledBlockInformation(
+                new HSubGraphBlockInformation(bodyGraph),
+                jumpHandler.labels(),
+                isContinue: true),
+            updateBlock);
+      } else if (target != null && target.isContinueTarget) {
+        beginBodyBlock.setBlockFlow(
+            new HLabeledBlockInformation.implicit(
+                new HSubGraphBlockInformation(bodyGraph),
+                target,
+                isContinue: true),
+            updateBlock);
+      }
+
+      localsHandler.enterLoopUpdates(loop);
+
+      update();
+
+      HBasicBlock updateEndBlock = close(new HGoto());
+      // The back-edge completing the cycle.
+      updateEndBlock.addSuccessor(conditionBlock);
+      updateGraph = new SubExpression(updateBlock, updateEndBlock);
+    }
+
+    if (jumpHandler.hasAnyContinue() || bodyBlock != null) {
+      endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals);
+      conditionBlock.postProcessLoopHeader();
+      HLoopBlockInformation info =
+          new HLoopBlockInformation(
+              HLoopBlockInformation.loopType(loop),
+              wrapExpressionGraph(initializerGraph),
+              wrapExpressionGraph(conditionExpression),
+              wrapStatementGraph(bodyGraph),
+              wrapExpressionGraph(updateGraph),
+              conditionBlock.loopInformation.target,
+              conditionBlock.loopInformation.labels,
+              sourceFileLocationForBeginToken(loop),
+              sourceFileLocationForEndToken(loop));
+
+      startBlock.setBlockFlow(info, current);
+      loopInfo.loopBlockInformation = info;
+    } else {
+      // There is no back edge for the loop, so we turn the code into:
+      // if (condition) {
+      //   body;
+      // } else {
+      //   // We always create an empty else block to avoid critical edges.
+      // }
+      //
+      // If there is any break in the body, we attach a synthetic
+      // label to the if.
+      HBasicBlock elseBlock = addNewBlock();
+      open(elseBlock);
+      close(new HGoto());
+      endLoop(conditionBlock, null, jumpHandler, savedLocals);
+
+      // [endLoop] will not create an exit block if there are no
+      // breaks.
+      if (current == null) open(addNewBlock());
+      elseBlock.addSuccessor(current);
+      SubGraph elseGraph = new SubGraph(elseBlock, elseBlock);
+      // Remove the loop information attached to the header.
+      conditionBlock.loopInformation = null;
+
+      // Remove the [HLoopBranch] instruction and replace it with
+      // [HIf].
+      HInstruction condition = conditionExitBlock.last.inputs[0];
+      conditionExitBlock.addAtExit(new HIf(condition));
+      conditionExitBlock.addSuccessor(elseBlock);
+      conditionExitBlock.remove(conditionExitBlock.last);
+      HIfBlockInformation info =
+          new HIfBlockInformation(
+            wrapExpressionGraph(conditionExpression),
+            wrapStatementGraph(bodyGraph),
+            wrapStatementGraph(elseGraph));
+
+      conditionBlock.setBlockFlow(info, current);
+      HIf ifBlock = conditionBlock.last;
+      ifBlock.blockInformation = conditionBlock.blockFlow;
+
+      // If the body has any break, attach a synthesized label to the
+      // if block.
+      if (jumpHandler.hasAnyBreak()) {
+        TargetElement target = elements[loop];
+        LabelElement label = target.addLabel(null, 'loop');
+        label.setBreakTarget();
+        SubGraph labelGraph = new SubGraph(conditionBlock, current);
+        HLabeledBlockInformation labelInfo = new HLabeledBlockInformation(
+                new HSubGraphBlockInformation(labelGraph),
+                <LabelElement>[label]);
+
+        conditionBlock.setBlockFlow(labelInfo, current);
+
+        jumpHandler.forEachBreak((HBreak breakInstruction, _) {
+          HBasicBlock block = breakInstruction.block;
+          block.addAtExit(new HBreak.toLabel(label));
+          block.remove(breakInstruction);
+        });
+      }
+    }
+    jumpHandler.close();
+  }
+
+  visitFor(For node) {
+    assert(node.body != null);
+    void buildInitializer() {
+      if (node.initializer == null) return;
+      Node initializer = node.initializer;
+      if (initializer != null) {
+        visit(initializer);
+        if (initializer.asExpression() != null) {
+          pop();
+        }
+      }
+    }
+    HInstruction buildCondition() {
+      if (node.condition == null) {
+        return graph.addConstantBool(true, constantSystem);
+      }
+      visit(node.condition);
+      return popBoolified();
+    }
+    void buildUpdate() {
+      for (Expression expression in node.update) {
+        visit(expression);
+        assert(!isAborted());
+        // The result of the update instruction isn't used, and can just
+        // be dropped.
+        HInstruction updateInstruction = pop();
+      }
+    }
+    void buildBody() {
+      visit(node.body);
+    }
+    handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody);
+  }
+
+  visitWhile(While node) {
+    HInstruction buildCondition() {
+      visit(node.condition);
+      return popBoolified();
+    }
+    handleLoop(node,
+               () {},
+               buildCondition,
+               () {},
+               () { visit(node.body); });
+  }
+
+  visitDoWhile(DoWhile node) {
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+    localsHandler.startLoop(node);
+    JumpHandler jumpHandler = beginLoopHeader(node);
+    HLoopInformation loopInfo = current.loopInformation;
+    HBasicBlock loopEntryBlock = current;
+    HBasicBlock bodyEntryBlock = current;
+    TargetElement target = elements[node];
+    bool hasContinues = target != null && target.isContinueTarget;
+    if (hasContinues) {
+      // Add extra block to hang labels on.
+      // It doesn't currently work if they are on the same block as the
+      // HLoopInfo. The handling of HLabeledBlockInformation will visit a
+      // SubGraph that starts at the same block again, so the HLoopInfo is
+      // either handled twice, or it's handled after the labeled block info,
+      // both of which generate the wrong code.
+      // Using a separate block is just a simple workaround.
+      bodyEntryBlock = openNewBlock();
+    }
+    localsHandler.enterLoopBody(node);
+    visit(node.body);
+
+    // If there are no continues we could avoid the creation of the condition
+    // block. This could also lead to a block having multiple entries and exits.
+    HBasicBlock bodyExitBlock;
+    bool isAbortingBody = false;
+    if (current != null) {
+      bodyExitBlock = close(new HGoto());
+    } else {
+      isAbortingBody = true;
+      bodyExitBlock = lastOpenedBlock;
+    }
+
+    SubExpression conditionExpression;
+    HBasicBlock conditionEndBlock;
+    if (!isAbortingBody || hasContinues) {
+      HBasicBlock conditionBlock = addNewBlock();
+
+      List<LocalsHandler> continueLocals = <LocalsHandler>[];
+      jumpHandler.forEachContinue((HContinue instruction,
+                                   LocalsHandler locals) {
+        instruction.block.addSuccessor(conditionBlock);
+        continueLocals.add(locals);
+      });
+
+      if (!isAbortingBody) {
+        bodyExitBlock.addSuccessor(conditionBlock);
+      }
+
+      if (!continueLocals.isEmpty) {
+        if (!isAbortingBody) continueLocals.add(localsHandler);
+        localsHandler =
+            savedLocals.mergeMultiple(continueLocals, conditionBlock);
+        SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
+        List<LabelElement> labels = jumpHandler.labels();
+        HSubGraphBlockInformation bodyInfo =
+            new HSubGraphBlockInformation(bodyGraph);
+        HLabeledBlockInformation info;
+        if (!labels.isEmpty) {
+          info = new HLabeledBlockInformation(bodyInfo, labels,
+                                              isContinue: true);
+        } else {
+          info = new HLabeledBlockInformation.implicit(bodyInfo, target,
+                                                       isContinue: true);
+        }
+        bodyEntryBlock.setBlockFlow(info, conditionBlock);
+      }
+      open(conditionBlock);
+
+      visit(node.condition);
+      assert(!isAborted());
+      HInstruction conditionInstruction = popBoolified();
+      conditionEndBlock = close(
+          new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP));
+
+      HBasicBlock avoidCriticalEdge = addNewBlock();
+      conditionEndBlock.addSuccessor(avoidCriticalEdge);
+      open(avoidCriticalEdge);
+      close(new HGoto());
+      avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
+
+      conditionExpression =
+          new SubExpression(conditionBlock, conditionEndBlock);
+    }
+
+    endLoop(loopEntryBlock, conditionEndBlock, jumpHandler, localsHandler);
+    if (!isAbortingBody || hasContinues) {
+      loopEntryBlock.postProcessLoopHeader();
+      SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock);
+      HLoopBlockInformation loopBlockInfo =
+          new HLoopBlockInformation(
+              HLoopBlockInformation.DO_WHILE_LOOP,
+              null,
+              wrapExpressionGraph(conditionExpression),
+              wrapStatementGraph(bodyGraph),
+              null,
+              loopEntryBlock.loopInformation.target,
+              loopEntryBlock.loopInformation.labels,
+              sourceFileLocationForBeginToken(node),
+              sourceFileLocationForEndToken(node));
+      loopEntryBlock.setBlockFlow(loopBlockInfo, current);
+      loopInfo.loopBlockInformation = loopBlockInfo;
+    } else {
+      // If the loop has no back edge, we remove the loop information
+      // on the header.
+      loopEntryBlock.loopInformation = null;
+
+      // If the body of the loop has any break, we attach a
+      // synthesized label to the body.
+      if (jumpHandler.hasAnyBreak()) {
+        SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
+        TargetElement target = elements[node];
+        LabelElement label = target.addLabel(null, 'loop');
+        label.setBreakTarget();
+        HLabeledBlockInformation info = new HLabeledBlockInformation(
+            new HSubGraphBlockInformation(bodyGraph), <LabelElement>[label]);
+        loopEntryBlock.setBlockFlow(info, current);
+        jumpHandler.forEachBreak((HBreak breakInstruction, _) {
+          HBasicBlock block = breakInstruction.block;
+          block.addAtExit(new HBreak.toLabel(label));
+          block.remove(breakInstruction);
+        });
+      }
+    }
+    jumpHandler.close();
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    ClosureClassMap nestedClosureData =
+        compiler.closureToClassMapper.getMappingForNestedFunction(node);
+    assert(nestedClosureData != null);
+    assert(nestedClosureData.closureClassElement != null);
+    ClassElement closureClassElement =
+        nestedClosureData.closureClassElement;
+    FunctionElement callElement = nestedClosureData.callElement;
+    // TODO(ahe): This should be registered in codegen, not here.
+    compiler.enqueuer.codegen.addToWorkList(callElement, elements);
+    // TODO(ahe): This should be registered in codegen, not here.
+    compiler.enqueuer.codegen.registerInstantiatedClass(closureClassElement);
+    assert(!closureClassElement.hasLocalScopeMembers);
+
+    List<HInstruction> capturedVariables = <HInstruction>[];
+    closureClassElement.forEachBackendMember((Element member) {
+      // The backendMembers also contains the call method(s). We are only
+      // interested in the fields.
+      if (member.isField()) {
+        Element capturedLocal = nestedClosureData.capturedFieldMapping[member];
+        assert(capturedLocal != null);
+        capturedVariables.add(localsHandler.readLocal(capturedLocal));
+      }
+    });
+
+    HType type = new HBoundedType.exact(
+        compiler.functionClass.computeType(compiler));
+    push(new HForeignNew(closureClassElement, type, capturedVariables));
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    visit(node.function);
+    localsHandler.updateLocal(elements[node], pop());
+  }
+
+  visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      stack.add(localsHandler.readThis());
+    } else {
+      compiler.internalError("SsaBuilder.visitIdentifier on non-this",
+                             node: node);
+    }
+  }
+
+  visitIf(If node) {
+    handleIf(node,
+             () => visit(node.condition),
+             () => visit(node.thenPart),
+             node.elsePart != null ? () => visit(node.elsePart) : null);
+  }
+
+  void handleIf(Node diagnosticNode,
+                void visitCondition(), void visitThen(), void visitElse()) {
+    SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode);
+    branchBuilder.handleIf(visitCondition, visitThen, visitElse);
+  }
+
+  void visitLogicalAndOr(Send node, Operator op) {
+    SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
+    branchBuilder.handleLogicalAndOrWithLeftNode(
+        node.receiver,
+        () { visit(node.argumentsNode); },
+        isAnd: (const SourceString("&&") == op.source));
+  }
+
+
+  void visitLogicalNot(Send node) {
+    assert(node.argumentsNode is Prefix);
+    visit(node.receiver);
+    HNot not = new HNot(popBoolified());
+    pushWithPosition(not, node);
+  }
+
+  void visitUnary(Send node, Operator op) {
+    if (node.isParameterCheck) {
+      Element element = elements[node.receiver];
+      Node function = element.enclosingElement.parseNode(compiler);
+      ClosureClassMap parameterClosureData =
+          compiler.closureToClassMapper.getMappingForNestedFunction(function);
+      Element fieldCheck =
+          parameterClosureData.parametersWithSentinel[element];
+      stack.add(localsHandler.readLocal(fieldCheck));
+      return;
+    }
+    assert(node.argumentsNode is Prefix);
+    visit(node.receiver);
+    assert(!identical(op.token.kind, PLUS_TOKEN));
+    HInstruction operand = pop();
+
+    // See if we can constant-fold right away. This avoids rewrites later on.
+    if (operand is HConstant) {
+      UnaryOperation operation = constantSystem.lookupUnary(op.source);
+      HConstant constant = operand;
+      Constant folded = operation.fold(constant.constant);
+      if (folded != null) {
+        stack.add(graph.addConstant(folded));
+        return;
+      }
+    }
+
+    HInvokeDynamicMethod result =
+        buildInvokeDynamic(node, elements.getSelector(node), operand, []);
+    pushWithPosition(result, node);
+  }
+
+  void visitBinary(
+      HInstruction left, Operator op, HInstruction right, Send send) {
+    Selector selector = null;
+    // TODO(ngeoffray): The resolver creates these selectors already
+    // but does not put them on the [send] instruction.
+    switch (op.source.stringValue) {
+      case "+":
+      case "+=":
+      case "++":
+        selector = new Selector.binaryOperator(const SourceString('+'));
+        break;
+      case "-":
+      case "-=":
+      case "--":
+        selector = new Selector.binaryOperator(const SourceString('-'));
+        break;
+      case "*":
+      case "*=":
+        selector = new Selector.binaryOperator(const SourceString('*'));
+        break;
+      case "/":
+      case "/=":
+        selector = new Selector.binaryOperator(const SourceString('/'));
+        break;
+      case "~/":
+      case "~/=":
+        selector = new Selector.binaryOperator(const SourceString('~/'));
+        break;
+      case "%":
+      case "%=":
+        selector = new Selector.binaryOperator(const SourceString('%'));
+        break;
+      case "<<":
+      case "<<=":
+        selector = new Selector.binaryOperator(const SourceString('<<'));
+        break;
+      case ">>":
+      case ">>=":
+        selector = new Selector.binaryOperator(const SourceString('>>'));
+        break;
+      case "|":
+      case "|=":
+        selector = new Selector.binaryOperator(const SourceString('|'));
+        break;
+      case "&":
+      case "&=":
+        selector = new Selector.binaryOperator(const SourceString('&'));
+        break;
+      case "^":
+      case "^=":
+        selector = new Selector.binaryOperator(const SourceString('^'));
+        break;
+      case "==":
+      case "!=":
+        selector = new Selector.binaryOperator(const SourceString('=='));
+        break;
+      case "<":
+        selector = new Selector.binaryOperator(const SourceString('<'));
+        break;
+      case "<=":
+        selector = new Selector.binaryOperator(const SourceString('<='));
+        break;
+      case ">":
+        selector = new Selector.binaryOperator(const SourceString('>'));
+        break;
+      case ">=":
+        selector = new Selector.binaryOperator(const SourceString('>='));
+        break;
+      case "===":
+        pushWithPosition(new HIdentity(left, right), op);
+        return;
+      case "!==":
+        HIdentity eq = new HIdentity(left, right);
+        add(eq);
+        pushWithPosition(new HNot(eq), op);
+        return;
+      default:
+        compiler.internalError("Unexpected operator $op", node: op);
+        break;
+    }
+
+    pushWithPosition(
+          buildInvokeDynamic(send, selector, left, [right]),
+          op);
+    if (op.source.stringValue == '!=') {
+      HBoolify bl = new HBoolify(pop());
+      add(bl);
+      pushWithPosition(new HNot(bl), op);
+    }
+  }
+
+  HInstruction generateInstanceSendReceiver(Send send) {
+    assert(Elements.isInstanceSend(send, elements));
+    if (send.receiver == null) {
+      return localsHandler.readThis();
+    }
+    visit(send.receiver);
+    return pop();
+  }
+
+  String getTargetName(ErroneousElement error, [String prefix]) {
+    String result = error.name.slowToString();
+    if (?prefix) {
+      result = '$prefix $result';
+    }
+    return result;
+  }
+
+  /**
+   * Returns a set of interceptor classes that contain a member whose
+   * signature matches the given [selector].
+   */
+  Set<ClassElement> getInterceptedClassesOn(Selector selector) {
+    return backend.getInterceptedClassesOn(selector);
+  }
+
+  void generateInstanceGetterWithCompiledReceiver(Send send,
+                                                  HInstruction receiver) {
+    assert(Elements.isInstanceSend(send, elements));
+    // TODO(kasperl): This is a convoluted way of checking if we're
+    // generating code for a compound assignment. If we are, we need
+    // to get the selector from the mapping for the AST selector node.
+    Selector selector = (send.asSendSet() == null)
+        ? elements.getSelector(send)
+        : elements.getSelector(send.selector);
+    assert(selector.isGetter());
+    SourceString getterName = selector.name;
+    Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+
+    bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
+    if (interceptedClasses != null) {
+      // If we're using an interceptor class, emit a call to the
+      // interceptor method and then the actual dynamic call on the
+      // interceptor object.
+      HInstruction instruction =
+          invokeInterceptor(interceptedClasses, receiver, send);
+      instruction = new HInvokeDynamicGetter(
+          selector, null, instruction, !hasGetter);
+      // Add the receiver as an argument to the getter call on the
+      // interceptor.
+      instruction.inputs.add(receiver);
+      pushWithPosition(instruction, send);
+    } else {
+      pushWithPosition(
+          new HInvokeDynamicGetter(selector, null, receiver, !hasGetter), send);
+    }
+  }
+
+  void generateGetter(Send send, Element element) {
+    if (Elements.isStaticOrTopLevelField(element)) {
+      Constant value;
+      if (element.isField() && !element.isAssignable()) {
+        // A static final or const. Get its constant value and inline it if
+        // the value can be compiled eagerly.
+        value = compileVariable(element);
+      }
+      if (value != null) {
+        stack.add(graph.addConstant(value));
+      } else if (element.isField() && isLazilyInitialized(element)) {
+        push(new HLazyStatic(element));
+      } else {
+        if (element.isGetter()) {
+          Selector selector = elements.getSelector(send);
+          if (tryInlineMethod(element, selector, const Link<Node>(), send)) {
+            return;
+          }
+        }
+        // TODO(5346): Try to avoid the need for calling [declaration] before
+        // creating an [HStatic].
+        push(new HStatic(element.declaration));
+        if (element.isGetter()) {
+          push(new HInvokeStatic(<HInstruction>[pop()], HType.UNKNOWN));
+        }
+      }
+    } else if (Elements.isInstanceSend(send, elements)) {
+      HInstruction receiver = generateInstanceSendReceiver(send);
+      generateInstanceGetterWithCompiledReceiver(send, receiver);
+    } else if (Elements.isStaticOrTopLevelFunction(element)) {
+      // TODO(5346): Try to avoid the need for calling [declaration] before
+      // creating an [HStatic].
+      push(new HStatic(element.declaration));
+      // TODO(ahe): This should be registered in codegen.
+      compiler.enqueuer.codegen.registerGetOfStaticFunction(element);
+    } else if (Elements.isErroneousElement(element)) {
+      // An erroneous element indicates an unresolved static getter.
+      generateThrowNoSuchMethod(send,
+                                getTargetName(element, 'get'),
+                                argumentNodes: const Link<Node>());
+    } else {
+      stack.add(localsHandler.readLocal(element));
+    }
+  }
+
+  void generateInstanceSetterWithCompiledReceiver(Send send,
+                                                  HInstruction receiver,
+                                                  HInstruction value) {
+    assert(Elements.isInstanceSend(send, elements));
+    Selector selector = elements.getSelector(send);
+    assert(selector.isSetter());
+    SourceString setterName = selector.name;
+    bool hasSetter = compiler.world.hasAnyUserDefinedSetter(selector);
+    Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+    if (interceptedClasses != null) {
+      // If we're using an interceptor class, emit a call to the
+      // getInterceptor method and then the actual dynamic call on the
+      // interceptor object.
+      HInstruction instruction =
+          invokeInterceptor(interceptedClasses, receiver, send);
+      instruction = new HInvokeDynamicSetter(
+          selector, null, instruction, receiver, !hasSetter);
+      // Add the value as an argument to the setter call on the
+      // interceptor.
+      instruction.inputs.add(value);
+      addWithPosition(instruction, send);
+    } else {
+      addWithPosition(
+          new HInvokeDynamicSetter(selector, null, receiver, value, !hasSetter),
+          send);
+    }
+    stack.add(value);
+  }
+
+  void generateSetter(SendSet send, Element element, HInstruction value) {
+    if (Elements.isStaticOrTopLevelField(element)) {
+      if (element.isSetter()) {
+        HStatic target = new HStatic(element);
+        add(target);
+        addWithPosition(
+            new HInvokeStatic(<HInstruction>[target, value], HType.UNKNOWN),
+            send);
+      } else {
+        value = potentiallyCheckType(value, element.computeType(compiler));
+        addWithPosition(new HStaticStore(element, value), send);
+      }
+      stack.add(value);
+    } else if (element == null || Elements.isInstanceField(element)) {
+      HInstruction receiver = generateInstanceSendReceiver(send);
+      generateInstanceSetterWithCompiledReceiver(send, receiver, value);
+    } else if (Elements.isErroneousElement(element)) {
+      // An erroneous element indicates an unresolved static setter.
+      generateThrowNoSuchMethod(send,
+                                getTargetName(element, 'set'),
+                                argumentNodes: send.arguments);
+    } else {
+      stack.add(value);
+      // If the value does not already have a name, give it here.
+      if (value.sourceElement == null) {
+        value.sourceElement = element;
+      }
+      HInstruction checked = potentiallyCheckType(
+          value, element.computeType(compiler));
+      if (!identical(checked, value)) {
+        pop();
+        stack.add(checked);
+      }
+      localsHandler.updateLocal(element, checked);
+    }
+  }
+
+  HInstruction invokeInterceptor(Set<ClassElement> intercepted,
+                                 HInstruction receiver,
+                                 Send send) {
+    HInterceptor interceptor = new HInterceptor(intercepted, receiver);
+    add(interceptor);
+    return interceptor;
+  }
+
+  void pushInvokeHelper0(Element helper, HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper1(Element helper, HInstruction a0, HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper2(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper3(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HInstruction a2,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper4(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HInstruction a2,
+                         HInstruction a3,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  void pushInvokeHelper5(Element helper,
+                         HInstruction a0,
+                         HInstruction a1,
+                         HInstruction a2,
+                         HInstruction a3,
+                         HInstruction a4,
+                         HType type) {
+    HInstruction reference = new HStatic(helper);
+    add(reference);
+    List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3, a4];
+    HInstruction result = new HInvokeStatic(inputs, type);
+    push(result);
+  }
+
+  HForeign createForeign(String code, HType type, List<HInstruction> inputs) {
+    return new HForeign(new LiteralDartString(code), type, inputs);
+  }
+
+  HInstruction getRuntimeTypeInfo(HInstruction target) {
+    pushInvokeHelper1(backend.getGetRuntimeTypeInfo(), target, HType.UNKNOWN);
+    return pop();
+  }
+
+  // TODO(karlklose): change construction of the representations to be GVN'able
+  // (dartbug.com/7182).
+  List<HInstruction> buildTypeArgumentRepresentations(DartType type) {
+    HInstruction createForeignArray(String code, inputs) {
+      return createForeign(code, HType.READABLE_ARRAY, inputs);
+    }
+    HInstruction typeInfo;
+
+    /// Helper to create an instruction that contains the runtime value of
+    /// the type variable [variable].
+    HInstruction getTypeArgument(TypeVariableType variable) {
+      if (typeInfo == null) {
+        typeInfo = getRuntimeTypeInfo(localsHandler.readThis());
+      }
+      int intIndex = RuntimeTypeInformation.getTypeVariableIndex(variable);
+      HInstruction index = graph.addConstantInt(intIndex, constantSystem);
+      return createForeignArray('#[#]', <HInstruction>[typeInfo, index]);
+    }
+
+    // Compute the representation of the type arguments, including access
+    // to the runtime type information for type variables as instructions.
+    HInstruction representations;
+    if (type.element.isTypeVariable()) {
+      return <HInstruction>[getTypeArgument(type)];
+    } else {
+      assert(type.element.isClass());
+      List<HInstruction> arguments = <HInstruction>[];
+      InterfaceType interface = type;
+      for (DartType argument in interface.typeArguments) {
+        List<HInstruction> inputs = <HInstruction>[];
+        String template = rti.getTypeRepresentation(argument, (variable) {
+          HInstruction runtimeType = getTypeArgument(variable);
+          add(runtimeType);
+          inputs.add(runtimeType);
+        });
+        HInstruction representation = createForeignArray(template, inputs);
+        add(representation);
+        arguments.add(representation);
+      }
+      return arguments;
+    }
+  }
+
+  visitOperatorSend(node) {
+    Operator op = node.selector;
+    if (const SourceString("[]") == op.source) {
+      visitDynamicSend(node);
+    } else if (const SourceString("&&") == op.source ||
+               const SourceString("||") == op.source) {
+      visitLogicalAndOr(node, op);
+    } else if (const SourceString("!") == op.source) {
+      visitLogicalNot(node);
+    } else if (node.argumentsNode is Prefix) {
+      visitUnary(node, op);
+    } else if (const SourceString("is") == op.source) {
+      visit(node.receiver);
+      HInstruction expression = pop();
+      Node argument = node.arguments.head;
+      TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
+      bool isNot = false;
+      // TODO(ngeoffray): Duplicating pattern in resolver. We should
+      // add a new kind of node.
+      if (typeAnnotation == null) {
+        typeAnnotation = argument.asSend().receiver;
+        isNot = true;
+      }
+      DartType type = elements.getType(typeAnnotation);
+      if (type.isMalformed) {
+        String reasons = Types.fetchReasonsFromMalformedType(type);
+        if (compiler.enableTypeAssertions) {
+          generateMalformedSubtypeError(node, expression, type, reasons);
+        } else {
+          generateRuntimeError(node, '$type is malformed: $reasons');
+        }
+        return;
+      }
+      if (type.element.isTypeVariable()) {
+        // TODO(karlklose): remove this check when the backend can deal with
+        // checks of the form [:o is T:] where [:T:] is a type variable.
+        stack.add(graph.addConstantBool(true, constantSystem));
+        return;
+      }
+
+      HInstruction instruction;
+      if (type.element.isTypeVariable() ||
+          RuntimeTypeInformation.hasTypeArguments(type)) {
+        HInstruction typeInfo = getRuntimeTypeInfo(expression);
+        // TODO(karlklose): make isSubtype a HInstruction to enable
+        // optimizations?
+        Element helper = compiler.findHelper(const SourceString('isSubtype'));
+        HInstruction isSubtype = new HStatic(helper);
+        add(isSubtype);
+        // Build a list of representations for the type arguments.
+        List<HInstruction> representations =
+            buildTypeArgumentRepresentations(type);
+        // For each type argument, build a call to isSubtype, with the type
+        // argument as first and the representation of the tested type as
+        // second argument.
+        List<HInstruction> checks = <HInstruction>[];
+        int index = 0;
+        representations.forEach((HInstruction representation) {
+          HInstruction position = graph.addConstantInt(index, constantSystem);
+          // Get the index'th type argument from the runtime type information.
+          HInstruction typeArgument =
+              createForeign('#[#]', HType.UNKNOWN, [typeInfo, position]);
+          add(typeArgument);
+          // Create the call to isSubtype.
+          List<HInstruction> inputs =
+              <HInstruction>[isSubtype, typeArgument, representation];
+          HInstruction call = new HInvokeStatic(inputs, HType.BOOLEAN);
+          add(call);
+          checks.add(call);
+          index++;
+        });
+        instruction = new HIs(type, <HInstruction>[expression]..addAll(checks));
+      } else {
+        instruction = new HIs(type, <HInstruction>[expression]);
+      }
+      if (isNot) {
+        add(instruction);
+        instruction = new HNot(instruction);
+      }
+      push(instruction);
+    } else if (const SourceString("as") == op.source) {
+      visit(node.receiver);
+      HInstruction expression = pop();
+      Node argument = node.arguments.head;
+      TypeAnnotation typeAnnotation = argument.asTypeAnnotation();
+      DartType type = elements.getType(typeAnnotation);
+      HInstruction converted = expression.convertType(
+          compiler, type, HTypeConversion.CAST_TYPE_CHECK);
+      if (converted != expression) add(converted);
+      stack.add(converted);
+    } else {
+      visit(node.receiver);
+      visit(node.argumentsNode);
+      var right = pop();
+      var left = pop();
+      visitBinary(left, op, right, node);
+    }
+  }
+
+  void addDynamicSendArgumentsToList(Send node, List<HInstruction> list) {
+    Selector selector = elements.getSelector(node);
+    if (selector.namedArgumentCount == 0) {
+      addGenericSendArgumentsToList(node.arguments, list);
+    } else {
+      // Visit positional arguments and add them to the list.
+      Link<Node> arguments = node.arguments;
+      int positionalArgumentCount = selector.positionalArgumentCount;
+      for (int i = 0;
+           i < positionalArgumentCount;
+           arguments = arguments.tail, i++) {
+        visit(arguments.head);
+        list.add(pop());
+      }
+
+      // Visit named arguments and add them into a temporary map.
+      Map<SourceString, HInstruction> instructions =
+          new Map<SourceString, HInstruction>();
+      List<SourceString> namedArguments = selector.namedArguments;
+      int nameIndex = 0;
+      for (; !arguments.isEmpty; arguments = arguments.tail) {
+        visit(arguments.head);
+        instructions[namedArguments[nameIndex++]] = pop();
+      }
+
+      // Iterate through the named arguments to add them to the list
+      // of instructions, in an order that can be shared with
+      // selectors with the same named arguments.
+      List<SourceString> orderedNames = selector.getOrderedNamedArguments();
+      for (SourceString name in orderedNames) {
+        list.add(instructions[name]);
+      }
+    }
+  }
+
+  /**
+   * Returns true if the arguments were compatible with the function signature.
+   *
+   * Invariant: [element] must be an implementation element.
+   */
+  bool addStaticSendArgumentsToList(Selector selector,
+                                    Link<Node> arguments,
+                                    FunctionElement element,
+                                    List<HInstruction> list) {
+    assert(invariant(element, element.isImplementation));
+
+    HInstruction compileArgument(Node argument) {
+      visit(argument);
+      return pop();
+    }
+
+    HInstruction handleConstant(Element parameter) {
+      Constant constant;
+      TreeElements calleeElements =
+          compiler.enqueuer.resolution.getCachedElements(element);
+      if (calleeElements.isParameterChecked(parameter)) {
+        constant = SentinelConstant.SENTINEL;
+      } else {
+        constant = compileConstant(parameter);
+      }
+      return graph.addConstant(constant);
+    }
+
+    return selector.addArgumentsToList(arguments,
+                                       list,
+                                       element,
+                                       compileArgument,
+                                       handleConstant,
+                                       compiler);
+  }
+
+  void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) {
+    for (; !link.isEmpty; link = link.tail) {
+      visit(link.head);
+      list.add(pop());
+    }
+  }
+
+  visitDynamicSend(Send node) {
+    Selector selector = elements.getSelector(node);
+
+    SourceString dartMethodName;
+    bool isNotEquals = false;
+    if (node.isIndex && !node.arguments.tail.isEmpty) {
+      dartMethodName = Elements.constructOperatorName(
+          const SourceString('[]='), false);
+    } else if (node.selector.asOperator() != null) {
+      SourceString name = node.selector.asIdentifier().source;
+      isNotEquals = identical(name.stringValue, '!=');
+      dartMethodName = Elements.constructOperatorName(
+          name, node.argumentsNode is Prefix);
+    } else {
+      dartMethodName = node.selector.asIdentifier().source;
+    }
+
+    Element element = elements[node];
+    bool isClosureCall = false;
+    if (element != null && compiler.world.hasNoOverridingMember(element)) {
+      if (tryInlineMethod(element, selector, node.arguments, node)) {
+        if (element.isGetter()) {
+          // If the element is a getter, we are doing a closure call
+          // on what this getter returns.
+          assert(selector.isCall());
+          isClosureCall = true;
+        } else {
+          return;
+        }
+      }
+    }
+
+    List<HInstruction> inputs = <HInstruction>[];
+    if (isClosureCall) inputs.add(pop());
+
+    HInstruction receiver;
+    if (!isClosureCall) {
+      if (node.receiver == null) {
+        receiver = localsHandler.readThis();
+      } else {
+        visit(node.receiver);
+        receiver = pop();
+      }
+    }
+
+    addDynamicSendArgumentsToList(node, inputs);
+
+    HInstruction invoke;
+    if (isClosureCall) {
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      invoke = new HInvokeClosure(closureSelector, inputs);
+    } else {
+      invoke = buildInvokeDynamic(node, selector, receiver, inputs);
+    }
+
+    pushWithPosition(invoke, node);
+
+    if (isNotEquals) {
+      HNot not = new HNot(popBoolified());
+      push(not);
+    }
+  }
+
+  visitClosureSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    assert(node.receiver == null);
+    Element element = elements[node];
+    HInstruction closureTarget;
+    if (element == null) {
+      visit(node.selector);
+      closureTarget = pop();
+    } else {
+      assert(Elements.isLocal(element));
+      closureTarget = localsHandler.readLocal(element);
+    }
+    var inputs = <HInstruction>[];
+    inputs.add(closureTarget);
+    addDynamicSendArgumentsToList(node, inputs);
+    Selector closureSelector = new Selector.callClosureFrom(selector);
+    pushWithPosition(new HInvokeClosure(closureSelector, inputs), node);
+  }
+
+  void handleForeignJs(Send node) {
+    Link<Node> link = node.arguments;
+    // If the invoke is on foreign code, don't visit the first
+    // argument, which is the type, and the second argument,
+    // which is the foreign code.
+    if (link.isEmpty || link.tail.isEmpty) {
+      compiler.cancel('At least two arguments expected',
+                      node: node.argumentsNode);
+    }
+    List<HInstruction> inputs = <HInstruction>[];
+    Node type = link.head;
+    Node code = link.tail.head;
+    addGenericSendArgumentsToList(link.tail.tail, inputs);
+
+    native.NativeBehavior nativeBehavior =
+        compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node);
+    HType ssaType = mapNativeBehaviorType(nativeBehavior);
+    if (code is StringNode) {
+      StringNode codeString = code;
+      if (!codeString.isInterpolation) {
+        // codeString may not be an interpolation, but may be a juxtaposition.
+        push(new HForeign(codeString.dartString, ssaType, inputs));
+        return;
+      }
+    }
+    compiler.cancel('JS code must be a string literal', node: code);
+  }
+
+  void handleForeignJsCurrentIsolate(Send node) {
+    if (!node.arguments.isEmpty) {
+      compiler.cancel(
+          'Too many arguments to JS_CURRENT_ISOLATE', node: node);
+    }
+
+    if (!compiler.hasIsolateSupport()) {
+      // If the isolate library is not used, we just generate code
+      // to fetch the Leg's current isolate.
+      String name = backend.namer.CURRENT_ISOLATE;
+      push(new HForeign(new DartString.literal(name),
+                        HType.UNKNOWN,
+                        <HInstruction>[]));
+    } else {
+      // Call a helper method from the isolate library. The isolate
+      // library uses its own isolate structure, that encapsulates
+      // Leg's isolate.
+      Element element = compiler.isolateHelperLibrary.find(
+          const SourceString('_currentIsolate'));
+      if (element == null) {
+        compiler.cancel(
+            'Isolate library and compiler mismatch', node: node);
+      }
+      pushInvokeHelper0(element, HType.UNKNOWN);
+    }
+  }
+
+  void handleForeignJsCallInIsolate(Send node) {
+    Link<Node> link = node.arguments;
+    if (!compiler.hasIsolateSupport()) {
+      // If the isolate library is not used, we just invoke the
+      // closure.
+      visit(link.tail.head);
+      Selector selector = new Selector.callClosure(0);
+      push(new HInvokeClosure(selector, <HInstruction>[pop()]));
+    } else {
+      // Call a helper method from the isolate library.
+      Element element = compiler.isolateHelperLibrary.find(
+          const SourceString('_callInIsolate'));
+      if (element == null) {
+        compiler.cancel(
+            'Isolate library and compiler mismatch', node: node);
+      }
+      HStatic target = new HStatic(element);
+      add(target);
+      List<HInstruction> inputs = <HInstruction>[target];
+      addGenericSendArgumentsToList(link, inputs);
+      push(new HInvokeStatic(inputs, HType.UNKNOWN));
+    }
+  }
+
+  FunctionSignature handleForeignRawFunctionRef(Send node, String name) {
+    if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
+      compiler.cancel('"$name" requires exactly one argument',
+                      node: node.argumentsNode);
+    }
+    Node closure = node.arguments.head;
+    Element element = elements[closure];
+    if (!Elements.isStaticOrTopLevelFunction(element)) {
+      compiler.cancel(
+          '"$name" requires a static or top-level method',
+          node: closure);
+    }
+    FunctionElement function = element;
+    // TODO(johnniwinther): Try to eliminate the need to distinguish declaration
+    // and implementation signatures. Currently it is need because the
+    // signatures have different elements for parameters.
+    FunctionElement implementation = function.implementation;
+    FunctionSignature params = implementation.computeSignature(compiler);
+    if (params.optionalParameterCount != 0) {
+      compiler.cancel(
+          '"$name" does not handle closure with optional parameters',
+          node: closure);
+    }
+    visit(closure);
+    return params;
+  }
+
+  void handleForeignDartClosureToJs(Send node, String name) {
+    FunctionSignature params = handleForeignRawFunctionRef(node, name);
+    List<HInstruction> inputs = <HInstruction>[pop()];
+    String invocationName = backend.namer.invocationName(
+        new Selector.callClosure(params.requiredParameterCount));
+    push(new HForeign(new DartString.literal('#.$invocationName'),
+                      HType.UNKNOWN,
+                      inputs));
+  }
+
+  void handleForeignSetCurrentIsolate(Send node) {
+    if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
+      compiler.cancel('Exactly one argument required',
+                      node: node.argumentsNode);
+    }
+    visit(node.arguments.head);
+    String isolateName = backend.namer.CURRENT_ISOLATE;
+    push(new HForeign(new DartString.literal("$isolateName = #"),
+                      HType.UNKNOWN,
+                      <HInstruction>[pop()]));
+  }
+
+  void handleForeignCreateIsolate(Send node) {
+    if (!node.arguments.isEmpty) {
+      compiler.cancel('Too many arguments',
+                      node: node.argumentsNode);
+    }
+    String constructorName = backend.namer.isolateName;
+    push(new HForeign(new DartString.literal("new $constructorName"),
+                      HType.UNKNOWN,
+                      <HInstruction>[]));
+  }
+
+  visitForeignSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    SourceString name = selector.name;
+    if (name == const SourceString('JS')) {
+      handleForeignJs(node);
+    } else if (name == const SourceString('JS_CURRENT_ISOLATE')) {
+      handleForeignJsCurrentIsolate(node);
+    } else if (name == const SourceString('JS_CALL_IN_ISOLATE')) {
+      handleForeignJsCallInIsolate(node);
+    } else if (name == const SourceString('DART_CLOSURE_TO_JS')) {
+      handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS');
+    } else if (name == const SourceString('RAW_DART_FUNCTION_REF')) {
+      handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF');
+    } else if (name == const SourceString('JS_SET_CURRENT_ISOLATE')) {
+      handleForeignSetCurrentIsolate(node);
+    } else if (name == const SourceString('JS_CREATE_ISOLATE')) {
+      handleForeignCreateIsolate(node);
+    } else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) {
+      stack.add(addConstantString(node, backend.namer.operatorIsPrefix()));
+    } else {
+      throw "Unknown foreign: ${selector}";
+    }
+  }
+
+  generateSuperNoSuchMethodSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    SourceString name = selector.name;
+
+    ClassElement cls = currentElement.getEnclosingClass();
+    Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD);
+    if (element.enclosingElement.declaration != compiler.objectClass) {
+      // Register the call as dynamic if [:noSuchMethod:] on the super class
+      // is _not_ the default implementation from [:Object:].
+      compiler.enqueuer.codegen.registerDynamicInvocation(name, selector);
+    }
+    HStatic target = new HStatic(element);
+    add(target);
+    HInstruction self = localsHandler.readThis();
+    Constant nameConstant = constantSystem.createString(
+        new DartString.literal(name.slowToString()), node);
+
+    String internalName = backend.namer.invocationName(selector);
+    Constant internalNameConstant =
+        constantSystem.createString(new DartString.literal(internalName), node);
+
+    Element createInvocationMirror =
+        compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR);
+
+    var arguments = new List<HInstruction>();
+    if (node.argumentsNode != null) {
+      addGenericSendArgumentsToList(node.arguments, arguments);
+    }
+    var argumentsInstruction = new HLiteralList(arguments);
+    add(argumentsInstruction);
+
+    var argumentNames = new List<HInstruction>();
+    for (SourceString argumentName in selector.namedArguments) {
+      Constant argumentNameConstant =
+          constantSystem.createString(new DartString.literal(
+              argumentName.slowToString()), node);
+      argumentNames.add(graph.addConstant(argumentNameConstant));
+    }
+    var argumentNamesInstruction = new HLiteralList(argumentNames);
+    add(argumentNamesInstruction);
+
+    Constant kindConstant =
+        constantSystem.createInt(selector.invocationMirrorKind);
+
+    pushInvokeHelper5(createInvocationMirror,
+                      graph.addConstant(nameConstant),
+                      graph.addConstant(internalNameConstant),
+                      graph.addConstant(kindConstant),
+                      argumentsInstruction,
+                      argumentNamesInstruction,
+                      HType.UNKNOWN);
+
+    var inputs = <HInstruction>[
+        target,
+        self,
+        pop()];
+    push(new HInvokeSuper(inputs));
+  }
+
+  visitSend(Send node) {
+    Element element = elements[node];
+    if (element != null && identical(element, currentElement)) {
+      graph.isRecursiveMethod = true;
+    }
+    super.visitSend(node);
+  }
+
+  visitSuperSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    Element element = elements[node];
+    if (element == null) return generateSuperNoSuchMethodSend(node);
+    // TODO(5346): Try to avoid the need for calling [declaration] before
+    // creating an [HStatic].
+    HInstruction target = new HStatic(element.declaration);
+    HInstruction context = localsHandler.readThis();
+    add(target);
+    var inputs = <HInstruction>[target, context];
+    if (node.isPropertyAccess) {
+      push(new HInvokeSuper(inputs));
+    } else if (element.isFunction() || element.isGenerativeConstructor()) {
+      // TODO(5347): Try to avoid the need for calling [implementation] before
+      // calling [addStaticSendArgumentsToList].
+      FunctionElement function = element.implementation;
+      bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
+                                                    function, inputs);
+      if (!succeeded) {
+        generateWrongArgumentCountError(node, element, node.arguments);
+      } else {
+        push(new HInvokeSuper(inputs));
+      }
+    } else {
+      target = new HInvokeSuper(inputs);
+      add(target);
+      inputs = <HInstruction>[target];
+      addDynamicSendArgumentsToList(node, inputs);
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      push(new HInvokeClosure(closureSelector, inputs));
+    }
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [argument] must not be malformed in checked mode.
+   */
+  HInstruction analyzeTypeArgument(DartType argument, Node currentNode) {
+    assert(invariant(currentNode,
+                     !compiler.enableTypeAssertions || !argument.isMalformed,
+                     message: '$argument is malformed in checked mode'));
+    if (argument == compiler.types.dynamicType || argument.isMalformed) {
+      // Represent [dynamic] as [null].
+      return graph.addConstantNull(constantSystem);
+    }
+
+    // These variables are shared between invocations of the helper.
+    HInstruction typeInfo;
+    List<HInstruction> inputs = <HInstruction>[];
+
+    /**
+     * Helper to create an instruction that gets the value of a type variable.
+     */
+    void addTypeVariableReference(TypeVariableType type) {
+      Element member = currentElement;
+      if (member.enclosingElement.isClosure()) {
+        ClosureClassElement closureClass = member.enclosingElement;
+        member = closureClass.methodElement;
+        member = member.getOutermostEnclosingMemberOrTopLevel();
+      }
+      if (member.isFactoryConstructor()) {
+        // The type variable is stored in a parameter of the factory.
+        inputs.add(localsHandler.readLocal(type.element));
+      } else if (member.isInstanceMember()
+                 || member.isGenerativeConstructor()) {
+        // The type variable is stored in [this].
+        if (typeInfo == null) {
+          pushInvokeHelper1(backend.getGetRuntimeTypeInfo(),
+                            localsHandler.readThis(),
+                            HType.UNKNOWN);
+          typeInfo = pop();
+        }
+        int index = RuntimeTypeInformation.getTypeVariableIndex(type);
+        HInstruction foreign = createForeign('#[$index]', HType.STRING,
+                                             <HInstruction>[typeInfo]);
+        add(foreign);
+        inputs.add(foreign);
+      } else {
+        // TODO(ngeoffray): Match the VM behavior and throw an
+        // exception at runtime.
+        compiler.cancel('Unimplemented unresolved type variable',
+                        node: currentNode);
+      }
+    }
+
+    String template = rti.getTypeRepresentation(argument,
+                                                addTypeVariableReference);
+    HInstruction result = createForeign(template, HType.STRING, inputs);
+    add(result);
+    return result;
+  }
+
+  void handleListConstructor(InterfaceType type,
+                             Node currentNode,
+                             HInstruction newObject) {
+    if (!compiler.world.needsRti(type.element)) return;
+    List<HInstruction> inputs = <HInstruction>[];
+    if (!type.isRaw) {
+      type.typeArguments.forEach((DartType argument) {
+        inputs.add(analyzeTypeArgument(argument, currentNode));
+      });
+    }
+    callSetRuntimeTypeInfo(type.element, inputs, newObject);
+  }
+
+  void callSetRuntimeTypeInfo(ClassElement element,
+                              List<HInstruction> rtiInputs,
+                              HInstruction newObject) {
+    if (!compiler.world.needsRti(element) || element.typeVariables.isEmpty) {
+      return;
+    }
+
+    HInstruction typeInfo = new HLiteralList(rtiInputs);
+    add(typeInfo);
+
+    // Set the runtime type information on the object.
+    Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo();
+    HInstruction typeInfoSetter = new HStatic(typeInfoSetterElement);
+    add(typeInfoSetter);
+    add(new HInvokeStatic(
+        <HInstruction>[typeInfoSetter, newObject, typeInfo], HType.UNKNOWN));
+  }
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [type] must not be malformed in checked mode.
+   */
+  visitNewSend(Send node, InterfaceType type) {
+    assert(invariant(node,
+                     !compiler.enableTypeAssertions || !type.isMalformed,
+                     message: '$type is malformed in checked mode'));
+    bool isListConstructor = false;
+    computeType(element) {
+      Element originalElement = elements[node];
+      if (identical(originalElement.getEnclosingClass(), compiler.listClass)) {
+        isListConstructor = true;
+        if (node.arguments.isEmpty) {
+          return HType.EXTENDABLE_ARRAY;
+        } else {
+          return HType.MUTABLE_ARRAY;
+        }
+      } else if (element.isGenerativeConstructor()) {
+        ClassElement cls = element.getEnclosingClass();
+        return new HBoundedType.exact(cls.thisType);
+      } else {
+        return HType.UNKNOWN;
+      }
+    }
+
+    Element constructor = elements[node];
+    Selector selector = elements.getSelector(node);
+    if (compiler.enqueuer.resolution.getCachedElements(constructor) == null) {
+      compiler.internalError("Unresolved element: $constructor", node: node);
+    }
+    FunctionElement functionElement = constructor;
+    constructor = functionElement.redirectionTarget;
+    // TODO(5346): Try to avoid the need for calling [declaration] before
+    // creating an [HStatic].
+    HInstruction target = new HStatic(constructor.declaration);
+    add(target);
+    var inputs = <HInstruction>[];
+    inputs.add(target);
+    // TODO(5347): Try to avoid the need for calling [implementation] before
+    // calling [addStaticSendArgumentsToList].
+    bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
+                                                  constructor.implementation,
+                                                  inputs);
+    if (!succeeded) {
+      generateWrongArgumentCountError(node, constructor, node.arguments);
+      return;
+    }
+
+    ClassElement cls = constructor.getEnclosingClass();
+    if (cls.isAbstract(compiler) && constructor.isGenerativeConstructor()) {
+      generateAbstractClassInstantiationError(node, cls.name.slowToString());
+      return;
+    }
+    if (compiler.world.needsRti(cls)) {
+      Link<DartType> typeVariable = cls.typeVariables;
+      type.typeArguments.forEach((DartType argument) {
+        inputs.add(analyzeTypeArgument(argument, node));
+        typeVariable = typeVariable.tail;
+      });
+      // Also add null to non-provided type variables to call the
+      // constructor with the right number of arguments.
+      while (!typeVariable.isEmpty) {
+        inputs.add(graph.addConstantNull(constantSystem));
+        typeVariable = typeVariable.tail;
+      }
+    }
+
+    HType elementType = computeType(constructor);
+    HInstruction newInstance = new HInvokeStatic(inputs, elementType);
+    pushWithPosition(newInstance, node);
+
+    // The List constructor forwards to a Dart static method that does
+    // not know about the type argument. Therefore we special case
+    // this constructor to have the setRuntimeTypeInfo called where
+    // the 'new' is done.
+    if (isListConstructor && compiler.world.needsRti(compiler.listClass)) {
+      handleListConstructor(type, node, newInstance);
+    }
+  }
+
+  visitStaticSend(Send node) {
+    Selector selector = elements.getSelector(node);
+    Element element = elements[node];
+    if (element.isForeign(compiler)) {
+      visitForeignSend(node);
+      return;
+    }
+    if (element.isErroneous()) {
+      generateThrowNoSuchMethod(node,
+                                getTargetName(element),
+                                argumentNodes: node.arguments);
+      return;
+    }
+    if (identical(element, compiler.assertMethod)
+        && !compiler.enableUserAssertions) {
+      stack.add(graph.addConstantNull(constantSystem));
+      return;
+    }
+    compiler.ensure(!element.isGenerativeConstructor());
+    if (element.isFunction()) {
+      bool isIdenticalFunction = element == compiler.identicalFunction;
+
+      if (!isIdenticalFunction
+          && tryInlineMethod(element, selector, node.arguments, node)) {
+        return;
+      }
+
+      HInstruction target = new HStatic(element);
+      add(target);
+      var inputs = <HInstruction>[target];
+      // TODO(5347): Try to avoid the need for calling [implementation] before
+      // calling [addStaticSendArgumentsToList].
+      bool succeeded = addStaticSendArgumentsToList(selector, node.arguments,
+                                                    element.implementation,
+                                                    inputs);
+      if (!succeeded) {
+        generateWrongArgumentCountError(node, element, node.arguments);
+        return;
+      }
+
+      if (isIdenticalFunction) {
+        pushWithPosition(new HIdentity(inputs[1], inputs[2]), node);
+        return;
+      }
+
+      HInvokeStatic instruction = new HInvokeStatic(inputs, HType.UNKNOWN);
+      // TODO(ngeoffray): Only do this if knowing the return type is
+      // useful.
+      HType returnType =
+          builder.backend.optimisticReturnTypesWithRecompilationOnTypeChange(
+              currentElement, element);
+      if (returnType != null) instruction.guaranteedType = returnType;
+      pushWithPosition(instruction, node);
+    } else {
+      generateGetter(node, element);
+      List<HInstruction> inputs = <HInstruction>[pop()];
+      addDynamicSendArgumentsToList(node, inputs);
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      pushWithPosition(new HInvokeClosure(closureSelector, inputs), node);
+    }
+  }
+
+  HConstant addConstantString(Node node, String string) {
+    DartString dartString = new DartString.literal(string);
+    Constant constant = constantSystem.createString(dartString, node);
+    return graph.addConstant(constant);
+  }
+
+  visitTypeReferenceSend(Send node) {
+    Element element = elements[node];
+    if (element.isClass() || element.isTypedef()) {
+      // TODO(karlklose): add type representation
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+    } else if (element.isTypeVariable()) {
+      // TODO(6248): implement support for type variables.
+      compiler.unimplemented('first class type for type variable', node: node);
+    } else {
+      internalError('unexpected element kind $element', node: node);
+    }
+    if (node.isCall) {
+      // This send is of the form 'e(...)', where e is resolved to a type
+      // reference. We create a regular closure call on the result of the type
+      // reference instead of creating a NoSuchMethodError to avoid pulling it
+      // in if it is not used (e.g., in a try/catch).
+      HInstruction target = pop();
+      Selector selector = elements.getSelector(node);
+      List<HInstruction> inputs = <HInstruction>[target];
+      addDynamicSendArgumentsToList(node, inputs);
+      Selector closureSelector = new Selector.callClosureFrom(selector);
+      push(new HInvokeClosure(closureSelector, inputs));
+    }
+  }
+
+  visitGetterSend(Send node) {
+    generateGetter(node, elements[node]);
+  }
+
+  // TODO(antonm): migrate rest of SsaBuilder to internalError.
+  internalError(String reason, {Node node}) {
+    compiler.internalError(reason, node: node);
+  }
+
+  void generateError(Node node, String message, Element helper) {
+    HInstruction errorMessage = addConstantString(node, message);
+    pushInvokeHelper1(helper, errorMessage, HType.UNKNOWN);
+  }
+
+  void generateRuntimeError(Node node, String message) {
+    generateError(node, message, backend.getThrowRuntimeError());
+  }
+
+  void generateAbstractClassInstantiationError(Node node, String message) {
+    generateError(node,
+                  message,
+                  backend.getThrowAbstractClassInstantiationError());
+  }
+
+  void generateThrowNoSuchMethod(Node diagnosticNode,
+                                 String methodName,
+                                 {Link<Node> argumentNodes,
+                                  List<HInstruction> argumentValues,
+                                  List<String> existingArguments}) {
+    Element helper =
+        compiler.findHelper(const SourceString('throwNoSuchMethod'));
+    Constant receiverConstant =
+        constantSystem.createString(new DartString.empty(), diagnosticNode);
+    HInstruction receiver = graph.addConstant(receiverConstant);
+    DartString dartString = new DartString.literal(methodName);
+    Constant nameConstant =
+        constantSystem.createString(dartString, diagnosticNode);
+    HInstruction name = graph.addConstant(nameConstant);
+    if (argumentValues == null) {
+      argumentValues = <HInstruction>[];
+      argumentNodes.forEach((argumentNode) {
+        visit(argumentNode);
+        HInstruction value = pop();
+        argumentValues.add(value);
+      });
+    }
+    HInstruction arguments = new HLiteralList(argumentValues);
+    add(arguments);
+    HInstruction existingNamesList;
+    if (existingArguments != null) {
+      List<HInstruction> existingNames = <HInstruction>[];
+      for (String name in existingArguments) {
+        HInstruction nameConstant =
+            graph.addConstantString(new DartString.literal(name),
+                                    diagnosticNode, constantSystem);
+        existingNames.add(nameConstant);
+      }
+      existingNamesList = new HLiteralList(existingNames);
+      add(existingNamesList);
+    } else {
+      existingNamesList = graph.addConstantNull(constantSystem);
+    }
+    pushInvokeHelper4(
+        helper, receiver, name, arguments, existingNamesList, HType.UNKNOWN);
+  }
+
+  /**
+   * Generate code to throw a [NoSuchMethodError] exception for calling a
+   * method with a wrong number of arguments or mismatching named optional
+   * arguments.
+   */
+  void generateWrongArgumentCountError(Node diagnosticNode,
+                                       FunctionElement function,
+                                       Link<Node> argumentNodes) {
+    List<String> existingArguments = <String>[];
+    FunctionSignature signature = function.computeSignature(compiler);
+    signature.forEachParameter((Element parameter) {
+      existingArguments.add(parameter.name.slowToString());
+    });
+    generateThrowNoSuchMethod(diagnosticNode,
+                              function.name.slowToString(),
+                              argumentNodes: argumentNodes,
+                              existingArguments: existingArguments);
+  }
+
+  void generateMalformedSubtypeError(Node node, HInstruction value,
+                                     DartType type, String reasons) {
+    HInstruction typeString = addConstantString(node, type.toString());
+    HInstruction reasonsString = addConstantString(node, reasons);
+    Element helper = backend.getThrowMalformedSubtypeError();
+    pushInvokeHelper3(helper, value, typeString, reasonsString, HType.UNKNOWN);
+  }
+
+  visitNewExpression(NewExpression node) {
+    Element element = elements[node.send];
+    if (!Elements.isErroneousElement(element)) {
+      FunctionElement function = element;
+      element = function.redirectionTarget;
+    }
+    if (Elements.isErroneousElement(element)) {
+      ErroneousElement error = element;
+      if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) {
+        generateThrowNoSuchMethod(node.send,
+                                  getTargetName(error, 'constructor'),
+                                  argumentNodes: node.send.arguments);
+      } else {
+        Message message = error.messageKind.message(error.messageArguments);
+        generateRuntimeError(node.send, message.toString());
+      }
+    } else if (node.isConst()) {
+      // TODO(karlklose): add type representation
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+    } else {
+      DartType type = elements.getType(node);
+      if (compiler.enableTypeAssertions && type.isMalformed) {
+        String reasons = Types.fetchReasonsFromMalformedType(type);
+        // TODO(johnniwinther): Change to resemble type errors from bounds check
+        // on type arguments.
+        generateRuntimeError(node, '$type is malformed: $reasons');
+      } else {
+        // TODO(karlklose): move this type registration to the codegen.
+        compiler.codegenWorld.instantiatedTypes.add(type);
+        Send send = node.send;
+        Element constructor = elements[send];
+        Selector selector = elements.getSelector(send);
+        if (!tryInlineMethod(constructor, selector, send.arguments, node)) {
+          visitNewSend(send, type);
+        }
+      }
+    }
+  }
+
+  HInvokeDynamicMethod buildInvokeDynamic(Node node,
+                                          Selector selector,
+                                          HInstruction receiver,
+                                          List<HInstruction> arguments) {
+    Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+    List<HInstruction> inputs = <HInstruction>[];
+    bool isIntercepted = interceptedClasses != null;
+    if (isIntercepted) {
+      assert(!interceptedClasses.isEmpty);
+      inputs.add(invokeInterceptor(interceptedClasses, receiver, node));
+    }
+    inputs.add(receiver);
+    inputs.addAll(arguments);
+    return new HInvokeDynamicMethod(selector, inputs, isIntercepted);
+  }
+
+  visitSendSet(SendSet node) {
+    Element element = elements[node];
+    if (!Elements.isUnresolved(element) && element.impliesType()) {
+      Identifier selector = node.selector;
+      generateThrowNoSuchMethod(node, selector.source.slowToString(),
+                                argumentNodes: node.arguments);
+      return;
+    }
+    Operator op = node.assignmentOperator;
+    if (node.isSuperCall) {
+      if (element == null) return generateSuperNoSuchMethodSend(node);
+      HInstruction target = new HStatic(element);
+      HInstruction context = localsHandler.readThis();
+      add(target);
+      var inputs = <HInstruction>[target, context];
+      addDynamicSendArgumentsToList(node, inputs);
+      if (!identical(node.assignmentOperator.source.stringValue, '=')) {
+        compiler.unimplemented('complex super assignment',
+                               node: node.assignmentOperator);
+      }
+      push(new HInvokeSuper(inputs, isSetter: true));
+    } else if (node.isIndex) {
+      if (const SourceString("=") == op.source) {
+        visitDynamicSend(node);
+        HInvokeDynamicMethod method = pop();
+        // Push the value.
+        stack.add(method.inputs.last);
+      } else {
+        visit(node.receiver);
+        HInstruction receiver = pop();
+        visit(node.argumentsNode);
+        HInstruction value;
+        HInstruction index;
+        // Compound assignments are considered as being prefix.
+        bool isCompoundAssignment = op.source.stringValue.endsWith('=');
+        bool isPrefix = !node.isPostfix;
+        Element getter = elements[node.selector];
+        if (isCompoundAssignment) {
+          value = pop();
+          index = pop();
+        } else {
+          index = pop();
+          value = graph.addConstantInt(1, constantSystem);
+        }
+
+        HInvokeDynamicMethod left = buildInvokeDynamic(
+            node, new Selector.index(), receiver, [index]);
+        add(left);
+        visitBinary(left, op, value, node);
+        value = pop();
+        HInvokeDynamicMethod assign = buildInvokeDynamic(
+            node, new Selector.indexSet(), receiver, [index, value]);
+        add(assign);
+        if (isPrefix) {
+          stack.add(value);
+        } else {
+          stack.add(left);
+        }
+      }
+    } else if (const SourceString("=") == op.source) {
+      Element element = elements[node];
+      Link<Node> link = node.arguments;
+      assert(!link.isEmpty && link.tail.isEmpty);
+      visit(link.head);
+      HInstruction value = pop();
+      generateSetter(node, element, value);
+    } else if (identical(op.source.stringValue, "is")) {
+      compiler.internalError("is-operator as SendSet", node: op);
+    } else {
+      assert(const SourceString("++") == op.source ||
+             const SourceString("--") == op.source ||
+             node.assignmentOperator.source.stringValue.endsWith("="));
+      Element element = elements[node];
+      bool isCompoundAssignment = !node.arguments.isEmpty;
+      bool isPrefix = !node.isPostfix;  // Compound assignments are prefix.
+
+      // [receiver] is only used if the node is an instance send.
+      HInstruction receiver = null;
+      Element selectorElement = elements[node];
+      if (Elements.isInstanceSend(node, elements)) {
+        receiver = generateInstanceSendReceiver(node);
+        generateInstanceGetterWithCompiledReceiver(node, receiver);
+      } else {
+        generateGetter(node, elements[node.selector]);
+      }
+      HInstruction left = pop();
+      HInstruction right;
+      if (isCompoundAssignment) {
+        visit(node.argumentsNode);
+        right = pop();
+      } else {
+        right = graph.addConstantInt(1, constantSystem);
+      }
+      visitBinary(left, op, right, node);
+      HInstruction operation = pop();
+      assert(operation != null);
+      if (Elements.isInstanceSend(node, elements)) {
+        assert(receiver != null);
+        generateInstanceSetterWithCompiledReceiver(node, receiver, operation);
+      } else {
+        assert(receiver == null);
+        generateSetter(node, element, operation);
+      }
+      if (!isPrefix) {
+        pop();
+        stack.add(left);
+      }
+    }
+  }
+
+  void visitLiteralInt(LiteralInt node) {
+    stack.add(graph.addConstantInt(node.value, constantSystem));
+  }
+
+  void visitLiteralDouble(LiteralDouble node) {
+    stack.add(graph.addConstantDouble(node.value, constantSystem));
+  }
+
+  void visitLiteralBool(LiteralBool node) {
+    stack.add(graph.addConstantBool(node.value, constantSystem));
+  }
+
+  void visitLiteralString(LiteralString node) {
+    stack.add(graph.addConstantString(node.dartString, node, constantSystem));
+  }
+
+  void visitStringJuxtaposition(StringJuxtaposition node) {
+    if (!node.isInterpolation) {
+      // This is a simple string with no interpolations.
+      stack.add(graph.addConstantString(node.dartString, node, constantSystem));
+      return;
+    }
+    StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
+    stringBuilder.visit(node);
+    stack.add(stringBuilder.result);
+  }
+
+  void visitLiteralNull(LiteralNull node) {
+    stack.add(graph.addConstantNull(constantSystem));
+  }
+
+  visitNodeList(NodeList node) {
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      if (isAborted()) {
+        compiler.reportWarning(link.head, 'dead code');
+      } else {
+        visit(link.head);
+      }
+    }
+  }
+
+  void visitParenthesizedExpression(ParenthesizedExpression node) {
+    visit(node.expression);
+  }
+
+  visitOperator(Operator node) {
+    // Operators are intercepted in their surrounding Send nodes.
+    compiler.internalError('visitOperator should not be called', node: node);
+  }
+
+  visitCascade(Cascade node) {
+    visit(node.expression);
+    // Remove the result and reveal the duplicated receiver on the stack.
+    pop();
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visit(node.expression);
+    dup();
+  }
+
+  void handleInTryStatement() {
+    if (!inTryStatement) return;
+    HBasicBlock block = close(new HExitTry());
+    HBasicBlock newBlock = graph.addNewBlock();
+    block.addSuccessor(newBlock);
+    open(newBlock);
+  }
+
+  visitReturn(Return node) {
+    if (identical(node.getBeginToken().stringValue, 'native')) {
+      native.handleSsaNative(this, node.expression);
+      return;
+    }
+    assert(invariant(node, !node.isRedirectingFactoryBody));
+    HInstruction value;
+    if (node.expression == null) {
+      value = graph.addConstantNull(constantSystem);
+    } else {
+      visit(node.expression);
+      value = pop();
+      value = potentiallyCheckType(value, returnType);
+    }
+
+    handleInTryStatement();
+
+    if (!inliningStack.isEmpty) {
+      localsHandler.updateLocal(returnElement, value);
+    } else {
+      close(attachPosition(new HReturn(value), node)).addSuccessor(graph.exit);
+    }
+  }
+
+  visitThrow(Throw node) {
+    if (node.expression == null) {
+      HInstruction exception = rethrowableException;
+      if (exception == null) {
+        exception = graph.addConstantNull(constantSystem);
+        compiler.internalError(
+            'rethrowableException should not be null', node: node);
+      }
+      close(new HThrow(exception, isRethrow: true));
+    } else {
+      visit(node.expression);
+      close(new HThrow(pop()));
+    }
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    compiler.internalError('visiting type annotation in SSA builder',
+                           node: node);
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    for (Link<Node> link = node.definitions.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      Node definition = link.head;
+      if (definition is Identifier) {
+        HInstruction initialValue = graph.addConstantNull(constantSystem);
+        localsHandler.updateLocal(elements[definition], initialValue);
+      } else {
+        assert(definition is SendSet);
+        visitSendSet(definition);
+        pop();  // Discard value.
+      }
+    }
+  }
+
+  visitLiteralList(LiteralList node) {
+    if (node.isConst()) {
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+      return;
+    }
+
+    List<HInstruction> inputs = <HInstruction>[];
+    for (Link<Node> link = node.elements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      visit(link.head);
+      inputs.add(pop());
+    }
+    push(new HLiteralList(inputs));
+  }
+
+  visitConditional(Conditional node) {
+    SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
+    brancher.handleConditional(() => visit(node.condition),
+                               () => visit(node.thenExpression),
+                               () => visit(node.elseExpression));
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
+    stringBuilder.visit(node);
+    stack.add(stringBuilder.result);
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    // The parts are iterated in visitStringInterpolation.
+    compiler.internalError('visitStringInterpolation should not be called',
+                           node: node);
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    // Do nothing, empty statement.
+  }
+
+  visitModifiers(Modifiers node) {
+    compiler.unimplemented('SsaBuilder.visitModifiers', node: node);
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    assert(!isAborted());
+    handleInTryStatement();
+    TargetElement target = elements[node];
+    assert(target != null);
+    JumpHandler handler = jumpTargets[target];
+    assert(handler != null);
+    if (node.target == null) {
+      handler.generateBreak();
+    } else {
+      LabelElement label = elements[node.target];
+      handler.generateBreak(label);
+    }
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    handleInTryStatement();
+    TargetElement target = elements[node];
+    assert(target != null);
+    JumpHandler handler = jumpTargets[target];
+    assert(handler != null);
+    if (node.target == null) {
+      handler.generateContinue();
+    } else {
+      LabelElement label = elements[node.target];
+      assert(label != null);
+      handler.generateContinue(label);
+    }
+  }
+
+  /**
+   * Creates a [JumpHandler] for a statement. The node must be a jump
+   * target. If there are no breaks or continues targeting the statement,
+   * a special "null handler" is returned.
+   */
+  JumpHandler createJumpHandler(Statement node) {
+    TargetElement element = elements[node];
+    if (element == null || !identical(element.statement, node)) {
+      // No breaks or continues to this node.
+      return new NullJumpHandler(compiler);
+    }
+    return new JumpHandler(this, element);
+  }
+
+  visitForIn(ForIn node) {
+    // Generate a structure equivalent to:
+    //   Iterator<E> $iter = <iterable>.iterator;
+    //   while ($iter.moveNext()) {
+    //     E <declaredIdentifier> = $iter.current;
+    //     <body>
+    //   }
+
+    // The iterator is shared between initializer, condition and body.
+    HInstruction iterator;
+    void buildInitializer() {
+      SourceString iteratorName = const SourceString("iterator");
+      Selector selector =
+          new Selector.getter(iteratorName, currentElement.getLibrary());
+      Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector);
+      visit(node.expression);
+      HInstruction receiver = pop();
+      bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
+      if (interceptedClasses == null) {
+        iterator =
+            new HInvokeDynamicGetter(selector, null, receiver, hasGetter);
+      } else {
+        HInterceptor interceptor =
+            invokeInterceptor(interceptedClasses, receiver, null);
+        iterator =
+            new HInvokeDynamicGetter(selector, null, interceptor, hasGetter);
+        // Add the receiver as an argument to the getter call on the
+        // interceptor.
+        iterator.inputs.add(receiver);
+      }
+      add(iterator);
+    }
+    HInstruction buildCondition() {
+      SourceString name = const SourceString('moveNext');
+      Selector selector = new Selector.call(
+          name, currentElement.getLibrary(), 0);
+      bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector);
+      push(new HInvokeDynamicMethod(selector, <HInstruction>[iterator]));
+      return popBoolified();
+    }
+    void buildBody() {
+      SourceString name = const SourceString('current');
+      Selector call = new Selector.getter(name, currentElement.getLibrary());
+      bool hasGetter = compiler.world.hasAnyUserDefinedGetter(call);
+      push(new HInvokeDynamicGetter(call, null, iterator, hasGetter));
+
+      Element variable;
+      if (node.declaredIdentifier.asSend() != null) {
+        variable = elements[node.declaredIdentifier];
+      } else {
+        assert(node.declaredIdentifier.asVariableDefinitions() != null);
+        VariableDefinitions variableDefinitions = node.declaredIdentifier;
+        variable = elements[variableDefinitions.definitions.nodes.head];
+      }
+      HInstruction oldVariable = pop();
+      if (variable.isErroneous()) {
+        generateThrowNoSuchMethod(node,
+                                  getTargetName(variable, 'set'),
+                                  argumentValues: <HInstruction>[oldVariable]);
+        pop();
+      } else {
+        localsHandler.updateLocal(variable, oldVariable);
+      }
+
+      visit(node.body);
+    }
+    handleLoop(node, buildInitializer, buildCondition, () {}, buildBody);
+  }
+
+  visitLabel(Label node) {
+    compiler.internalError('SsaBuilder.visitLabel', node: node);
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    Statement body = node.statement;
+    if (body is Loop || body is SwitchStatement) {
+      // Loops and switches handle their own labels.
+      visit(body);
+      return;
+    }
+    // Non-loop statements can only be break targets, not continue targets.
+    TargetElement targetElement = elements[body];
+    if (targetElement == null || !identical(targetElement.statement, body)) {
+      // Labeled statements with no element on the body have no breaks.
+      // A different target statement only happens if the body is itself
+      // a break or continue for a different target. In that case, this
+      // label is also always unused.
+      visit(body);
+      return;
+    }
+    LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler);
+    assert(targetElement.isBreakTarget);
+    JumpHandler handler = new JumpHandler(this, targetElement);
+    // Introduce a new basic block.
+    HBasicBlock entryBlock = openNewBlock();
+    visit(body);
+    SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock);
+
+    HBasicBlock joinBlock = graph.addNewBlock();
+    List<LocalsHandler> breakLocals = <LocalsHandler>[];
+    handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
+      breakInstruction.block.addSuccessor(joinBlock);
+      breakLocals.add(locals);
+    });
+    bool hasBreak = breakLocals.length > 0;
+    if (!isAborted()) {
+      goto(current, joinBlock);
+      breakLocals.add(localsHandler);
+    }
+    open(joinBlock);
+    localsHandler = beforeLocals.mergeMultiple(breakLocals, joinBlock);
+
+    if (hasBreak) {
+      // There was at least one reachable break, so the label is needed.
+      entryBlock.setBlockFlow(
+          new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph),
+                                       handler.labels()),
+          joinBlock);
+    }
+    handler.close();
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    if (node.isConst()) {
+      ConstantHandler handler = compiler.constantHandler;
+      Constant constant = handler.compileNodeWithDefinitions(node, elements);
+      stack.add(graph.addConstant(constant));
+      return;
+    }
+    List<HInstruction> inputs = <HInstruction>[];
+    for (Link<Node> link = node.entries.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      visit(link.head);
+      inputs.addLast(pop());
+      inputs.addLast(pop());
+    }
+    HLiteralList keyValuePairs = new HLiteralList(inputs);
+    add(keyValuePairs);
+    pushInvokeHelper1(backend.getMapMaker(), keyValuePairs,
+        new HType.fromBoundedType(compiler.mapClass.computeType(compiler),
+                                  compiler,
+                                  false));
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    visit(node.value);
+    visit(node.key);
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visit(node.expression);
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    if (tryBuildConstantSwitch(node)) return;
+
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+    HBasicBlock startBlock = openNewBlock();
+    visit(node.expression);
+    HInstruction expression = pop();
+    if (node.cases.isEmpty) {
+      return;
+    }
+
+    Link<Node> cases = node.cases.nodes;
+    JumpHandler jumpHandler = createJumpHandler(node);
+
+    buildSwitchCases(cases, expression);
+
+    HBasicBlock lastBlock = lastOpenedBlock;
+
+    // Create merge block for break targets.
+    HBasicBlock joinBlock = new HBasicBlock();
+    List<LocalsHandler> caseLocals = <LocalsHandler>[];
+    jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
+      instruction.block.addSuccessor(joinBlock);
+      caseLocals.add(locals);
+    });
+    if (!isAborted()) {
+      // The current flow is only aborted if the switch has a default that
+      // aborts (all previous cases must abort, and if there is no default,
+      // it's possible to miss all the cases).
+      caseLocals.add(localsHandler);
+      goto(current, joinBlock);
+    }
+    if (caseLocals.length != 0) {
+      graph.addBlock(joinBlock);
+      open(joinBlock);
+      if (caseLocals.length == 1) {
+        localsHandler = caseLocals[0];
+      } else {
+        localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock);
+      }
+    } else {
+      // The joinblock is not used.
+      joinBlock = null;
+    }
+    startBlock.setBlockFlow(
+        new HLabeledBlockInformation.implicit(
+            new HSubGraphBlockInformation(new SubGraph(startBlock, lastBlock)),
+            elements[node]),
+        joinBlock);
+    jumpHandler.close();
+  }
+
+  bool tryBuildConstantSwitch(SwitchStatement node) {
+    Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>();
+    // First check whether all case expressions are compile-time constants,
+    // and all have the same type that doesn't override operator==.
+    // TODO(lrn): Move the constant resolution to the resolver, so
+    // we can report an error before reaching the backend.
+    DartType firstConstantType = null;
+    bool failure = false;
+    for (SwitchCase switchCase in node.cases) {
+      for (Node labelOrCase in switchCase.labelsAndCases) {
+        if (labelOrCase is CaseMatch) {
+          CaseMatch match = labelOrCase;
+          Constant constant =
+            compiler.constantHandler.tryCompileNodeWithDefinitions(
+                match.expression, elements);
+          if (constant == null) {
+            compiler.reportWarning(match.expression,
+                MessageKind.NOT_A_COMPILE_TIME_CONSTANT.error());
+            failure = true;
+            continue;
+          }
+          if (firstConstantType == null) {
+            firstConstantType = constant.computeType(compiler);
+            if (nonPrimitiveTypeOverridesEquals(constant)) {
+              compiler.reportWarning(match.expression,
+                  MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS.error());
+              failure = true;
+            }
+          } else {
+            DartType constantType =
+                constant.computeType(compiler);
+            if (constantType != firstConstantType) {
+              compiler.reportWarning(match.expression,
+                  MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL.error());
+              failure = true;
+            }
+          }
+          constants[labelOrCase] = constant;
+        } else {
+          compiler.reportWarning(node, "Unsupported: Labels on cases");
+          failure = true;
+        }
+      }
+    }
+    if (failure) {
+      return false;
+    }
+
+    // TODO(ngeoffray): Handle switch-instruction in bailout code.
+    work.allowSpeculativeOptimization = false;
+    // Then build a switch structure.
+    HBasicBlock expressionStart = openNewBlock();
+    visit(node.expression);
+    HInstruction expression = pop();
+    if (node.cases.isEmpty) {
+      return true;
+    }
+    HBasicBlock expressionEnd = current;
+
+    HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]);
+    HBasicBlock expressionBlock = close(switchInstruction);
+    JumpHandler jumpHandler = createJumpHandler(node);
+    LocalsHandler savedLocals = localsHandler;
+
+    List<List<Constant>> matchExpressions = <List<Constant>>[];
+    List<HStatementInformation> statements = <HStatementInformation>[];
+    bool hasDefault = false;
+    Element getFallThroughErrorElement =
+        compiler.findHelper(const SourceString("getFallThroughError"));
+    HasNextIterator<Node> caseIterator =
+        new HasNextIterator<Node>(node.cases.iterator);
+    while (caseIterator.hasNext) {
+      SwitchCase switchCase = caseIterator.next();
+      List<Constant> caseConstants = <Constant>[];
+      HBasicBlock block = graph.addNewBlock();
+      for (Node labelOrCase in switchCase.labelsAndCases) {
+        if (labelOrCase is CaseMatch) {
+          Constant constant = constants[labelOrCase];
+          caseConstants.add(constant);
+          HConstant hConstant = graph.addConstant(constant);
+          switchInstruction.inputs.add(hConstant);
+          hConstant.usedBy.add(switchInstruction);
+          expressionBlock.addSuccessor(block);
+        }
+      }
+      matchExpressions.add(caseConstants);
+
+      if (switchCase.isDefaultCase) {
+        // An HSwitch has n inputs and n+1 successors, the last being the
+        // default case.
+        expressionBlock.addSuccessor(block);
+        hasDefault = true;
+      }
+      open(block);
+      localsHandler = new LocalsHandler.from(savedLocals);
+      visit(switchCase.statements);
+      if (!isAborted() && caseIterator.hasNext) {
+        pushInvokeHelper0(getFallThroughErrorElement, HType.UNKNOWN);
+        HInstruction error = pop();
+        close(new HThrow(error));
+      }
+      statements.add(
+          new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock)));
+    }
+
+    // Add a join-block if necessary.
+    // We create [joinBlock] early, and then go through the cases that might
+    // want to jump to it. In each case, if we add [joinBlock] as a successor
+    // of another block, we also add an element to [caseLocals] that is used
+    // to create the phis in [joinBlock].
+    // If we never jump to the join block, [caseLocals] will stay empty, and
+    // the join block is never added to the graph.
+    HBasicBlock joinBlock = new HBasicBlock();
+    List<LocalsHandler> caseLocals = <LocalsHandler>[];
+    jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
+      instruction.block.addSuccessor(joinBlock);
+      caseLocals.add(locals);
+    });
+    if (!isAborted()) {
+      current.close(new HGoto());
+      lastOpenedBlock.addSuccessor(joinBlock);
+      caseLocals.add(localsHandler);
+    }
+    if (!hasDefault) {
+      // The current flow is only aborted if the switch has a default that
+      // aborts (all previous cases must abort, and if there is no default,
+      // it's possible to miss all the cases).
+      expressionEnd.addSuccessor(joinBlock);
+      caseLocals.add(savedLocals);
+    }
+    assert(caseLocals.length == joinBlock.predecessors.length);
+    if (caseLocals.length != 0) {
+      graph.addBlock(joinBlock);
+      open(joinBlock);
+      if (caseLocals.length == 1) {
+        localsHandler = caseLocals[0];
+      } else {
+        localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock);
+      }
+    } else {
+      // The joinblock is not used.
+      joinBlock = null;
+    }
+
+    HSubExpressionBlockInformation expressionInfo =
+        new HSubExpressionBlockInformation(new SubExpression(expressionStart,
+                                                             expressionEnd));
+    expressionStart.setBlockFlow(
+        new HSwitchBlockInformation(expressionInfo,
+                                    matchExpressions,
+                                    statements,
+                                    hasDefault,
+                                    jumpHandler.target,
+                                    jumpHandler.labels()),
+        joinBlock);
+
+    jumpHandler.close();
+    return true;
+  }
+
+  bool nonPrimitiveTypeOverridesEquals(Constant constant) {
+    // Function values override equals. Even static ones, since
+    // they inherit from [Function].
+    if (constant.isFunction()) return true;
+
+    // [Map] and [List] do not override equals.
+    // If constant is primitive, just return false. We know
+    // about the equals methods of num/String classes.
+    if (!constant.isConstructedObject()) return false;
+
+    ConstructedConstant constructedConstant = constant;
+    DartType type = constructedConstant.type;
+    assert(type != null);
+    Element element = type.element;
+    // If the type is not a class, we'll just assume it overrides
+    // operator==. Typedefs do, since [Function] does.
+    if (!element.isClass()) return true;
+    ClassElement classElement = element;
+    return typeOverridesObjectEquals(classElement);
+  }
+
+  bool typeOverridesObjectEquals(ClassElement classElement) {
+    Element operatorEq =
+        lookupOperator(classElement, const SourceString('=='));
+    if (operatorEq == null) return false;
+    // If the operator== declaration is in Object, it's not overridden.
+    return (operatorEq.getEnclosingClass() != compiler.objectClass);
+  }
+
+  Element lookupOperator(ClassElement classElement, SourceString operatorName) {
+    SourceString dartMethodName =
+        Elements.constructOperatorName(operatorName, false);
+    return classElement.lookupMember(dartMethodName);
+  }
+
+
+  // Recursively build an if/else structure to match the cases.
+  void buildSwitchCases(Link<Node> cases, HInstruction expression,
+                        [int encounteredCaseTypes = 0]) {
+    final int NO_TYPE = 0;
+    final int INT_TYPE = 1;
+    final int STRING_TYPE = 2;
+    final int CONFLICT_TYPE = 3;
+    int combine(int type1, int type2) => type1 | type2;
+
+    SwitchCase node = cases.head;
+    // Called for the statements on all but the last case block.
+    // Ensures that a user expecting a fallthrough gets an error.
+    void visitStatementsAndAbort() {
+      visit(node.statements);
+      if (!isAborted()) {
+        compiler.reportWarning(node, 'Missing break at end of switch case');
+        Element element =
+            compiler.findHelper(const SourceString("getFallThroughError"));
+        pushInvokeHelper0(element, HType.UNKNOWN);
+        HInstruction error = pop();
+        close(new HThrow(error));
+      }
+    }
+
+    Link<Node> skipLabels(Link<Node> labelsAndCases) {
+      while (!labelsAndCases.isEmpty && labelsAndCases.head is Label) {
+        labelsAndCases = labelsAndCases.tail;
+      }
+      return labelsAndCases;
+    }
+
+    Link<Node> labelsAndCases = skipLabels(node.labelsAndCases.nodes);
+    if (labelsAndCases.isEmpty) {
+      // Default case with no expressions.
+      if (!node.isDefaultCase) {
+        compiler.internalError("Case with no expression and not default",
+                               node: node);
+      }
+      visit(node.statements);
+      // This must be the final case (otherwise "default" would be invalid),
+      // so we don't need to check for fallthrough.
+      return;
+    }
+
+    // Recursively build the test conditions. Leaves the result on the
+    // expression stack.
+    void buildTests(Link<Node> remainingCases) {
+      // Build comparison for one case expression.
+      void left() {
+        CaseMatch match = remainingCases.head;
+        // TODO(lrn): Move the constant resolution to the resolver, so
+        // we can report an error before reaching the backend.
+        Constant constant =
+            compiler.constantHandler.tryCompileNodeWithDefinitions(
+                match.expression, elements);
+        if (constant != null) {
+          stack.add(graph.addConstant(constant));
+        } else {
+          visit(match.expression);
+        }
+        push(new HIdentity(pop(), expression));
+      }
+
+      // If this is the last expression, just return it.
+      Link<Node> tail = skipLabels(remainingCases.tail);
+      if (tail.isEmpty) {
+        left();
+        return;
+      }
+
+      void right() {
+        buildTests(tail);
+      }
+      SsaBranchBuilder branchBuilder =
+          new SsaBranchBuilder(this, remainingCases.head);
+      branchBuilder.handleLogicalAndOr(left, right, isAnd: false);
+    }
+
+    if (node.isDefaultCase) {
+      // Default case must be last.
+      assert(cases.tail.isEmpty);
+      // Perform the tests until one of them match, but then always execute the
+      // statements.
+      // TODO(lrn): Stop performing tests when all expressions are compile-time
+      // constant strings or integers.
+      handleIf(node, () { buildTests(labelsAndCases); }, (){}, null);
+      visit(node.statements);
+    } else {
+      if (cases.tail.isEmpty) {
+        handleIf(node,
+                 () { buildTests(labelsAndCases); },
+                 () { visit(node.statements); },
+                 null);
+      } else {
+        handleIf(node,
+                 () { buildTests(labelsAndCases); },
+                 () { visitStatementsAndAbort(); },
+                 () { buildSwitchCases(cases.tail, expression,
+                                       encounteredCaseTypes); });
+      }
+    }
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    compiler.internalError('SsaBuilder.visitSwitchCase');
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    compiler.internalError('SsaBuilder.visitCaseMatch');
+  }
+
+  visitTryStatement(TryStatement node) {
+    work.allowSpeculativeOptimization = false;
+    // Save the current locals. The catch block and the finally block
+    // must not reuse the existing locals handler. None of the variables
+    // that have been defined in the body-block will be used, but for
+    // loops we will add (unnecessary) phis that will reference the body
+    // variables. This makes it look as if the variables were used
+    // in a non-dominated block.
+    LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
+    HBasicBlock enterBlock = openNewBlock();
+    HTry tryInstruction = new HTry();
+    close(tryInstruction);
+    bool oldInTryStatement = inTryStatement;
+    inTryStatement = true;
+
+    HBasicBlock startTryBlock;
+    HBasicBlock endTryBlock;
+    HBasicBlock startCatchBlock;
+    HBasicBlock endCatchBlock;
+    HBasicBlock startFinallyBlock;
+    HBasicBlock endFinallyBlock;
+
+    startTryBlock = graph.addNewBlock();
+    open(startTryBlock);
+    visit(node.tryBlock);
+    if (!isAborted()) endTryBlock = close(new HGoto());
+    SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock);
+    SubGraph catchGraph = null;
+    HLocalValue exception = null;
+
+    if (!node.catchBlocks.isEmpty) {
+      localsHandler = new LocalsHandler.from(savedLocals);
+      startCatchBlock = graph.addNewBlock();
+      open(startCatchBlock);
+      // TODO(kasperl): Bad smell. We shouldn't be constructing elements here.
+      // Note that the name of this element is irrelevant.
+      Element element = new ElementX(const SourceString('exception'),
+                                     ElementKind.PARAMETER,
+                                     currentElement);
+      exception = new HLocalValue(element);
+      add(exception);
+      HInstruction oldRethrowableException = rethrowableException;
+      rethrowableException = exception;
+
+      pushInvokeHelper1(
+          backend.getExceptionUnwrapper(), exception, HType.UNKNOWN);
+      HInvokeStatic unwrappedException = pop();
+      tryInstruction.exception = exception;
+      Link<Node> link = node.catchBlocks.nodes;
+
+      void pushCondition(CatchBlock catchBlock) {
+        if (catchBlock.onKeyword != null) {
+          DartType type = elements.getType(catchBlock.type);
+          if (type == null) {
+            compiler.cancel('On with unresolved type',
+                            node: catchBlock.type);
+          }
+          HInstruction condition =
+              new HIs(type, <HInstruction>[unwrappedException]);
+          push(condition);
+        }
+        else {
+          VariableDefinitions declaration = catchBlock.formals.nodes.head;
+          HInstruction condition = null;
+          if (declaration.type == null) {
+            condition = graph.addConstantBool(true, constantSystem);
+            stack.add(condition);
+          } else {
+            // TODO(aprelev@gmail.com): Once old catch syntax is removed
+            // "if" condition above and this "else" branch should be deleted as
+            // type of declared variable won't matter for the catch
+            // condition.
+            DartType type = elements.getType(declaration.type);
+            if (type == null) {
+              compiler.cancel('Catch with unresolved type', node: catchBlock);
+            }
+            condition =
+                new HIs(type, <HInstruction>[unwrappedException], nullOk: true);
+            push(condition);
+          }
+        }
+      }
+
+      void visitThen() {
+        CatchBlock catchBlock = link.head;
+        link = link.tail;
+        if (catchBlock.exception != null) {
+          localsHandler.updateLocal(elements[catchBlock.exception],
+                                    unwrappedException);
+        }
+        Node trace = catchBlock.trace;
+        if (trace != null) {
+          pushInvokeHelper1(
+              backend.getTraceFromException(), exception, HType.UNKNOWN);
+          HInstruction traceInstruction = pop();
+          localsHandler.updateLocal(elements[trace], traceInstruction);
+        }
+        visit(catchBlock);
+      }
+
+      void visitElse() {
+        if (link.isEmpty) {
+          close(new HThrow(exception, isRethrow: true));
+        } else {
+          CatchBlock newBlock = link.head;
+          handleIf(node,
+                   () { pushCondition(newBlock); },
+                   visitThen, visitElse);
+        }
+      }
+
+      CatchBlock firstBlock = link.head;
+      handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse);
+      if (!isAborted()) endCatchBlock = close(new HGoto());
+
+      rethrowableException = oldRethrowableException;
+      tryInstruction.catchBlock = startCatchBlock;
+      catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock);
+    }
+
+    SubGraph finallyGraph = null;
+    if (node.finallyBlock != null) {
+      localsHandler = new LocalsHandler.from(savedLocals);
+      startFinallyBlock = graph.addNewBlock();
+      open(startFinallyBlock);
+      visit(node.finallyBlock);
+      if (!isAborted()) endFinallyBlock = close(new HGoto());
+      tryInstruction.finallyBlock = startFinallyBlock;
+      finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock);
+    }
+
+    HBasicBlock exitBlock = graph.addNewBlock();
+
+    addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); }
+    addExitTrySuccessor(successor) {
+      if (successor == null) return;
+      // Iterate over all blocks created inside this try/catch, and
+      // attach successor information to blocks that end with
+      // [HExitTry].
+      for (int i = startTryBlock.id; i < successor.id; i++) {
+        HBasicBlock block = graph.blocks[i];
+        var last = block.last;
+        if (last is HExitTry) {
+          block.addSuccessor(successor);
+        } else if (last is HTry) {
+          // Skip all blocks inside this nested try/catch.
+          i = last.joinBlock.id;
+        }
+      }
+    }
+
+    // Setup all successors. The entry block that contains the [HTry]
+    // has 1) the body, 2) the catch, 3) the finally, and 4) the exit
+    // blocks as successors.
+    enterBlock.addSuccessor(startTryBlock);
+    addOptionalSuccessor(enterBlock, startCatchBlock);
+    addOptionalSuccessor(enterBlock, startFinallyBlock);
+    enterBlock.addSuccessor(exitBlock);
+
+    // The body has either the catch or the finally block as successor.
+    if (endTryBlock != null) {
+      assert(startCatchBlock != null || startFinallyBlock != null);
+      endTryBlock.addSuccessor(
+          startCatchBlock != null ? startCatchBlock : startFinallyBlock);
+    }
+
+    // The catch block has either the finally or the exit block as
+    // successor.
+    if (endCatchBlock != null) {
+      endCatchBlock.addSuccessor(
+          startFinallyBlock != null ? startFinallyBlock : exitBlock);
+    }
+
+    // The finally block has the exit block as successor.
+    if (endFinallyBlock != null) {
+      endFinallyBlock.addSuccessor(exitBlock);
+    }
+
+    // If a block inside try/catch aborts (eg with a return statement),
+    // we explicitely mark this block a predecessor of the catch
+    // block and the finally block.
+    addExitTrySuccessor(startCatchBlock);
+    addExitTrySuccessor(startFinallyBlock);
+
+    // Use the locals handler not altered by the catch and finally
+    // blocks.
+    localsHandler = savedLocals;
+    open(exitBlock);
+    enterBlock.setBlockFlow(
+        new HTryBlockInformation(
+          wrapStatementGraph(bodyGraph),
+          exception,
+          wrapStatementGraph(catchGraph),
+          wrapStatementGraph(finallyGraph)),
+        exitBlock);
+    inTryStatement = oldInTryStatement;
+  }
+
+  visitScriptTag(ScriptTag node) {
+    compiler.unimplemented('SsaBuilder.visitScriptTag', node: node);
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    visit(node.block);
+  }
+
+  visitTypedef(Typedef node) {
+    compiler.unimplemented('SsaBuilder.visitTypedef', node: node);
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    compiler.internalError('SsaBuilder.visitTypeVariable');
+  }
+
+  HType mapBaseType(BaseType baseType) {
+    if (!baseType.isClass()) return HType.UNKNOWN;
+    ClassBaseType classBaseType = baseType;
+    return new HType.fromBoundedType(
+        classBaseType.element.computeType(compiler), compiler, false);
+  }
+
+  HType mapInferredType(ConcreteType concreteType) {
+    if (concreteType == null) return HType.UNKNOWN;
+    HType ssaType = HType.CONFLICTING;
+    for (BaseType baseType in concreteType.baseTypes) {
+      ssaType = ssaType.union(mapBaseType(baseType), compiler);
+    }
+    assert(!ssaType.isConflicting());
+    return ssaType;
+  }
+
+  HType mapNativeType(type) {
+    if (type == native.SpecialType.JsObject) {
+      return new HBoundedType.exact(
+          compiler.objectClass.computeType(compiler));
+    } else if (type == native.SpecialType.JsArray) {
+      return HType.READABLE_ARRAY;
+    } else {
+      return new HType.fromBoundedType(type, compiler, false);
+    }
+  }
+
+  HType mapNativeBehaviorType(native.NativeBehavior nativeBehavior) {
+    if (nativeBehavior.typesInstantiated.isEmpty) return HType.UNKNOWN;
+
+    HType ssaType = HType.CONFLICTING;
+    for (final type in nativeBehavior.typesInstantiated) {
+      ssaType = ssaType.union(mapNativeType(type), compiler);
+    }
+    assert(!ssaType.isConflicting());
+    return ssaType;
+  }
+}
+
+/**
+ * Visitor that handles generation of string literals (LiteralString,
+ * StringInterpolation), and otherwise delegates to the given visitor for
+ * non-literal subexpressions.
+ * TODO(lrn): Consider whether to handle compile time constant int/boolean
+ * expressions as well.
+ */
+class StringBuilderVisitor extends Visitor {
+  final SsaBuilder builder;
+  final Node diagnosticNode;
+
+  /**
+   * The string value generated so far.
+   */
+  HInstruction result = null;
+
+  StringBuilderVisitor(this.builder, this.diagnosticNode);
+
+  void visit(Node node) {
+    node.accept(this);
+  }
+
+  visitNode(Node node) {
+    builder.compiler.internalError('unexpected node', node: node);
+  }
+
+  void visitExpression(Node node) {
+    node.accept(builder);
+    HInstruction expression = builder.pop();
+    result = (result == null) ? expression : concat(result, expression);
+  }
+
+  void visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+  }
+
+  void visitStringInterpolationPart(StringInterpolationPart node) {
+    visit(node.expression);
+    visit(node.string);
+  }
+
+  void visitStringJuxtaposition(StringJuxtaposition node) {
+    node.visitChildren(this);
+  }
+
+  void visitNodeList(NodeList node) {
+     node.visitChildren(this);
+  }
+
+  HInstruction concat(HInstruction left, HInstruction right) {
+    HInstruction instruction = new HStringConcat(left, right, diagnosticNode);
+    builder.add(instruction);
+    return instruction;
+  }
+}
+
+/**
+ * This class visits the method that is a candidate for inlining and
+ * finds whether it is too difficult to inline.
+ */
+class InlineWeeder extends Visitor {
+  final TreeElements elements;
+  bool seenReturn = false;
+  bool tooDifficult = false;
+
+  InlineWeeder(this.elements);
+
+  static bool canBeInlined(FunctionExpression functionExpression,
+                           TreeElements elements) {
+    InlineWeeder weeder = new InlineWeeder(elements);
+    weeder.visit(functionExpression.body);
+    if (weeder.tooDifficult) return false;
+    return true;
+  }
+
+  void visit(Node node) {
+    node.accept(this);
+  }
+
+  void visitNode(Node node) {
+    if (seenReturn) {
+      tooDifficult = true;
+    } else {
+      node.visitChildren(this);
+    }
+  }
+
+  void visitFunctionExpression(Node node) {
+    tooDifficult = true;
+  }
+
+  void visitFunctionDeclaration(Node node) {
+    tooDifficult = true;
+  }
+
+  void visitSend(Send node) {
+    if (node.isParameterCheck) {
+      tooDifficult = true;
+      return;
+    }
+    node.visitChildren(this);
+  }
+
+  visitLoop(Node node) {
+    node.visitChildren(this);
+    if (seenReturn) tooDifficult = true;
+  }
+
+  void visitReturn(Return node) {
+    if (seenReturn
+        || identical(node.getBeginToken().stringValue, 'native')
+        || node.isRedirectingFactoryBody) {
+      tooDifficult = true;
+      return;
+    }
+    node.visitChildren(this);
+    seenReturn = true;
+  }
+
+  void visitTryStatement(Node node) {
+    tooDifficult = true;
+  }
+
+  void visitThrow(Node node) {
+    tooDifficult = true;
+  }
+}
+
+class InliningState {
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: [function] must be an implementation element.
+   */
+  final PartialFunctionElement function;
+  final Element oldReturnElement;
+  final DartType oldReturnType;
+  final TreeElements oldElements;
+  final List<HInstruction> oldStack;
+
+  InliningState(this.function,
+                this.oldReturnElement,
+                this.oldReturnType,
+                this.oldElements,
+                this.oldStack) {
+    assert(function.isImplementation);
+  }
+}
+
+class SsaBranch {
+  final SsaBranchBuilder branchBuilder;
+  final HBasicBlock block;
+  LocalsHandler startLocals;
+  LocalsHandler exitLocals;
+  SubGraph graph;
+
+  SsaBranch(this.branchBuilder) : block = new HBasicBlock();
+}
+
+class SsaBranchBuilder {
+  final SsaBuilder builder;
+  final Node diagnosticNode;
+
+  SsaBranchBuilder(this.builder, [this.diagnosticNode]);
+
+  Compiler get compiler => builder.compiler;
+
+  void checkNotAborted() {
+    if (builder.isAborted()) {
+      compiler.unimplemented("aborted control flow", node: diagnosticNode);
+    }
+  }
+
+  void buildCondition(void visitCondition(),
+                      SsaBranch conditionBranch,
+                      SsaBranch thenBranch,
+                      SsaBranch elseBranch) {
+    startBranch(conditionBranch);
+    visitCondition();
+    checkNotAborted();
+    assert(identical(builder.current, builder.lastOpenedBlock));
+    HInstruction conditionValue = builder.popBoolified();
+    HIf branch = new HIf(conditionValue);
+    HBasicBlock conditionExitBlock = builder.current;
+    builder.close(branch);
+    conditionBranch.exitLocals = builder.localsHandler;
+    conditionExitBlock.addSuccessor(thenBranch.block);
+    conditionExitBlock.addSuccessor(elseBranch.block);
+    bool conditionBranchLocalsCanBeReused =
+        mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true);
+    mergeLocals(conditionBranch, elseBranch,
+                mayReuseFromLocals: conditionBranchLocalsCanBeReused);
+
+    conditionBranch.graph =
+        new SubExpression(conditionBranch.block, conditionExitBlock);
+  }
+
+  /**
+   * Returns true if the locals of the [fromBranch] may be reused. A [:true:]
+   * return value implies that [mayReuseFromLocals] was set to [:true:].
+   */
+  bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch,
+                   {bool mayReuseFromLocals}) {
+    LocalsHandler fromLocals = fromBranch.exitLocals;
+    if (toBranch.startLocals == null) {
+      if (mayReuseFromLocals) {
+        toBranch.startLocals = fromLocals;
+        return false;
+      } else {
+        toBranch.startLocals = new LocalsHandler.from(fromLocals);
+        return true;
+      }
+    } else {
+      toBranch.startLocals.mergeWith(fromLocals, toBranch.block);
+      return true;
+    }
+  }
+
+  void startBranch(SsaBranch branch) {
+    builder.graph.addBlock(branch.block);
+    builder.localsHandler = branch.startLocals;
+    builder.open(branch.block);
+  }
+
+  HInstruction buildBranch(SsaBranch branch,
+                           void visitBranch(),
+                           SsaBranch joinBranch,
+                           bool isExpression) {
+    startBranch(branch);
+    visitBranch();
+    branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock);
+    branch.exitLocals = builder.localsHandler;
+    if (!builder.isAborted()) {
+      builder.goto(builder.current, joinBranch.block);
+      mergeLocals(branch, joinBranch, mayReuseFromLocals: true);
+    }
+    if (isExpression) {
+      checkNotAborted();
+      return builder.pop();
+    }
+    return null;
+  }
+
+  handleIf(void visitCondition(), void visitThen(), void visitElse()) {
+    if (visitElse == null) {
+      // Make sure to have an else part to avoid a critical edge. A
+      // critical edge is an edge that connects a block with multiple
+      // successors to a block with multiple predecessors. We avoid
+      // such edges because they prevent inserting copies during code
+      // generation of phi instructions.
+      visitElse = () {};
+    }
+
+    _handleDiamondBranch(visitCondition, visitThen, visitElse, false);
+  }
+
+  handleConditional(void visitCondition(), void visitThen(), void visitElse()) {
+    assert(visitElse != null);
+    _handleDiamondBranch(visitCondition, visitThen, visitElse, true);
+  }
+
+  void handleLogicalAndOr(void left(), void right(), {bool isAnd}) {
+    // x && y is transformed into:
+    //   t0 = boolify(x);
+    //   if (t0) {
+    //     t1 = boolify(y);
+    //   }
+    //   result = phi(t1, false);
+    //
+    // x || y is transformed into:
+    //   t0 = boolify(x);
+    //   if (not(t0)) {
+    //     t1 = boolify(y);
+    //   }
+    //   result = phi(t1, true);
+    HInstruction boolifiedLeft;
+    HInstruction boolifiedRight;
+
+    void visitCondition() {
+      left();
+      boolifiedLeft = builder.popBoolified();
+      builder.stack.add(boolifiedLeft);
+      if (!isAnd) {
+        builder.push(new HNot(builder.pop()));
+      }
+    }
+
+    void visitThen() {
+      right();
+      boolifiedRight = builder.popBoolified();
+    }
+
+    handleIf(visitCondition, visitThen, null);
+    HConstant notIsAnd =
+        builder.graph.addConstantBool(!isAnd, builder.constantSystem);
+    HPhi result = new HPhi.manyInputs(null,
+                                      <HInstruction>[boolifiedRight, notIsAnd]);
+    builder.current.addPhi(result);
+    builder.stack.add(result);
+  }
+
+  void handleLogicalAndOrWithLeftNode(Node left,
+                                      void visitRight(),
+                                      {bool isAnd}) {
+    // This method is similar to [handleLogicalAndOr] but optimizes the case
+    // where left is a logical "and" or logical "or".
+    //
+    // For example (x && y) && z is transformed into x && (y && z):
+    //   t0 = boolify(x);
+    //   if (t0) {
+    //     t1 = boolify(y);
+    //     if (t1) {
+    //       t2 = boolify(z);
+    //     }
+    //     t3 = phi(t2, false);
+    //   }
+    //   result = phi(t3, false);
+
+    Send send = left.asSend();
+    if (send != null &&
+        (isAnd ? send.isLogicalAnd : send.isLogicalOr)) {
+      Node newLeft = send.receiver;
+      Link<Node> link = send.argumentsNode.nodes;
+      assert(link.tail.isEmpty);
+      Node middle = link.head;
+      handleLogicalAndOrWithLeftNode(
+          newLeft,
+          () => handleLogicalAndOrWithLeftNode(middle, visitRight,
+                                               isAnd: isAnd),
+          isAnd: isAnd);
+    } else {
+      handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd);
+    }
+  }
+
+  void _handleDiamondBranch(void visitCondition(),
+                            void visitThen(),
+                            void visitElse(),
+                            bool isExpression) {
+    SsaBranch conditionBranch = new SsaBranch(this);
+    SsaBranch thenBranch = new SsaBranch(this);
+    SsaBranch elseBranch = new SsaBranch(this);
+    SsaBranch joinBranch = new SsaBranch(this);
+
+    conditionBranch.startLocals = builder.localsHandler;
+    builder.goto(builder.current, conditionBranch.block);
+
+    buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch);
+    HInstruction thenValue =
+        buildBranch(thenBranch, visitThen, joinBranch, isExpression);
+    HInstruction elseValue =
+        buildBranch(elseBranch, visitElse, joinBranch, isExpression);
+
+    if (isExpression) {
+      assert(thenValue != null && elseValue != null);
+      HPhi phi =
+          new HPhi.manyInputs(null, <HInstruction>[thenValue, elseValue]);
+      joinBranch.block.addPhi(phi);
+      builder.stack.add(phi);
+    }
+
+    HBasicBlock thenBlock = thenBranch.block;
+    HBasicBlock elseBlock = elseBranch.block;
+    HBasicBlock joinBlock;
+    // If at least one branch did not abort, open the joinBranch.
+    if (!joinBranch.block.predecessors.isEmpty) {
+      startBranch(joinBranch);
+      joinBlock = joinBranch.block;
+    }
+
+    HIfBlockInformation info =
+        new HIfBlockInformation(
+          new HSubExpressionBlockInformation(conditionBranch.graph),
+          new HSubGraphBlockInformation(thenBranch.graph),
+          new HSubGraphBlockInformation(elseBranch.graph));
+
+    HBasicBlock conditionStartBlock = conditionBranch.block;
+    conditionStartBlock.setBlockFlow(info, joinBlock);
+    SubGraph conditionGraph = conditionBranch.graph;
+    HIf branch = conditionGraph.end.last;
+    assert(branch is HIf);
+    branch.blockInformation = conditionStartBlock.blockFlow;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart
new file mode 100644
index 0000000..e5caac1
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart
@@ -0,0 +1,3006 @@
+// 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.
+
+part of ssa;
+
+class SsaCodeGeneratorTask extends CompilerTask {
+
+  final JavaScriptBackend backend;
+
+  SsaCodeGeneratorTask(JavaScriptBackend backend)
+      : this.backend = backend,
+        super(backend.compiler);
+  String get name => 'SSA code generator';
+  NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter;
+
+
+  js.Fun buildJavaScriptFunction(FunctionElement element,
+                                 List<js.Parameter> parameters,
+                                 js.Block body) {
+    FunctionExpression expression =
+        element.implementation.parseNode(backend.compiler);
+    js.Fun result = new js.Fun(parameters, body);
+    // TODO(johnniwinther): remove the 'element.patch' hack.
+    Element sourceElement = element.patch == null ? element : element.patch;
+    SourceFile sourceFile = sourceElement.getCompilationUnit().script.file;
+    // TODO(podivilov): find the right sourceFile here and remove offset checks
+    // below.
+    if (expression.getBeginToken().charOffset < sourceFile.text.length) {
+      result.sourcePosition = new SourceFileLocation(
+          sourceFile, expression.getBeginToken());
+    }
+    if (expression.getEndToken().charOffset < sourceFile.text.length) {
+      result.endSourcePosition = new SourceFileLocation(
+          sourceFile, expression.getEndToken());
+    }
+    return result;
+  }
+
+  CodeBuffer prettyPrint(js.Node node) {
+    var code = js.prettyPrint(node, compiler, allowVariableMinification: true);
+    return code;
+  }
+
+  js.Expression generateCode(CodegenWorkItem work, HGraph graph) {
+    if (work.element.isField()) {
+      return generateLazyInitializer(work, graph);
+    } else {
+      return generateMethod(work, graph);
+    }
+  }
+
+  js.Expression generateLazyInitializer(work, graph) {
+    return measure(() {
+      compiler.tracer.traceGraph("codegen", graph);
+      SsaOptimizedCodeGenerator codegen =
+          new SsaOptimizedCodeGenerator(backend, work);
+      codegen.visitGraph(graph);
+      return new js.Fun(codegen.parameters, codegen.body);
+    });
+  }
+
+  js.Expression generateMethod(CodegenWorkItem work, HGraph graph) {
+    return measure(() {
+      compiler.tracer.traceGraph("codegen", graph);
+      SsaOptimizedCodeGenerator codegen =
+          new SsaOptimizedCodeGenerator(backend, work);
+      codegen.visitGraph(graph);
+
+      FunctionElement element = work.element;
+      js.Block body;
+      ClassElement enclosingClass = element.getEnclosingClass();
+
+      if (element.isInstanceMember()
+          && enclosingClass.isNative()
+          && native.isOverriddenMethod(
+              element, enclosingClass, nativeEmitter)) {
+        // Record that this method is overridden. In case of optional
+        // arguments, the emitter will generate stubs to handle them,
+        // and needs to know if the method is overridden.
+        nativeEmitter.overriddenMethods.add(element);
+        StringBuffer buffer = new StringBuffer();
+        body =
+            nativeEmitter.generateMethodBodyWithPrototypeCheckForElement(
+                element, codegen.body, codegen.parameters);
+      } else {
+        body = codegen.body;
+      }
+
+      return buildJavaScriptFunction(element, codegen.parameters, body);
+    });
+  }
+
+  js.Expression generateBailoutMethod(CodegenWorkItem work, HGraph graph) {
+    return measure(() {
+      compiler.tracer.traceGraph("codegen-bailout", graph);
+
+      SsaUnoptimizedCodeGenerator codegen =
+          new SsaUnoptimizedCodeGenerator(backend, work);
+      codegen.visitGraph(graph);
+
+      js.Block body = new js.Block(<js.Statement>[]);
+      body.statements.add(codegen.body);
+      js.Fun fun =
+          buildJavaScriptFunction(work.element, codegen.newParameters, body);
+      return fun;
+    });
+  }
+}
+
+// Stop-gap until the core classes have such a class.
+class OrderedSet<T> {
+  final LinkedHashMap<T, bool> map = new LinkedHashMap<T, bool>();
+
+  void add(T x) {
+    if (!map.containsKey(x)) {
+      map[x] = true;
+    }
+  }
+
+  bool contains(T x) => map.containsKey(x);
+
+  bool remove(T x) => map.remove(x) != null;
+
+  bool get isEmpty => map.isEmpty;
+
+  void forEach(f) => map.keys.forEach(f);
+
+  T get first {
+    var iterator = map.keys.iterator;
+    if (!iterator.moveNext()) throw new StateError("No elements");
+    return iterator.current;
+  }
+
+  get length => map.length;
+}
+
+typedef void ElementAction(Element element);
+
+abstract class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
+  /**
+   * Returned by [expressionType] to tell how code can be generated for
+   * a subgraph.
+   * - [TYPE_STATEMENT] means that the graph must be generated as a statement,
+   * which is always possible.
+   * - [TYPE_EXPRESSION] means that the graph can be generated as an expression,
+   * or possibly several comma-separated expressions.
+   * - [TYPE_DECLARATION] means that the graph can be generated as an
+   * expression, and that it only generates expressions of the form
+   *   variable = expression
+   * which are also valid as parts of a "var" declaration.
+   */
+  static const int TYPE_STATEMENT = 0;
+  static const int TYPE_EXPRESSION = 1;
+  static const int TYPE_DECLARATION = 2;
+
+  /**
+   * Whether we are currently generating expressions instead of statements.
+   * This includes declarations, which are generated as expressions.
+   */
+  bool isGeneratingExpression = false;
+
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final HTypeMap types;
+
+  final Set<HInstruction> generateAtUseSite;
+  final Set<HInstruction> controlFlowOperators;
+  final Map<Element, ElementAction> breakAction;
+  final Map<Element, ElementAction> continueAction;
+  final List<js.Parameter> parameters;
+
+  js.Block currentContainer;
+  js.Block get body => currentContainer;
+  List<js.Expression> expressionStack;
+  List<js.Block> oldContainerStack;
+
+  /**
+   * Contains the names of the instructions, as well as the parallel
+   * copies to perform on block transitioning.
+   */
+  VariableNames variableNames;
+  bool shouldGroupVarDeclarations = false;
+
+  /**
+   * While generating expressions, we can't insert variable declarations.
+   * Instead we declare them at the start of the function.  When minifying
+   * we do this most of the time, because it reduces the size unless there
+   * is only one variable.
+   */
+  final OrderedSet<String> collectedVariableDeclarations;
+
+  /**
+   * Set of variables and parameters that have already been declared.
+   */
+  final Set<String> declaredLocals;
+
+  int indent = 0;
+  HGraph currentGraph;
+
+  // Records a block-information that is being handled specially.
+  // Used to break bad recursion.
+  HBlockInformation currentBlockInformation;
+  // The subgraph is used to delimit traversal for some constructions, e.g.,
+  // if branches.
+  SubGraph subGraph;
+
+  SsaCodeGenerator(this.backend, CodegenWorkItem work)
+    : this.work = work,
+      this.types =
+          (work.compilationContext as JavaScriptItemCompilationContext).types,
+      declaredLocals = new Set<String>(),
+      collectedVariableDeclarations = new OrderedSet<String>(),
+      currentContainer = new js.Block.empty(),
+      parameters = <js.Parameter>[],
+      expressionStack = <js.Expression>[],
+      oldContainerStack = <js.Block>[],
+      generateAtUseSite = new Set<HInstruction>(),
+      controlFlowOperators = new Set<HInstruction>(),
+      breakAction = new Map<Element, ElementAction>(),
+      continueAction = new Map<Element, ElementAction>();
+
+  Compiler get compiler => backend.compiler;
+  NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter;
+  CodegenEnqueuer get world => backend.compiler.enqueuer.codegen;
+
+  bool isGenerateAtUseSite(HInstruction instruction) {
+    return generateAtUseSite.contains(instruction);
+  }
+
+  bool isNonNegativeInt32Constant(HInstruction instruction) {
+    if (instruction.isConstantInteger()) {
+      HConstant constantInstruction = instruction;
+      PrimitiveConstant primitiveConstant = constantInstruction.constant;
+      int value = primitiveConstant.value;
+      if (value >= 0 && value < (1 << 31)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool hasNonBitOpUser(HInstruction instruction, Set<HPhi> phiSet) {
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HPhi) {
+        if (!phiSet.contains(user)) {
+          phiSet.add(user);
+          if (hasNonBitOpUser(user, phiSet)) return true;
+        }
+      } else if (user is! HBitNot && user is! HBinaryBitOp) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // We want the outcome of bit-operations to be positive. However, if
+  // the result of a bit-operation is only used by other bit
+  // operations we do not have to convert to an unsigned
+  // integer. Also, if we are using & with a positive constant we know
+  // that the result is positive already and need no conversion.
+  bool requiresUintConversion(HInstruction instruction) {
+    if (instruction is HBitAnd) {
+      HBitAnd bitAnd = instruction;
+      if (isNonNegativeInt32Constant(bitAnd.left) ||
+          isNonNegativeInt32Constant(bitAnd.right)) {
+        return false;
+      }
+    }
+    return hasNonBitOpUser(instruction, new Set<HPhi>());
+  }
+
+  /**
+   * If the [instruction] is not `null` it will be used to attach the position
+   * to the [statement].
+   */
+  void pushStatement(js.Statement statement, [HInstruction instruction]) {
+    assert(expressionStack.isEmpty);
+    if (instruction != null) {
+      attachLocation(statement, instruction);
+    }
+    currentContainer.statements.add(statement);
+  }
+
+  void insertStatementAtStart(js.Statement statement) {
+    currentContainer.statements.insertRange(0, 1, statement);
+  }
+
+  /**
+   * If the [instruction] is not `null` it will be used to attach the position
+   * to the [expression].
+   */
+  pushExpressionAsStatement(js.Expression expression,
+                            [HInstruction instruction]) {
+    pushStatement(new js.ExpressionStatement(expression), instruction);
+  }
+
+  /**
+   * If the [instruction] is not `null` it will be used to attach the position
+   * to the [expression].
+   */
+  push(js.Expression expression, [HInstruction instruction]) {
+    if (instruction != null) {
+      attachLocation(expression, instruction);
+    }
+    expressionStack.add(expression);
+  }
+
+  js.Expression pop() {
+    return expressionStack.removeLast();
+  }
+
+  attachLocationToLast(HInstruction instruction) {
+    attachLocation(expressionStack.last, instruction);
+  }
+
+  js.Node attachLocation(js.Node jsNode, HInstruction instruction) {
+    jsNode.sourcePosition = instruction.sourcePosition;
+    return jsNode;
+  }
+
+  js.Node attachLocationRange(js.Node jsNode,
+                              SourceFileLocation sourcePosition,
+                              SourceFileLocation endSourcePosition) {
+    jsNode.sourcePosition = sourcePosition;
+    jsNode.endSourcePosition = endSourcePosition;
+    return jsNode;
+  }
+
+  visitTypeGuard(HTypeGuard node);
+  visitBailoutTarget(HBailoutTarget node);
+
+  beginGraph(HGraph graph);
+  endGraph(HGraph graph);
+
+  preLabeledBlock(HLabeledBlockInformation labeledBlockInfo);
+  startLabeledBlock(HLabeledBlockInformation labeledBlockInfo);
+  endLabeledBlock(HLabeledBlockInformation labeledBlockInfo);
+
+  void preGenerateMethod(HGraph graph) {
+    new SsaInstructionMerger(types, generateAtUseSite).visitGraph(graph);
+    new SsaConditionMerger(
+        types, generateAtUseSite, controlFlowOperators).visitGraph(graph);
+    SsaLiveIntervalBuilder intervalBuilder =
+        new SsaLiveIntervalBuilder(compiler, generateAtUseSite);
+    intervalBuilder.visitGraph(graph);
+    SsaVariableAllocator allocator = new SsaVariableAllocator(
+        compiler,
+        intervalBuilder.liveInstructions,
+        intervalBuilder.liveIntervals,
+        generateAtUseSite);
+    allocator.visitGraph(graph);
+    variableNames = allocator.names;
+    shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1;
+
+    // Don't register a return type for lazily initialized variables.
+    if (work.element is! FunctionElement) return;
+
+    // Register return types to the backend.
+    graph.exit.predecessors.forEach((HBasicBlock block) {
+      HInstruction last = block.last;
+      assert(last is HGoto || last is HReturn);
+      if (last is HReturn) {
+        backend.registerReturnType(work.element, types[last.inputs[0]]);
+      } else {
+        backend.registerReturnType(work.element, HType.NULL);
+      }
+    });
+  }
+
+  void handleDelayedVariableDeclarations() {
+    // If we have only one variable declaration and the first statement is an
+    // assignment to that variable then we can merge the two.  We count the
+    // number of variables in the variable allocator to try to avoid this issue,
+    // but it sometimes happens that the variable allocator introduces a
+    // temporary variable that it later eliminates.
+    if (!collectedVariableDeclarations.isEmpty) {
+      if (collectedVariableDeclarations.length == 1 &&
+          currentContainer.statements.length >= 1 &&
+          currentContainer.statements[0] is js.ExpressionStatement) {
+        String name = collectedVariableDeclarations.first;
+        js.ExpressionStatement statement = currentContainer.statements[0];
+        if (statement.expression is js.Assignment) {
+          js.Assignment assignment = statement.expression;
+          if (!assignment.isCompound &&
+              assignment.leftHandSide is js.VariableReference) {
+            js.VariableReference variableReference = assignment.leftHandSide;
+            if (variableReference.name == name) {
+              js.VariableDeclaration decl = new js.VariableDeclaration(name);
+              js.VariableInitialization initialization =
+                  new js.VariableInitialization(decl, assignment.value);
+              currentContainer.statements[0] = new js.ExpressionStatement(
+                  new js.VariableDeclarationList([initialization]));
+              return;
+            }
+          }
+        }
+      }
+      // If we can't merge the declaration with the first assignment then we
+      // just do it with a new var z,y,x; statement.
+      List<js.VariableInitialization> declarations =
+          <js.VariableInitialization>[];
+      collectedVariableDeclarations.forEach((String name) {
+        declarations.add(new js.VariableInitialization(
+            new js.VariableDeclaration(name), null));
+      });
+      var declarationList = new js.VariableDeclarationList(declarations);
+      insertStatementAtStart(new js.ExpressionStatement(declarationList));
+    }
+  }
+
+  visitGraph(HGraph graph) {
+    preGenerateMethod(graph);
+    currentGraph = graph;
+    indent++;  // We are already inside a function.
+    subGraph = new SubGraph(graph.entry, graph.exit);
+    HBasicBlock start = beginGraph(graph);
+    visitBasicBlock(start);
+    handleDelayedVariableDeclarations();
+    endGraph(graph);
+  }
+
+  void visitSubGraph(SubGraph newSubGraph) {
+    SubGraph oldSubGraph = subGraph;
+    subGraph = newSubGraph;
+    visitBasicBlock(subGraph.start);
+    subGraph = oldSubGraph;
+  }
+
+  /**
+   * Check whether a sub-graph can be generated as an expression, or even
+   * as a declaration, or if it has to fall back to being generated as
+   * a statement.
+   * Expressions are anything that doesn't generate control flow constructs.
+   * Declarations must only generate assignments on the form "id = expression",
+   * and not, e.g., expressions where the value isn't assigned, or where it's
+   * assigned to something that's not a simple variable.
+   */
+  int expressionType(HExpressionInformation info) {
+    // The only HExpressionInformation used as part of a HBlockInformation is
+    // current HSubExpressionBlockInformation, so it's the only one reaching
+    // here. If we start using the other HExpressionInformation types too,
+    // this code should be generalized.
+    assert(info is HSubExpressionBlockInformation);
+    HSubExpressionBlockInformation expressionInfo = info;
+    SubGraph limits = expressionInfo.subExpression;
+
+    // Start assuming that we can generate declarations. If we find a
+    // counter-example, we degrade our assumption to either expression or
+    // statement, and in the latter case, we can return immediately since
+    // it can't get any worse. E.g., a function call where the return value
+    // isn't used can't be in a declaration. A bailout can't be in an
+    // expression.
+    int result = TYPE_DECLARATION;
+    HBasicBlock basicBlock = limits.start;
+    do {
+      HInstruction current = basicBlock.first;
+      while (current != basicBlock.last) {
+        // E.g, type guards.
+        if (current.isControlFlow()) {
+          return TYPE_STATEMENT;
+        }
+        // HFieldSet generates code on the form x.y = ..., which isn't
+        // valid in a declaration, but it also always have no uses, so
+        // it's caught by that test too.
+        assert(current is! HFieldSet || current.usedBy.isEmpty);
+        if (current.usedBy.isEmpty) {
+          result = TYPE_EXPRESSION;
+        }
+        current = current.next;
+      }
+      if (current is HGoto) {
+        basicBlock = basicBlock.successors[0];
+      } else if (current is HConditionalBranch) {
+        if (generateAtUseSite.contains(current)) {
+          // Short-circuit control flow operator trickery.
+          // Check the second half, which will continue into the join.
+          // (The first half is [inputs[0]], the second half is [successors[0]],
+          // and [successors[1]] is the join-block).
+          basicBlock = basicBlock.successors[0];
+        } else {
+          // We allow an expression to end on an HIf (a condition expression).
+          return identical(basicBlock, limits.end) ? result : TYPE_STATEMENT;
+        }
+      } else {
+        // Expression-incompatible control flow.
+        return TYPE_STATEMENT;
+      }
+    } while (limits.contains(basicBlock));
+    return result;
+  }
+
+  bool isJSExpression(HExpressionInformation info) {
+    return !identical(expressionType(info), TYPE_STATEMENT);
+  }
+
+  bool isJSDeclaration(HExpressionInformation info) {
+    return identical(expressionType(info), TYPE_DECLARATION);
+  }
+
+  bool isJSCondition(HExpressionInformation info) {
+    HSubExpressionBlockInformation graph = info;
+    SubExpression limits = graph.subExpression;
+    return !identical(expressionType(info), TYPE_STATEMENT) &&
+       (limits.end.last is HConditionalBranch);
+  }
+
+  /**
+   * Generate statements from block information.
+   * If the block information contains expressions, generate only
+   * assignments, and if it ends in a conditional branch, don't generate
+   * the condition.
+   */
+  void generateStatements(HBlockInformation block) {
+    if (block is HStatementInformation) {
+      block.accept(this);
+    } else {
+      HSubExpressionBlockInformation expression = block;
+      visitSubGraph(expression.subExpression);
+    }
+  }
+
+  js.Block generateStatementsInNewBlock(HBlockInformation block) {
+    js.Block result = new js.Block.empty();
+    js.Block oldContainer = currentContainer;
+    currentContainer = result;
+    generateStatements(block);
+    currentContainer = oldContainer;
+    return result;
+  }
+
+  /**
+   * If the [block] only contains one statement returns that statement. If the
+   * that statement itself is a block, recursively calls this method.
+   *
+   * If the block is empty, returns a new instance of [js.NOP].
+   */
+  js.Statement unwrapStatement(js.Block block) {
+    int len = block.statements.length;
+    if (len == 0) return new js.EmptyStatement();
+    if (len == 1) {
+      js.Statement result = block.statements[0];
+      if (result is Block) return unwrapStatement(result);
+      return result;
+    }
+    return block;
+  }
+
+  /**
+   * Generate expressions from block information.
+   */
+  js.Expression generateExpression(HExpressionInformation expression) {
+    // Currently we only handle sub-expression graphs.
+    assert(expression is HSubExpressionBlockInformation);
+
+    bool oldIsGeneratingExpression = isGeneratingExpression;
+    isGeneratingExpression = true;
+    List<js.Expression> oldExpressionStack = expressionStack;
+    List<js.Expression> sequenceElements = <js.Expression>[];
+    expressionStack = sequenceElements;
+    HSubExpressionBlockInformation expressionSubGraph = expression;
+    visitSubGraph(expressionSubGraph.subExpression);
+    expressionStack = oldExpressionStack;
+    isGeneratingExpression = oldIsGeneratingExpression;
+    if (sequenceElements.isEmpty) {
+      // Happens when the initializer, condition or update of a loop is empty.
+      return null;
+    } else if (sequenceElements.length == 1) {
+      return sequenceElements[0];
+    } else {
+      return new js.Sequence(sequenceElements);
+    }
+  }
+
+  /**
+    * Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET].
+    */
+  List<js.Expression> visitArguments(List<HInstruction> inputs) {
+    assert(inputs.length >= HInvoke.ARGUMENTS_OFFSET);
+    List<js.Expression> result = <js.Expression>[];
+    for (int i = HInvoke.ARGUMENTS_OFFSET; i < inputs.length; i++) {
+      use(inputs[i]);
+      result.add(pop());
+    }
+    return result;
+  }
+
+  bool isVariableDeclared(String variableName) {
+    return declaredLocals.contains(variableName) ||
+        collectedVariableDeclarations.contains(variableName);
+  }
+
+  js.Expression generateExpressionAssignment(String variableName,
+                                             js.Expression value) {
+    if (value is js.Binary) {
+      js.Binary binary = value;
+      String op = binary.op;
+      if (op == '+' || op == '-' || op == '/' || op == '*' || op == '%' ||
+          op == '^' || op == '&' || op == '|') {
+        if (binary.left is js.VariableUse &&
+            (binary.left as js.VariableUse).name == variableName) {
+          // We know now, that we can shorten x = x + y into x += y.
+          // Also check for the shortcut where y equals 1: x++ and x--.
+          if ((op == '+' || op == '-') &&
+              binary.right is js.LiteralNumber &&
+              (binary.right as js.LiteralNumber).value == "1") {
+            return new js.Prefix(op == '+' ? '++' : '--', binary.left);
+          }
+          return new js.Assignment.compound(binary.left, op, binary.right);
+        }
+      }
+    }
+    return new js.Assignment(new js.VariableUse(variableName), value);
+  }
+
+  void assignVariable(String variableName, js.Expression value) {
+    if (isGeneratingExpression) {
+      // If we are in an expression then we can't declare the variable here.
+      // We have no choice, but to use it and then declare it separately.
+      if (!isVariableDeclared(variableName)) {
+        collectedVariableDeclarations.add(variableName);
+      }
+      push(generateExpressionAssignment(variableName, value));
+      // Otherwise if we are trying to declare inline and we are in a statement
+      // then we declare (unless it was already declared).
+    } else if (!shouldGroupVarDeclarations &&
+               !declaredLocals.contains(variableName)) {
+      // It may be necessary to remove it from the ones to be declared later.
+      collectedVariableDeclarations.remove(variableName);
+      declaredLocals.add(variableName);
+      js.VariableDeclaration decl = new js.VariableDeclaration(variableName);
+      js.VariableInitialization initialization =
+          new js.VariableInitialization(decl, value);
+
+      pushExpressionAsStatement(new js.VariableDeclarationList(
+          <js.VariableInitialization>[initialization]));
+    } else {
+      // Otherwise we are just going to use it.  If we have not already declared
+      // it then we make sure we will declare it later.
+      if (!declaredLocals.contains(variableName)) {
+        collectedVariableDeclarations.add(variableName);
+      }
+      pushExpressionAsStatement(
+          generateExpressionAssignment(variableName, value));
+    }
+  }
+
+  void define(HInstruction instruction) {
+    // For simple type checks like i = intTypeCheck(i), we don't have to
+    // emit an assignment, because the intTypeCheck just returns its
+    // argument.
+    bool needsAssignment = true;
+    if (instruction is HTypeConversion) {
+      String inputName = variableNames.getName(instruction.checkedInput);
+      if (variableNames.getName(instruction) == inputName) {
+        needsAssignment = false;
+      }
+    }
+    if (instruction is HLocalValue) {
+      needsAssignment = false;
+    }
+
+    if (needsAssignment &&
+        !instruction.isControlFlow() && variableNames.hasName(instruction)) {
+      visitExpression(instruction);
+      assignVariable(variableNames.getName(instruction), pop());
+      return;
+    }
+
+    if (isGeneratingExpression) {
+      visitExpression(instruction);
+    } else {
+      visitStatement(instruction);
+    }
+  }
+
+  void use(HInstruction argument) {
+    if (isGenerateAtUseSite(argument)) {
+      visitExpression(argument);
+    } else if (argument is HCheck && argument.isControlFlow()) {
+      // A [HCheck] that has control flow can never be used as an
+      // expression and may not have a name. Therefore we just use the
+      // checked instruction.
+      HCheck check = argument;
+      use(check.checkedInput);
+    } else {
+      push(new js.VariableUse(variableNames.getName(argument)));
+    }
+  }
+
+  visit(HInstruction node) {
+    node.accept(this);
+  }
+
+  visitExpression(HInstruction node) {
+    bool oldIsGeneratingExpression = isGeneratingExpression;
+    isGeneratingExpression = true;
+    visit(node);
+    isGeneratingExpression = oldIsGeneratingExpression;
+  }
+
+  visitStatement(HInstruction node) {
+    assert(!isGeneratingExpression);
+    visit(node);
+    if (!expressionStack.isEmpty) {
+      assert(expressionStack.length == 1);
+      pushExpressionAsStatement(pop());
+    }
+  }
+
+  void continueAsBreak(LabelElement target) {
+    pushStatement(new js.Break(backend.namer.continueLabelName(target)));
+  }
+
+  void implicitContinueAsBreak(TargetElement target) {
+    pushStatement(new js.Break(
+        backend.namer.implicitContinueLabelName(target)));
+  }
+
+  void implicitBreakWithLabel(TargetElement target) {
+    pushStatement(new js.Break(backend.namer.implicitBreakLabelName(target)));
+  }
+
+  js.Statement wrapIntoLabels(js.Statement result, List<LabelElement> labels) {
+    for (LabelElement label in labels) {
+      if (label.isTarget) {
+        String breakLabelString = backend.namer.breakLabelName(label);
+        result = new js.LabeledStatement(breakLabelString, result);
+      }
+    }
+    return result;
+  }
+
+
+  // The regular [visitIf] method implements the needed logic.
+  bool visitIfInfo(HIfBlockInformation info) => false;
+
+  bool visitSwitchInfo(HSwitchBlockInformation info) {
+    bool isExpression = isJSExpression(info.expression);
+    if (!isExpression) {
+      generateStatements(info.expression);
+    }
+
+    if (isExpression) {
+      push(generateExpression(info.expression));
+    } else {
+      use(info.expression.conditionExpression);
+    }
+    js.Expression key = pop();
+    List<js.SwitchClause> cases = <js.SwitchClause>[];
+
+    js.Block oldContainer = currentContainer;
+    for (int i = 0; i < info.matchExpressions.length; i++) {
+      for (Constant constant in info.matchExpressions[i]) {
+        generateConstant(constant);
+        currentContainer = new js.Block.empty();
+        cases.add(new js.Case(pop(), currentContainer));
+      }
+      if (i == info.matchExpressions.length - 1 && info.hasDefault) {
+        currentContainer = new js.Block.empty();
+        cases.add(new js.Default(currentContainer));
+      }
+      generateStatements(info.statements[i]);
+    }
+    currentContainer = oldContainer;
+
+    js.Statement result = new js.Switch(key, cases);
+    pushStatement(wrapIntoLabels(result, info.labels));
+    return true;
+  }
+
+  bool visitSequenceInfo(HStatementSequenceInformation info) {
+    return false;
+  }
+
+  bool visitSubGraphInfo(HSubGraphBlockInformation info) {
+    visitSubGraph(info.subGraph);
+    return true;
+  }
+
+  bool visitSubExpressionInfo(HSubExpressionBlockInformation info) {
+    return false;
+  }
+
+  bool visitAndOrInfo(HAndOrBlockInformation info) {
+    return false;
+  }
+
+  bool visitTryInfo(HTryBlockInformation info) {
+    js.Block body = generateStatementsInNewBlock(info.body);
+    js.Catch catchPart = null;
+    js.Block finallyPart = null;
+    if (info.catchBlock != null) {
+      HLocalValue exception = info.catchVariable;
+      String name = variableNames.getName(exception);
+      js.VariableDeclaration decl = new js.VariableDeclaration(name);
+      js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock);
+      catchPart = new js.Catch(decl, catchBlock);
+    }
+    if (info.finallyBlock != null) {
+      finallyPart = generateStatementsInNewBlock(info.finallyBlock);
+    }
+    pushStatement(new js.Try(body, catchPart, finallyPart));
+    return true;
+  }
+
+  void visitBodyIgnoreLabels(HLoopBlockInformation info) {
+    if (info.body.start.isLabeledBlock()) {
+      HBlockInformation oldInfo = currentBlockInformation;
+      currentBlockInformation = info.body.start.blockFlow.body;
+      generateStatements(info.body);
+      currentBlockInformation = oldInfo;
+    } else {
+      generateStatements(info.body);
+    }
+  }
+
+  bool visitLoopInfo(HLoopBlockInformation info) {
+    HExpressionInformation condition = info.condition;
+    bool isConditionExpression = isJSCondition(condition);
+
+    js.Loop loop;
+
+    switch (info.kind) {
+      // Treate all three "test-first" loops the same way.
+      case HLoopBlockInformation.FOR_LOOP:
+      case HLoopBlockInformation.WHILE_LOOP:
+      case HLoopBlockInformation.FOR_IN_LOOP:
+        HBlockInformation initialization = info.initializer;
+        int initializationType = TYPE_STATEMENT;
+        if (initialization != null) {
+          initializationType = expressionType(initialization);
+          if (initializationType == TYPE_STATEMENT) {
+            generateStatements(initialization);
+            initialization = null;
+          }
+        }
+        if (isConditionExpression &&
+            info.updates != null && isJSExpression(info.updates)) {
+          // If we have an updates graph, and it's expressible as an
+          // expression, generate a for-loop.
+          js.Expression jsInitialization = null;
+          if (initialization != null) {
+            int delayedVariablesCount = collectedVariableDeclarations.length;
+            jsInitialization = generateExpression(initialization);
+            if (!shouldGroupVarDeclarations &&
+                delayedVariablesCount < collectedVariableDeclarations.length) {
+              // We just added a new delayed variable-declaration. See if we
+              // can put in a 'var' in front of the initialization to make it
+              // go away.
+              List<js.Expression> expressions;
+              if (jsInitialization is js.Sequence) {
+                expressions = jsInitialization.expressions;
+              } else {
+                expressions = <js.Expression>[jsInitialization];
+              }
+              bool canTransformToVariableDeclaration = true;
+              for (js.Expression expression in expressions) {
+                bool expressionIsVariableAssignment = false;
+                if (expression is js.Assignment) {
+                  js.Assignment assignment = expression;
+                  if (assignment.leftHandSide is js.VariableUse &&
+                      assignment.compoundTarget == null) {
+                    expressionIsVariableAssignment = true;
+                  }
+                }
+                if (!expressionIsVariableAssignment) {
+                  canTransformToVariableDeclaration = false;
+                  break;
+                }
+              }
+              if (canTransformToVariableDeclaration) {
+                List<js.VariableInitialization> inits =
+                    <js.VariableInitialization>[];
+                for (js.Assignment assignment in expressions) {
+                  String id = (assignment.leftHandSide as js.VariableUse).name;
+                  js.Node declaration = new js.VariableDeclaration(id);
+                  inits.add(new js.VariableInitialization(declaration,
+                                                          assignment.value));
+                  collectedVariableDeclarations.remove(id);
+                }
+                jsInitialization = new js.VariableDeclarationList(inits);
+              }
+            }
+          }
+          js.Expression jsCondition = generateExpression(condition);
+          js.Expression jsUpdates = generateExpression(info.updates);
+          // The body might be labeled. Ignore this when recursing on the
+          // subgraph.
+          // TODO(lrn): Remove this extra labeling when handling all loops
+          // using subgraphs.
+          js.Block oldContainer = currentContainer;
+          js.Statement body = new js.Block.empty();
+          currentContainer = body;
+          visitBodyIgnoreLabels(info);
+          currentContainer = oldContainer;
+          body = unwrapStatement(body);
+          loop = new js.For(jsInitialization, jsCondition, jsUpdates, body);
+        } else {
+          // We have either no update graph, or it's too complex to
+          // put in an expression.
+          if (initialization != null) {
+            generateStatements(initialization);
+          }
+          js.Expression jsCondition;
+          js.Block oldContainer = currentContainer;
+          js.Statement body = new js.Block.empty();
+          if (isConditionExpression) {
+            jsCondition = generateExpression(condition);
+            currentContainer = body;
+          } else {
+            jsCondition = newLiteralBool(true);
+            currentContainer = body;
+            generateStatements(condition);
+            use(condition.conditionExpression);
+            js.Expression ifTest = new js.Prefix("!", pop());
+            js.Break jsBreak = new js.Break(null);
+            pushStatement(new js.If.noElse(ifTest, jsBreak));
+          }
+          if (info.updates != null) {
+            wrapLoopBodyForContinue(info);
+            generateStatements(info.updates);
+          } else {
+            visitBodyIgnoreLabels(info);
+          }
+          currentContainer = oldContainer;
+          body = unwrapStatement(body);
+          loop = new js.While(jsCondition, body);
+        }
+        break;
+      case HLoopBlockInformation.DO_WHILE_LOOP:
+        if (info.initializer != null) {
+          generateStatements(info.initializer);
+        }
+        js.Block oldContainer = currentContainer;
+        js.Block body = new js.Block.empty();
+        // If there are phi copies in the block that jumps to the
+        // loop entry, we must emit the condition like this:
+        // do {
+        //   body;
+        //   if (condition) {
+        //     phi updates;
+        //     continue;
+        //   } else {
+        //     break;
+        //   }
+        // } while (true);
+        HBasicBlock avoidEdge = info.end.successors[0];
+        js.Block updateBody = new js.Block.empty();
+        currentContainer = updateBody;
+        assignPhisOfSuccessors(avoidEdge);
+        bool hasPhiUpdates = !updateBody.statements.isEmpty;
+        currentContainer = body;
+        visitBodyIgnoreLabels(info);
+        if (info.updates != null) {
+          generateStatements(info.updates);
+        }
+        if (isConditionExpression) {
+          push(generateExpression(condition));
+        } else {
+          generateStatements(condition);
+          use(condition.conditionExpression);
+        }
+        js.Expression jsCondition = pop();
+        if (hasPhiUpdates) {
+          updateBody.statements.add(new js.Continue(null));
+          body.statements.add(
+              new js.If(jsCondition, updateBody, new js.Break(null)));
+          jsCondition = newLiteralBool(true);
+        }
+        loop = new js.Do(unwrapStatement(body), jsCondition);
+        currentContainer = oldContainer;
+        break;
+      default:
+        compiler.internalError(
+          'Unexpected loop kind: ${info.kind}',
+          instruction: condition.conditionExpression);
+    }
+    attachLocationRange(loop, info.sourcePosition, info.endSourcePosition);
+    pushStatement(wrapIntoLabels(loop, info.labels));
+    return true;
+  }
+
+  bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) {
+    preLabeledBlock(labeledBlockInfo);
+    Link<Element> continueOverrides = const Link<Element>();
+
+    js.Block oldContainer = currentContainer;
+    js.Block body = new js.Block.empty();
+    js.Statement result = body;
+
+    currentContainer = body;
+
+    // If [labeledBlockInfo.isContinue], the block is an artificial
+    // block around the body of a loop with an update block, so that
+    // continues of the loop can be written as breaks of the body
+    // block.
+    if (labeledBlockInfo.isContinue) {
+      for (LabelElement label in labeledBlockInfo.labels) {
+        if (label.isContinueTarget) {
+          String labelName = backend.namer.continueLabelName(label);
+          result = new js.LabeledStatement(labelName, result);
+          continueAction[label] = continueAsBreak;
+          continueOverrides = continueOverrides.prepend(label);
+        }
+      }
+      // For handling unlabeled continues from the body of a loop.
+      // TODO(lrn): Consider recording whether the target is in fact
+      // a target of an unlabeled continue, and not generate this if it isn't.
+      TargetElement target = labeledBlockInfo.target;
+      String labelName = backend.namer.implicitContinueLabelName(target);
+      result = new js.LabeledStatement(labelName, result);
+      continueAction[target] = implicitContinueAsBreak;
+      continueOverrides = continueOverrides.prepend(target);
+    } else {
+      for (LabelElement label in labeledBlockInfo.labels) {
+        if (label.isBreakTarget) {
+          String labelName = backend.namer.breakLabelName(label);
+          result = new js.LabeledStatement(labelName, result);
+        }
+      }
+      TargetElement target = labeledBlockInfo.target;
+      if (target.isSwitch) {
+        // This is an extra block around a switch that is generated
+        // as a nested if/else chain. We add an extra break target
+        // so that case code can break.
+        String labelName = backend.namer.implicitBreakLabelName(target);
+        result = new js.LabeledStatement(labelName, result);
+        breakAction[target] = implicitBreakWithLabel;
+      }
+    }
+
+    currentContainer = body;
+    startLabeledBlock(labeledBlockInfo);
+    generateStatements(labeledBlockInfo.body);
+    endLabeledBlock(labeledBlockInfo);
+
+    if (labeledBlockInfo.isContinue) {
+      while (!continueOverrides.isEmpty) {
+        continueAction.remove(continueOverrides.head);
+        continueOverrides = continueOverrides.tail;
+      }
+    } else {
+      breakAction.remove(labeledBlockInfo.target);
+    }
+
+    currentContainer = oldContainer;
+    pushStatement(result);
+    return true;
+  }
+
+  // Wraps a loop body in a block to make continues have a target to break
+  // to (if necessary).
+  void wrapLoopBodyForContinue(HLoopBlockInformation info) {
+    TargetElement target = info.target;
+    if (target != null && target.isContinueTarget) {
+      js.Block oldContainer = currentContainer;
+      js.Block body = new js.Block.empty();
+      currentContainer = body;
+      js.Statement result = body;
+      for (LabelElement label in info.labels) {
+        if (label.isContinueTarget) {
+          String labelName = backend.namer.continueLabelName(label);
+          result = new js.LabeledStatement(labelName, result);
+          continueAction[label] = continueAsBreak;
+        }
+      }
+      String labelName = backend.namer.implicitContinueLabelName(target);
+      result = new js.LabeledStatement(labelName, result);
+      continueAction[info.target] = implicitContinueAsBreak;
+      visitBodyIgnoreLabels(info);
+      continueAction.remove(info.target);
+      for (LabelElement label in info.labels) {
+        if (label.isContinueTarget) {
+          continueAction.remove(label);
+        }
+      }
+      currentContainer = oldContainer;
+      pushStatement(result);
+    } else {
+      // Loop body contains no continues, so we don't need a break target.
+      generateStatements(info.body);
+    }
+  }
+
+  bool handleBlockFlow(HBlockFlow block) {
+    HBlockInformation info = block.body;
+    // If we reach here again while handling the attached information,
+    // e.g., because we call visitSubGraph on a subgraph starting on
+    // the same block, don't handle it again.
+    // When the structure graph is complete, we will be able to have
+    // different structures starting on the same basic block (e.g., an
+    // "if" and its condition).
+    if (identical(info, currentBlockInformation)) return false;
+
+    HBlockInformation oldBlockInformation = currentBlockInformation;
+    currentBlockInformation = info;
+    bool success = info.accept(this);
+    currentBlockInformation = oldBlockInformation;
+    if (success) {
+      HBasicBlock continuation = block.continuation;
+      if (continuation != null) {
+        visitBasicBlock(continuation);
+      }
+    }
+    return success;
+  }
+
+  void visitBasicBlock(HBasicBlock node) {
+    // Abort traversal if we are leaving the currently active sub-graph.
+    if (!subGraph.contains(node)) return;
+
+    // If this node has block-structure based information attached,
+    // try using that to traverse from here.
+    if (node.blockFlow != null && handleBlockFlow(node.blockFlow)) {
+      return;
+    }
+    iterateBasicBlock(node);
+  }
+
+  void emitAssignment(String destination, String source) {
+    assignVariable(destination, new js.VariableUse(source));
+  }
+
+  /**
+   * Sequentialize a list of conceptually parallel copies. Parallel
+   * copies may contain cycles, that this method breaks.
+   */
+  void sequentializeCopies(Iterable<Copy> copies,
+                           String tempName,
+                           void doAssignment(String target, String source)) {
+    // Map to keep track of the current location (ie the variable that
+    // holds the initial value) of a variable.
+    Map<String, String> currentLocation = new Map<String, String>();
+
+    // Map to keep track of the initial value of a variable.
+    Map<String, String> initialValue = new Map<String, String>();
+
+    // List of variables to assign a value.
+    List<String> worklist = <String>[];
+
+    // List of variables that we can assign a value to (ie are not
+    // being used anymore).
+    List<String> ready = <String>[];
+
+    // Prune [copies] by removing self-copies.
+    List<Copy> prunedCopies = <Copy>[];
+    for (Copy copy in copies) {
+      if (copy.source != copy.destination) {
+        prunedCopies.add(copy);
+      }
+    }
+    copies = prunedCopies;
+
+
+    // For each copy, set the current location of the source to
+    // itself, and the initial value of the destination to the source.
+    // Add the destination to the list of copies to make.
+    for (Copy copy in copies) {
+      currentLocation[copy.source] = copy.source;
+      initialValue[copy.destination] = copy.source;
+      worklist.add(copy.destination);
+    }
+
+    // For each copy, if the destination does not have a current
+    // location, then we can safely assign to it.
+    for (Copy copy in copies) {
+      if (currentLocation[copy.destination] == null) {
+        ready.add(copy.destination);
+      }
+    }
+
+    while (!worklist.isEmpty) {
+      while (!ready.isEmpty) {
+        String destination = ready.removeLast();
+        String source = initialValue[destination];
+        // Since [source] might have been updated, use the current
+        // location of [source]
+        String copy = currentLocation[source];
+        doAssignment(destination, copy);
+        // Now [destination] is the current location of [source].
+        currentLocation[source] = destination;
+        // If [source] hasn't been updated and needs to have a value,
+        // add it to the list of variables that can be updated. Copies
+        // of [source] will now use [destination].
+        if (source == copy && initialValue[source] != null) {
+          ready.add(source);
+        }
+      }
+
+      // Check if we have a cycle.
+      String current = worklist.removeLast();
+      // If [current] is used as a source, and the assignment has been
+      // done, we are done with this variable. Otherwise there is a
+      // cycle that we break by using a temporary name.
+      if (currentLocation[current] != null
+          && current != currentLocation[initialValue[current]]) {
+        doAssignment(tempName, current);
+        currentLocation[current] = tempName;
+        // [current] can now be safely updated. Copies of [current]
+        // will now use [tempName].
+        ready.add(current);
+      }
+    }
+  }
+
+  void assignPhisOfSuccessors(HBasicBlock node) {
+    CopyHandler handler = variableNames.getCopyHandler(node);
+    if (handler == null) return;
+
+    // Map the instructions to strings.
+    Iterable<Copy> copies = handler.copies.map((Copy copy) {
+      return new Copy(variableNames.getName(copy.source),
+                      variableNames.getName(copy.destination));
+    });
+
+    sequentializeCopies(copies, variableNames.getSwapTemp(), emitAssignment);
+
+    for (Copy copy in handler.assignments) {
+      String name = variableNames.getName(copy.destination);
+      use(copy.source);
+      assignVariable(name, pop());
+    }
+  }
+
+  void iterateBasicBlock(HBasicBlock node) {
+    HInstruction instruction = node.first;
+    while (!identical(instruction, node.last)) {
+      if (instruction is HTypeGuard || instruction is HBailoutTarget) {
+        visit(instruction);
+      } else if (!isGenerateAtUseSite(instruction)) {
+        define(instruction);
+      }
+      instruction = instruction.next;
+    }
+    assignPhisOfSuccessors(node);
+    visit(instruction);
+  }
+
+  visitInvokeBinary(HInvokeBinary node, String op) {
+    use(node.left);
+    js.Expression jsLeft = pop();
+    use(node.right);
+    push(new js.Binary(op, jsLeft, pop()), node);
+  }
+
+  visitRelational(HRelational node, String op) => visitInvokeBinary(node, op);
+
+  // We want the outcome of bit-operations to be positive. We use the unsigned
+  // shift operator to achieve this.
+  visitBitInvokeBinary(HBinaryBitOp node, String op) {
+    visitInvokeBinary(node, op);
+    if (requiresUintConversion(node)) {
+      push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node);
+    }
+  }
+
+  visitInvokeUnary(HInvokeUnary node, String op) {
+    use(node.operand);
+    push(new js.Prefix(op, pop()), node);
+  }
+
+  // We want the outcome of bit-operations to be positive. We use the unsigned
+  // shift operator to achieve this.
+  visitBitInvokeUnary(HInvokeUnary node, String op) {
+    visitInvokeUnary(node, op);
+    if (requiresUintConversion(node)) {
+      push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node);
+    }
+  }
+
+  void emitIdentityComparison(HInstruction left, HInstruction right) {
+    String op = singleIdentityComparison(left, right, types);
+    if (op != null) {
+      use(left);
+      js.Expression jsLeft = pop();
+      use(right);
+      push(new js.Binary(op, jsLeft, pop()));
+    } else {
+      assert(NullConstant.JsNull == 'null');
+      use(left);
+      js.Binary leftEqualsNull =
+          new js.Binary("==", pop(), new js.LiteralNull());
+      use(right);
+      js.Binary rightEqualsNull =
+          new js.Binary("==", pop(), new js.LiteralNull());
+      use(right);
+      use(left);
+      js.Binary tripleEq = new js.Binary("===", pop(), pop());
+
+      push(new js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq));
+    }
+  }
+
+  visitIdentity(HIdentity node) {
+    emitIdentityComparison(node.left, node.right);
+  }
+
+  visitAdd(HAdd node)               => visitInvokeBinary(node, '+');
+  visitDivide(HDivide node)         => visitInvokeBinary(node, '/');
+  visitMultiply(HMultiply node)     => visitInvokeBinary(node, '*');
+  visitSubtract(HSubtract node)     => visitInvokeBinary(node, '-');
+  visitBitAnd(HBitAnd node)         => visitBitInvokeBinary(node, '&');
+  visitBitNot(HBitNot node)         => visitBitInvokeUnary(node, '~');
+  visitBitOr(HBitOr node)           => visitBitInvokeBinary(node, '|');
+  visitBitXor(HBitXor node)         => visitBitInvokeBinary(node, '^');
+  visitShiftLeft(HShiftLeft node)   => visitBitInvokeBinary(node, '<<');
+
+  visitNegate(HNegate node)         => visitInvokeUnary(node, '-');
+
+  visitLess(HLess node)                 => visitRelational(node, '<');
+  visitLessEqual(HLessEqual node)       => visitRelational(node, '<=');
+  visitGreater(HGreater node)           => visitRelational(node, '>');
+  visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>=');
+
+  visitBoolify(HBoolify node) {
+    assert(node.inputs.length == 1);
+    use(node.inputs[0]);
+    push(new js.Binary('===', pop(), newLiteralBool(true)), node);
+  }
+
+  visitExit(HExit node) {
+    // Don't do anything.
+  }
+
+  visitGoto(HGoto node) {
+    HBasicBlock block = node.block;
+    assert(block.successors.length == 1);
+    List<HBasicBlock> dominated = block.dominatedBlocks;
+    // With the exception of the entry-node which dominates its successor
+    // and the exit node, no block finishing with a 'goto' can have more than
+    // one dominated block (since it has only one successor).
+    // If the successor is dominated by another block, then the other block
+    // is responsible for visiting the successor.
+    if (dominated.isEmpty) return;
+    if (dominated.length > 2) {
+      compiler.internalError('dominated.length = ${dominated.length}',
+                             instruction: node);
+    }
+    if (dominated.length == 2 && block != currentGraph.entry) {
+      compiler.internalError('node.block != currentGraph.entry',
+                             instruction: node);
+    }
+    assert(dominated[0] == block.successors[0]);
+    visitBasicBlock(dominated[0]);
+  }
+
+  visitLoopBranch(HLoopBranch node) {
+    assert(node.block == subGraph.end);
+    // We are generating code for a loop condition.
+    // If we are generating the subgraph as an expression, the
+    // condition will be generated as the expression.
+    // Otherwise, we don't generate the expression, and leave that
+    // to the code that called [visitSubGraph].
+    if (isGeneratingExpression) {
+      use(node.inputs[0]);
+    }
+  }
+
+  /**
+   * Checks if [map] contains an [ElementAction] for [element], and
+   * if so calls that action and returns true.
+   * Otherwise returns false.
+   */
+  bool tryCallAction(Map<Element, ElementAction> map, Element element) {
+    ElementAction action = map[element];
+    if (action == null) return false;
+    action(element);
+    return true;
+  }
+
+  visitBreak(HBreak node) {
+    assert(node.block.successors.length == 1);
+    if (node.label != null) {
+      LabelElement label = node.label;
+      if (!tryCallAction(breakAction, label)) {
+        pushStatement(new js.Break(backend.namer.breakLabelName(label)), node);
+      }
+    } else {
+      TargetElement target = node.target;
+      if (!tryCallAction(breakAction, target)) {
+        pushStatement(new js.Break(null), node);
+      }
+    }
+  }
+
+  visitContinue(HContinue node) {
+    assert(node.block.successors.length == 1);
+    if (node.label != null) {
+      LabelElement label = node.label;
+      if (!tryCallAction(continueAction, label)) {
+        // TODO(floitsch): should this really be the breakLabelName?
+        pushStatement(new js.Continue(backend.namer.breakLabelName(label)),
+                      node);
+      }
+    } else {
+      TargetElement target = node.target;
+      if (!tryCallAction(continueAction, target)) {
+        pushStatement(new js.Continue(null), node);
+      }
+    }
+  }
+
+  visitExitTry(HExitTry node) {
+    // An [HExitTry] is used to represent the control flow graph of a
+    // try/catch block, ie the try body is always a predecessor
+    // of the catch and finally. Here, we continue visiting the try
+    // body by visiting the block that contains the user-level control
+    // flow instruction.
+    visitBasicBlock(node.bodyTrySuccessor);
+  }
+
+  visitTry(HTry node) {
+    // We should never get here. Try/catch/finally is always handled using block
+    // information in [visitTryInfo], or not at all, in the case of the bailout
+    // generator.
+    compiler.internalError('visitTry should not be called', instruction: node);
+  }
+
+  bool tryControlFlowOperation(HIf node) {
+    if (!controlFlowOperators.contains(node)) return false;
+    HPhi phi = node.joinBlock.phis.first;
+    bool atUseSite = isGenerateAtUseSite(phi);
+    // Don't generate a conditional operator in this situation:
+    // i = condition ? bar() : i;
+    // But generate this instead:
+    // if (condition) i = bar();
+    // Usually, the variable name is longer than 'if' and it takes up
+    // more space to duplicate the name.
+    if (!atUseSite
+        && variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) {
+      return false;
+    }
+    if (!atUseSite) define(phi);
+    visitBasicBlock(node.joinBlock);
+    return true;
+  }
+
+  void generateIf(HIf node, HIfBlockInformation info) {
+    use(node.inputs[0]);
+    js.Expression test = pop();
+
+    HStatementInformation thenGraph = info.thenGraph;
+    HStatementInformation elseGraph = info.elseGraph;
+    js.Statement thenPart =
+        unwrapStatement(generateStatementsInNewBlock(thenGraph));
+    js.Statement elsePart =
+        unwrapStatement(generateStatementsInNewBlock(elseGraph));
+
+    pushStatement(new js.If(test, thenPart, elsePart), node);
+  }
+
+  visitIf(HIf node) {
+    if (tryControlFlowOperation(node)) return;
+
+    HInstruction condition = node.inputs[0];
+    HIfBlockInformation info = node.blockInformation.body;
+
+    if (condition.isConstant()) {
+      HConstant constant = condition;
+      if (constant.constant.isTrue()) {
+        generateStatements(info.thenGraph);
+      } else {
+        generateStatements(info.elseGraph);
+      }
+    } else {
+      generateIf(node, info);
+    }
+
+    HBasicBlock joinBlock = node.joinBlock;
+    if (joinBlock != null && !identical(joinBlock.dominator, node.block)) {
+      // The join block is dominated by a block in one of the branches.
+      // The subgraph traversal never reached it, so we visit it here
+      // instead.
+      visitBasicBlock(joinBlock);
+    }
+
+    // Visit all the dominated blocks that are not part of the then or else
+    // branches, and is not the join block.
+    // Depending on how the then/else branches terminate
+    // (e.g., return/throw/break) there can be any number of these.
+    List<HBasicBlock> dominated = node.block.dominatedBlocks;
+    for (int i = 2; i < dominated.length; i++) {
+      visitBasicBlock(dominated[i]);
+    }
+  }
+
+  js.Call jsPropertyCall(js.Expression receiver,
+                         String fieldName,
+                         List<js.Expression> arguments) {
+    return new js.Call(new js.PropertyAccess.field(receiver, fieldName),
+                       arguments);
+  }
+
+  void visitInterceptor(HInterceptor node) {
+    backend.registerSpecializedGetInterceptor(node.interceptedClasses);
+    String name = backend.namer.getInterceptorName(
+        backend.getInterceptorMethod, node.interceptedClasses);
+    var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE);
+    use(node.receiver);
+    List<js.Expression> arguments = <js.Expression>[pop()];
+    push(jsPropertyCall(isolate, name, arguments), node);
+  }
+
+  visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
+    use(node.receiver);
+    js.Expression object = pop();
+    SourceString name = node.selector.name;
+    String methodName;
+    List<js.Expression> arguments = visitArguments(node.inputs);
+    Element target = node.element;
+
+    if (target != null) {
+      // Avoid adding the generative constructor name to the list of
+      // seen selectors.
+      if (target.isGenerativeConstructorBody()) {
+        methodName = name.slowToString();
+      } else if (!node.isInterceptorCall) {
+        if (target == backend.jsArrayAdd) {
+          methodName = 'push';
+        } else if (target == backend.jsArrayRemoveLast) {
+          methodName = 'pop';
+        } else if (target == backend.jsStringSplit) {
+          methodName = 'split';
+          // Split returns a List, so we make sure the backend knows the
+          // list class is instantiated.
+          world.registerInstantiatedClass(compiler.listClass);
+        } else if (target == backend.jsStringConcat) {
+          push(new js.Binary('+', object, arguments[0]), node);
+          return;
+        }
+      }
+    }
+
+    if (methodName == null) {
+      methodName = backend.namer.invocationName(node.selector);
+      registerMethodInvoke(node);
+    }
+    push(jsPropertyCall(object, methodName, arguments), node);
+  }
+
+  void visitOneShotInterceptor(HOneShotInterceptor node) {
+    List<js.Expression> arguments = visitArguments(node.inputs);
+    var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE);
+    Selector selector = node.selector;
+    String methodName = backend.namer.oneShotInterceptorName(selector);
+    push(jsPropertyCall(isolate, methodName, arguments), node);
+    backend.registerSpecializedGetInterceptor(node.interceptedClasses);
+    backend.addOneShotInterceptor(selector);
+    if (selector.isGetter()) {
+      registerGetter(node);
+    } else if (selector.isSetter()) {
+      registerSetter(node);
+    } else {
+      registerMethodInvoke(node);
+    }
+  }
+
+  Selector getOptimizedSelectorFor(HInvokeDynamic node,
+                                   Selector defaultSelector) {
+    // If [JSInvocationMirror.invokeOn] has been called, we must not create a
+    // typed selector based on the receiver type.
+    if (node.element == null && // Invocation is not exact.
+        backend.compiler.enabledInvokeOn) {
+      return defaultSelector;
+    }
+    int receiverIndex = node.isInterceptorCall ? 1 : 0;
+    HType receiverHType = types[node.inputs[receiverIndex]];
+    DartType receiverType = receiverHType.computeType(compiler);
+    if (receiverType != null &&
+        !identical(receiverType.kind, TypeKind.MALFORMED_TYPE)) {
+      return new TypedSelector(receiverType, defaultSelector);
+    } else {
+      return defaultSelector;
+    }
+  }
+
+  void registerInvoke(HInvokeDynamic node) {
+    bool inLoop = node.block.enclosingLoopHeader != null;
+    SourceString name = node.selector.name;
+    if (inLoop) {
+      Element target = node.element;
+      if (target != null) {
+        backend.builder.functionsCalledInLoop.add(target);
+      } else {
+        backend.builder.selectorsCalledInLoop[name] = node.selector;
+      }
+    }
+
+    if (node.isInterceptorCall) {
+      backend.addInterceptedSelector(node.selector);
+    }
+  }
+
+  void registerMethodInvoke(HInvokeDynamic node) {
+    Selector selector = getOptimizedSelectorFor(node, node.selector);
+    // Register this invocation to collect the types used at all call sites.
+    backend.registerDynamicInvocation(node, selector, types);
+
+    // If we don't know what we're calling or if we are calling a getter,
+    // we need to register that fact that we may be calling a closure
+    // with the same arguments.
+    Element target = node.element;
+    if (target == null || target.isGetter()) {
+      // TODO(kasperl): If we have a typed selector for the call, we
+      // may know something about the types of closures that need
+      // the specific closure call method.
+      Selector call = new Selector.callClosureFrom(selector);
+      world.registerDynamicInvocation(call.name, call);
+    }
+
+    if (target != null) {
+      // If we know we're calling a specific method, register that
+      // method only.
+      world.registerDynamicInvocationOf(target, selector);
+    } else {
+      SourceString name = node.selector.name;
+      world.registerDynamicInvocation(name, selector);
+    }
+    registerInvoke(node);
+  }
+
+  void registerSetter(HInvokeDynamic node) {
+    Selector selector = getOptimizedSelectorFor(node, node.selector);
+    world.registerDynamicSetter(selector.name, selector);
+    HType valueType = node.isInterceptorCall
+        ? types[node.inputs[2]]
+        : types[node.inputs[1]];
+    backend.addedDynamicSetter(selector, valueType);
+    registerInvoke(node);
+  }
+
+  void registerGetter(HInvokeDynamic node) {
+    Selector getter = node.selector;
+    world.registerDynamicGetter(
+        getter.name, getOptimizedSelectorFor(node, getter));
+    world.registerInstantiatedClass(compiler.functionClass);
+    registerInvoke(node);
+  }
+
+  visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
+    use(node.receiver);
+    Selector setter = node.selector;
+    String name = backend.namer.invocationName(setter);
+    push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node);
+    registerSetter(node);
+  }
+
+  visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
+    use(node.receiver);
+    Selector getter = node.selector;
+    String name = backend.namer.invocationName(getter);
+    push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node);
+    registerGetter(node);
+  }
+
+  visitInvokeClosure(HInvokeClosure node) {
+    Selector call = new Selector.callClosureFrom(node.selector);
+    use(node.receiver);
+    push(jsPropertyCall(pop(),
+                        backend.namer.invocationName(call),
+                        visitArguments(node.inputs)),
+         node);
+    world.registerDynamicInvocation(call.name, call);
+  }
+
+  visitInvokeStatic(HInvokeStatic node) {
+    if (node.typeCode() == HInstruction.INVOKE_STATIC_TYPECODE) {
+      // Register this invocation to collect the types used at all call sites.
+      backend.registerStaticInvocation(node, types);
+    }
+    use(node.target);
+    push(new js.Call(pop(), visitArguments(node.inputs)), node);
+  }
+
+  visitInvokeSuper(HInvokeSuper node) {
+    Element superMethod = node.element;
+    Element superClass = superMethod.getEnclosingClass();
+    if (superMethod.kind == ElementKind.FIELD) {
+      ClassElement currentClass = work.element.getEnclosingClass();
+      if (currentClass.isClosure()) {
+        ClosureClassElement closure = currentClass;
+        currentClass = closure.methodElement.getEnclosingClass();
+      }
+      String fieldName = currentClass.isShadowedByField(superMethod)
+          ? backend.namer.shadowedFieldName(superMethod)
+          : backend.namer.instanceFieldName(superMethod);
+      use(node.inputs[1]);
+      js.PropertyAccess access =
+          new js.PropertyAccess.field(pop(), fieldName);
+      if (node.isSetter) {
+        use(node.value);
+        push(new js.Assignment(access, pop()), node);
+      } else {
+        push(access, node);
+      }
+    } else {
+      String methodName = backend.namer.getName(superMethod);
+      String className = backend.namer.isolateAccess(superClass);
+      js.VariableUse classReference = new js.VariableUse(className);
+      js.PropertyAccess prototype =
+          new js.PropertyAccess.field(classReference, "prototype");
+      js.PropertyAccess method =
+          new js.PropertyAccess.field(prototype, methodName);
+      push(jsPropertyCall(method, "call", visitArguments(node.inputs)), node);
+    }
+    world.registerStaticUse(superMethod);
+  }
+
+  visitFieldGet(HFieldGet node) {
+    use(node.receiver);
+    if (node.element == backend.jsArrayLength
+        || node.element == backend.jsStringLength) {
+      // We're accessing a native JavaScript property called 'length'
+      // on a JS String or a JS array. Therefore, the name of that
+      // property should not be mangled.
+      push(new js.PropertyAccess.field(pop(), 'length'), node);
+    } else {
+      String name = _fieldPropertyName(node.element);
+      push(new js.PropertyAccess.field(pop(), name), node);
+      HType receiverHType = types[node.receiver];
+      DartType type = receiverHType.computeType(compiler);
+      if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) {
+        world.registerFieldGetter(
+            node.element.name, node.element.getLibrary(), type);
+      }
+    }
+  }
+
+  visitFieldSet(HFieldSet node) {
+    String name = _fieldPropertyName(node.element);
+    DartType type = types[node.receiver].computeType(compiler);
+    if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) {
+      // Field setters in the generative constructor body are handled in a
+      // step "SsaConstructionFieldTypes" in the ssa optimizer.
+      if (!work.element.isGenerativeConstructorBody()) {
+        world.registerFieldSetter(
+            node.element.name, node.element.getLibrary(), type);
+        backend.registerFieldSetter(
+            work.element, node.element, types[node.value]);
+      }
+    }
+    use(node.receiver);
+    js.Expression receiver = pop();
+    use(node.value);
+    push(new js.Assignment(new js.PropertyAccess.field(receiver, name), pop()),
+        node);
+  }
+
+  String _fieldPropertyName(Element element) => element.hasFixedBackendName()
+      ? element.fixedBackendName()
+      : backend.namer.getName(element);
+
+  visitLocalGet(HLocalGet node) {
+    use(node.receiver);
+  }
+
+  visitLocalSet(HLocalSet node) {
+    use(node.value);
+    assignVariable(variableNames.getName(node.receiver), pop());
+  }
+
+  void registerForeignType(HType type) {
+    DartType dartType = type.computeType(compiler);
+    if (dartType == null) {
+      assert(type == HType.UNKNOWN);
+      return;
+    }
+    world.registerInstantiatedClass(dartType.element);
+  }
+
+  visitForeign(HForeign node) {
+    String code = node.code.slowToString();
+    List<HInstruction> inputs = node.inputs;
+    if (node.isJsStatement()) {
+      if (!inputs.isEmpty) {
+        compiler.internalError("foreign statement with inputs: $code",
+                               instruction: node);
+      }
+      pushStatement(new js.LiteralStatement(code), node);
+    } else {
+      List<js.Expression> data = <js.Expression>[];
+      for (int i = 0; i < inputs.length; i++) {
+        use(inputs[i]);
+        data.add(pop());
+      }
+      push(new js.LiteralExpression.withData(code, data), node);
+    }
+    registerForeignType(types[node]);
+    // TODO(sra): Tell world.nativeEnqueuer about the types created here.
+  }
+
+  visitForeignNew(HForeignNew node) {
+    String jsClassReference = backend.namer.isolateAccess(node.element);
+    List<HInstruction> inputs = node.inputs;
+    // We can't use 'visitArguments', since our arguments start at input[0].
+    List<js.Expression> arguments = <js.Expression>[];
+    for (int i = 0; i < inputs.length; i++) {
+      use(inputs[i]);
+      arguments.add(pop());
+    }
+    // TODO(floitsch): jsClassReference is an Access. We shouldn't treat it
+    // as if it was a string.
+    push(new js.New(new js.VariableUse(jsClassReference), arguments), node);
+    registerForeignType(types[node]);
+  }
+
+  js.Expression newLiteralBool(bool value) {
+    if (compiler.enableMinification) {
+      // Use !0 for true, !1 for false.
+      return new js.Prefix("!", new js.LiteralNumber(value ? "0" : "1"));
+    } else {
+      return new js.LiteralBool(value);
+    }
+  }
+
+  void generateConstant(Constant constant) {
+    if (constant.isFunction()) {
+      FunctionConstant function = constant;
+      world.registerStaticUse(function.element);
+    }
+    push(backend.emitter.constantReference(constant));
+  }
+
+  visitConstant(HConstant node) {
+    assert(isGenerateAtUseSite(node));
+    generateConstant(node.constant);
+    DartType type = node.constant.computeType(compiler);
+    if (node.constant is ConstructedConstant) {
+      ConstantHandler handler = compiler.constantHandler;
+      handler.registerCompileTimeConstant(node.constant);
+    }
+    world.registerInstantiatedClass(type.element);
+  }
+
+  visitNot(HNot node) {
+    assert(node.inputs.length == 1);
+    generateNot(node.inputs[0]);
+    attachLocationToLast(node);
+  }
+
+  void generateNot(HInstruction input) {
+    bool canGenerateOptimizedComparison(HInstruction instruction) {
+      if (instruction is !HRelational) return false;
+      HRelational relational = instruction;
+      HInstruction left = relational.left;
+      HInstruction right = relational.right;
+      // This optimization doesn't work for NaN, so we only do it if the
+      // type is known to be an integer.
+      return types[left].isUseful() && left.isInteger(types)
+          && types[right].isUseful() && right.isInteger(types);
+    }
+
+    if (input is HBoolify && isGenerateAtUseSite(input)) {
+      use(input.inputs[0]);
+      push(new js.Binary("!==", pop(), newLiteralBool(true)), input);
+    } else if (canGenerateOptimizedComparison(input) &&
+               isGenerateAtUseSite(input)) {
+      Map<String, String> inverseOperator = const <String, String>{
+        "==" : "!=",
+        "!=" : "==",
+        "===": "!==",
+        "!==": "===",
+        "<"  : ">=",
+        "<=" : ">",
+        ">"  : "<=",
+        ">=" : "<"
+      };
+      HRelational relational = input;
+      BinaryOperation operation = relational.operation(backend.constantSystem);
+      visitRelational(input, inverseOperator[operation.name.stringValue]);
+    } else {
+      use(input);
+      push(new js.Prefix("!", pop()));
+    }
+  }
+
+  visitParameterValue(HParameterValue node) {
+    assert(!isGenerateAtUseSite(node));
+    String name = variableNames.getName(node);
+    parameters.add(new js.Parameter(name));
+    declaredLocals.add(name);
+  }
+
+  visitLocalValue(HLocalValue node) {
+    assert(!isGenerateAtUseSite(node));
+    String name = variableNames.getName(node);
+    collectedVariableDeclarations.add(name);
+  }
+
+  visitPhi(HPhi node) {
+    // This method is only called for phis that are generated at use
+    // site. A phi can be generated at use site only if it is the
+    // result of a control flow operation.
+    HBasicBlock ifBlock = node.block.dominator;
+    assert(controlFlowOperators.contains(ifBlock.last));
+    HInstruction input = ifBlock.last.inputs[0];
+    if (input.isConstantFalse()) {
+      use(node.inputs[1]);
+    } else if (input.isConstantTrue()) {
+      use(node.inputs[0]);
+    } else if (node.inputs[1].isConstantBoolean()) {
+      String operation = node.inputs[1].isConstantFalse() ? '&&' : '||';
+      if (operation == '||') {
+        if (input is HNot) {
+          use(input.inputs[0]);
+        } else {
+          generateNot(input);
+        }
+      } else {
+        use(input);
+      }
+      js.Expression left = pop();
+      use(node.inputs[0]);
+      push(new js.Binary(operation, left, pop()));
+    } else {
+      use(input);
+      js.Expression test = pop();
+      use(node.inputs[0]);
+      js.Expression then = pop();
+      use(node.inputs[1]);
+      push(new js.Conditional(test, then, pop()));
+    }
+  }
+
+  visitReturn(HReturn node) {
+    assert(node.inputs.length == 1);
+    HInstruction input = node.inputs[0];
+    if (input.isConstantNull()) {
+      pushStatement(new js.Return(null), node);
+    } else {
+      use(node.inputs[0]);
+      pushStatement(new js.Return(pop()), node);
+    }
+  }
+
+  visitThis(HThis node) {
+    push(new js.This());
+  }
+
+  visitThrow(HThrow node) {
+    if (node.isRethrow) {
+      use(node.inputs[0]);
+      pushStatement(new js.Throw(pop()), node);
+    } else {
+      generateThrowWithHelper(r'$throw', node.inputs[0]);
+    }
+  }
+
+  visitRangeConversion(HRangeConversion node) {
+    // Range conversion instructions are removed by the value range
+    // analyzer.
+    assert(false);
+  }
+
+  visitBoundsCheck(HBoundsCheck node) {
+    // TODO(ngeoffray): Separate the two checks of the bounds check, so,
+    // e.g., the zero checks can be shared if possible.
+
+    // If the checks always succeeds, we would have removed the bounds check
+    // completely.
+    assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE);
+    if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) {
+      js.Expression under;
+      js.Expression over;
+      if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) {
+        use(node.index);
+        under = new js.Binary("<", pop(), new js.LiteralNumber("0"));
+      }
+      if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) {
+        var index = node.index;
+        use(index);
+        js.Expression jsIndex = pop();
+        use(node.length);
+        over = new js.Binary(">=", jsIndex, pop());
+      }
+      assert(over != null || under != null);
+      js.Expression underOver = under == null
+          ? over
+          : over == null
+              ? under
+              : new js.Binary("||", under, over);
+      js.Statement thenBody = new js.Block.empty();
+      js.Block oldContainer = currentContainer;
+      currentContainer = thenBody;
+      generateThrowWithHelper('ioore', node.index);
+      currentContainer = oldContainer;
+      thenBody = unwrapStatement(thenBody);
+      pushStatement(new js.If.noElse(underOver, thenBody), node);
+    } else {
+      generateThrowWithHelper('ioore', node.index);
+    }
+  }
+
+  visitIntegerCheck(HIntegerCheck node) {
+    if (!node.alwaysFalse) {
+      checkInt(node.value, '!==');
+      js.Expression test = pop();
+      js.Statement thenBody = new js.Block.empty();
+      js.Block oldContainer = currentContainer;
+      currentContainer = thenBody;
+      generateThrowWithHelper('iae', node.value);
+      currentContainer = oldContainer;
+      thenBody = unwrapStatement(thenBody);
+      pushStatement(new js.If.noElse(test, thenBody), node);
+    } else {
+      generateThrowWithHelper('iae', node.value);
+    }
+  }
+
+  void generateThrowWithHelper(String helperName, HInstruction argument) {
+    Element helper = compiler.findHelper(new SourceString(helperName));
+    world.registerStaticUse(helper);
+    js.VariableUse jsHelper =
+        new js.VariableUse(backend.namer.isolateAccess(helper));
+    js.Call value = new js.Call(jsHelper, visitArguments([null, argument]));
+    attachLocation(value, argument);
+    // BUG(4906): Using throw here adds to the size of the generated code
+    // but it has the advantage of explicitly telling the JS engine that
+    // this code path will terminate abruptly. Needs more work.
+    pushStatement(new js.Throw(value));
+  }
+
+  void visitSwitch(HSwitch node) {
+    // Switches are handled using [visitSwitchInfo].
+  }
+
+  void visitStatic(HStatic node) {
+    // Check whether this static is used for anything else than as a target in
+    // a static call.
+    node.usedBy.forEach((HInstruction instr) {
+      if (instr is !HInvokeStatic) {
+        backend.registerNonCallStaticUse(node);
+        if (node.element.isFunction()) {
+          world.registerInstantiatedClass(compiler.functionClass);
+        }
+      } else if (instr.target != node) {
+        backend.registerNonCallStaticUse(node);
+      }
+    });
+    Element element = node.element;
+    world.registerStaticUse(element);
+    ClassElement cls = element.getEnclosingClass();
+    if (element.isGenerativeConstructor()
+        || (element.isFactoryConstructor() && cls == compiler.listClass)) {
+      world.registerInstantiatedClass(cls);
+    }
+    push(new js.VariableUse(backend.namer.isolateAccess(node.element)));
+  }
+
+  void visitLazyStatic(HLazyStatic node) {
+    Element element = node.element;
+    world.registerStaticUse(element);
+    String lazyGetter = backend.namer.isolateLazyInitializerAccess(element);
+    js.VariableUse target = new js.VariableUse(lazyGetter);
+    js.Call call = new js.Call(target, <js.Expression>[]);
+    push(call, node);
+  }
+
+  void visitStaticStore(HStaticStore node) {
+    world.registerStaticUse(node.element);
+    js.VariableUse variableUse =
+        new js.VariableUse(backend.namer.isolateAccess(node.element));
+    use(node.inputs[0]);
+    push(new js.Assignment(variableUse, pop()), node);
+  }
+
+  void visitStringConcat(HStringConcat node) {
+    if (isEmptyString(node.left)) {
+      useStringified(node.right);
+   } else if (isEmptyString(node.right)) {
+      useStringified(node.left);
+    } else {
+      useStringified(node.left);
+      js.Expression left = pop();
+      useStringified(node.right);
+      push(new js.Binary("+", left, pop()), node);
+    }
+  }
+
+  bool isEmptyString(HInstruction node) {
+    if (!node.isConstantString()) return false;
+    HConstant constant = node;
+    StringConstant string = constant.constant;
+    return string.value.length == 0;
+  }
+
+  void useStringified(HInstruction node) {
+    if (node.isString(types)) {
+      use(node);
+    } else {
+      Element convertToString = compiler.findHelper(const SourceString("S"));
+      world.registerStaticUse(convertToString);
+      js.VariableUse variableUse =
+          new js.VariableUse(backend.namer.isolateAccess(convertToString));
+      use(node);
+      push(new js.Call(variableUse, <js.Expression>[pop()]), node);
+    }
+  }
+
+  void visitLiteralList(HLiteralList node) {
+    world.registerInstantiatedClass(compiler.listClass);
+    generateArrayLiteral(node);
+  }
+
+  void generateArrayLiteral(HLiteralList node) {
+    int len = node.inputs.length;
+    List<js.ArrayElement> elements = <js.ArrayElement>[];
+    for (int i = 0; i < len; i++) {
+      use(node.inputs[i]);
+      elements.add(new js.ArrayElement(i, pop()));
+    }
+    push(new js.ArrayInitializer(len, elements), node);
+  }
+
+  void visitIndex(HIndex node) {
+    use(node.receiver);
+    js.Expression receiver = pop();
+    use(node.index);
+    push(new js.PropertyAccess(receiver, pop()), node);
+  }
+
+  void visitIndexAssign(HIndexAssign node) {
+    use(node.receiver);
+    js.Expression receiver = pop();
+    use(node.index);
+    js.Expression index = pop();
+    use(node.value);
+    push(new js.Assignment(new js.PropertyAccess(receiver, index), pop()),
+         node);
+  }
+
+  void checkInt(HInstruction input, String cmp) {
+    use(input);
+    js.Expression left = pop();
+    use(input);
+    js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0"));
+    push(new js.Binary(cmp, left, or0));
+  }
+
+  void checkBigInt(HInstruction input, String cmp) {
+    use(input);
+    js.Expression left = pop();
+    use(input);
+    js.Expression right = pop();
+    // TODO(4984): Deal with infinity and -0.0.
+    push(new js.LiteralExpression.withData('Math.floor(#) === #',
+                                           <js.Expression>[left, right]));
+  }
+
+  void checkTypeOf(HInstruction input, String cmp, String typeName) {
+    use(input);
+    js.Expression typeOf = new js.Prefix("typeof", pop());
+    push(new js.Binary(cmp, typeOf, js.string(typeName)));
+  }
+
+  void checkNum(HInstruction input, String cmp)
+      => checkTypeOf(input, cmp, 'number');
+
+  void checkDouble(HInstruction input, String cmp)  => checkNum(input, cmp);
+
+  void checkString(HInstruction input, String cmp)
+      => checkTypeOf(input, cmp, 'string');
+
+  void checkBool(HInstruction input, String cmp)
+      => checkTypeOf(input, cmp, 'boolean');
+
+  void checkObject(HInstruction input, String cmp) {
+    assert(NullConstant.JsNull == 'null');
+    if (cmp == "===") {
+      checkTypeOf(input, '===', 'object');
+      js.Expression left = pop();
+      use(input);
+      js.Expression notNull = new js.Binary("!==", pop(), new js.LiteralNull());
+      push(new js.Binary("&&", left, notNull));
+    } else {
+      assert(cmp == "!==");
+      checkTypeOf(input, '!==', 'object');
+      js.Expression left = pop();
+      use(input);
+      js.Expression eqNull = new js.Binary("===", pop(), new js.LiteralNull());
+      push(new js.Binary("||", left, eqNull));
+    }
+  }
+
+  void checkArray(HInstruction input, String cmp) {
+    use(input);
+    js.PropertyAccess constructor =
+        new js.PropertyAccess.field(pop(), 'constructor');
+    push(new js.Binary(cmp, constructor, new js.VariableUse('Array')));
+  }
+
+  void checkFieldExists(HInstruction input, String fieldName) {
+    use(input);
+    js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName);
+    // Double negate to boolify the result.
+    push(new js.Prefix('!', new js.Prefix('!', field)));
+  }
+
+  void checkImmutableArray(HInstruction input) {
+    checkFieldExists(input, 'immutable\$list');
+  }
+
+  void checkExtendableArray(HInstruction input) {
+    checkFieldExists(input, 'fixed\$length');
+  }
+
+  void checkFixedArray(HInstruction input) {
+    checkFieldExists(input, 'fixed\$length');
+  }
+
+  void checkNull(HInstruction input) {
+    use(input);
+    push(new js.Binary('==', pop(), new js.LiteralNull()));
+  }
+
+  void checkNonNull(HInstruction input) {
+    use(input);
+    push(new js.Binary('!=', pop(), new js.LiteralNull()));
+  }
+
+  void checkFunction(HInstruction input, DartType type) {
+    checkTypeOf(input, '===', 'function');
+    js.Expression functionTest = pop();
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkType(input, type);
+    push(new js.Binary('||',
+                       functionTest,
+                       new js.Binary('&&', objectTest, pop())));
+  }
+
+  void checkType(HInstruction input, DartType type, {bool negative: false}) {
+    assert(invariant(input, !type.isMalformed,
+                     message: 'Attempt to check malformed type $type'));
+    world.registerIsCheck(type);
+    Element element = type.element;
+    use(input);
+    js.PropertyAccess field =
+        new js.PropertyAccess.field(pop(), backend.namer.operatorIs(element));
+    if (backend.emitter.nativeEmitter.requiresNativeIsCheck(element)) {
+      push(new js.Call(field, <js.Expression>[]));
+      if (negative) push(new js.Prefix('!', pop()));
+    } else {
+      // We always negate at least once so that the result is boolified.
+      push(new js.Prefix('!', field));
+      // If the result is not negated, put another '!' in front.
+      if (!negative) push(new js.Prefix('!', pop()));
+    }
+  }
+
+  void handleNumberOrStringSupertypeCheck(HInstruction input, DartType type) {
+    assert(!identical(type.element, compiler.listClass)
+           && !Elements.isListSupertype(type.element, compiler)
+           && !Elements.isStringOnlySupertype(type.element, compiler));
+    checkNum(input, '===');
+    js.Expression numberTest = pop();
+    checkString(input, '===');
+    js.Expression stringTest = pop();
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkType(input, type);
+    push(new js.Binary('||',
+                       new js.Binary('||', numberTest, stringTest),
+                       new js.Binary('&&', objectTest, pop())));
+  }
+
+  void handleStringSupertypeCheck(HInstruction input, DartType type) {
+    assert(!identical(type.element, compiler.listClass)
+           && !Elements.isListSupertype(type.element, compiler)
+           && !Elements.isNumberOrStringSupertype(type.element, compiler));
+    checkString(input, '===');
+    js.Expression stringTest = pop();
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkType(input, type);
+    push(new js.Binary('||',
+                       stringTest,
+                       new js.Binary('&&', objectTest, pop())));
+  }
+
+  void handleListOrSupertypeCheck(HInstruction input, DartType type) {
+    assert(!identical(type.element, compiler.stringClass)
+           && !Elements.isStringOnlySupertype(type.element, compiler)
+           && !Elements.isNumberOrStringSupertype(type.element, compiler));
+    checkObject(input, '===');
+    js.Expression objectTest = pop();
+    checkArray(input, '===');
+    js.Expression arrayTest = pop();
+    checkType(input, type);
+    push(new js.Binary('&&',
+                       objectTest,
+                       new js.Binary('||', arrayTest, pop())));
+  }
+
+  void visitIs(HIs node) {
+    DartType type = node.typeExpression;
+    world.registerIsCheck(type);
+    Element element = type.element;
+    if (identical(element.kind, ElementKind.TYPE_VARIABLE)) {
+      compiler.unimplemented("visitIs for type variables",
+                             instruction: node.expression);
+    }
+    LibraryElement coreLibrary = compiler.coreLibrary;
+    ClassElement objectClass = compiler.objectClass;
+    HInstruction input = node.expression;
+
+    if (identical(element, objectClass) ||
+        identical(element, compiler.dynamicClass)) {
+      // The constant folder also does this optimization, but we make
+      // it safe by assuming it may have not run.
+      push(newLiteralBool(true), node);
+    } else if (element == compiler.stringClass) {
+      checkString(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.doubleClass) {
+      checkDouble(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.numClass) {
+      checkNum(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.boolClass) {
+      checkBool(input, '===');
+      attachLocationToLast(node);
+    } else if (element == compiler.functionClass) {
+      checkFunction(input, type);
+      attachLocationToLast(node);
+    } else if (element == compiler.intClass) {
+      // The is check in the code tells us that it might not be an
+      // int. So we do a typeof first to avoid possible
+      // deoptimizations on the JS engine due to the Math.floor check.
+      checkNum(input, '===');
+      js.Expression numTest = pop();
+      checkBigInt(input, '===');
+      push(new js.Binary('&&', numTest, pop()), node);
+    } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
+      handleNumberOrStringSupertypeCheck(input, type);
+      attachLocationToLast(node);
+    } else if (Elements.isStringOnlySupertype(element, compiler)) {
+      handleStringSupertypeCheck(input, type);
+      attachLocationToLast(node);
+    } else if (identical(element, compiler.listClass)
+               || Elements.isListSupertype(element, compiler)) {
+      handleListOrSupertypeCheck(input, type);
+      attachLocationToLast(node);
+    } else if (element.isTypedef()) {
+      checkNonNull(input);
+      js.Expression nullTest = pop();
+      checkType(input, type);
+      push(new js.Binary('&&', nullTest, pop()));
+      attachLocationToLast(node);
+    } else if (types[input].canBePrimitive() || types[input].canBeNull()) {
+      checkObject(input, '===');
+      js.Expression objectTest = pop();
+      checkType(input, type);
+      push(new js.Binary('&&', objectTest, pop()), node);
+    } else {
+      checkType(input, type);
+      attachLocationToLast(node);
+    }
+    if (node.hasArgumentChecks()) {
+      InterfaceType interfaceType = type;
+      ClassElement cls = type.element;
+      Link<DartType> arguments = interfaceType.typeArguments;
+      js.Expression result = pop();
+      for (int i = 0; i < node.checkCount; i++) {
+        use(node.getCheck(i));
+        result = new js.Binary('&&', result, pop());
+      }
+      push(result, node);
+    }
+    if (node.nullOk) {
+      checkNull(input);
+      push(new js.Binary('||', pop(), pop()), node);
+    }
+  }
+
+  // TODO(johnniwinther): Refactor this method.
+  void visitTypeConversion(HTypeConversion node) {
+    Map<String, SourceString> castNames = const <String, SourceString> {
+      "stringTypeCheck":
+          const SourceString("stringTypeCast"),
+      "doubleTypeCheck":
+          const SourceString("doubleTypeCast"),
+      "numTypeCheck":
+          const SourceString("numTypeCast"),
+      "boolTypeCheck":
+          const SourceString("boolTypeCast"),
+      "functionTypeCheck":
+          const SourceString("functionTypeCast"),
+      "intTypeCheck":
+          const SourceString("intTypeCast"),
+      "numberOrStringSuperNativeTypeCheck":
+          const SourceString("numberOrStringSuperNativeTypeCast"),
+      "numberOrStringSuperTypeCheck":
+          const SourceString("numberOrStringSuperTypeCast"),
+      "stringSuperNativeTypeCheck":
+          const SourceString("stringSuperNativeTypeCast"),
+      "stringSuperTypeCheck":
+          const SourceString("stringSuperTypeCast"),
+      "listTypeCheck":
+          const SourceString("listTypeCast"),
+      "listSuperNativeTypeCheck":
+          const SourceString("listSuperNativeTypeCast"),
+      "listSuperTypeCheck":
+          const SourceString("listSuperTypeCast"),
+      "callTypeCheck":
+          const SourceString("callTypeCast"),
+      "propertyTypeCheck":
+          const SourceString("propertyTypeCast"),
+      // TODO(johnniwinther): Add a malformedTypeCast which produces a TypeError
+      // with another message.
+      "malformedTypeCheck":
+          const SourceString("malformedTypeCheck")
+    };
+
+    if (node.isChecked) {
+      DartType type = node.type.computeType(compiler);
+      Element element = type.element;
+      world.registerIsCheck(type);
+
+      if (node.isArgumentTypeCheck) {
+        if (element == backend.jsIntClass) {
+          checkInt(node.checkedInput, '!==');
+        } else {
+          assert(element == backend.jsNumberClass);
+          checkNum(node.checkedInput, '!==');
+        }
+        js.Expression test = pop();
+        js.Block oldContainer = currentContainer;
+        js.Statement body = new js.Block.empty();
+        currentContainer = body;
+        generateThrowWithHelper('iae', node.checkedInput);
+        currentContainer = oldContainer;
+        body = unwrapStatement(body);
+        pushStatement(new js.If.noElse(test, body), node);
+        return;
+      }
+      assert(node.isCheckedModeCheck || node.isCastTypeCheck);
+
+      SourceString helper;
+      if (node.isBooleanConversionCheck) {
+        helper = const SourceString('boolConversionCheck');
+      } else {
+        helper = backend.getCheckedModeHelper(type);
+        if (node.isCastTypeCheck) {
+          helper = castNames[helper.stringValue];
+        }
+      }
+      FunctionElement helperElement = compiler.findHelper(helper);
+      world.registerStaticUse(helperElement);
+      List<js.Expression> arguments = <js.Expression>[];
+      use(node.checkedInput);
+      arguments.add(pop());
+      int parameterCount =
+          helperElement.computeSignature(compiler).parameterCount;
+      if (parameterCount == 2) {
+        // 2 arguments implies that the method is either [propertyTypeCheck]
+        // or [propertyTypeCast].
+        assert(!type.isMalformed);
+        String additionalArgument = backend.namer.operatorIs(element);
+        arguments.add(js.string(additionalArgument));
+      } else if (parameterCount == 3) {
+        // 3 arguments implies that the method is [malformedTypeCheck].
+        assert(type.isMalformed);
+        String reasons = Types.fetchReasonsFromMalformedType(type);
+        arguments.add(js.string('$type'));
+        // TODO(johnniwinther): Handle escaping correctly.
+        arguments.add(js.string(reasons));
+      } else {
+        assert(!type.isMalformed);
+      }
+      String helperName = backend.namer.isolateAccess(helperElement);
+      push(new js.Call(new js.VariableUse(helperName), arguments));
+    } else {
+      use(node.checkedInput);
+    }
+  }
+}
+
+class SsaOptimizedCodeGenerator extends SsaCodeGenerator {
+  SsaOptimizedCodeGenerator(backend, work) : super(backend, work);
+
+  HBasicBlock beginGraph(HGraph graph) {
+    return graph.entry;
+  }
+
+  void endGraph(HGraph graph) {}
+
+  // Called by visitTypeGuard to generate the actual bailout call, something
+  // like "return $.foo$bailout(t0, t1);"
+  js.Statement bailout(HTypeGuard guard, String reason) {
+    HBailoutTarget target = guard.bailoutTarget;
+    List<js.Expression> arguments = <js.Expression>[];
+    arguments.add(new js.LiteralNumber("${guard.state}"));
+
+    for (int i = 0; i < target.inputs.length; i++) {
+      HInstruction parameter = target.inputs[i];
+      for (int pad = target.padding[i]; pad != 0; pad--) {
+        // This argument will not be used by the bailout function, because
+        // of the control flow (controlled by the state argument passed
+        // above).  We need to pass it to get later arguments in the right
+        // position.
+        arguments.add(new js.LiteralNumber('0'));
+      }
+      use(parameter);
+      arguments.add(pop());
+    }
+    // Don't bother emitting the rest of the pending nulls.  Doing so might make
+    // the function invocation a little faster by having the call site and
+    // function defintion have the same number of arguments, but it would be
+    // more verbose and we don't expect the calls to bailout functions to be
+    // hot.
+
+    Element method = work.element;
+    js.Expression bailoutTarget;  // Receiver of the bailout call.
+    Namer namer = backend.namer;
+    if (method.isInstanceMember()) {
+      String bailoutName = namer.getBailoutName(method);
+      bailoutTarget = new js.PropertyAccess.field(new js.This(), bailoutName);
+    } else {
+      assert(!method.isField());
+      bailoutTarget = new js.VariableUse(namer.isolateBailoutAccess(method));
+    }
+    js.Call call = new js.Call(bailoutTarget, arguments);
+    attachLocation(call, guard);
+    return new js.Return(call);
+  }
+
+  // Generate a type guard, something like "if (typeof t0 == 'number')" and the
+  // corresponding bailout call, something like "return $.foo$bailout(t0, t1);"
+  void visitTypeGuard(HTypeGuard node) {
+    HInstruction input = node.guarded;
+    DartType indexingBehavior =
+        backend.jsIndexingBehaviorInterface.computeType(compiler);
+    if (node.isInteger(types)) {
+      // if (input is !int) bailout
+      checkInt(input, '!==');
+      js.Statement then = bailout(node, 'Not an integer');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isNumber(types)) {
+      // if (input is !num) bailout
+      checkNum(input, '!==');
+      js.Statement then = bailout(node, 'Not a number');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isBoolean(types)) {
+      // if (input is !bool) bailout
+      checkBool(input, '!==');
+      js.Statement then = bailout(node, 'Not a boolean');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isString(types)) {
+      // if (input is !string) bailout
+      checkString(input, '!==');
+      js.Statement then = bailout(node, 'Not a string');
+      pushStatement(new js.If.noElse(pop(), then), node);
+    } else if (node.isExtendableArray(types)) {
+      // if (input is !Object || input is !Array || input.isFixed) bailout
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkFixedArray(input);
+      js.Binary test = new js.Binary('||', objectTest, arrayTest);
+      test = new js.Binary('||', test, pop());
+      js.Statement then = bailout(node, 'Not an extendable array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else if (node.isMutableArray(types)) {
+      // if (input is !Object
+      //     || ((input is !Array || input.isImmutable)
+      //         && input is !JsIndexingBehavior)) bailout
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkImmutableArray(input);
+      js.Binary notArrayOrImmutable = new js.Binary('||', arrayTest, pop());
+      checkType(input, indexingBehavior, negative: true);
+      js.Binary notIndexing = new js.Binary('&&', notArrayOrImmutable, pop());
+      js.Binary test = new js.Binary('||', objectTest, notIndexing);
+      js.Statement then = bailout(node, 'Not a mutable array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else if (node.isReadableArray(types)) {
+      // if (input is !Object
+      //     || (input is !Array && input is !JsIndexingBehavior)) bailout
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkType(input, indexingBehavior, negative: true);
+      js.Expression notIndexing = new js.Binary('&&', arrayTest, pop());
+      js.Binary test = new js.Binary('||', objectTest, notIndexing);
+      js.Statement then = bailout(node, 'Not an array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else if (node.isIndexablePrimitive(types)) {
+      // if (input is !String
+      //     && (input is !Object
+      //         || (input is !Array && input is !JsIndexingBehavior))) bailout
+      checkString(input, '!==');
+      js.Expression stringTest = pop();
+      checkObject(input, '!==');
+      js.Expression objectTest = pop();
+      checkArray(input, '!==');
+      js.Expression arrayTest = pop();
+      checkType(input, indexingBehavior, negative: true);
+      js.Binary notIndexingTest = new js.Binary('&&', arrayTest, pop());
+      js.Binary notObjectOrIndexingTest =
+          new js.Binary('||', objectTest, notIndexingTest);
+      js.Binary test =
+          new js.Binary('&&', stringTest, notObjectOrIndexingTest);
+      js.Statement then = bailout(node, 'Not a string or array');
+      pushStatement(new js.If.noElse(test, then), node);
+    } else {
+      compiler.internalError('Unexpected type guard', instruction: input);
+    }
+  }
+
+  void visitBailoutTarget(HBailoutTarget target) {
+    // Do nothing. Bailout targets are only used in the non-optimized version.
+  }
+
+  void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+  }
+
+  void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+  }
+
+  void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+  }
+}
+
+class SsaUnoptimizedCodeGenerator extends SsaCodeGenerator {
+
+  js.Switch currentBailoutSwitch;
+  final List<js.Switch> oldBailoutSwitches;
+  final List<js.Parameter> newParameters;
+  final List<String> labels;
+  int labelId = 0;
+  /**
+   * Keeps track if a bailout switch already used its [:default::] clause. New
+   * bailout-switches just push [:false:] on the stack and replace it when
+   * they used the [:default::] clause.
+   */
+  final List<bool> defaultClauseUsedInBailoutStack;
+
+  SsaBailoutPropagator propagator;
+  HInstruction savedFirstInstruction;
+
+  SsaUnoptimizedCodeGenerator(backend, work)
+    : super(backend, work),
+      oldBailoutSwitches = <js.Switch>[],
+      newParameters = <js.Parameter>[],
+      labels = <String>[],
+      defaultClauseUsedInBailoutStack = <bool>[];
+
+  String pushLabel() {
+    String label = 'L${labelId++}';
+    labels.addLast(label);
+    return label;
+  }
+
+  String popLabel() {
+    return labels.removeLast();
+  }
+
+  String currentLabel() {
+    return labels.last;
+  }
+
+  js.VariableUse generateStateUse()
+      => new js.VariableUse(variableNames.stateName);
+
+  HBasicBlock beginGraph(HGraph graph) {
+    propagator = new SsaBailoutPropagator(compiler, variableNames);
+    propagator.visitGraph(graph);
+    // TODO(ngeoffray): We could avoid generating the state at the
+    // call site for non-complex bailout methods.
+    newParameters.add(new js.Parameter(variableNames.stateName));
+
+    List<String> names = new List<String>(propagator.bailoutArity);
+    for (String variable in propagator.parameterNames.keys) {
+      int index = propagator.parameterNames[variable];
+      assert(names[index] == null);
+      names[index] = variable;
+    }
+    for (int i = 0; i < names.length; i++) {
+      declaredLocals.add(names[i]);
+      newParameters.add(new js.Parameter(names[i]));
+    }
+
+    if (propagator.hasComplexBailoutTargets) {
+      startBailoutSwitch();
+
+      return graph.entry;
+    } else {
+      // We change the first instruction of the first guard to be the
+      // bailout target. We will change it back in the call to [endGraph].
+      HBasicBlock block = propagator.firstBailoutTarget.block;
+      savedFirstInstruction = block.first;
+      block.first = propagator.firstBailoutTarget;
+      return block;
+    }
+  }
+
+  // If argument is a [HCheck] and it does not have a name, we try to
+  // find the name of its checked input. Note that there must be a
+  // name, otherwise the instruction would not be in the live
+  // environment.
+  HInstruction unwrap(HInstruction argument) {
+    while (argument is HCheck && !variableNames.hasName(argument)) {
+      argument = argument.checkedInput;
+    }
+    assert(variableNames.hasName(argument));
+    return argument;
+  }
+
+  void endGraph(HGraph graph) {
+    if (propagator.hasComplexBailoutTargets) {
+      endBailoutSwitch();
+    } else {
+      // Put back the original first instruction of the block.
+      propagator.firstBailoutTarget.block.first = savedFirstInstruction;
+    }
+  }
+
+  visitParameterValue(HParameterValue node) {
+    // Nothing to do, parameters are dealt with specially in a bailout
+    // method.
+  }
+
+  bool visitAndOrInfo(HAndOrBlockInformation info) => false;
+
+  visitLoopBranch(HLoopBranch node) {
+    if (node.computeLoopHeader().hasBailoutTargets()) {
+      // The graph visitor in [visitLoopInfo] does not handle the
+      // condition. We must instead manually emit it here.
+      handleLoopCondition(node);
+      // We must also visit the body from here.
+      // For a do while loop, the body has already been visited.
+      if (!node.isDoWhile()) {
+        visitBasicBlock(node.block.dominatedBlocks[0]);
+      }
+    } else {
+      super.visitLoopBranch(node);
+    }
+  }
+
+
+  bool visitIfInfo(HIfBlockInformation info) {
+    if (info.thenGraph.start.hasBailoutTargets()) return false;
+    if (info.elseGraph.start.hasBailoutTargets()) return false;
+    return super.visitIfInfo(info);
+  }
+
+  bool visitLoopInfo(HLoopBlockInformation info) {
+    // Always emit with block flow traversal.
+    if (info.loopHeader.hasBailoutTargets()) {
+      // If there are any bailout targets in the loop, we cannot use
+      // the pretty [SsaCodeGenerator.visitLoopInfo] printer.
+      if (info.initializer != null) {
+        generateStatements(info.initializer);
+      }
+      beginLoop(info.loopHeader);
+      if (!info.isDoWhile()) {
+        generateStatements(info.condition);
+      }
+      generateStatements(info.body);
+      if (info.isDoWhile()) {
+        generateStatements(info.condition);
+      }
+      if (info.updates != null) {
+        generateStatements(info.updates);
+      }
+      endLoop(info.end);
+      return true;
+    }
+    return super.visitLoopInfo(info);
+  }
+
+  bool visitTryInfo(HTryBlockInformation info) => false;
+  bool visitSequenceInfo(HStatementSequenceInformation info) => false;
+
+  void visitTypeGuard(HTypeGuard node) {
+    // Do nothing. Type guards are only used in the optimized version.
+  }
+
+  void visitBailoutTarget(HBailoutTarget node) {
+    if (propagator.hasComplexBailoutTargets) {
+      js.Block nextBlock = new js.Block.empty();
+      js.Case clause = new js.Case(new js.LiteralNumber('${node.state}'),
+                                   nextBlock);
+      currentBailoutSwitch.cases.add(clause);
+      currentContainer = nextBlock;
+      pushExpressionAsStatement(new js.Assignment(generateStateUse(),
+                                                  new js.LiteralNumber('0')));
+    }
+    // Here we need to rearrange the inputs of the bailout target, so that they
+    // are output in the correct order, perhaps with interspersed nulls, to
+    // match the order in the bailout function, which is of course common to all
+    // the bailout points.
+    var newInputs = new List<HInstruction>(propagator.bailoutArity);
+    for (HInstruction input in node.inputs) {
+      int index = propagator.parameterNames[variableNames.getName(input)];
+      newInputs[index] = input;
+    }
+    // We record the count of unused arguments instead of just filling in the
+    // inputs list with dummy arguments because it is useful to be able easily
+    // to distinguish between a dummy argument (eg 0 or null) and a real
+    // argument that happens to have the same value.  The dummy arguments are
+    // not going to be accessed by the bailout function due to the control flow
+    // implied by the state argument, so we can put anything there, including
+    // just not emitting enough arguments and letting the JS engine insert
+    // undefined for the trailing arguments.
+    node.padding = new List<int>(node.inputs.length);
+    int j = 0;
+    int pendingUnusedArguments = 0;
+    for (int i = 0; i < newInputs.length; i++) {
+      HInstruction input = newInputs[i];
+      if (input == null) {
+        pendingUnusedArguments++;
+      } else {
+        node.padding[j] = pendingUnusedArguments;
+        pendingUnusedArguments = 0;
+        node.updateInput(j, input);
+        j++;
+      }
+    }
+    assert(j == node.inputs.length);
+  }
+
+  void startBailoutCase(List<HBailoutTarget> bailouts1,
+                        [List<HBailoutTarget> bailouts2 = const []]) {
+    if (!defaultClauseUsedInBailoutStack.last &&
+        bailouts1.length + bailouts2.length >= 2) {
+      currentContainer = new js.Block.empty();
+      currentBailoutSwitch.cases.add(new js.Default(currentContainer));
+      int len = defaultClauseUsedInBailoutStack.length;
+      defaultClauseUsedInBailoutStack[len - 1] = true;
+    } else {
+      _handleBailoutCase(bailouts1);
+      _handleBailoutCase(bailouts2);
+      currentContainer = currentBailoutSwitch.cases.last.body;
+    }
+  }
+
+  void _handleBailoutCase(List<HBailoutTarget> targets) {
+    for (int i = 0, len = targets.length; i < len; i++) {
+      js.LiteralNumber expr = new js.LiteralNumber('${targets[i].state}');
+      currentBailoutSwitch.cases.add(new js.Case(expr, new js.Block.empty()));
+    }
+  }
+
+  void startBailoutSwitch() {
+    defaultClauseUsedInBailoutStack.add(false);
+    oldBailoutSwitches.add(currentBailoutSwitch);
+    List<js.SwitchClause> cases = <js.SwitchClause>[];
+    js.Block firstBlock = new js.Block.empty();
+    cases.add(new js.Case(new js.LiteralNumber("0"), firstBlock));
+    currentBailoutSwitch = new js.Switch(generateStateUse(), cases);
+    pushStatement(currentBailoutSwitch);
+    oldContainerStack.add(currentContainer);
+    currentContainer = firstBlock;
+  }
+
+  js.Switch endBailoutSwitch() {
+    js.Switch result = currentBailoutSwitch;
+    currentBailoutSwitch = oldBailoutSwitches.removeLast();
+    defaultClauseUsedInBailoutStack.removeLast();
+    currentContainer = oldContainerStack.removeLast();
+    return result;
+  }
+
+  void beginLoop(HBasicBlock block) {
+    String loopLabel = pushLabel();
+    if (block.hasBailoutTargets()) {
+      startBailoutCase(block.bailoutTargets);
+    }
+    oldContainerStack.add(currentContainer);
+    currentContainer = new js.Block.empty();
+    if (block.hasBailoutTargets()) {
+      startBailoutSwitch();
+      HLoopInformation loopInformation = block.loopInformation;
+      if (loopInformation.target != null) {
+        breakAction[loopInformation.target] = (TargetElement target) {
+          pushStatement(new js.Break(loopLabel));
+        };
+      }
+    }
+  }
+
+  void endLoop(HBasicBlock block) {
+    String loopLabel = popLabel();
+
+    HBasicBlock header = block.isLoopHeader() ? block : block.parentLoopHeader;
+    HLoopInformation info = header.loopInformation;
+    if (header.hasBailoutTargets()) {
+      endBailoutSwitch();
+      if (info.target != null) breakAction.remove(info.target);
+    }
+
+    js.Statement body = unwrapStatement(currentContainer);
+    currentContainer = oldContainerStack.removeLast();
+
+    js.Statement result = new js.While(newLiteralBool(true), body);
+    attachLocationRange(result,
+                        info.loopBlockInformation.sourcePosition,
+                        info.loopBlockInformation.endSourcePosition);
+    result = new js.LabeledStatement(loopLabel, result);
+    result = wrapIntoLabels(result, info.labels);
+    pushStatement(result);
+  }
+
+  void handleLoopCondition(HLoopBranch node) {
+    use(node.inputs[0]);
+    js.Expression test = new js.Prefix('!', pop());
+    js.Statement then = new js.Break(currentLabel());
+    pushStatement(new js.If.noElse(test, then), node);
+  }
+
+  void generateIf(HIf node, HIfBlockInformation info) {
+    HStatementInformation thenGraph = info.thenGraph;
+    HStatementInformation elseGraph = info.elseGraph;
+    bool thenHasGuards = thenGraph.start.hasBailoutTargets();
+    bool elseHasGuards = elseGraph.start.hasBailoutTargets();
+    bool hasGuards = thenHasGuards || elseHasGuards;
+    if (!hasGuards) {
+      super.generateIf(node, info);
+      return;
+    }
+
+    startBailoutCase(thenGraph.start.bailoutTargets,
+                     elseGraph.start.bailoutTargets);
+
+    use(node.inputs[0]);
+    js.Binary stateEquals0 =
+        new js.Binary('===', generateStateUse(), new js.LiteralNumber('0'));
+    js.Expression condition = new js.Binary('&&', stateEquals0, pop());
+    // TODO(ngeoffray): Put the condition initialization in the
+    // arguments?
+    List<HBailoutTarget> targets = node.thenBlock.bailoutTargets;
+    for (int i = 0, len = targets.length; i < len; i++) {
+      js.VariableUse stateRef = generateStateUse();
+      js.Expression targetState = new js.LiteralNumber('${targets[i].state}');
+      js.Binary stateTest = new js.Binary('===', stateRef, targetState);
+      condition = new js.Binary('||', stateTest, condition);
+    }
+
+    js.Statement thenBody = new js.Block.empty();
+    js.Block oldContainer = currentContainer;
+    currentContainer = thenBody;
+    if (thenHasGuards) startBailoutSwitch();
+    generateStatements(thenGraph);
+    if (thenHasGuards) endBailoutSwitch();
+    thenBody = unwrapStatement(thenBody);
+
+    js.Statement elseBody = null;
+    elseBody = new js.Block.empty();
+    currentContainer = elseBody;
+    if (elseHasGuards) startBailoutSwitch();
+    generateStatements(elseGraph);
+    if (elseHasGuards) endBailoutSwitch();
+    elseBody = unwrapStatement(elseBody);
+
+    currentContainer = oldContainer;
+    pushStatement(new js.If(condition, thenBody, elseBody), node);
+  }
+
+  void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+    if (labeledBlockInfo.body.start.hasBailoutTargets()) {
+      indent--;
+      startBailoutCase(labeledBlockInfo.body.start.bailoutTargets);
+      indent++;
+    }
+  }
+
+  void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+    if (labeledBlockInfo.body.start.hasBailoutTargets()) {
+      startBailoutSwitch();
+    }
+  }
+
+  void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) {
+    if (labeledBlockInfo.body.start.hasBailoutTargets()) {
+      endBailoutSwitch();
+    }
+  }
+}
+
+String singleIdentityComparison(HInstruction left,
+                                HInstruction right,
+                                HTypeMap propagatedTypes) {
+  // Returns the single identity comparison (== or ===) or null if a more
+  // complex expression is required.
+  if ((left.isConstant() && left.isConstantSentinel()) ||
+      (right.isConstant() && right.isConstantSentinel())) return '===';
+  HType leftType = propagatedTypes[left];
+  HType rightType = propagatedTypes[right];
+  if (leftType.canBeNull() && rightType.canBeNull()) {
+    if (left.isConstantNull() || right.isConstantNull() ||
+        (leftType.isPrimitive() && leftType == rightType)) {
+      return '==';
+    }
+    return null;
+  } else {
+    return '===';
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart
new file mode 100644
index 0000000..625eafb
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart
@@ -0,0 +1,380 @@
+// 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.
+
+part of ssa;
+
+/**
+ * Instead of emitting each SSA instruction with a temporary variable
+ * mark instructions that can be emitted at their use-site.
+ * For example, in:
+ *   t0 = 4;
+ *   t1 = 3;
+ *   t2 = add(t0, t1);
+ * t0 and t1 would be marked and the resulting code would then be:
+ *   t2 = add(4, 3);
+ */
+class SsaInstructionMerger extends HBaseVisitor {
+  HTypeMap types;
+  /**
+   * List of [HInstruction] that the instruction merger expects in
+   * order when visiting the inputs of an instruction.
+   */
+  List<HInstruction> expectedInputs;
+  /**
+   * Set of pure [HInstruction] that the instruction merger expects to
+   * find. The order of pure instructions do not matter, as they will
+   * not be affected by side effects.
+   */
+  Set<HInstruction> pureInputs;
+  Set<HInstruction> generateAtUseSite;
+
+  void markAsGenerateAtUseSite(HInstruction instruction) {
+    assert(!instruction.isJsStatement());
+    generateAtUseSite.add(instruction);
+  }
+
+  SsaInstructionMerger(this.types, this.generateAtUseSite);
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+  void analyzeInputs(HInstruction user, int start) {
+    List<HInstruction> inputs = user.inputs;
+    for (int i = start; i < inputs.length; i++) {
+      HInstruction input = inputs[i];
+      if (!generateAtUseSite.contains(input)
+          && !input.isCodeMotionInvariant()
+          && input.usedBy.length == 1
+          && input is !HPhi
+          && input is !HLocalValue
+          && !input.isJsStatement()) {
+        if (input.isPure()) {
+          // Only consider a pure input if it is in the same loop.
+          // Otherwise, we might move GVN'ed instruction back into the
+          // loop.
+          if (user.hasSameLoopHeaderAs(input)) {
+            // Move it closer to [user], so that instructions in
+            // between do not prevent making it generate at use site.
+            input.moveBefore(user);
+            pureInputs.add(input);
+            // Visit the pure input now so that the expected inputs
+            // are after the expected inputs of [user].
+            input.accept(this);
+          }
+        } else {
+          expectedInputs.add(input);
+        }
+      }
+    }
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    // A code motion invariant instruction is dealt before visiting it.
+    assert(!instruction.isCodeMotionInvariant());
+    analyzeInputs(instruction, 0);
+  }
+
+  // The codegen might use the input multiple times, so it must not be
+  // set generate at use site.
+  void visitIs(HIs instruction) {}
+
+  // A bounds check method must not have its first input generated at use site,
+  // because it's using it twice.
+  void visitBoundsCheck(HBoundsCheck instruction) {
+    analyzeInputs(instruction, 1);
+  }
+
+  // An integer check method must not have its input generated at use site,
+  // because it's using it twice.
+  void visitIntegerCheck(HIntegerCheck instruction) {}
+
+  // A type guard should not generate its input at use site, otherwise
+  // they would not be alive.
+  void visitTypeGuard(HTypeGuard instruction) {}
+
+  // An identity operation must only have its inputs generated at use site if
+  // does not require an expression with multiple uses (because of null /
+  // undefined).
+  void visitIdentity(HIdentity instruction) {
+    HInstruction left = instruction.left;
+    HInstruction right = instruction.right;
+    if (singleIdentityComparison(left, right, types) != null) {
+      super.visitIdentity(instruction);
+    }
+    // Do nothing.
+  }
+
+  void visitTypeConversion(HTypeConversion instruction) {
+    if (!instruction.isChecked) {
+      markAsGenerateAtUseSite(instruction);
+    } else if (!instruction.isArgumentTypeCheck) {
+      assert(instruction.isCheckedModeCheck || instruction.isCastTypeCheck);
+      // Checked mode checks and cast checks compile to code that
+      // only use their input once, so we can safely visit them
+      // and try to merge the input.
+      visitInstruction(instruction);
+    }
+  }
+
+  void tryGenerateAtUseSite(HInstruction instruction) {
+    if (instruction.isControlFlow()) return;
+    markAsGenerateAtUseSite(instruction);
+  }
+
+  bool isBlockSinglePredecessor(HBasicBlock block) {
+    return block.successors.length == 1
+        && block.successors[0].predecessors.length == 1;
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    // Compensate from not merging blocks: if the block is the
+    // single predecessor of its single successor, let the successor
+    // visit it.
+    if (isBlockSinglePredecessor(block)) return;
+
+    tryMergingExpressions(block);
+  }
+
+  void tryMergingExpressions(HBasicBlock block) {
+    // Visit each instruction of the basic block in last-to-first order.
+    // Keep a list of expected inputs of the current "expression" being
+    // merged. If instructions occur in the expected order, they are
+    // included in the expression.
+
+    // The expectedInputs list holds non-trivial instructions that may
+    // be generated at their use site, if they occur in the correct order.
+    if (expectedInputs == null) expectedInputs = new List<HInstruction>();
+    if (pureInputs == null) pureInputs = new Set<HInstruction>();
+
+    // Pop instructions from expectedInputs until instruction is found.
+    // Return true if it is found, or false if not.
+    bool findInInputsAndPopNonMatching(HInstruction instruction) {
+      assert(!instruction.isPure());
+      while (!expectedInputs.isEmpty) {
+        HInstruction nextInput = expectedInputs.removeLast();
+        assert(!generateAtUseSite.contains(nextInput));
+        assert(nextInput.usedBy.length == 1);
+        if (identical(nextInput, instruction)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    block.last.accept(this);
+    bool dontVisitPure = false;
+    for (HInstruction instruction = block.last.previous;
+         instruction != null;
+         instruction = instruction.previous) {
+      if (generateAtUseSite.contains(instruction)) {
+        continue;
+      }
+      if (instruction.isCodeMotionInvariant()) {
+        markAsGenerateAtUseSite(instruction);
+        continue;
+      }
+      if (instruction.isJsStatement()) {
+        expectedInputs.clear();
+      }
+      if (instruction.isPure()) {
+        if (pureInputs.contains(instruction)) {
+          tryGenerateAtUseSite(instruction);
+        } else {
+          // If the input is not in the [pureInputs] set, it has not
+          // been visited.
+          instruction.accept(this);
+        }
+      } else {
+        if (findInInputsAndPopNonMatching(instruction)) {
+          // The current instruction is the next non-trivial
+          // expected input.
+          tryGenerateAtUseSite(instruction);
+        } else {
+          assert(expectedInputs.isEmpty);
+        }
+        instruction.accept(this);
+      }
+    }
+
+    if (block.predecessors.length == 1
+        && isBlockSinglePredecessor(block.predecessors[0])) {
+      assert(block.phis.isEmpty);
+      tryMergingExpressions(block.predecessors[0]);
+    } else {
+      expectedInputs = null;
+      pureInputs = null;
+    }
+  }
+}
+
+/**
+ *  Detect control flow arising from short-circuit logical and
+ *  conditional operators, and prepare the program to be generated
+ *  using these operators instead of nested ifs and boolean variables.
+ */
+class SsaConditionMerger extends HGraphVisitor {
+  final HTypeMap types;
+  Set<HInstruction> generateAtUseSite;
+  Set<HInstruction> controlFlowOperators;
+
+  void markAsGenerateAtUseSite(HInstruction instruction) {
+    assert(!instruction.isJsStatement());
+    generateAtUseSite.add(instruction);
+  }
+
+  SsaConditionMerger(this.types,
+                     this.generateAtUseSite,
+                     this.controlFlowOperators);
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+  }
+
+  /**
+   * Check if a block has at least one statement other than
+   * [instruction].
+   */
+  bool hasAnyStatement(HBasicBlock block, HInstruction instruction) {
+    // If [instruction] is not in [block], then if the block is not
+    // empty, we know there will be a statement to emit.
+    if (!identical(instruction.block, block)) return !identical(block.last, block.first);
+
+    // If [instruction] is not the last instruction of the block
+    // before the control flow instruction, or the last instruction,
+    // then we will have to emit a statement for that last instruction.
+    if (instruction != block.last
+        && !identical(instruction, block.last.previous)) return true;
+
+    // If one of the instructions in the block until [instruction] is
+    // not generated at use site, then we will have to emit a
+    // statement for it.
+    // TODO(ngeoffray): we could generate a comma separated
+    // list of expressions.
+    for (HInstruction temp = block.first;
+         !identical(temp, instruction);
+         temp = temp.next) {
+      if (!generateAtUseSite.contains(temp)) return true;
+    }
+
+    return false;
+  }
+
+  bool isSafeToGenerateAtUseSite(HInstruction user, HInstruction input) {
+    // A [HForeign] instruction uses operators and if we generate
+    // [input] at use site, the precedence might be wrong.
+    if (user is HForeign) return false;
+    // A [HCheck] instruction with control flow uses its input
+    // multiple times, so we avoid generating it at use site.
+    if (user is HCheck && user.isControlFlow()) return false;
+    // A [HIs] instruction uses its input multiple times, so we
+    // avoid generating it at use site.
+    if (user is HIs) return false;
+    return true;
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    if (block.last is !HIf) return;
+    HIf startIf = block.last;
+    HBasicBlock end = startIf.joinBlock;
+
+    // We check that the structure is the following:
+    //         If
+    //       /    \
+    //      /      \
+    //   1 expr    goto
+    //    goto     /
+    //      \     /
+    //       \   /
+    // phi(expr, true|false)
+    //
+    // and the same for nested nodes:
+    //
+    //            If
+    //          /    \
+    //         /      \
+    //      1 expr1    \
+    //       If         \
+    //      /  \         \
+    //     /    \         goto
+    //  1 expr2            |
+    //    goto    goto     |
+    //      \     /        |
+    //       \   /         |
+    //   phi1(expr2, true|false)
+    //          \          |
+    //           \         |
+    //             phi(phi1, true|false)
+
+    if (end == null) return;
+    if (end.phis.isEmpty) return;
+    if (!identical(end.phis.first, end.phis.last)) return;
+    HBasicBlock elseBlock = startIf.elseBlock;
+
+    if (!identical(end.predecessors[1], elseBlock)) return;
+    HPhi phi = end.phis.first;
+    HInstruction thenInput = phi.inputs[0];
+    HInstruction elseInput = phi.inputs[1];
+    if (thenInput.isJsStatement() || elseInput.isJsStatement()) return;
+
+    if (hasAnyStatement(elseBlock, elseInput)) return;
+    assert(elseBlock.successors.length == 1);
+    assert(end.predecessors.length == 2);
+
+    HBasicBlock thenBlock = startIf.thenBlock;
+    // Skip trivial goto blocks.
+    while (thenBlock.successors[0] != end && thenBlock.first is HGoto) {
+      thenBlock = thenBlock.successors[0];
+    }
+
+    // If the [thenBlock] is already a control flow operation, and does not
+    // have any statement and its join block is [end], we can emit a
+    // sequence of control flow operation.
+    if (controlFlowOperators.contains(thenBlock.last)) {
+      HIf otherIf = thenBlock.last;
+      if (!identical(otherIf.joinBlock, end)) {
+        // This could be a join block that just feeds into our join block.
+        HBasicBlock otherJoin = otherIf.joinBlock;
+        if (otherJoin.first != otherJoin.last) return;
+        if (otherJoin.successors.length != 1) return;
+        if (otherJoin.successors[0] != end) return;
+        if (otherJoin.phis.isEmpty) return;
+        if (!identical(otherJoin.phis.first, otherJoin.phis.last)) return;
+        HPhi otherPhi = otherJoin.phis.first;
+        if (thenInput != otherPhi) return;
+        if (elseInput != otherPhi.inputs[1]) return;
+      }
+      if (hasAnyStatement(thenBlock, otherIf)) return;
+    } else {
+      if (!identical(end.predecessors[0], thenBlock)) return;
+      if (hasAnyStatement(thenBlock, thenInput)) return;
+      assert(thenBlock.successors.length == 1);
+    }
+
+    // From now on, we have recognized a control flow operation built from
+    // the builder. Mark the if instruction as such.
+    controlFlowOperators.add(startIf);
+
+    // If the operation is only used by the first instruction
+    // of its block and is safe to be generated at use site, mark it
+    // so.
+    if (phi.usedBy.length == 1
+        && identical(phi.usedBy[0], phi.block.first)
+        && isSafeToGenerateAtUseSite(phi.usedBy[0], phi)) {
+      markAsGenerateAtUseSite(phi);
+    }
+
+    if (identical(elseInput.block, elseBlock)) {
+      assert(elseInput.usedBy.length == 1);
+      markAsGenerateAtUseSite(elseInput);
+    }
+
+    // If [thenInput] is defined in the first predecessor, then it is only used
+    // by [phi] and can be generated at use site.
+    if (identical(thenInput.block, end.predecessors[0])) {
+      assert(thenInput.usedBy.length == 1);
+      markAsGenerateAtUseSite(thenInput);
+    }
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart
new file mode 100644
index 0000000..375f617
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart
@@ -0,0 +1,620 @@
+// Copyright (c) 2013, 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.
+
+part of ssa;
+
+/**
+ * [InvokeDynamicSpecializer] and its subclasses are helpers to
+ * optimize intercepted dynamic calls. It knows what input types
+ * would be beneficial for performance, and how to change a invoke
+ * dynamic to a builtin instruction (e.g. HIndex, HBitNot).
+ */
+class InvokeDynamicSpecializer {
+  const InvokeDynamicSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return HType.UNKNOWN;
+  }
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    return null;
+  }
+
+  Operation operation(ConstantSystem constantSystem) => null;
+
+  static InvokeDynamicSpecializer lookupSpecializer(Selector selector) {
+    if (selector.kind == SelectorKind.INDEX) {
+      return selector.name == const SourceString('[]')
+          ? const IndexSpecializer()
+          : const IndexAssignSpecializer();
+    } else if (selector.kind == SelectorKind.OPERATOR) {
+      if (selector.name == const SourceString('unary-')) {
+        return const UnaryNegateSpecializer();
+      } else if (selector.name == const SourceString('~')) {
+        return const BitNotSpecializer();
+      } else if (selector.name == const SourceString('+')) {
+        return const AddSpecializer();
+      } else if (selector.name == const SourceString('-')) {
+        return const SubtractSpecializer();
+      } else if (selector.name == const SourceString('*')) {
+        return const MultiplySpecializer();
+      } else if (selector.name == const SourceString('/')) {
+        return const DivideSpecializer();
+      } else if (selector.name == const SourceString('~/')) {
+        return const TruncatingDivideSpecializer();
+      } else if (selector.name == const SourceString('%')) {
+        return const ModuloSpecializer();
+      } else if (selector.name == const SourceString('>>')) {
+        return const ShiftRightSpecializer();
+      } else if (selector.name == const SourceString('<<')) {
+        return const ShiftLeftSpecializer();
+      } else if (selector.name == const SourceString('&')) {
+        return const BitAndSpecializer();
+      } else if (selector.name == const SourceString('|')) {
+        return const BitOrSpecializer();
+      } else if (selector.name == const SourceString('^')) {
+        return const BitXorSpecializer();
+      } else if (selector.name == const SourceString('==')) {
+        return const EqualsSpecializer();
+      } else if (selector.name == const SourceString('<')) {
+        return const LessSpecializer();
+      } else if (selector.name == const SourceString('<=')) {
+        return const LessEqualSpecializer();
+      } else if (selector.name == const SourceString('>')) {
+        return const GreaterSpecializer();
+      } else if (selector.name == const SourceString('>=')) {
+        return const GreaterEqualSpecializer();
+      }
+    }
+    return const InvokeDynamicSpecializer();
+  }
+}
+
+class IndexAssignSpecializer extends InvokeDynamicSpecializer {
+  const IndexAssignSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HInstruction index = instruction.inputs[2];
+    if (input == instruction.inputs[1] &&
+        (index.isTypeUnknown(types) || index.isNumber(types))) {
+      return HType.MUTABLE_ARRAY;
+    }
+    // The index should be an int when the receiver is a string or array.
+    // However it turns out that inserting an integer check in the optimized
+    // version is cheaper than having another bailout case. This is true,
+    // because the integer check will simply throw if it fails.
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    if (instruction.inputs[1].isMutableArray(types)) {
+      return new HIndexAssign(instruction.inputs[1],
+                              instruction.inputs[2],
+                              instruction.inputs[3]);
+    }
+    return null;
+  }
+}
+
+class IndexSpecializer extends InvokeDynamicSpecializer {
+  const IndexSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HInstruction index = instruction.inputs[2];
+    if (input == instruction.inputs[1] &&
+        (index.isTypeUnknown(types) || index.isNumber(types))) {
+      return HType.INDEXABLE_PRIMITIVE;
+    }
+    // The index should be an int when the receiver is a string or array.
+    // However it turns out that inserting an integer check in the optimized
+    // version is cheaper than having another bailout case. This is true,
+    // because the integer check will simply throw if it fails.
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    if (instruction.inputs[1].isIndexablePrimitive(types)) {
+      return new HIndex(instruction.inputs[1], instruction.inputs[2]);
+    }
+    return null;
+  }
+}
+
+class BitNotSpecializer extends InvokeDynamicSpecializer {
+  const BitNotSpecializer();
+
+  UnaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitNot;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[1]) {
+      HType propagatedType = types[instruction];
+      if (propagatedType.isUnknown() || propagatedType.isNumber()) {
+        return HType.INTEGER;
+      }
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    // All bitwise operations on primitive types either produce an
+    // integer or throw an error.
+    if (instruction.inputs[1].isPrimitive(types)) return HType.INTEGER;
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction input = instruction.inputs[1];
+    if (input.isNumber(types)) return new HBitNot(input);
+    return null;
+  }
+}
+
+class UnaryNegateSpecializer extends InvokeDynamicSpecializer {
+  const UnaryNegateSpecializer();
+
+  UnaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.negate;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[1]) {
+      HType propagatedType = types[instruction];
+      // If the outgoing type should be a number (integer, double or both) we
+      // want the outgoing type to be the input too.
+      // If we don't know the outgoing type we try to make it a number.
+      if (propagatedType.isNumber()) return propagatedType;
+      if (propagatedType.isUnknown()) return HType.NUMBER;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    HType operandType = types[instruction.inputs[1]];
+    if (operandType.isNumber()) return operandType;
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction input = instruction.inputs[1];
+    if (input.isNumber(types)) return new HNegate(input);
+    return null;
+  }
+}
+
+abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer {
+  const BinaryArithmeticSpecializer();
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER;
+    if (left.isNumber(types)) {
+      if (left.isDouble(types) || right.isDouble(types)) return HType.DOUBLE;
+      return HType.NUMBER;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+
+    HType propagatedType = types[instruction];
+    // If the desired output type should be an integer we want to get two
+    // integers as arguments.
+    if (propagatedType.isInteger()) return HType.INTEGER;
+    // If the outgoing type should be a number we can get that if both inputs
+    // are numbers. If we don't know the outgoing type we try to make it a
+    // number.
+    if (propagatedType.isUnknown() || propagatedType.isNumber()) {
+      return HType.NUMBER;
+    }
+    // Even if the desired outgoing type is not a number we still want the
+    // second argument to be a number if the first one is a number. This will
+    // not help for the outgoing type, but at least the binary arithmetic
+    // operation will not have type problems.
+    // TODO(floitsch): normally we shouldn't request a number, but simply
+    // throw an ArgumentError if it isn't. This would be similar
+    // to the array case.
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (input == right && left.isNumber(types)) return HType.NUMBER;
+    return HType.UNKNOWN;
+  }
+
+  bool isBuiltin(HInvokeDynamic instruction, HTypeMap types) {
+    return instruction.inputs[1].isNumber(types)
+        && instruction.inputs[2].isNumber(types);
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    if (isBuiltin(instruction, types)) {
+      HInstruction builtin =
+          newBuiltinVariant(instruction.inputs[1], instruction.inputs[2]);
+      if (builtin != null) return builtin;
+      // Even if there is no builtin equivalent instruction, we know
+      // the instruction does not have any side effect, and that it
+      // can be GVN'ed.
+      instruction.clearAllSideEffects();
+      instruction.clearAllDependencies();
+      instruction.setUseGvn();
+    }
+    return null;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right);
+}
+
+class AddSpecializer extends BinaryArithmeticSpecializer {
+  const AddSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.add;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HAdd(left, right);
+  }
+}
+
+class DivideSpecializer extends BinaryArithmeticSpecializer {
+  const DivideSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.divide;
+  }
+
+  HType computeTypeFromInputTypes(HInstruction instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    HInstruction left = instruction.inputs[1];
+    if (left.isNumber(types)) return HType.DOUBLE;
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInstruction instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+    // A division can never return an integer. So don't ask for integer inputs.
+    if (instruction.isInteger(types)) return HType.UNKNOWN;
+    return super.computeDesiredTypeForInput(
+        instruction, input, types, compiler);
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HDivide(left, right);
+  }
+}
+
+class ModuloSpecializer extends BinaryArithmeticSpecializer {
+  const ModuloSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.modulo;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    // Modulo cannot be mapped to the native operator (different semantics).    
+    return null;
+  }
+}
+
+class MultiplySpecializer extends BinaryArithmeticSpecializer {
+  const MultiplySpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.multiply;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HMultiply(left, right);
+  }
+}
+
+class SubtractSpecializer extends BinaryArithmeticSpecializer {
+  const SubtractSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.subtract;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HSubtract(left, right);
+  }
+}
+
+class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer {
+  const TruncatingDivideSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.truncatingDivide;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    // Truncating divide does not have a JS equivalent.    
+    return null;
+  }
+}
+
+abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer {
+  const BinaryBitOpSpecializer();
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    // All bitwise operations on primitive types either produce an
+    // integer or throw an error.
+    HInstruction left = instruction.inputs[1];
+    if (left.isPrimitive(types)) return HType.INTEGER;
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+    HType propagatedType = types[instruction];
+    // If the outgoing type should be a number we can get that only if both
+    // inputs are integers. If we don't know the outgoing type we try to make
+    // it an integer.
+    if (propagatedType.isUnknown() || propagatedType.isNumber()) {
+      return HType.INTEGER;
+    }
+    return HType.UNKNOWN;
+  }
+}
+
+class ShiftLeftSpecializer extends BinaryBitOpSpecializer {
+  const ShiftLeftSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.shiftLeft;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (!left.isNumber(types) || !right.isConstantInteger()) return null;
+    HConstant rightConstant = right;
+    IntConstant intConstant = rightConstant.constant;
+    int count = intConstant.value;
+    if (count >= 0 && count <= 31) {
+      return newBuiltinVariant(left, right);
+    }
+    return null;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HShiftLeft(left, right);
+  }
+}
+
+class ShiftRightSpecializer extends BinaryBitOpSpecializer {
+  const ShiftRightSpecializer();
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    // Shift right cannot be mapped to the native operator easily.    
+    return null;
+  }
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.shiftRight;
+  }
+}
+
+class BitOrSpecializer extends BinaryBitOpSpecializer {
+  const BitOrSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitOr;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HBitOr(left, right);
+  }
+}
+
+class BitAndSpecializer extends BinaryBitOpSpecializer {
+  const BitAndSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitAnd;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HBitAnd(left, right);
+  }
+}
+
+class BitXorSpecializer extends BinaryBitOpSpecializer {
+  const BitXorSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.bitXor;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HBitXor(left, right);
+  }
+}
+
+abstract class RelationalSpecializer extends InvokeDynamicSpecializer {
+  const RelationalSpecializer();
+
+  HType computeTypeFromInputTypes(HInvokeDynamic instruction,
+                                  HTypeMap types,
+                                  Compiler compiler) {
+    if (types[instruction.inputs[1]].isPrimitiveOrNull()) return HType.BOOLEAN;
+    return HType.UNKNOWN;
+  }
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (input == instruction.inputs[0]) return HType.UNKNOWN;
+    HType propagatedType = types[instruction];
+    // For all relational operations except HIdentity, we expect to get numbers
+    // only. With numbers the outgoing type is a boolean. If something else
+    // is desired, then numbers are incorrect, though.
+    if (propagatedType.isUnknown() || propagatedType.isBoolean()) {
+      HInstruction left = instruction.inputs[1];
+      if (left.isTypeUnknown(types) || left.isNumber(types)) {
+        return HType.NUMBER;
+      }
+    }
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isNumber(types) && right.isNumber(types)) {
+      return newBuiltinVariant(left, right);
+    }
+    return null;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right);
+}
+
+class EqualsSpecializer extends RelationalSpecializer {
+  const EqualsSpecializer();
+
+  HType computeDesiredTypeForInput(HInvokeDynamic instruction,
+                                   HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    HType propagatedType = types[instruction];
+    if (input == left && types[right].isUseful()) {
+      // All our useful types have 'identical' semantics. But we don't want to
+      // speculatively test for all possible types. Therefore we try to match
+      // the two types. That is, if we see x == 3, then we speculatively test
+      // if x is a number and bailout if it isn't.
+      // If right is a number we don't need more than a number (no need to match
+      // the exact type of right).
+      if (right.isNumber(types)) return HType.NUMBER;
+      return types[right];
+    }
+    // String equality testing is much more common than array equality testing.
+    if (input == left && left.isIndexablePrimitive(types)) {
+      return HType.READABLE_ARRAY;
+    }
+    // String equality testing is much more common than array equality testing.
+    if (input == right && right.isIndexablePrimitive(types)) {
+      return HType.STRING;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
+                                   HTypeMap types) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (types[left].isPrimitiveOrNull() || right.isConstantNull()) {
+      return newBuiltinVariant(left, right);
+    }
+    return null;
+  }
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.equal;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HIdentity(left, right);
+  }
+}
+
+class LessSpecializer extends RelationalSpecializer {
+  const LessSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.less;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HLess(left, right);
+  }
+}
+
+class GreaterSpecializer extends RelationalSpecializer {
+  const GreaterSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.greater;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HGreater(left, right);
+  }
+}
+
+class GreaterEqualSpecializer extends RelationalSpecializer {
+  const GreaterEqualSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.greaterEqual;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HGreaterEqual(left, right);
+  }
+}
+
+class LessEqualSpecializer extends RelationalSpecializer {
+  const LessEqualSpecializer();
+
+  BinaryOperation operation(ConstantSystem constantSystem) {
+    return constantSystem.lessEqual;
+  }
+
+  HInstruction newBuiltinVariant(HInstruction left, HInstruction right) {
+    return new HLessEqual(left, right);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart
new file mode 100644
index 0000000..afbd2e1
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart
@@ -0,0 +1,2711 @@
+// 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.
+
+part of ssa;
+
+abstract class HVisitor<R> {
+  R visitAdd(HAdd node);
+  R visitBailoutTarget(HBailoutTarget node);
+  R visitBitAnd(HBitAnd node);
+  R visitBitNot(HBitNot node);
+  R visitBitOr(HBitOr node);
+  R visitBitXor(HBitXor node);
+  R visitBoolify(HBoolify node);
+  R visitBoundsCheck(HBoundsCheck node);
+  R visitBreak(HBreak node);
+  R visitConstant(HConstant node);
+  R visitContinue(HContinue node);
+  R visitDivide(HDivide node);
+  R visitExit(HExit node);
+  R visitExitTry(HExitTry node);
+  R visitFieldGet(HFieldGet node);
+  R visitFieldSet(HFieldSet node);
+  R visitForeign(HForeign node);
+  R visitForeignNew(HForeignNew node);
+  R visitGoto(HGoto node);
+  R visitGreater(HGreater node);
+  R visitGreaterEqual(HGreaterEqual node);
+  R visitIdentity(HIdentity node);
+  R visitIf(HIf node);
+  R visitIndex(HIndex node);
+  R visitIndexAssign(HIndexAssign node);
+  R visitIntegerCheck(HIntegerCheck node);
+  R visitInterceptor(HInterceptor node);
+  R visitInvokeClosure(HInvokeClosure node);
+  R visitInvokeDynamicGetter(HInvokeDynamicGetter node);
+  R visitInvokeDynamicMethod(HInvokeDynamicMethod node);
+  R visitInvokeDynamicSetter(HInvokeDynamicSetter node);
+  R visitInvokeStatic(HInvokeStatic node);
+  R visitInvokeSuper(HInvokeSuper node);
+  R visitIs(HIs node);
+  R visitLazyStatic(HLazyStatic node);
+  R visitLess(HLess node);
+  R visitLessEqual(HLessEqual node);
+  R visitLiteralList(HLiteralList node);
+  R visitLocalGet(HLocalGet node);
+  R visitLocalSet(HLocalSet node);
+  R visitLocalValue(HLocalValue node);
+  R visitLoopBranch(HLoopBranch node);
+  R visitMultiply(HMultiply node);
+  R visitNegate(HNegate node);
+  R visitNot(HNot node);
+  R visitOneShotInterceptor(HOneShotInterceptor);
+  R visitParameterValue(HParameterValue node);
+  R visitPhi(HPhi node);
+  R visitRangeConversion(HRangeConversion node);
+  R visitReturn(HReturn node);
+  R visitShiftLeft(HShiftLeft node);
+  R visitStatic(HStatic node);
+  R visitStaticStore(HStaticStore node);
+  R visitStringConcat(HStringConcat node);
+  R visitSubtract(HSubtract node);
+  R visitSwitch(HSwitch node);
+  R visitThis(HThis node);
+  R visitThrow(HThrow node);
+  R visitTry(HTry node);
+  R visitTypeGuard(HTypeGuard node);
+  R visitTypeConversion(HTypeConversion node);
+}
+
+abstract class HGraphVisitor {
+  visitDominatorTree(HGraph graph) {
+    void visitBasicBlockAndSuccessors(HBasicBlock block) {
+      visitBasicBlock(block);
+      List dominated = block.dominatedBlocks;
+      for (int i = 0; i < dominated.length; i++) {
+        visitBasicBlockAndSuccessors(dominated[i]);
+      }
+    }
+
+    visitBasicBlockAndSuccessors(graph.entry);
+  }
+
+  visitPostDominatorTree(HGraph graph) {
+    void visitBasicBlockAndSuccessors(HBasicBlock block) {
+      List dominated = block.dominatedBlocks;
+      for (int i = dominated.length - 1; i >= 0; i--) {
+        visitBasicBlockAndSuccessors(dominated[i]);
+      }
+      visitBasicBlock(block);
+    }
+
+    visitBasicBlockAndSuccessors(graph.entry);
+  }
+
+  visitBasicBlock(HBasicBlock block);
+}
+
+abstract class HInstructionVisitor extends HGraphVisitor {
+  HBasicBlock currentBlock;
+
+  visitInstruction(HInstruction node);
+
+  visitBasicBlock(HBasicBlock node) {
+    void visitInstructionList(HInstructionList list) {
+      HInstruction instruction = list.first;
+      while (instruction != null) {
+        visitInstruction(instruction);
+        instruction = instruction.next;
+        assert(instruction != list.first);
+      }
+    }
+
+    currentBlock = node;
+    visitInstructionList(node);
+  }
+}
+
+class HGraph {
+  HBasicBlock entry;
+  HBasicBlock exit;
+  HThis thisInstruction;
+  bool isRecursiveMethod = false;
+  bool calledInLoop = false;
+  final List<HBasicBlock> blocks;
+
+  // We canonicalize all constants used within a graph so we do not
+  // have to worry about them for global value numbering.
+  Map<Constant, HConstant> constants;
+
+  HGraph()
+      : blocks = new List<HBasicBlock>(),
+        constants = new Map<Constant, HConstant>() {
+    entry = addNewBlock();
+    // The exit block will be added later, so it has an id that is
+    // after all others in the system.
+    exit = new HBasicBlock();
+  }
+
+  void addBlock(HBasicBlock block) {
+    int id = blocks.length;
+    block.id = id;
+    blocks.add(block);
+    assert(identical(blocks[id], block));
+  }
+
+  HBasicBlock addNewBlock() {
+    HBasicBlock result = new HBasicBlock();
+    addBlock(result);
+    return result;
+  }
+
+  HBasicBlock addNewLoopHeaderBlock(TargetElement target,
+                                    List<LabelElement> labels) {
+    HBasicBlock result = addNewBlock();
+    result.loopInformation =
+        new HLoopInformation(result, target, labels);
+    return result;
+  }
+
+  static HType mapConstantTypeToSsaType(Constant constant) {
+    if (constant.isNull()) return HType.NULL;
+    if (constant.isBool()) return HType.BOOLEAN;
+    if (constant.isInt()) return HType.INTEGER;
+    if (constant.isDouble()) return HType.DOUBLE;
+    if (constant.isString()) return HType.STRING;
+    if (constant.isList()) return HType.READABLE_ARRAY;
+    if (constant.isFunction()) return HType.UNKNOWN;
+    if (constant.isSentinel()) return HType.UNKNOWN;
+    ObjectConstant objectConstant = constant;
+    return new HBoundedType.exact(objectConstant.type);
+  }
+
+  HConstant addConstant(Constant constant) {
+    HConstant result = constants[constant];
+    if (result == null) {
+      HType type = mapConstantTypeToSsaType(constant);
+      result = new HConstant.internal(constant, type);
+      entry.addAtExit(result);
+      constants[constant] = result;
+    } else if (result.block == null) {
+      // The constant was not used anymore.
+      entry.addAtExit(result);
+    }
+    return result;
+  }
+
+  HConstant addConstantInt(int i, ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createInt(i));
+  }
+
+  HConstant addConstantDouble(double d, ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createDouble(d));
+  }
+
+  HConstant addConstantString(DartString str,
+                              Node diagnosticNode,
+                              ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createString(str, diagnosticNode));
+  }
+
+  HConstant addConstantBool(bool value, ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createBool(value));
+  }
+
+  HConstant addConstantNull(ConstantSystem constantSystem) {
+    return addConstant(constantSystem.createNull());
+  }
+
+  void finalize() {
+    addBlock(exit);
+    exit.open();
+    exit.close(new HExit());
+    assignDominators();
+  }
+
+  void assignDominators() {
+    // Run through the blocks in order of increasing ids so we are
+    // guaranteed that we have computed dominators for all blocks
+    // higher up in the dominator tree.
+    for (int i = 0, length = blocks.length; i < length; i++) {
+      HBasicBlock block = blocks[i];
+      List<HBasicBlock> predecessors = block.predecessors;
+      if (block.isLoopHeader()) {
+        block.assignCommonDominator(predecessors[0]);
+      } else {
+        for (int j = predecessors.length - 1; j >= 0; j--) {
+          block.assignCommonDominator(predecessors[j]);
+        }
+      }
+    }
+  }
+
+  bool isValid() {
+    HValidator validator = new HValidator();
+    validator.visitGraph(this);
+    return validator.isValid;
+  }
+}
+
+class HBaseVisitor extends HGraphVisitor implements HVisitor {
+  HBasicBlock currentBlock;
+
+  visitBasicBlock(HBasicBlock node) {
+    currentBlock = node;
+
+    HInstruction instruction = node.first;
+    while (instruction != null) {
+      instruction.accept(this);
+      instruction = instruction.next;
+    }
+  }
+
+  visitInstruction(HInstruction instruction) {}
+
+  visitBinaryArithmetic(HBinaryArithmetic node) => visitInvokeBinary(node);
+  visitBinaryBitOp(HBinaryBitOp node) => visitBinaryArithmetic(node);
+  visitInvoke(HInvoke node) => visitInstruction(node);
+  visitInvokeBinary(HInvokeBinary node) => visitInstruction(node);
+  visitInvokeDynamic(HInvokeDynamic node) => visitInvoke(node);
+  visitInvokeDynamicField(HInvokeDynamicField node) => visitInvokeDynamic(node);
+  visitInvokeUnary(HInvokeUnary node) => visitInstruction(node);
+  visitConditionalBranch(HConditionalBranch node) => visitControlFlow(node);
+  visitControlFlow(HControlFlow node) => visitInstruction(node);
+  visitFieldAccess(HFieldAccess node) => visitInstruction(node);
+  visitRelational(HRelational node) => visitInvokeBinary(node);
+
+  visitAdd(HAdd node) => visitBinaryArithmetic(node);
+  visitBailoutTarget(HBailoutTarget node) => visitInstruction(node);
+  visitBitAnd(HBitAnd node) => visitBinaryBitOp(node);
+  visitBitNot(HBitNot node) => visitInvokeUnary(node);
+  visitBitOr(HBitOr node) => visitBinaryBitOp(node);
+  visitBitXor(HBitXor node) => visitBinaryBitOp(node);
+  visitBoolify(HBoolify node) => visitInstruction(node);
+  visitBoundsCheck(HBoundsCheck node) => visitCheck(node);
+  visitBreak(HBreak node) => visitJump(node);
+  visitContinue(HContinue node) => visitJump(node);
+  visitCheck(HCheck node) => visitInstruction(node);
+  visitConstant(HConstant node) => visitInstruction(node);
+  visitDivide(HDivide node) => visitBinaryArithmetic(node);
+  visitExit(HExit node) => visitControlFlow(node);
+  visitExitTry(HExitTry node) => visitControlFlow(node);
+  visitFieldGet(HFieldGet node) => visitFieldAccess(node);
+  visitFieldSet(HFieldSet node) => visitFieldAccess(node);
+  visitForeign(HForeign node) => visitInstruction(node);
+  visitForeignNew(HForeignNew node) => visitForeign(node);
+  visitGoto(HGoto node) => visitControlFlow(node);
+  visitGreater(HGreater node) => visitRelational(node);
+  visitGreaterEqual(HGreaterEqual node) => visitRelational(node);
+  visitIdentity(HIdentity node) => visitRelational(node);
+  visitIf(HIf node) => visitConditionalBranch(node);
+  visitIndex(HIndex node) => visitInstruction(node);
+  visitIndexAssign(HIndexAssign node) => visitInstruction(node);
+  visitIntegerCheck(HIntegerCheck node) => visitCheck(node);
+  visitInterceptor(HInterceptor node) => visitInstruction(node);
+  visitInvokeClosure(HInvokeClosure node)
+      => visitInvokeDynamic(node);
+  visitInvokeDynamicMethod(HInvokeDynamicMethod node)
+      => visitInvokeDynamic(node);
+  visitInvokeDynamicGetter(HInvokeDynamicGetter node)
+      => visitInvokeDynamicField(node);
+  visitInvokeDynamicSetter(HInvokeDynamicSetter node)
+      => visitInvokeDynamicField(node);
+  visitInvokeStatic(HInvokeStatic node) => visitInvoke(node);
+  visitInvokeSuper(HInvokeSuper node) => visitInvoke(node);
+  visitJump(HJump node) => visitControlFlow(node);
+  visitLazyStatic(HLazyStatic node) => visitInstruction(node);
+  visitLess(HLess node) => visitRelational(node);
+  visitLessEqual(HLessEqual node) => visitRelational(node);
+  visitLiteralList(HLiteralList node) => visitInstruction(node);
+  visitLocalGet(HLocalGet node) => visitFieldAccess(node);
+  visitLocalSet(HLocalSet node) => visitFieldAccess(node);
+  visitLocalValue(HLocalValue node) => visitInstruction(node);
+  visitLoopBranch(HLoopBranch node) => visitConditionalBranch(node);
+  visitNegate(HNegate node) => visitInvokeUnary(node);
+  visitNot(HNot node) => visitInstruction(node);
+  visitOneShotInterceptor(HOneShotInterceptor node)
+      => visitInvokeDynamic(node);
+  visitPhi(HPhi node) => visitInstruction(node);
+  visitMultiply(HMultiply node) => visitBinaryArithmetic(node);
+  visitParameterValue(HParameterValue node) => visitLocalValue(node);
+  visitRangeConversion(HRangeConversion node) => visitCheck(node);
+  visitReturn(HReturn node) => visitControlFlow(node);
+  visitShiftLeft(HShiftLeft node) => visitBinaryBitOp(node);
+  visitSubtract(HSubtract node) => visitBinaryArithmetic(node);
+  visitSwitch(HSwitch node) => visitControlFlow(node);
+  visitStatic(HStatic node) => visitInstruction(node);
+  visitStaticStore(HStaticStore node) => visitInstruction(node);
+  visitStringConcat(HStringConcat node) => visitInstruction(node);
+  visitThis(HThis node) => visitParameterValue(node);
+  visitThrow(HThrow node) => visitControlFlow(node);
+  visitTry(HTry node) => visitControlFlow(node);
+  visitTypeGuard(HTypeGuard node) => visitCheck(node);
+  visitIs(HIs node) => visitInstruction(node);
+  visitTypeConversion(HTypeConversion node) => visitCheck(node);
+}
+
+class SubGraph {
+  // The first and last block of the sub-graph.
+  final HBasicBlock start;
+  final HBasicBlock end;
+
+  const SubGraph(this.start, this.end);
+
+  bool contains(HBasicBlock block) {
+    assert(start != null);
+    assert(end != null);
+    assert(block != null);
+    return start.id <= block.id && block.id <= end.id;
+  }
+}
+
+class SubExpression extends SubGraph {
+  const SubExpression(HBasicBlock start, HBasicBlock end)
+      : super(start, end);
+
+  /** Find the condition expression if this sub-expression is a condition. */
+  HInstruction get conditionExpression {
+    HInstruction last = end.last;
+    if (last is HConditionalBranch || last is HSwitch) return last.inputs[0];
+    return null;
+  }
+}
+
+class HInstructionList {
+  HInstruction first = null;
+  HInstruction last = null;
+
+  bool get isEmpty {
+    return first == null;
+  }
+
+  void internalAddAfter(HInstruction cursor, HInstruction instruction) {
+    if (cursor == null) {
+      assert(isEmpty);
+      first = last = instruction;
+    } else if (identical(cursor, last)) {
+      last.next = instruction;
+      instruction.previous = last;
+      last = instruction;
+    } else {
+      instruction.previous = cursor;
+      instruction.next = cursor.next;
+      cursor.next.previous = instruction;
+      cursor.next = instruction;
+    }
+  }
+
+  void internalAddBefore(HInstruction cursor, HInstruction instruction) {
+    if (cursor == null) {
+      assert(isEmpty);
+      first = last = instruction;
+    } else if (identical(cursor, first)) {
+      first.previous = instruction;
+      instruction.next = first;
+      first = instruction;
+    } else {
+      instruction.next = cursor;
+      instruction.previous = cursor.previous;
+      cursor.previous.next = instruction;
+      cursor.previous = instruction;
+    }
+  }
+
+  void detach(HInstruction instruction) {
+    assert(contains(instruction));
+    assert(instruction.isInBasicBlock());
+    if (instruction.previous == null) {
+      first = instruction.next;
+    } else {
+      instruction.previous.next = instruction.next;
+    }
+    if (instruction.next == null) {
+      last = instruction.previous;
+    } else {
+      instruction.next.previous = instruction.previous;
+    }
+    instruction.previous = null;
+    instruction.next = null;
+  }
+
+  void remove(HInstruction instruction) {
+    assert(instruction.usedBy.isEmpty);
+    detach(instruction);
+  }
+
+  /** Linear search for [instruction]. */
+  bool contains(HInstruction instruction) {
+    HInstruction cursor = first;
+    while (cursor != null) {
+      if (identical(cursor, instruction)) return true;
+      cursor = cursor.next;
+    }
+    return false;
+  }
+}
+
+class HBasicBlock extends HInstructionList {
+  // The [id] must be such that any successor's id is greater than
+  // this [id]. The exception are back-edges.
+  int id;
+
+  static const int STATUS_NEW = 0;
+  static const int STATUS_OPEN = 1;
+  static const int STATUS_CLOSED = 2;
+  int status = STATUS_NEW;
+
+  HInstructionList phis;
+
+  HLoopInformation loopInformation = null;
+  HBlockFlow blockFlow = null;
+  HBasicBlock parentLoopHeader = null;
+  List<HBailoutTarget> bailoutTargets;
+
+  final List<HBasicBlock> predecessors;
+  List<HBasicBlock> successors;
+
+  HBasicBlock dominator = null;
+  final List<HBasicBlock> dominatedBlocks;
+
+  HBasicBlock() : this.withId(null);
+  HBasicBlock.withId(this.id)
+      : phis = new HInstructionList(),
+        predecessors = <HBasicBlock>[],
+        successors = const <HBasicBlock>[],
+        dominatedBlocks = <HBasicBlock>[],
+        bailoutTargets = <HBailoutTarget>[];
+
+  int get hashCode => id;
+
+  bool isNew() => status == STATUS_NEW;
+  bool isOpen() => status == STATUS_OPEN;
+  bool isClosed() => status == STATUS_CLOSED;
+
+  bool isLoopHeader() {
+    return loopInformation != null;
+  }
+
+  void setBlockFlow(HBlockInformation blockInfo, HBasicBlock continuation) {
+    blockFlow = new HBlockFlow(blockInfo, continuation);
+  }
+
+  bool isLabeledBlock() =>
+    blockFlow != null &&
+    blockFlow.body is HLabeledBlockInformation;
+
+  HBasicBlock get enclosingLoopHeader {
+    if (isLoopHeader()) return this;
+    return parentLoopHeader;
+  }
+
+  bool hasBailoutTargets() => !bailoutTargets.isEmpty;
+
+  void open() {
+    assert(isNew());
+    status = STATUS_OPEN;
+  }
+
+  void close(HControlFlow end) {
+    assert(isOpen());
+    addAfter(last, end);
+    status = STATUS_CLOSED;
+  }
+
+  void addAtEntry(HInstruction instruction) {
+    assert(instruction is !HPhi);
+    internalAddBefore(first, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void addAtExit(HInstruction instruction) {
+    assert(isClosed());
+    assert(last is HControlFlow);
+    assert(instruction is !HPhi);
+    internalAddBefore(last, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void moveAtExit(HInstruction instruction) {
+    assert(instruction is !HPhi);
+    assert(instruction.isInBasicBlock());
+    assert(isClosed());
+    assert(last is HControlFlow);
+    internalAddBefore(last, instruction);
+    instruction.block = this;
+    assert(isValid());
+  }
+
+  void add(HInstruction instruction) {
+    assert(instruction is !HControlFlow);
+    assert(instruction is !HPhi);
+    internalAddAfter(last, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void addPhi(HPhi phi) {
+    phis.internalAddAfter(phis.last, phi);
+    phi.notifyAddedToBlock(this);
+  }
+
+  void removePhi(HPhi phi) {
+    phis.remove(phi);
+    assert(phi.block == this);
+    phi.notifyRemovedFromBlock();
+  }
+
+  void addAfter(HInstruction cursor, HInstruction instruction) {
+    assert(cursor is !HPhi);
+    assert(instruction is !HPhi);
+    assert(isOpen() || isClosed());
+    internalAddAfter(cursor, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void addBefore(HInstruction cursor, HInstruction instruction) {
+    assert(cursor is !HPhi);
+    assert(instruction is !HPhi);
+    assert(isOpen() || isClosed());
+    internalAddBefore(cursor, instruction);
+    instruction.notifyAddedToBlock(this);
+  }
+
+  void remove(HInstruction instruction) {
+    assert(isOpen() || isClosed());
+    assert(instruction is !HPhi);
+    super.remove(instruction);
+    assert(instruction.block == this);
+    instruction.notifyRemovedFromBlock();
+  }
+
+  void addSuccessor(HBasicBlock block) {
+    if (successors.isEmpty) {
+      successors = [block];
+    } else {
+      successors.add(block);
+    }
+    block.predecessors.add(this);
+  }
+
+  void postProcessLoopHeader() {
+    assert(isLoopHeader());
+    // Only the first entry into the loop is from outside the
+    // loop. All other entries must be back edges.
+    for (int i = 1, length = predecessors.length; i < length; i++) {
+      loopInformation.addBackEdge(predecessors[i]);
+    }
+  }
+
+  /**
+   * Rewrites all uses of the [from] instruction to using the [to]
+   * instruction instead.
+   */
+  void rewrite(HInstruction from, HInstruction to) {
+    for (HInstruction use in from.usedBy) {
+      use.rewriteInput(from, to);
+    }
+    to.usedBy.addAll(from.usedBy);
+    from.usedBy.clear();
+  }
+
+  /**
+   * Rewrites all uses of the [from] instruction to using either the
+   * [to] instruction, or a [HCheck] instruction that has better type
+   * information on [to], and that dominates the user.
+   */
+  void rewriteWithBetterUser(HInstruction from, HInstruction to) {
+    Link<HCheck> better = const Link<HCheck>();
+    for (HInstruction user in to.usedBy) {
+      if (user is HCheck && identical((user as HCheck).checkedInput, to)) {
+        better = better.prepend(user);
+      }
+    }
+
+    if (better.isEmpty) return rewrite(from, to);
+
+    L1: for (HInstruction user in from.usedBy) {
+      for (HCheck check in better) {
+        if (check.dominates(user)) {
+          user.rewriteInput(from, check);
+          check.usedBy.add(user);
+          continue L1;
+        }
+      }
+      user.rewriteInput(from, to);
+      to.usedBy.add(user);
+    }
+    from.usedBy.clear();
+  }
+
+  bool isExitBlock() {
+    return identical(first, last) && first is HExit;
+  }
+
+  void addDominatedBlock(HBasicBlock block) {
+    assert(isClosed());
+    assert(id != null && block.id != null);
+    assert(dominatedBlocks.indexOf(block) < 0);
+    // Keep the list of dominated blocks sorted such that if there are two
+    // succeeding blocks in the list, the predecessor is before the successor.
+    // Assume that we add the dominated blocks in the right order.
+    int index = dominatedBlocks.length;
+    while (index > 0 && dominatedBlocks[index - 1].id > block.id) {
+      index--;
+    }
+    if (index == dominatedBlocks.length) {
+      dominatedBlocks.add(block);
+    } else {
+      dominatedBlocks.insertRange(index, 1, block);
+    }
+    assert(block.dominator == null);
+    block.dominator = this;
+  }
+
+  void removeDominatedBlock(HBasicBlock block) {
+    assert(isClosed());
+    assert(id != null && block.id != null);
+    int index = dominatedBlocks.indexOf(block);
+    assert(index >= 0);
+    if (index == dominatedBlocks.length - 1) {
+      dominatedBlocks.removeLast();
+    } else {
+      dominatedBlocks.removeRange(index, 1);
+    }
+    assert(identical(block.dominator, this));
+    block.dominator = null;
+  }
+
+  void assignCommonDominator(HBasicBlock predecessor) {
+    assert(isClosed());
+    if (dominator == null) {
+      // If this basic block doesn't have a dominator yet we use the
+      // given predecessor as the dominator.
+      predecessor.addDominatedBlock(this);
+    } else if (predecessor.dominator != null) {
+      // If the predecessor has a dominator and this basic block has a
+      // dominator, we find a common parent in the dominator tree and
+      // use that as the dominator.
+      HBasicBlock block0 = dominator;
+      HBasicBlock block1 = predecessor;
+      while (!identical(block0, block1)) {
+        if (block0.id > block1.id) {
+          block0 = block0.dominator;
+        } else {
+          block1 = block1.dominator;
+        }
+        assert(block0 != null && block1 != null);
+      }
+      if (!identical(dominator, block0)) {
+        dominator.removeDominatedBlock(this);
+        block0.addDominatedBlock(this);
+      }
+    }
+  }
+
+  void forEachPhi(void f(HPhi phi)) {
+    HPhi current = phis.first;
+    while (current != null) {
+      HInstruction saved = current.next;
+      f(current);
+      current = saved;
+    }
+  }
+
+  void forEachInstruction(void f(HInstruction instruction)) {
+    HInstruction current = first;
+    while (current != null) {
+      HInstruction saved = current.next;
+      f(current);
+      current = saved;
+    }
+  }
+
+  bool isValid() {
+    assert(isClosed());
+    HValidator validator = new HValidator();
+    validator.visitBasicBlock(this);
+    return validator.isValid;
+  }
+
+  // TODO(ngeoffray): Cache the information if this method ends up
+  // being hot.
+  bool dominates(HBasicBlock other) {
+    do {
+      if (identical(this, other)) return true;
+      other = other.dominator;
+    } while (other != null && other.id >= id);
+    return false;
+  }
+}
+
+
+abstract class HInstruction implements Spannable {
+  Element sourceElement;
+  SourceFileLocation sourcePosition;
+
+  final int id;
+  static int idCounter;
+
+  final List<HInstruction> inputs;
+  final List<HInstruction> usedBy;
+
+  HBasicBlock block;
+  HInstruction previous = null;
+  HInstruction next = null;
+  int flags = 0;
+
+  // Changes flags.
+  static const int FLAG_CHANGES_INDEX = 0;
+  static const int FLAG_CHANGES_INSTANCE_PROPERTY = FLAG_CHANGES_INDEX + 1;
+  static const int FLAG_CHANGES_STATIC_PROPERTY
+      = FLAG_CHANGES_INSTANCE_PROPERTY + 1;
+  static const int FLAG_CHANGES_COUNT = FLAG_CHANGES_STATIC_PROPERTY + 1;
+
+  // Depends flags (one for each changes flag).
+  static const int FLAG_DEPENDS_ON_INDEX_STORE = FLAG_CHANGES_COUNT;
+  static const int FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE =
+      FLAG_DEPENDS_ON_INDEX_STORE + 1;
+  static const int FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE =
+      FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE + 1;
+  static const int FLAG_DEPENDS_ON_COUNT =
+      FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE + 1;
+
+  // Other flags.
+  static const int FLAG_USE_GVN = FLAG_DEPENDS_ON_COUNT;
+
+  // Type codes.
+  static const int UNDEFINED_TYPECODE = -1;
+  static const int BOOLIFY_TYPECODE = 0;
+  static const int TYPE_GUARD_TYPECODE = 1;
+  static const int BOUNDS_CHECK_TYPECODE = 2;
+  static const int INTEGER_CHECK_TYPECODE = 3;
+  static const int INTERCEPTOR_TYPECODE = 4;
+  static const int ADD_TYPECODE = 5;
+  static const int DIVIDE_TYPECODE = 6;
+  static const int MULTIPLY_TYPECODE = 7;
+  static const int SUBTRACT_TYPECODE = 8;
+  static const int SHIFT_LEFT_TYPECODE = 9;
+  static const int BIT_OR_TYPECODE = 10;
+  static const int BIT_AND_TYPECODE = 11;
+  static const int BIT_XOR_TYPECODE = 12;
+  static const int NEGATE_TYPECODE = 13;
+  static const int BIT_NOT_TYPECODE = 14;
+  static const int NOT_TYPECODE = 15;
+  static const int IDENTITY_TYPECODE = 16;
+  static const int GREATER_TYPECODE = 17;
+  static const int GREATER_EQUAL_TYPECODE = 18;
+  static const int LESS_TYPECODE = 19;
+  static const int LESS_EQUAL_TYPECODE = 20;
+  static const int STATIC_TYPECODE = 21;
+  static const int STATIC_STORE_TYPECODE = 22;
+  static const int FIELD_GET_TYPECODE = 23;
+  static const int TYPE_CONVERSION_TYPECODE = 24;
+  static const int BAILOUT_TARGET_TYPECODE = 25;
+  static const int INVOKE_STATIC_TYPECODE = 26;
+  static const int INDEX_TYPECODE = 27;
+  static const int IS_TYPECODE = 28;
+  static const int INVOKE_DYNAMIC_TYPECODE = 29;
+
+  HInstruction(this.inputs) : id = idCounter++, usedBy = <HInstruction>[];
+
+  int get hashCode => id;
+
+  bool getFlag(int position) => (flags & (1 << position)) != 0;
+  void setFlag(int position) { flags |= (1 << position); }
+  void clearFlag(int position) { flags &= ~(1 << position); }
+
+  static int computeDependsOnFlags(int flags) => flags << FLAG_CHANGES_COUNT;
+
+  int getChangesFlags() => flags & ((1 << FLAG_CHANGES_COUNT) - 1);
+  int getDependsOnFlags() {
+    return (flags & ((1 << FLAG_DEPENDS_ON_COUNT) - 1)) >> FLAG_CHANGES_COUNT;
+  }
+
+  bool hasSideEffects() => getChangesFlags() != 0;
+  bool dependsOnSomething() => getDependsOnFlags() != 0;
+
+  void setAllSideEffects() { flags |= ((1 << FLAG_CHANGES_COUNT) - 1); }
+  void clearAllSideEffects() { flags &= ~((1 << FLAG_CHANGES_COUNT) - 1); }
+
+  void setDependsOnSomething() {
+    int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT;
+    flags |= (((1 << count) - 1) << FLAG_CHANGES_COUNT);
+  }
+  void clearAllDependencies() {
+    int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT;
+    flags &= ~(((1 << count) - 1) << FLAG_CHANGES_COUNT);
+  }
+
+  bool dependsOnStaticPropertyStore() {
+    return getFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE);
+  }
+  void setDependsOnStaticPropertyStore() {
+    setFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE);
+  }
+  void setChangesStaticProperty() { setFlag(FLAG_CHANGES_STATIC_PROPERTY); }
+
+  bool dependsOnIndexStore() => getFlag(FLAG_DEPENDS_ON_INDEX_STORE);
+  void setDependsOnIndexStore() { setFlag(FLAG_DEPENDS_ON_INDEX_STORE); }
+  void setChangesIndex() { setFlag(FLAG_CHANGES_INDEX); }
+
+  bool dependsOnInstancePropertyStore() {
+    return getFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE);
+  }
+  void setDependsOnInstancePropertyStore() {
+    setFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE);
+  }
+  void setChangesInstanceProperty() { setFlag(FLAG_CHANGES_INSTANCE_PROPERTY); }
+
+  bool useGvn() => getFlag(FLAG_USE_GVN);
+  void setUseGvn() { setFlag(FLAG_USE_GVN); }
+
+  void updateInput(int i, HInstruction insn) {
+    inputs[i] = insn;
+  }
+
+  /**
+   * A pure instruction is an instruction that does not have any side
+   * effect, nor any dependency. They can be moved anywhere in the
+   * graph.
+   */
+  bool isPure() => !hasSideEffects() && !dependsOnSomething() && !canThrow();
+
+  // Can this node throw an exception?
+  bool canThrow() => false;
+
+  // Does this node potentially affect control flow.
+  bool isControlFlow() => false;
+
+  // All isFunctions work on the propagated types.
+  bool isArray(HTypeMap types) => types[this].isArray();
+  bool isReadableArray(HTypeMap types) => types[this].isReadableArray();
+  bool isMutableArray(HTypeMap types) => types[this].isMutableArray();
+  bool isExtendableArray(HTypeMap types) => types[this].isExtendableArray();
+  bool isFixedArray(HTypeMap types) => types[this].isFixedArray();
+  bool isBoolean(HTypeMap types) => types[this].isBoolean();
+  bool isInteger(HTypeMap types) => types[this].isInteger();
+  bool isDouble(HTypeMap types) => types[this].isDouble();
+  bool isNumber(HTypeMap types) => types[this].isNumber();
+  bool isNumberOrNull(HTypeMap types) => types[this].isNumberOrNull();
+  bool isString(HTypeMap types) => types[this].isString();
+  bool isTypeUnknown(HTypeMap types) => types[this].isUnknown();
+  bool isIndexablePrimitive(HTypeMap types)
+      => types[this].isIndexablePrimitive();
+  bool isPrimitive(HTypeMap types) => types[this].isPrimitive();
+  bool canBePrimitive(HTypeMap types) => types[this].canBePrimitive();
+  bool canBeNull(HTypeMap types) => types[this].canBeNull();
+
+  /**
+   * This is the type the instruction is guaranteed to have. It does not
+   * take any propagation into account.
+   */
+  HType guaranteedType = HType.UNKNOWN;
+  bool hasGuaranteedType() => !guaranteedType.isUnknown();
+
+  /**
+   * Some instructions have a good idea of their return type, but cannot
+   * guarantee the type. The computed does not need to be more specialized
+   * than the provided type for [this].
+   *
+   * Examples: the likely type of [:x == y:] is a boolean. In most cases this
+   * cannot be guaranteed, but when merging types we still want to use this
+   * information.
+   *
+   * Similarily the [HAdd] instruction is likely a number. Note that, even if
+   * the incoming type is already set to integer, the likely type might still
+   * just return the number type.
+   */
+  HType computeLikelyType(HTypeMap types, Compiler compiler) => types[this];
+
+  /**
+   * Compute the type of the instruction by propagating the input types through
+   * the instruction.
+   *
+   * By default just copy the guaranteed type.
+   */
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return guaranteedType;
+  }
+
+  /**
+   * Compute the desired type for the the given [input]. Aside from using
+   * other inputs to compute the desired type one should also use
+   * the given [types] which, during the invocation of this method,
+   * represents the desired type of [this].
+   */
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return HType.UNKNOWN;
+  }
+
+  bool isInBasicBlock() => block != null;
+
+  String inputsToString() {
+    void addAsCommaSeparated(StringBuffer buffer, List<HInstruction> list) {
+      for (int i = 0; i < list.length; i++) {
+        if (i != 0) buffer.add(', ');
+        buffer.add("@${list[i].id}");
+      }
+    }
+
+    StringBuffer buffer = new StringBuffer();
+    buffer.add('(');
+    addAsCommaSeparated(buffer, inputs);
+    buffer.add(') - used at [');
+    addAsCommaSeparated(buffer, usedBy);
+    buffer.add(']');
+    return buffer.toString();
+  }
+
+  bool gvnEquals(HInstruction other) {
+    assert(useGvn() && other.useGvn());
+    // Check that the type and the flags match.
+    bool hasSameType = typeEquals(other);
+    assert(hasSameType == (typeCode() == other.typeCode()));
+    if (!hasSameType) return false;
+    if (flags != other.flags) return false;
+    // Check that the inputs match.
+    final int inputsLength = inputs.length;
+    final List<HInstruction> otherInputs = other.inputs;
+    if (inputsLength != otherInputs.length) return false;
+    for (int i = 0; i < inputsLength; i++) {
+      if (!identical(inputs[i], otherInputs[i])) return false;
+    }
+    // Check that the data in the instruction matches.
+    return dataEquals(other);
+  }
+
+  int gvnHashCode() {
+    int result = typeCode();
+    int length = inputs.length;
+    for (int i = 0; i < length; i++) {
+      result = (result * 19) + (inputs[i].id) + (result >> 7);
+    }
+    return result;
+  }
+
+  // These methods should be overwritten by instructions that
+  // participate in global value numbering.
+  int typeCode() => HInstruction.UNDEFINED_TYPECODE;
+  bool typeEquals(HInstruction other) => false;
+  bool dataEquals(HInstruction other) => false;
+
+  accept(HVisitor visitor);
+
+  void notifyAddedToBlock(HBasicBlock targetBlock) {
+    assert(!isInBasicBlock());
+    assert(block == null);
+    // Add [this] to the inputs' uses.
+    for (int i = 0; i < inputs.length; i++) {
+      assert(inputs[i].isInBasicBlock());
+      inputs[i].usedBy.add(this);
+    }
+    block = targetBlock;
+    assert(isValid());
+  }
+
+  void notifyRemovedFromBlock() {
+    assert(isInBasicBlock());
+    assert(usedBy.isEmpty);
+
+    // Remove [this] from the inputs' uses.
+    for (int i = 0; i < inputs.length; i++) {
+      inputs[i].removeUser(this);
+    }
+    this.block = null;
+    assert(isValid());
+  }
+
+  void rewriteInput(HInstruction from, HInstruction to) {
+    for (int i = 0; i < inputs.length; i++) {
+      if (identical(inputs[i], from)) inputs[i] = to;
+    }
+  }
+
+  /** Removes all occurrences of [user] from [usedBy]. */
+  void removeUser(HInstruction user) {
+    List<HInstruction> users = usedBy;
+    int length = users.length;
+    for (int i = 0; i < length; i++) {
+      if (identical(users[i], user)) {
+        users[i] = users[length - 1];
+        length--;
+      }
+    }
+    users.length = length;
+  }
+
+  // Change all uses of [oldInput] by [this] to [newInput]. Also
+  // updates the [usedBy] of [oldInput] and [newInput].
+  void changeUse(HInstruction oldInput, HInstruction newInput) {
+    for (int i = 0; i < inputs.length; i++) {
+      if (identical(inputs[i], oldInput)) {
+        inputs[i] = newInput;
+        newInput.usedBy.add(this);
+      }
+    }
+    List<HInstruction> oldInputUsers = oldInput.usedBy;
+    int i = 0;
+    while (i < oldInputUsers.length) {
+      if (oldInputUsers[i] == this) {
+        oldInputUsers[i] = oldInputUsers[oldInput.usedBy.length - 1];
+        oldInputUsers.length--;
+      } else {
+        i++;
+      }
+    }
+  }
+
+  // Compute the set of users of this instruction that is dominated by
+  // [other]. If [other] is a user of [this], it is included in the
+  // returned set.
+  Set<HInstruction> dominatedUsers(HInstruction other) {
+    // Keep track of all instructions that we have to deal with later
+    // and count the number of them that are in the current block.
+    Set<HInstruction> users = new Set<HInstruction>();
+    int usersInCurrentBlock = 0;
+
+    // Run through all the users and see if they are dominated or
+    // potentially dominated by [other].
+    HBasicBlock otherBlock = other.block;
+    for (int i = 0, length = usedBy.length; i < length; i++) {
+      HInstruction current = usedBy[i];
+      if (otherBlock.dominates(current.block)) {
+        if (identical(current.block, otherBlock)) usersInCurrentBlock++;
+        users.add(current);
+      }
+    }
+
+    // Run through all the phis in the same block as [other] and remove them
+    // from the users set.
+    if (usersInCurrentBlock > 0) {
+      for (HPhi phi = otherBlock.phis.first; phi != null; phi = phi.next) {
+        if (users.contains(phi)) {
+          users.remove(phi);
+          if (--usersInCurrentBlock == 0) break;
+        }
+      }
+    }
+
+    // Run through all the instructions before [other] and remove them
+    // from the users set.
+    if (usersInCurrentBlock > 0) {
+      HInstruction current = otherBlock.first;
+      while (!identical(current, other)) {
+        if (users.contains(current)) {
+          users.remove(current);
+          if (--usersInCurrentBlock == 0) break;
+        }
+        current = current.next;
+      }
+    }
+
+    return users;
+  }
+
+  void moveBefore(HInstruction other) {
+    assert(this is !HControlFlow);
+    assert(this is !HPhi);
+    assert(other is !HPhi);
+    block.detach(this);
+    other.block.internalAddBefore(other, this);
+    block = other.block;
+  }
+
+  bool isConstant() => false;
+  bool isConstantBoolean() => false;
+  bool isConstantNull() => false;
+  bool isConstantNumber() => false;
+  bool isConstantInteger() => false;
+  bool isConstantString() => false;
+  bool isConstantList() => false;
+  bool isConstantMap() => false;
+  bool isConstantFalse() => false;
+  bool isConstantTrue() => false;
+  bool isConstantSentinel() => false;
+
+  bool isValid() {
+    HValidator validator = new HValidator();
+    validator.currentBlock = block;
+    validator.visitInstruction(this);
+    return validator.isValid;
+  }
+
+  /**
+   * The code for computing a bailout environment, and the code
+   * generation must agree on what does not need to be captured,
+   * so should always be generated at use site.
+   */
+  bool isCodeMotionInvariant() => false;
+
+  bool isJsStatement() => false;
+
+  bool dominates(HInstruction other) {
+    // An instruction does not dominates itself.
+    if (this == other) return false;
+    if (block != other.block) return block.dominates(other.block);
+
+    HInstruction current = this.next;
+    while (current != null) {
+      if (current == other) return true;
+      current = current.next;
+    }
+    return false;
+  }
+
+
+  HInstruction convertType(Compiler compiler, DartType type, int kind) {
+    if (type == null) return this;
+    if (identical(type.element, compiler.dynamicClass)) return this;
+    if (identical(type.element, compiler.objectClass)) return this;
+
+    // If the original can't be null, type conversion also can't produce null.
+    bool canBeNull = this.guaranteedType.canBeNull();
+    HType convertedType =
+        new HType.fromBoundedType(type, compiler, canBeNull);
+
+    // No need to convert if we know the instruction has
+    // [convertedType] as a bound.
+    if (this.guaranteedType == convertedType) {
+      return this;
+    }
+
+    return new HTypeConversion(convertedType, this, kind);
+  }
+
+    /**
+   * Return whether the instructions do not belong to a loop or
+   * belong to the same loop.
+   */
+  bool hasSameLoopHeaderAs(HInstruction other) {
+    return block.enclosingLoopHeader == other.block.enclosingLoopHeader;
+  }
+}
+
+class HBoolify extends HInstruction {
+  HBoolify(HInstruction value) : super(<HInstruction>[value]) {
+    assert(!hasSideEffects());
+    setUseGvn();
+  }
+
+  HType get guaranteedType => HType.BOOLEAN;
+
+  accept(HVisitor visitor) => visitor.visitBoolify(this);
+  int typeCode() => HInstruction.BOOLIFY_TYPECODE;
+  bool typeEquals(other) => other is HBoolify;
+  bool dataEquals(HInstruction other) => true;
+}
+
+/**
+ * A [HCheck] instruction is an instruction that might do a dynamic
+ * check at runtime on another instruction. To have proper instruction
+ * dependencies in the graph, instructions that depend on the check
+ * being done reference the [HCheck] instruction instead of the
+ * instruction itself.
+ */
+abstract class HCheck extends HInstruction {
+  HCheck(inputs) : super(inputs) {
+    assert(!hasSideEffects());
+    setUseGvn();
+  }
+  HInstruction get checkedInput => inputs[0];
+  bool isJsStatement() => true;
+  bool canThrow() => true;
+}
+
+class HBailoutTarget extends HInstruction {
+  final int state;
+  bool isEnabled = true;
+  // For each argument we record how many dummy (unused) arguments should
+  // precede it, to make sure it lands in the correctly named parameter in the
+  // bailout function.
+  List<int> padding;
+  HBailoutTarget(this.state) : super(<HInstruction>[]) {
+    assert(!hasSideEffects());
+    setUseGvn();
+  }
+
+  bool isControlFlow() => isEnabled;
+  bool isJsStatement() => isEnabled;
+
+  accept(HVisitor visitor) => visitor.visitBailoutTarget(this);
+  int typeCode() => HInstruction.BAILOUT_TARGET_TYPECODE;
+  bool typeEquals(other) => other is HBailoutTarget;
+  bool dataEquals(HBailoutTarget other) => other.state == state;
+}
+
+class HTypeGuard extends HCheck {
+  final HType guardedType;
+  bool isEnabled = false;
+
+  HTypeGuard(this.guardedType, HInstruction guarded, HInstruction bailoutTarget)
+      : super(<HInstruction>[guarded, bailoutTarget]);
+
+  HInstruction get guarded => inputs[0];
+  HInstruction get checkedInput => guarded;
+  HBailoutTarget get bailoutTarget => inputs[1];
+  int get state => bailoutTarget.state;
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return isEnabled ? guardedType : types[guarded];
+  }
+
+  HType get guaranteedType => isEnabled ? guardedType : HType.UNKNOWN;
+
+  bool isControlFlow() => true;
+  bool isJsStatement() => isEnabled;
+  bool canThrow() => isEnabled;
+
+  accept(HVisitor visitor) => visitor.visitTypeGuard(this);
+  int typeCode() => HInstruction.TYPE_GUARD_TYPECODE;
+  bool typeEquals(other) => other is HTypeGuard;
+  bool dataEquals(HTypeGuard other) => guardedType == other.guardedType;
+}
+
+class HBoundsCheck extends HCheck {
+  static const int ALWAYS_FALSE = 0;
+  static const int FULL_CHECK = 1;
+  static const int ALWAYS_ABOVE_ZERO = 2;
+  static const int ALWAYS_BELOW_LENGTH = 3;
+  static const int ALWAYS_TRUE = 4;
+  /**
+   * Details which tests have been done statically during compilation.
+   * Default is that all checks must be performed dynamically.
+   */
+  int staticChecks = FULL_CHECK;
+
+  HBoundsCheck(length, index) : super(<HInstruction>[length, index]);
+
+  HInstruction get length => inputs[1];
+  HInstruction get index => inputs[0];
+  bool isControlFlow() => true;
+
+  HType get guaranteedType => HType.INTEGER;
+
+  accept(HVisitor visitor) => visitor.visitBoundsCheck(this);
+  int typeCode() => HInstruction.BOUNDS_CHECK_TYPECODE;
+  bool typeEquals(other) => other is HBoundsCheck;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HIntegerCheck extends HCheck {
+  bool alwaysFalse = false;
+
+  HIntegerCheck(value) : super(<HInstruction>[value]);
+
+  HInstruction get value => inputs[0];
+  bool isControlFlow() => true;
+
+  HType get guaranteedType => HType.INTEGER;
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    // If the desired type of the input is already a number, we want
+    // to specialize it to an integer.
+    return input.isNumber(types)
+      ? HType.INTEGER
+      : super.computeDesiredTypeForInput(input, types, compiler);
+  }
+
+  accept(HVisitor visitor) => visitor.visitIntegerCheck(this);
+  int typeCode() => HInstruction.INTEGER_CHECK_TYPECODE;
+  bool typeEquals(other) => other is HIntegerCheck;
+  bool dataEquals(HInstruction other) => true;
+}
+
+abstract class HConditionalBranch extends HControlFlow {
+  HConditionalBranch(inputs) : super(inputs);
+  HInstruction get condition => inputs[0];
+  HBasicBlock get trueBranch => block.successors[0];
+  HBasicBlock get falseBranch => block.successors[1];
+}
+
+abstract class HControlFlow extends HInstruction {
+  HControlFlow(inputs) : super(inputs);
+  bool isControlFlow() => true;
+  bool isJsStatement() => true;
+}
+
+abstract class HInvoke extends HInstruction {
+  /**
+    * The first argument must be the target: either an [HStatic] node, or
+    * the receiver of a method-call. The remaining inputs are the arguments
+    * to the invocation.
+    */
+  HInvoke(List<HInstruction> inputs) : super(inputs) {
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+  static const int ARGUMENTS_OFFSET = 1;
+  bool canThrow() => true;
+}
+
+abstract class HInvokeDynamic extends HInvoke {
+  final InvokeDynamicSpecializer specializer;
+  final Selector selector;
+  Element element;
+
+  HInvokeDynamic(Selector selector,
+                 this.element,
+                 List<HInstruction> inputs,
+                 [bool isIntercepted = false])
+    : super(inputs),
+      this.selector = selector,
+      specializer = isIntercepted
+          ? InvokeDynamicSpecializer.lookupSpecializer(selector)
+          : const InvokeDynamicSpecializer();
+  toString() => 'invoke dynamic: $selector';
+  HInstruction get receiver => inputs[0];
+
+  bool get isInterceptorCall {
+    // We know it's a selector call if it follows the interceptor
+    // calling convention, which adds the actual receiver as a
+    // parameter to the call.
+    return inputs.length - 2 == selector.argumentCount;
+  }
+
+  int typeCode() => HInstruction.INVOKE_DYNAMIC_TYPECODE;
+  bool typeEquals(other) => other is HInvokeDynamic;
+  bool dataEquals(HInvokeDynamic other) {
+    return selector == other.selector
+        && element == other.element;
+  }
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return specializer.computeDesiredTypeForInput(this, input, types, compiler);
+  }
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return specializer.computeTypeFromInputTypes(this, types, compiler);
+  }
+}
+
+class HInvokeClosure extends HInvokeDynamic {
+  HInvokeClosure(Selector selector, List<HInstruction> inputs)
+    : super(selector, null, inputs) {
+    assert(selector.isClosureCall());
+  }
+  accept(HVisitor visitor) => visitor.visitInvokeClosure(this);
+}
+
+class HInvokeDynamicMethod extends HInvokeDynamic {
+  HInvokeDynamicMethod(Selector selector,
+                       List<HInstruction> inputs,
+                       [bool isIntercepted = false])
+    : super(selector, null, inputs, isIntercepted);
+
+  String toString() => 'invoke dynamic method: $selector';
+  accept(HVisitor visitor) => visitor.visitInvokeDynamicMethod(this);
+
+  bool isIndexOperatorOnIndexablePrimitive(HTypeMap types) {
+    return isInterceptorCall
+        && selector.kind == SelectorKind.INDEX
+        && selector.name == const SourceString('[]')
+        && inputs[1].isIndexablePrimitive(types);
+  }
+}
+
+abstract class HInvokeDynamicField extends HInvokeDynamic {
+  final bool isSideEffectFree;
+  HInvokeDynamicField(
+      Selector selector, Element element, List<HInstruction> inputs,
+      this.isSideEffectFree)
+      : super(selector, element, inputs);
+  toString() => 'invoke dynamic field: $selector';
+}
+
+class HInvokeDynamicGetter extends HInvokeDynamicField {
+  HInvokeDynamicGetter(selector, element, receiver, isSideEffectFree)
+    : super(selector, element, [receiver], isSideEffectFree) {
+    clearAllSideEffects();
+    if (isSideEffectFree) {
+      setUseGvn();
+      setDependsOnInstancePropertyStore();
+    } else {
+      setDependsOnSomething();
+      setAllSideEffects();
+    }
+  }
+  toString() => 'invoke dynamic getter: $selector';
+  accept(HVisitor visitor) => visitor.visitInvokeDynamicGetter(this);
+}
+
+class HInvokeDynamicSetter extends HInvokeDynamicField {
+  HInvokeDynamicSetter(selector, element, receiver, value, isSideEffectFree)
+    : super(selector, element, [receiver, value], isSideEffectFree) {
+    clearAllSideEffects();
+    if (isSideEffectFree) {
+      setChangesInstanceProperty();
+    } else {
+      setAllSideEffects();
+      setDependsOnSomething();
+    }
+  }
+  toString() => 'invoke dynamic setter: $selector';
+  accept(HVisitor visitor) => visitor.visitInvokeDynamicSetter(this);
+}
+
+class HInvokeStatic extends HInvoke {
+  /** The first input must be the target. */
+  HInvokeStatic(inputs, HType type) : super(inputs) {
+    guaranteedType = type;
+  }
+
+  toString() => 'invoke static: ${element.name}';
+  accept(HVisitor visitor) => visitor.visitInvokeStatic(this);
+  int typeCode() => HInstruction.INVOKE_STATIC_TYPECODE;
+  Element get element => target.element;
+  HStatic get target => inputs[0];
+}
+
+class HInvokeSuper extends HInvokeStatic {
+  final bool isSetter;
+  HInvokeSuper(inputs, {this.isSetter: false}) : super(inputs, HType.UNKNOWN);
+  toString() => 'invoke super: ${element.name}';
+  accept(HVisitor visitor) => visitor.visitInvokeSuper(this);
+
+  HInstruction get value {
+    assert(isSetter);
+    // Index 0: the element, index 1: 'this'.
+    return inputs[2];
+  }
+}
+
+abstract class HFieldAccess extends HInstruction {
+  final Element element;
+
+  HFieldAccess(Element element, List<HInstruction> inputs)
+      : this.element = element, super(inputs);
+
+  HInstruction get receiver => inputs[0];
+}
+
+class HFieldGet extends HFieldAccess {
+  final bool isAssignable;
+
+  HFieldGet(Element element, HInstruction receiver, {bool isAssignable})
+      : this.isAssignable = (isAssignable != null)
+            ? isAssignable
+            : element.isAssignable(),
+        super(element, <HInstruction>[receiver]) {
+    clearAllSideEffects();
+    setUseGvn();
+    if (this.isAssignable) {
+      setDependsOnInstancePropertyStore();
+    }
+  }
+
+  // TODO(ngeoffray): Only if input can be null.
+  bool canThrow() => true;
+
+  accept(HVisitor visitor) => visitor.visitFieldGet(this);
+
+  int typeCode() => HInstruction.FIELD_GET_TYPECODE;
+  bool typeEquals(other) => other is HFieldGet;
+  bool dataEquals(HFieldGet other) => element == other.element;
+  String toString() => "FieldGet $element";
+}
+
+class HFieldSet extends HFieldAccess {
+  HFieldSet(Element element,
+            HInstruction receiver,
+            HInstruction value)
+      : super(element, <HInstruction>[receiver, value]) {
+    clearAllSideEffects();
+    setChangesInstanceProperty();
+  }
+
+  // TODO(ngeoffray): Only if input can be null.
+  bool canThrow() => true;
+
+  HInstruction get value => inputs[1];
+  accept(HVisitor visitor) => visitor.visitFieldSet(this);
+
+  bool isJsStatement() => true;
+  String toString() => "FieldSet $element";
+}
+
+class HLocalGet extends HFieldAccess {
+  // No need to use GVN for a [HLocalGet], it is just a local
+  // access.
+  HLocalGet(Element element, HLocalValue local)
+      : super(element, <HInstruction>[local]);
+
+  accept(HVisitor visitor) => visitor.visitLocalGet(this);
+
+  HLocalValue get local => inputs[0];
+}
+
+class HLocalSet extends HFieldAccess {
+  HLocalSet(Element element, HLocalValue local, HInstruction value)
+      : super(element, <HInstruction>[local, value]);
+
+  accept(HVisitor visitor) => visitor.visitLocalSet(this);
+
+  HLocalValue get local => inputs[0];
+  HInstruction get value => inputs[1];
+  bool isJsStatement() => true;
+}
+
+class HForeign extends HInstruction {
+  final DartString code;
+  final HType type;
+  final bool isStatement;
+
+  HForeign(this.code,
+           this.type,
+           List<HInstruction> inputs,
+           {this.isStatement: false})
+      : super(inputs) {
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+
+  HForeign.statement(code, List<HInstruction> inputs)
+      : this(code, HType.UNKNOWN, inputs, isStatement: true);
+
+  accept(HVisitor visitor) => visitor.visitForeign(this);
+
+  HType get guaranteedType => type;
+
+  bool isJsStatement() => isStatement;
+  bool canThrow() => true;
+}
+
+class HForeignNew extends HForeign {
+  ClassElement element;
+  HForeignNew(this.element, HType type, List<HInstruction> inputs)
+      : super(const LiteralDartString("new"), type, inputs);
+  accept(HVisitor visitor) => visitor.visitForeignNew(this);
+}
+
+abstract class HInvokeBinary extends HInstruction {
+  HInvokeBinary(HInstruction left, HInstruction right)
+      : super(<HInstruction>[left, right]) {
+    clearAllSideEffects();
+    setUseGvn();
+  }
+
+  HInstruction get left => inputs[0];
+  HInstruction get right => inputs[1];
+
+  BinaryOperation operation(ConstantSystem constantSystem);
+}
+
+abstract class HBinaryArithmetic extends HInvokeBinary {
+  HBinaryArithmetic(HInstruction left, HInstruction right) : super(left, right);
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER;
+    if (left.isDouble(types)) return HType.DOUBLE;
+    return HType.NUMBER;
+  }
+
+  BinaryOperation operation(ConstantSystem constantSystem);
+}
+
+class HAdd extends HBinaryArithmetic {
+  HAdd(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitAdd(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.add;
+  int typeCode() => HInstruction.ADD_TYPECODE;
+  bool typeEquals(other) => other is HAdd;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HDivide extends HBinaryArithmetic {
+  HDivide(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitDivide(this);
+
+  HType get guaranteedType => HType.DOUBLE;
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.divide;
+  int typeCode() => HInstruction.DIVIDE_TYPECODE;
+  bool typeEquals(other) => other is HDivide;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HMultiply extends HBinaryArithmetic {
+  HMultiply(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitMultiply(this);
+
+  BinaryOperation operation(ConstantSystem operations)
+      => operations.multiply;
+  int typeCode() => HInstruction.MULTIPLY_TYPECODE;
+  bool typeEquals(other) => other is HMultiply;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HSubtract extends HBinaryArithmetic {
+  HSubtract(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitSubtract(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.subtract;
+  int typeCode() => HInstruction.SUBTRACT_TYPECODE;
+  bool typeEquals(other) => other is HSubtract;
+  bool dataEquals(HInstruction other) => true;
+}
+
+/**
+ * An [HSwitch] instruction has one input for the incoming
+ * value, and one input per constant that it can switch on.
+ * Its block has one successor per constant, and one for the default.
+ */
+class HSwitch extends HControlFlow {
+  HSwitch(List<HInstruction> inputs) : super(inputs);
+
+  HConstant constant(int index) => inputs[index + 1];
+  HInstruction get expression => inputs[0];
+
+  /**
+   * Provides the target to jump to if none of the constants match
+   * the expression. If the switch had no default case, this is the
+   * following join-block.
+   */
+  HBasicBlock get defaultTarget => block.successors.last;
+
+  accept(HVisitor visitor) => visitor.visitSwitch(this);
+
+  String toString() => "HSwitch cases = $inputs";
+}
+
+// TODO(floitsch): Should HBinaryArithmetic really be the super class of
+// HBinaryBitOp?
+abstract class HBinaryBitOp extends HBinaryArithmetic {
+  HBinaryBitOp(HInstruction left, HInstruction right) : super(left, right);
+  HType get guaranteedType => HType.INTEGER;
+}
+
+class HShiftLeft extends HBinaryBitOp {
+  HShiftLeft(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitShiftLeft(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.shiftLeft;
+  int typeCode() => HInstruction.SHIFT_LEFT_TYPECODE;
+  bool typeEquals(other) => other is HShiftLeft;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitOr extends HBinaryBitOp {
+  HBitOr(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitBitOr(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitOr;
+  int typeCode() => HInstruction.BIT_OR_TYPECODE;
+  bool typeEquals(other) => other is HBitOr;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitAnd extends HBinaryBitOp {
+  HBitAnd(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitBitAnd(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitAnd;
+  int typeCode() => HInstruction.BIT_AND_TYPECODE;
+  bool typeEquals(other) => other is HBitAnd;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitXor extends HBinaryBitOp {
+  HBitXor(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitBitXor(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitXor;
+  int typeCode() => HInstruction.BIT_XOR_TYPECODE;
+  bool typeEquals(other) => other is HBitXor;
+  bool dataEquals(HInstruction other) => true;
+}
+
+abstract class HInvokeUnary extends HInstruction {
+  HInvokeUnary(HInstruction input) : super(<HInstruction>[input]) {
+    clearAllSideEffects();
+    setUseGvn();
+  }
+
+  HInstruction get operand => inputs[0];
+
+  UnaryOperation operation(ConstantSystem constantSystem);
+}
+
+class HNegate extends HInvokeUnary {
+  HNegate(HInstruction input) : super(input);
+  accept(HVisitor visitor) => visitor.visitNegate(this);
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    return types[operand];
+  }
+
+  UnaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.negate;
+  int typeCode() => HInstruction.NEGATE_TYPECODE;
+  bool typeEquals(other) => other is HNegate;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HBitNot extends HInvokeUnary {
+  HBitNot(HInstruction input) : super(input);
+  accept(HVisitor visitor) => visitor.visitBitNot(this);
+  
+  HType get guaranteedType => HType.INTEGER;
+  UnaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.bitNot;
+  int typeCode() => HInstruction.BIT_NOT_TYPECODE;
+  bool typeEquals(other) => other is HBitNot;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HExit extends HControlFlow {
+  HExit() : super(const <HInstruction>[]);
+  toString() => 'exit';
+  accept(HVisitor visitor) => visitor.visitExit(this);
+}
+
+class HGoto extends HControlFlow {
+  HGoto() : super(const <HInstruction>[]);
+  toString() => 'goto';
+  accept(HVisitor visitor) => visitor.visitGoto(this);
+}
+
+abstract class HJump extends HControlFlow {
+  final TargetElement target;
+  final LabelElement label;
+  HJump(this.target) : label = null, super(const <HInstruction>[]);
+  HJump.toLabel(LabelElement label)
+      : label = label, target = label.target, super(const <HInstruction>[]);
+}
+
+class HBreak extends HJump {
+  HBreak(TargetElement target) : super(target);
+  HBreak.toLabel(LabelElement label) : super.toLabel(label);
+  toString() => (label != null) ? 'break ${label.labelName}' : 'break';
+  accept(HVisitor visitor) => visitor.visitBreak(this);
+}
+
+class HContinue extends HJump {
+  HContinue(TargetElement target) : super(target);
+  HContinue.toLabel(LabelElement label) : super.toLabel(label);
+  toString() => (label != null) ? 'continue ${label.labelName}' : 'continue';
+  accept(HVisitor visitor) => visitor.visitContinue(this);
+}
+
+class HTry extends HControlFlow {
+  HLocalValue exception;
+  HBasicBlock catchBlock;
+  HBasicBlock finallyBlock;
+  HTry() : super(const <HInstruction>[]);
+  toString() => 'try';
+  accept(HVisitor visitor) => visitor.visitTry(this);
+  HBasicBlock get joinBlock => this.block.successors.last;
+}
+
+// An [HExitTry] control flow node is used when the body of a try or
+// the body of a catch contains a return, break or continue. To build
+// the control flow graph, we explicitly mark the body that
+// leads to one of this instruction a predecessor of catch and
+// finally.
+class HExitTry extends HControlFlow {
+  HExitTry() : super(const <HInstruction>[]);
+  toString() => 'exit try';
+  accept(HVisitor visitor) => visitor.visitExitTry(this);
+  HBasicBlock get bodyTrySuccessor => block.successors[0];
+}
+
+class HIf extends HConditionalBranch {
+  HBlockFlow blockInformation = null;
+  HIf(HInstruction condition) : super(<HInstruction>[condition]);
+  toString() => 'if';
+  accept(HVisitor visitor) => visitor.visitIf(this);
+
+  HBasicBlock get thenBlock {
+    assert(identical(block.dominatedBlocks[0], block.successors[0]));
+    return block.successors[0];
+  }
+
+  HBasicBlock get elseBlock {
+    assert(identical(block.dominatedBlocks[1], block.successors[1]));
+    return block.successors[1];
+  }
+
+  HBasicBlock get joinBlock => blockInformation.continuation;
+}
+
+class HLoopBranch extends HConditionalBranch {
+  static const int CONDITION_FIRST_LOOP = 0;
+  static const int DO_WHILE_LOOP = 1;
+
+  final int kind;
+  HLoopBranch(HInstruction condition, [this.kind = CONDITION_FIRST_LOOP])
+      : super(<HInstruction>[condition]);
+  toString() => 'loop-branch';
+  accept(HVisitor visitor) => visitor.visitLoopBranch(this);
+
+  bool isDoWhile() {
+    return identical(kind, DO_WHILE_LOOP);
+  }
+
+  HBasicBlock computeLoopHeader() {
+    HBasicBlock result;
+    if (isDoWhile()) {
+      // In case of a do/while, the successor is a block that avoids
+      // a critical edge and branchs to the loop header.
+      result = block.successors[0].successors[0];
+    } else {
+      // For other loops, the loop header might be up the dominator
+      // tree if the loop condition has control flow.
+      result = block;
+      while (!result.isLoopHeader()) result = result.dominator;
+    }
+
+    assert(result.isLoopHeader());
+    return result;
+  }
+}
+
+class HConstant extends HInstruction {
+  final Constant constant;
+  final HType constantType;
+  HConstant.internal(this.constant, HType this.constantType)
+      : super(<HInstruction>[]);
+
+  toString() => 'literal: $constant';
+  accept(HVisitor visitor) => visitor.visitConstant(this);
+
+  HType get guaranteedType => constantType;
+
+  bool isConstant() => true;
+  bool isConstantBoolean() => constant.isBool();
+  bool isConstantNull() => constant.isNull();
+  bool isConstantNumber() => constant.isNum();
+  bool isConstantInteger() => constant.isInt();
+  bool isConstantString() => constant.isString();
+  bool isConstantList() => constant.isList();
+  bool isConstantMap() => constant.isMap();
+  bool isConstantFalse() => constant.isFalse();
+  bool isConstantTrue() => constant.isTrue();
+  bool isConstantSentinel() => constant.isSentinel();
+
+  // Maybe avoid this if the literal is big?
+  bool isCodeMotionInvariant() => true;
+}
+
+class HNot extends HInstruction {
+  HNot(HInstruction value) : super(<HInstruction>[value]) {
+    setUseGvn();
+  }
+
+  HType get guaranteedType => HType.BOOLEAN;
+
+  // 'Not' only works on booleans. That's what we want as input.
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    return HType.BOOLEAN;
+  }
+
+  accept(HVisitor visitor) => visitor.visitNot(this);
+  int typeCode() => HInstruction.NOT_TYPECODE;
+  bool typeEquals(other) => other is HNot;
+  bool dataEquals(HInstruction other) => true;
+}
+
+/**
+  * An [HLocalValue] represents a local. Unlike [HParameterValue]s its
+  * first use must be in an HLocalSet. That is, [HParameterValue]s have a
+  * value from the start, whereas [HLocalValue]s need to be initialized first.
+  */
+class HLocalValue extends HInstruction {
+  HLocalValue(Element element) : super(<HInstruction>[]) {
+    sourceElement = element;
+  }
+
+  toString() => 'local ${sourceElement.name}';
+  accept(HVisitor visitor) => visitor.visitLocalValue(this);
+}
+
+class HParameterValue extends HLocalValue {
+  HParameterValue(Element element) : super(element);
+
+  toString() => 'parameter ${sourceElement.name.slowToString()}';
+  accept(HVisitor visitor) => visitor.visitParameterValue(this);
+}
+
+class HThis extends HParameterValue {
+  HThis(Element element, [HType type = HType.UNKNOWN]) : super(element) {
+    guaranteedType = type;
+  }
+  toString() => 'this';
+  accept(HVisitor visitor) => visitor.visitThis(this);
+  bool isCodeMotionInvariant() => true;
+}
+
+class HPhi extends HInstruction {
+  static const IS_NOT_LOGICAL_OPERATOR = 0;
+  static const IS_AND = 1;
+  static const IS_OR = 2;
+
+  int logicalOperatorType = IS_NOT_LOGICAL_OPERATOR;
+
+  // The order of the [inputs] must correspond to the order of the
+  // predecessor-edges. That is if an input comes from the first predecessor
+  // of the surrounding block, then the input must be the first in the [HPhi].
+  HPhi(Element element, List<HInstruction> inputs) : super(inputs) {
+    sourceElement = element;
+  }
+  HPhi.noInputs(Element element) : this(element, <HInstruction>[]);
+  HPhi.singleInput(Element element, HInstruction input)
+      : this(element, <HInstruction>[input]);
+  HPhi.manyInputs(Element element, List<HInstruction> inputs)
+      : this(element, inputs);
+
+  void addInput(HInstruction input) {
+    assert(isInBasicBlock());
+    inputs.add(input);
+    input.usedBy.add(this);
+  }
+
+  // Compute the (shared) type of the inputs if any. If all inputs
+  // have the same known type return it. If any two inputs have
+  // different known types, we'll return a conflict -- otherwise we'll
+  // simply return an unknown type.
+  HType computeInputsType(bool ignoreUnknowns,
+                          HTypeMap types,
+                          Compiler compiler) {
+    HType candidateType = HType.CONFLICTING;
+    for (int i = 0, length = inputs.length; i < length; i++) {
+      HType inputType = types[inputs[i]];
+      if (ignoreUnknowns && inputType.isUnknown()) continue;
+      // Phis need to combine the incoming types using the union operation.
+      // For example, if one incoming edge has type integer and the other has
+      // type double, then the phi is either an integer or double and thus has
+      // type number.
+      candidateType = candidateType.union(inputType, compiler);
+      if (candidateType.isUnknown()) return HType.UNKNOWN;
+    }
+    return candidateType;
+  }
+
+  HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) {
+    HType inputsType = computeInputsType(false, types, compiler);
+    if (inputsType.isConflicting()) return HType.UNKNOWN;
+    return inputsType;
+  }
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    HType propagatedType = types[this];
+    // Best case scenario for a phi is, when all inputs have the same type. If
+    // there is no desired outgoing type we therefore try to unify the input
+    // types (which is basically the [likelyType]).
+    if (propagatedType.isUnknown()) return computeLikelyType(types, compiler);
+    // When the desired outgoing type is conflicting we don't need to give any
+    // requirements on the inputs.
+    if (propagatedType.isConflicting()) return HType.UNKNOWN;
+    // Otherwise the input type must match the desired outgoing type.
+    return propagatedType;
+  }
+
+  HType computeLikelyType(HTypeMap types, Compiler compiler) {
+    HType agreedType = computeInputsType(true, types, compiler);
+    if (agreedType.isConflicting()) return HType.UNKNOWN;
+    // Don't be too restrictive. If the agreed type is integer or double just
+    // say that the likely type is number. If more is expected the type will be
+    // propagated back.
+    if (agreedType.isNumber()) return HType.NUMBER;
+    return agreedType;
+  }
+
+  bool isLogicalOperator() => logicalOperatorType != IS_NOT_LOGICAL_OPERATOR;
+
+  String logicalOperator() {
+    assert(isLogicalOperator());
+    if (logicalOperatorType == IS_AND) return "&&";
+    assert(logicalOperatorType == IS_OR);
+    return "||";
+  }
+
+  toString() => 'phi';
+  accept(HVisitor visitor) => visitor.visitPhi(this);
+}
+
+abstract class HRelational extends HInvokeBinary {
+  bool usesBoolifiedInterceptor = false;
+  HRelational(HInstruction left, HInstruction right) : super(left, right);
+  HType get guaranteedType => HType.BOOLEAN;
+}
+
+class HIdentity extends HRelational {
+  HIdentity(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitIdentity(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.identity;
+  int typeCode() => HInstruction.IDENTITY_TYPECODE;
+  bool typeEquals(other) => other is HIdentity;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HGreater extends HRelational {
+  HGreater(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitGreater(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.greater;
+  int typeCode() => HInstruction.GREATER_TYPECODE;
+  bool typeEquals(other) => other is HGreater;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HGreaterEqual extends HRelational {
+  HGreaterEqual(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitGreaterEqual(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.greaterEqual;
+  int typeCode() => HInstruction.GREATER_EQUAL_TYPECODE;
+  bool typeEquals(other) => other is HGreaterEqual;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HLess extends HRelational {
+  HLess(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitLess(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.less;
+  int typeCode() => HInstruction.LESS_TYPECODE;
+  bool typeEquals(other) => other is HLess;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HLessEqual extends HRelational {
+  HLessEqual(HInstruction left, HInstruction right) : super(left, right);
+  accept(HVisitor visitor) => visitor.visitLessEqual(this);
+
+  BinaryOperation operation(ConstantSystem constantSystem)
+      => constantSystem.lessEqual;
+  int typeCode() => HInstruction.LESS_EQUAL_TYPECODE;
+  bool typeEquals(other) => other is HLessEqual;
+  bool dataEquals(HInstruction other) => true;
+}
+
+class HReturn extends HControlFlow {
+  HReturn(value) : super(<HInstruction>[value]);
+  toString() => 'return';
+  accept(HVisitor visitor) => visitor.visitReturn(this);
+}
+
+class HThrow extends HControlFlow {
+  final bool isRethrow;
+  HThrow(value, {this.isRethrow: false}) : super(<HInstruction>[value]);
+  toString() => 'throw';
+  accept(HVisitor visitor) => visitor.visitThrow(this);
+}
+
+class HStatic extends HInstruction {
+  final Element element;
+  HStatic(this.element) : super(<HInstruction>[]) {
+    assert(element != null);
+    assert(invariant(this, element.isDeclaration));
+    clearAllSideEffects();
+    if (element.isAssignable()) {
+      setDependsOnStaticPropertyStore();
+    }
+    setUseGvn();
+  }
+  toString() => 'static ${element.name}';
+  accept(HVisitor visitor) => visitor.visitStatic(this);
+
+  int gvnHashCode() => super.gvnHashCode() ^ element.hashCode;
+  int typeCode() => HInstruction.STATIC_TYPECODE;
+  bool typeEquals(other) => other is HStatic;
+  bool dataEquals(HStatic other) => element == other.element;
+  bool isCodeMotionInvariant() => !element.isAssignable();
+}
+
+class HInterceptor extends HInstruction {
+  Set<ClassElement> interceptedClasses;
+  HInterceptor(this.interceptedClasses, HInstruction receiver)
+      : super(<HInstruction>[receiver]) {
+    clearAllSideEffects();
+    setUseGvn();
+  }
+  String toString() => 'interceptor on $interceptedClasses';
+  accept(HVisitor visitor) => visitor.visitInterceptor(this);
+  HInstruction get receiver => inputs[0];
+
+  HType computeDesiredTypeForInput(HInstruction input,
+                                   HTypeMap types,
+                                   Compiler compiler) {
+    if (interceptedClasses.length != 1) return HType.UNKNOWN;
+    // If the only class being intercepted is of type number, we
+    // make this interceptor call say it wants that class as input.
+    Element interceptor = interceptedClasses.toList()[0];
+    JavaScriptBackend backend = compiler.backend;
+    if (interceptor == backend.jsNumberClass) {
+      return HType.NUMBER;
+    } else if (interceptor == backend.jsIntClass) {
+      return HType.INTEGER;
+    } else if (interceptor == backend.jsDoubleClass) {
+      return HType.DOUBLE;
+    }
+    return HType.UNKNOWN;
+  }
+
+  int typeCode() => HInstruction.INTERCEPTOR_TYPECODE;
+  bool typeEquals(other) => other is HInterceptor;
+  bool dataEquals(HInterceptor other) {
+    return interceptedClasses == other.interceptedClasses
+        || (interceptedClasses.length == other.interceptedClasses.length
+            && interceptedClasses.containsAll(other.interceptedClasses));
+  }
+}
+
+/**
+ * A "one-shot" interceptor is a call to a synthetized method that
+ * will fetch the interceptor of its first parameter, and make a call
+ * on a given selector with the remaining parameters.
+ *
+ * In order to share the same optimizations with regular interceptor
+ * calls, this class extends [HInvokeDynamic] and also has the null
+ * constant as the first input.
+ */
+class HOneShotInterceptor extends HInvokeDynamic {
+  Set<ClassElement> interceptedClasses;
+  HOneShotInterceptor(Selector selector,
+                      List<HInstruction> inputs,
+                      this.interceptedClasses)
+      : super(selector, null, inputs, true) {
+    assert(inputs[0] is HConstant);
+    assert(inputs[0].guaranteedType == HType.NULL);
+  }
+
+  String toString() => 'one shot interceptor on $selector';
+  accept(HVisitor visitor) => visitor.visitOneShotInterceptor(this);
+}
+
+/** An [HLazyStatic] is a static that is initialized lazily at first read. */
+class HLazyStatic extends HInstruction {
+  final Element element;
+  HLazyStatic(this.element) : super(<HInstruction>[]) {
+    // TODO(4931): The first access has side-effects, but we afterwards we
+    // should be able to GVN.
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+
+  toString() => 'lazy static ${element.name}';
+  accept(HVisitor visitor) => visitor.visitLazyStatic(this);
+
+  int typeCode() => 30;
+  // TODO(4931): can we do better here?
+  bool isCodeMotionInvariant() => false;
+  bool canThrow() => true;
+}
+
+class HStaticStore extends HInstruction {
+  Element element;
+  HStaticStore(this.element, HInstruction value)
+      : super(<HInstruction>[value]) {
+    clearAllSideEffects();
+    setChangesStaticProperty();
+  }
+  toString() => 'static store ${element.name}';
+  accept(HVisitor visitor) => visitor.visitStaticStore(this);
+
+  int typeCode() => HInstruction.STATIC_STORE_TYPECODE;
+  bool typeEquals(other) => other is HStaticStore;
+  bool dataEquals(HStaticStore other) => element == other.element;
+  bool isJsStatement() => true;
+}
+
+class HLiteralList extends HInstruction {
+  HLiteralList(inputs) : super(inputs);
+  toString() => 'literal list';
+  accept(HVisitor visitor) => visitor.visitLiteralList(this);
+
+  HType get guaranteedType => HType.EXTENDABLE_ARRAY;
+}
+
+/**
+ * The primitive array indexing operation. Note that this instruction
+ * does not throw because we generate the checks explicitly.
+ */
+class HIndex extends HInstruction {
+  HIndex(HInstruction receiver, HInstruction index)
+      : super(<HInstruction>[receiver, index]) {
+    clearAllSideEffects();
+    setDependsOnIndexStore();
+    setUseGvn();
+  }
+
+  String toString() => 'index operator';
+  accept(HVisitor visitor) => visitor.visitIndex(this);
+
+  HInstruction get receiver => inputs[0];
+  HInstruction get index => inputs[1];
+
+  int typeCode() => HInstruction.INDEX_TYPECODE;
+  bool typeEquals(HInstruction other) => other is HIndex;
+  bool dataEquals(HIndex other) => true;
+}
+
+/**
+ * The primitive array assignment operation. Note that this instruction
+ * does not throw because we generate the checks explicitly.
+ */
+class HIndexAssign extends HInstruction {
+  HIndexAssign(HInstruction receiver,
+               HInstruction index,
+               HInstruction value)
+      : super(<HInstruction>[receiver, index, value]) {
+    clearAllSideEffects();
+    setChangesIndex();
+  }
+  String toString() => 'index assign operator';
+  accept(HVisitor visitor) => visitor.visitIndexAssign(this);
+
+  HInstruction get receiver => inputs[0];
+  HInstruction get index => inputs[1];
+  HInstruction get value => inputs[2];
+}
+
+class HIs extends HInstruction {
+  final DartType typeExpression;
+  final bool nullOk;
+
+  HIs(this.typeExpression, List<HInstruction> inputs, {this.nullOk: false})
+     : super(inputs) {
+    setUseGvn();
+  }
+
+  HInstruction get expression => inputs[0];
+  HInstruction getCheck(int index) => inputs[index + 1];
+  int get checkCount => inputs.length - 1;
+
+  bool hasArgumentChecks() => inputs.length > 1;
+
+  HType get guaranteedType => HType.BOOLEAN;
+
+  accept(HVisitor visitor) => visitor.visitIs(this);
+
+  toString() => "$expression is $typeExpression";
+
+  int typeCode() => HInstruction.IS_TYPECODE;
+  bool typeEquals(HInstruction other) => other is HIs;
+  bool dataEquals(HIs other) {
+    return typeExpression == other.typeExpression
+        && nullOk == other.nullOk;
+  }
+}
+
+class HTypeConversion extends HCheck {
+  HType type;
+  final int kind;
+
+  static const int NO_CHECK = 0;
+  static const int CHECKED_MODE_CHECK = 1;
+  static const int ARGUMENT_TYPE_CHECK = 2;
+  static const int CAST_TYPE_CHECK = 3;
+  static const int BOOLEAN_CONVERSION_CHECK = 4;
+
+  HTypeConversion(this.type, HInstruction input, [this.kind = NO_CHECK])
+      : super(<HInstruction>[input]) {
+    sourceElement = input.sourceElement;
+  }
+  HTypeConversion.checkedModeCheck(HType type, HInstruction input)
+      : this(type, input, CHECKED_MODE_CHECK);
+  HTypeConversion.argumentTypeCheck(HType type, HInstruction input)
+      : this(type, input, ARGUMENT_TYPE_CHECK);
+  HTypeConversion.castCheck(HType type, HInstruction input)
+      : this(type, input, CAST_TYPE_CHECK);
+
+
+  bool get isChecked => kind != NO_CHECK;
+  bool get isCheckedModeCheck {
+    return kind == CHECKED_MODE_CHECK || kind == BOOLEAN_CONVERSION_CHECK;
+  }
+  bool get isArgumentTypeCheck => kind == ARGUMENT_TYPE_CHECK;
+  bool get isCastTypeCheck => kind == CAST_TYPE_CHECK;
+  bool get isBooleanConversionCheck => kind == BOOLEAN_CONVERSION_CHECK;
+
+  HType get guaranteedType => type;
+
+  accept(HVisitor visitor) => visitor.visitTypeConversion(this);
+
+  bool isJsStatement() => kind == ARGUMENT_TYPE_CHECK;
+  bool isControlFlow() => kind == ARGUMENT_TYPE_CHECK;
+  bool canThrow() => isChecked;
+
+  int typeCode() => HInstruction.TYPE_CONVERSION_TYPECODE;
+  bool typeEquals(HInstruction other) => other is HTypeConversion;
+  bool dataEquals(HTypeConversion other) {
+    return type == other.type && kind == other.kind;
+  }
+}
+
+class HRangeConversion extends HCheck {
+  HRangeConversion(HInstruction input) : super(<HInstruction>[input]) {
+    sourceElement = input.sourceElement;
+  }
+  accept(HVisitor visitor) => visitor.visitRangeConversion(this);
+
+  // We currently only do range analysis for integers.
+  HType get guaranteedType => HType.INTEGER;
+}
+
+class HStringConcat extends HInstruction {
+  final Node node;
+  HStringConcat(HInstruction left, HInstruction right, this.node)
+      : super(<HInstruction>[left, right]) {
+    setAllSideEffects();
+    setDependsOnSomething();
+  }
+  HType get guaranteedType => HType.STRING;
+
+  HInstruction get left => inputs[0];
+  HInstruction get right => inputs[1];
+
+  accept(HVisitor visitor) => visitor.visitStringConcat(this);
+  toString() => "string concat";
+}
+
+/** Non-block-based (aka. traditional) loop information. */
+class HLoopInformation {
+  final HBasicBlock header;
+  final List<HBasicBlock> blocks;
+  final List<HBasicBlock> backEdges;
+  final List<LabelElement> labels;
+  final TargetElement target;
+
+  /** Corresponding block information for the loop. */
+  HLoopBlockInformation loopBlockInformation;
+
+  HLoopInformation(this.header, this.target, this.labels)
+      : blocks = new List<HBasicBlock>(),
+        backEdges = new List<HBasicBlock>();
+
+  void addBackEdge(HBasicBlock predecessor) {
+    backEdges.add(predecessor);
+    addBlock(predecessor);
+  }
+
+  // Adds a block and transitively all its predecessors in the loop as
+  // loop blocks.
+  void addBlock(HBasicBlock block) {
+    if (identical(block, header)) return;
+    HBasicBlock parentHeader = block.parentLoopHeader;
+    if (identical(parentHeader, header)) {
+      // Nothing to do in this case.
+    } else if (parentHeader != null) {
+      addBlock(parentHeader);
+    } else {
+      block.parentLoopHeader = header;
+      blocks.add(block);
+      for (int i = 0, length = block.predecessors.length; i < length; i++) {
+        addBlock(block.predecessors[i]);
+      }
+    }
+  }
+
+  HBasicBlock getLastBackEdge() {
+    int maxId = -1;
+    HBasicBlock result = null;
+    for (int i = 0, length = backEdges.length; i < length; i++) {
+      HBasicBlock current = backEdges[i];
+      if (current.id > maxId) {
+        maxId = current.id;
+        result = current;
+      }
+    }
+    return result;
+  }
+}
+
+
+/**
+ * Embedding of a [HBlockInformation] for block-structure based traversal
+ * in a dominator based flow traversal by attaching it to a basic block.
+ * To go back to dominator-based traversal, a [HSubGraphBlockInformation]
+ * structure can be added in the block structure.
+ */
+class HBlockFlow {
+  final HBlockInformation body;
+  final HBasicBlock continuation;
+  HBlockFlow(this.body, this.continuation);
+}
+
+
+/**
+ * Information about a syntactic-like structure.
+ */
+abstract class HBlockInformation {
+  HBasicBlock get start;
+  HBasicBlock get end;
+  bool accept(HBlockInformationVisitor visitor);
+}
+
+
+/**
+ * Information about a statement-like structure.
+ */
+abstract class HStatementInformation extends HBlockInformation {
+  bool accept(HStatementInformationVisitor visitor);
+}
+
+
+/**
+ * Information about an expression-like structure.
+ */
+abstract class HExpressionInformation extends HBlockInformation {
+  bool accept(HExpressionInformationVisitor visitor);
+  HInstruction get conditionExpression;
+}
+
+
+abstract class HStatementInformationVisitor {
+  bool visitLabeledBlockInfo(HLabeledBlockInformation info);
+  bool visitLoopInfo(HLoopBlockInformation info);
+  bool visitIfInfo(HIfBlockInformation info);
+  bool visitTryInfo(HTryBlockInformation info);
+  bool visitSwitchInfo(HSwitchBlockInformation info);
+  bool visitSequenceInfo(HStatementSequenceInformation info);
+  // Pseudo-structure embedding a dominator-based traversal into
+  // the block-structure traversal. This will eventually go away.
+  bool visitSubGraphInfo(HSubGraphBlockInformation info);
+}
+
+
+abstract class HExpressionInformationVisitor {
+  bool visitAndOrInfo(HAndOrBlockInformation info);
+  bool visitSubExpressionInfo(HSubExpressionBlockInformation info);
+}
+
+
+abstract class HBlockInformationVisitor
+    implements HStatementInformationVisitor, HExpressionInformationVisitor {
+}
+
+
+/**
+ * Generic class wrapping a [SubGraph] as a block-information until
+ * all structures are handled properly.
+ */
+class HSubGraphBlockInformation implements HStatementInformation {
+  final SubGraph subGraph;
+  HSubGraphBlockInformation(this.subGraph);
+
+  HBasicBlock get start => subGraph.start;
+  HBasicBlock get end => subGraph.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitSubGraphInfo(this);
+}
+
+
+/**
+ * Generic class wrapping a [SubExpression] as a block-information until
+ * expressions structures are handled properly.
+ */
+class HSubExpressionBlockInformation implements HExpressionInformation {
+  final SubExpression subExpression;
+  HSubExpressionBlockInformation(this.subExpression);
+
+  HBasicBlock get start => subExpression.start;
+  HBasicBlock get end => subExpression.end;
+
+  HInstruction get conditionExpression => subExpression.conditionExpression;
+
+  bool accept(HExpressionInformationVisitor visitor) =>
+    visitor.visitSubExpressionInfo(this);
+}
+
+
+/** A sequence of separate statements. */
+class HStatementSequenceInformation implements HStatementInformation {
+  final List<HStatementInformation> statements;
+  HStatementSequenceInformation(this.statements);
+
+  HBasicBlock get start => statements[0].start;
+  HBasicBlock get end => statements.last.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitSequenceInfo(this);
+}
+
+
+class HLabeledBlockInformation implements HStatementInformation {
+  final HStatementInformation body;
+  final List<LabelElement> labels;
+  final TargetElement target;
+  final bool isContinue;
+
+  HLabeledBlockInformation(this.body,
+                           List<LabelElement> labels,
+                           {this.isContinue: false}) :
+      this.labels = labels, this.target = labels[0].target;
+
+  HLabeledBlockInformation.implicit(this.body,
+                                    this.target,
+                                    {this.isContinue: false})
+      : this.labels = const<LabelElement>[];
+
+  HBasicBlock get start => body.start;
+  HBasicBlock get end => body.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitLabeledBlockInfo(this);
+}
+
+class LoopTypeVisitor extends Visitor {
+  const LoopTypeVisitor();
+  int visitNode(Node node) => HLoopBlockInformation.NOT_A_LOOP;
+  int visitWhile(While node) => HLoopBlockInformation.WHILE_LOOP;
+  int visitFor(For node) => HLoopBlockInformation.FOR_LOOP;
+  int visitDoWhile(DoWhile node) => HLoopBlockInformation.DO_WHILE_LOOP;
+  int visitForIn(ForIn node) => HLoopBlockInformation.FOR_IN_LOOP;
+}
+
+class HLoopBlockInformation implements HStatementInformation {
+  static const int WHILE_LOOP = 0;
+  static const int FOR_LOOP = 1;
+  static const int DO_WHILE_LOOP = 2;
+  static const int FOR_IN_LOOP = 3;
+  static const int NOT_A_LOOP = -1;
+
+  final int kind;
+  final HExpressionInformation initializer;
+  final HExpressionInformation condition;
+  final HStatementInformation body;
+  final HExpressionInformation updates;
+  final TargetElement target;
+  final List<LabelElement> labels;
+  final SourceFileLocation sourcePosition;
+  final SourceFileLocation endSourcePosition;
+
+  HLoopBlockInformation(this.kind,
+                        this.initializer,
+                        this.condition,
+                        this.body,
+                        this.updates,
+                        this.target,
+                        this.labels,
+                        this.sourcePosition,
+                        this.endSourcePosition) {
+    assert(
+        (kind == DO_WHILE_LOOP ? body.start : condition.start).isLoopHeader());
+  }
+
+  HBasicBlock get start {
+    if (initializer != null) return initializer.start;
+    if (kind == DO_WHILE_LOOP) {
+      return body.start;
+    }
+    return condition.start;
+  }
+
+  HBasicBlock get loopHeader {
+    return kind == DO_WHILE_LOOP ? body.start : condition.start;
+  }
+
+  HBasicBlock get end {
+    if (updates != null) return updates.end;
+    if (kind == DO_WHILE_LOOP && condition != null) {
+      return condition.end;
+    }
+    return body.end;
+  }
+
+  static int loopType(Node node) {
+    return node.accept(const LoopTypeVisitor());
+  }
+
+  bool isDoWhile() => kind == DO_WHILE_LOOP;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitLoopInfo(this);
+}
+
+class HIfBlockInformation implements HStatementInformation {
+  final HExpressionInformation condition;
+  final HStatementInformation thenGraph;
+  final HStatementInformation elseGraph;
+  HIfBlockInformation(this.condition,
+                      this.thenGraph,
+                      this.elseGraph);
+
+  HBasicBlock get start => condition.start;
+  HBasicBlock get end => elseGraph == null ? thenGraph.end : elseGraph.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitIfInfo(this);
+}
+
+class HAndOrBlockInformation implements HExpressionInformation {
+  final bool isAnd;
+  final HExpressionInformation left;
+  final HExpressionInformation right;
+  HAndOrBlockInformation(this.isAnd,
+                         this.left,
+                         this.right);
+
+  HBasicBlock get start => left.start;
+  HBasicBlock get end => right.end;
+
+  // We don't currently use HAndOrBlockInformation.
+  HInstruction get conditionExpression {
+    return null;
+  }
+  bool accept(HExpressionInformationVisitor visitor) =>
+    visitor.visitAndOrInfo(this);
+}
+
+class HTryBlockInformation implements HStatementInformation {
+  final HStatementInformation body;
+  final HLocalValue catchVariable;
+  final HStatementInformation catchBlock;
+  final HStatementInformation finallyBlock;
+  HTryBlockInformation(this.body,
+                       this.catchVariable,
+                       this.catchBlock,
+                       this.finallyBlock);
+
+  HBasicBlock get start => body.start;
+  HBasicBlock get end =>
+      finallyBlock == null ? catchBlock.end : finallyBlock.end;
+
+  bool accept(HStatementInformationVisitor visitor) =>
+    visitor.visitTryInfo(this);
+}
+
+
+
+class HSwitchBlockInformation implements HStatementInformation {
+  final HExpressionInformation expression;
+  final List<List<Constant>> matchExpressions;
+  final List<HStatementInformation> statements;
+  // If the switch has a default, it's the last statement block, which
+  // may or may not have other expresions.
+  final bool hasDefault;
+  final TargetElement target;
+  final List<LabelElement> labels;
+
+  HSwitchBlockInformation(this.expression,
+                          this.matchExpressions,
+                          this.statements,
+                          this.hasDefault,
+                          this.target,
+                          this.labels);
+
+  HBasicBlock get start => expression.start;
+  HBasicBlock get end {
+    // We don't create a switch block if there are no cases.
+    assert(!statements.isEmpty);
+    return statements.last.end;
+  }
+
+  bool accept(HStatementInformationVisitor visitor) =>
+      visitor.visitSwitchInfo(this);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart
new file mode 100644
index 0000000..35ba2cb
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart
@@ -0,0 +1,1545 @@
+// 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.
+
+part of ssa;
+
+abstract class OptimizationPhase {
+  String get name;
+  void visitGraph(HGraph graph);
+}
+
+class SsaOptimizerTask extends CompilerTask {
+  final JavaScriptBackend backend;
+  SsaOptimizerTask(JavaScriptBackend backend)
+    : this.backend = backend,
+      super(backend.compiler);
+  String get name => 'SSA optimizer';
+  Compiler get compiler => backend.compiler;
+
+  void runPhases(HGraph graph, List<OptimizationPhase> phases) {
+    for (OptimizationPhase phase in phases) {
+      runPhase(graph, phase);
+    }
+  }
+
+  void runPhase(HGraph graph, OptimizationPhase phase) {
+    phase.visitGraph(graph);
+    compiler.tracer.traceGraph(phase.name, graph);
+    assert(graph.isValid());
+  }
+
+  void optimize(CodegenWorkItem work, HGraph graph, bool speculative) {
+    ConstantSystem constantSystem = compiler.backend.constantSystem;
+    JavaScriptItemCompilationContext context = work.compilationContext;
+    HTypeMap types = context.types;
+    measure(() {
+      List<OptimizationPhase> phases = <OptimizationPhase>[
+          // Run trivial constant folding first to optimize
+          // some patterns useful for type conversion.
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          new SsaTypeConversionInserter(compiler),
+          new SsaTypePropagator(compiler, types),
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          // The constant folder affects the types of instructions, so
+          // we run the type propagator again. Note that this would
+          // not be necessary if types were directly stored on
+          // instructions.
+          new SsaTypePropagator(compiler, types),
+          new SsaCheckInserter(backend, work, types, context.boundsChecked),
+          new SsaRedundantPhiEliminator(),
+          new SsaDeadPhiEliminator(),
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          new SsaTypePropagator(compiler, types),
+          new SsaReceiverSpecialization(compiler),
+          new SsaGlobalValueNumberer(compiler, types),
+          new SsaCodeMotion(),
+          new SsaValueRangeAnalyzer(constantSystem, types, work),
+          // Previous optimizations may have generated new
+          // opportunities for constant folding.
+          new SsaConstantFolder(constantSystem, backend, work, types),
+          new SsaSimplifyInterceptors(constantSystem),
+          new SsaDeadCodeEliminator(types)];
+      runPhases(graph, phases);
+      if (!speculative) {
+        runPhase(graph, new SsaConstructionFieldTypes(backend, work, types));
+      }
+    });
+  }
+
+  bool trySpeculativeOptimizations(CodegenWorkItem work, HGraph graph) {
+    if (work.element.isField()) {
+      // Lazy initializers may not have bailout methods.
+      return false;
+    }
+    JavaScriptItemCompilationContext context = work.compilationContext;
+    HTypeMap types = context.types;
+    return measure(() {
+      // Run the phases that will generate type guards.
+      List<OptimizationPhase> phases = <OptimizationPhase>[
+          new SsaSpeculativeTypePropagator(compiler, types),
+          new SsaTypeGuardInserter(compiler, work, types),
+          new SsaEnvironmentBuilder(compiler),
+          // Change the propagated types back to what they were before we
+          // speculatively propagated, so that we can generate the bailout
+          // version.
+          // Note that we do this even if there were no guards inserted. If a
+          // guard is not beneficial enough we don't emit one, but there might
+          // still be speculative types on the instructions.
+          new SsaTypePropagator(compiler, types),
+          // Then run the [SsaCheckInserter] because the type propagator also
+          // propagated types non-speculatively. For example, it might have
+          // propagated the type array for a call to the List constructor.
+          new SsaCheckInserter(backend, work, types, context.boundsChecked)];
+      runPhases(graph, phases);
+      return !work.guards.isEmpty;
+    });
+  }
+
+  void prepareForSpeculativeOptimizations(CodegenWorkItem work, HGraph graph) {
+    JavaScriptItemCompilationContext context = work.compilationContext;
+    HTypeMap types = context.types;
+    measure(() {
+      // In order to generate correct code for the bailout version, we did not
+      // propagate types from the instruction to the type guard. We do it
+      // now to be able to optimize further.
+      work.guards.forEach((HTypeGuard guard) {
+        guard.bailoutTarget.isEnabled = false;
+        guard.isEnabled = true;
+      });
+      // We also need to insert range and integer checks for the type
+      // guards. Now that they claim to have a certain type, some
+      // depending instructions might become builtin (like native array
+      // accesses) and need to be checked.
+      // Also run the type propagator, to please the codegen in case
+      // no other optimization is run.
+      runPhases(graph, <OptimizationPhase>[
+          new SsaCheckInserter(backend, work, types, context.boundsChecked),
+          new SsaTypePropagator(compiler, types)]);
+    });
+  }
+}
+
+/**
+ * If both inputs to known operations are available execute the operation at
+ * compile-time.
+ */
+class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase {
+  final String name = "SsaConstantFolder";
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final ConstantSystem constantSystem;
+  final HTypeMap types;
+  HGraph graph;
+  Compiler get compiler => backend.compiler;
+
+  SsaConstantFolder(this.constantSystem, this.backend, this.work, this.types);
+
+  void visitGraph(HGraph visitee) {
+    graph = visitee;
+    visitDominatorTree(visitee);
+  }
+
+  visitBasicBlock(HBasicBlock block) {
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      HInstruction replacement = instruction.accept(this);
+      if (replacement != instruction) {
+        block.rewrite(instruction, replacement);
+
+        // If we can replace [instruction] with [replacement], then
+        // [replacement]'s type can be narrowed.
+        types[replacement] =
+            types[replacement].intersection(types[instruction], compiler);
+
+        // If the replacement instruction does not know its
+        // source element, use the source element of the
+        // instruction.
+        if (replacement.sourceElement == null) {
+          replacement.sourceElement = instruction.sourceElement;
+        }
+        if (replacement.sourcePosition == null) {
+          replacement.sourcePosition = instruction.sourcePosition;
+        }
+        if (!replacement.isInBasicBlock()) {
+          // The constant folding can return an instruction that is already
+          // part of the graph (like an input), so we only add the replacement
+          // if necessary.
+          block.addAfter(instruction, replacement);
+          // Visit the replacement as the next instruction in case it
+          // can also be constant folded away.
+          next = replacement;
+        }
+        block.remove(instruction);
+      }
+      instruction = next;
+    }
+  }
+
+  HInstruction visitInstruction(HInstruction node) {
+    return node;
+  }
+
+  HInstruction visitBoolify(HBoolify node) {
+    List<HInstruction> inputs = node.inputs;
+    assert(inputs.length == 1);
+    HInstruction input = inputs[0];
+    HType type = types[input];
+    if (type.isBoolean()) return input;
+    // All values !== true are boolified to false.
+    if (!type.isBooleanOrNull() && !type.isUnknown()) {
+      return graph.addConstantBool(false, constantSystem);
+    }
+    return node;
+  }
+
+  HInstruction visitNot(HNot node) {
+    List<HInstruction> inputs = node.inputs;
+    assert(inputs.length == 1);
+    HInstruction input = inputs[0];
+    if (input is HConstant) {
+      HConstant constant = input;
+      bool isTrue = constant.constant.isTrue();
+      return graph.addConstantBool(!isTrue, constantSystem);
+    } else if (input is HNot) {
+      return input.inputs[0];
+    }
+    return node;
+  }
+
+  HInstruction visitInvokeUnary(HInvokeUnary node) {
+    HInstruction folded =
+        foldUnary(node.operation(constantSystem), node.operand);
+    return folded != null ? folded : node;
+  }
+
+  HInstruction foldUnary(UnaryOperation operation, HInstruction operand) {
+    if (operand is HConstant) {
+      HConstant receiver = operand;
+      Constant folded = operation.fold(receiver.constant);
+      if (folded != null) return graph.addConstant(folded);
+    }
+    return null;
+  }
+
+  HInstruction optimizeLengthInterceptedGetter(HInvokeDynamic node) {
+    HInstruction actualReceiver = node.inputs[1];
+    if (actualReceiver.isIndexablePrimitive(types)) {
+      if (actualReceiver.isConstantString()) {
+        HConstant constantInput = actualReceiver;
+        StringConstant constant = constantInput.constant;
+        return graph.addConstantInt(constant.length, constantSystem);
+      } else if (actualReceiver.isConstantList()) {
+        HConstant constantInput = actualReceiver;
+        ListConstant constant = constantInput.constant;
+        return graph.addConstantInt(constant.length, constantSystem);
+      }
+      Element element;
+      bool isAssignable;
+      if (actualReceiver.isString(types)) {
+        element = backend.jsStringLength;
+        isAssignable = false;
+      } else {
+        element = backend.jsArrayLength;
+        isAssignable = !actualReceiver.isFixedArray(types);
+      }
+      HFieldGet result = new HFieldGet(
+          element, actualReceiver, isAssignable: isAssignable);
+      result.guaranteedType = HType.INTEGER;
+      types[result] = HType.INTEGER;
+      return result;
+    } else if (actualReceiver.isConstantMap()) {
+      HConstant constantInput = actualReceiver;
+      MapConstant constant = constantInput.constant;
+      return graph.addConstantInt(constant.length, constantSystem);
+    }
+    return node;
+  }
+
+  HInstruction handleInterceptorCall(HInvokeDynamic node) {
+    // We only optimize for intercepted method calls in this method.
+    Selector selector = node.selector;
+
+    // Try constant folding the instruction.
+    Operation operation = node.specializer.operation(constantSystem);
+    if (operation != null) {
+      HInstruction instruction = node.inputs.length == 2
+          ? foldUnary(operation, node.inputs[1])
+          : foldBinary(operation, node.inputs[1], node.inputs[2]);
+      if (instruction != null) return instruction;
+    }
+
+    // Try converting the instruction to a builtin instruction.
+    HInstruction instruction =
+        node.specializer.tryConvertToBuiltin(node, types);
+    if (instruction != null) return instruction;
+
+    // Check if this call does not need to be intercepted.
+    HInstruction input = node.inputs[1];
+    HType type = types[input];
+    var interceptor = node.inputs[0];
+
+    if (interceptor.isConstant() && selector.isCall()) {
+      DartType type = types[interceptor].computeType(compiler);
+      ClassElement cls = type.element;
+      node.element = cls.lookupSelector(selector);
+    }
+
+    if (interceptor is !HThis && !type.canBePrimitive()) {
+      // If the type can be null, and the intercepted method can be in
+      // the object class, keep the interceptor.
+      if (type.canBeNull()) {
+        Set<ClassElement> interceptedClasses;
+        if (interceptor is HInterceptor) {
+          interceptedClasses = interceptor.interceptedClasses;
+        } else if (node is HOneShotInterceptor) {
+          var oneShotInterceptor = node;
+          interceptedClasses = oneShotInterceptor.interceptedClasses;
+        }
+        if (interceptedClasses.contains(compiler.objectClass)) return node;
+      }
+      if (selector.isGetter()) {
+        // Change the call to a regular invoke dynamic call.
+        return new HInvokeDynamicGetter(selector, null, input, false);
+      } else if (selector.isSetter()) {
+        return new HInvokeDynamicSetter(
+            selector, null, input, node.inputs[2], false);
+      } else {
+        // Change the call to a regular invoke dynamic call.
+        return new HInvokeDynamicMethod(
+            selector, node.inputs.getRange(1, node.inputs.length - 1));
+      }
+    }
+
+    if (selector.isCall()) {
+      Element target;
+      if (input.isExtendableArray(types)) {
+        if (selector.applies(backend.jsArrayRemoveLast, compiler)) {
+          target = backend.jsArrayRemoveLast;
+        } else if (selector.applies(backend.jsArrayAdd, compiler)) {
+          // The codegen special cases array calls, but does not
+          // inline argument type checks.
+          if (!compiler.enableTypeAssertions) {
+            target = backend.jsArrayAdd;
+          }
+        }
+      } else if (input.isString(types)) {
+        if (selector.applies(backend.jsStringSplit, compiler)) {
+          if (node.inputs[2].isString(types)) {
+            target = backend.jsStringSplit;
+          }
+        } else if (selector.applies(backend.jsStringConcat, compiler)) {
+          if (node.inputs[2].isString(types)) {
+            target = backend.jsStringConcat;
+          }
+        } else if (selector.applies(backend.jsStringToString, compiler)) {
+          return input;
+        }
+      }
+      if (target != null) {
+        // TODO(ngeoffray): There is a strong dependency between codegen
+        // and this optimization that the dynamic invoke does not need an
+        // interceptor. We currently need to keep a
+        // HInvokeDynamicMethod and not create a HForeign because
+        // HForeign is too opaque for the SssaCheckInserter (that adds a
+        // bounds check on removeLast). Once we start inlining, the
+        // bounds check will become explicit, so we won't need this
+        // optimization.
+        HInvokeDynamicMethod result = new HInvokeDynamicMethod(
+            node.selector, node.inputs.getRange(1, node.inputs.length - 1));
+        result.element = target;
+        return result;
+      }
+    } else if (selector.isGetter()) {
+      if (selector.applies(backend.jsArrayLength, compiler)) {
+        return optimizeLengthInterceptedGetter(node);
+      }
+    }
+    return node;
+  }
+
+  bool isFixedSizeListConstructor(HInvokeStatic node) {
+    Element element = node.target.element;
+    if (backend.fixedLengthListConstructor == null) {
+      backend.fixedLengthListConstructor =
+        compiler.listClass.lookupConstructor(
+            new Selector.callConstructor(const SourceString("fixedLength"),
+                                         compiler.listClass.getLibrary()));
+    }
+    // TODO(ngeoffray): checking if the second input is an integer
+    // should not be necessary but it currently makes it easier for
+    // other optimizations to reason on a fixed length constructor
+    // that we know takes an int.
+    return element == backend.fixedLengthListConstructor
+        && node.inputs[1].isInteger(types);
+  }
+
+  HInstruction visitInvokeStatic(HInvokeStatic node) {
+    if (isFixedSizeListConstructor(node)) {
+      node.guaranteedType = HType.FIXED_ARRAY;
+    }
+    return node;
+  }
+
+  HInstruction visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
+    if (node.isInterceptorCall) return handleInterceptorCall(node);
+    HType receiverType = types[node.receiver];
+    if (receiverType.isExact()) {
+      HBoundedType type = receiverType;
+      Element element = type.lookupMember(node.selector.name);
+      // TODO(ngeoffray): Also fold if it's a getter or variable.
+      if (element != null && element.isFunction()) {
+        if (node.selector.applies(element, compiler)) {
+          FunctionElement method = element;
+          FunctionSignature parameters = method.computeSignature(compiler);
+          if (parameters.optionalParameterCount == 0) {
+            node.element = element;
+          }
+          // TODO(ngeoffray): If the method has optional parameters,
+          // we should pass the default values here.
+        }
+      }
+    }
+    return node;
+  }
+
+  HInstruction visitIntegerCheck(HIntegerCheck node) {
+    HInstruction value = node.value;
+    if (value.isInteger(types)) return value;
+    if (value.isConstant()) {
+      HConstant constantInstruction = value;
+      assert(!constantInstruction.constant.isInt());
+      if (!constantSystem.isInt(constantInstruction.constant)) {
+        // -0.0 is a double but will pass the runtime integer check.
+        node.alwaysFalse = true;
+      }
+    }
+    return node;
+  }
+
+  HInstruction foldBinary(BinaryOperation operation,
+                          HInstruction left,
+                          HInstruction right) {
+    if (left is HConstant && right is HConstant) {
+      HConstant op1 = left;
+      HConstant op2 = right;
+      Constant folded = operation.fold(op1.constant, op2.constant);
+      if (folded != null) return graph.addConstant(folded);
+    }
+    return null;
+  }
+
+  HInstruction visitInvokeBinary(HInvokeBinary node) {
+    HInstruction left = node.left;
+    HInstruction right = node.right;
+    BinaryOperation operation = node.operation(constantSystem);
+    HConstant folded = foldBinary(operation, left, right);
+    if (folded != null) return folded;
+    return node;
+  }
+
+  bool allUsersAreBoolifies(HInstruction instruction) {
+    List<HInstruction> users = instruction.usedBy;
+    int length = users.length;
+    for (int i = 0; i < length; i++) {
+      if (users[i] is! HBoolify) return false;
+    }
+    return true;
+  }
+
+  HInstruction visitRelational(HRelational node) {
+    if (allUsersAreBoolifies(node)) {
+      // TODO(ngeoffray): Call a boolified selector.
+      // This node stays the same, but the Boolify node will go away.
+    }
+    // Note that we still have to call [super] to make sure that we end up
+    // in the remaining optimizations.
+    return super.visitRelational(node);
+  }
+
+  HInstruction handleIdentityCheck(HRelational node) {
+    HInstruction left = node.left;
+    HInstruction right = node.right;
+    HType leftType = types[left];
+    HType rightType = types[right];
+
+    // We don't optimize on numbers to preserve the runtime semantics.
+    if (!(left.isNumberOrNull(types) && right.isNumberOrNull(types)) &&
+        leftType.intersection(rightType, compiler).isConflicting()) {
+      return graph.addConstantBool(false, constantSystem);
+    }
+
+    if (left.isConstantBoolean() && right.isBoolean(types)) {
+      HConstant constant = left;
+      if (constant.constant.isTrue()) {
+        return right;
+      } else {
+        return new HNot(right);
+      }
+    }
+
+    if (right.isConstantBoolean() && left.isBoolean(types)) {
+      HConstant constant = right;
+      if (constant.constant.isTrue()) {
+        return left;
+      } else {
+        return new HNot(left);
+      }
+    }
+
+    return null;
+  }
+
+  HInstruction visitIdentity(HIdentity node) {
+    HInstruction newInstruction = handleIdentityCheck(node);
+    return newInstruction == null ? super.visitIdentity(node) : newInstruction;
+  }
+
+  HInstruction visitTypeGuard(HTypeGuard node) {
+    HInstruction value = node.guarded;
+    // If the intersection of the types is still the incoming type then
+    // the incoming type was a subtype of the guarded type, and no check
+    // is required.
+    HType combinedType = types[value].intersection(node.guardedType, compiler);
+    return (combinedType == types[value]) ? value : node;
+  }
+
+  HInstruction visitIs(HIs node) {
+    DartType type = node.typeExpression;
+    Element element = type.element;
+    if (element.isTypeVariable()) {
+      compiler.unimplemented("visitIs for type variables");
+    } if (element.isTypedef()) {
+      return node;
+    }
+
+    HType expressionType = types[node.expression];
+    if (identical(element, compiler.objectClass)
+        || identical(element, compiler.dynamicClass)) {
+      return graph.addConstantBool(true, constantSystem);
+    } else if (expressionType.isInteger()) {
+      if (identical(element, compiler.intClass)
+          || identical(element, compiler.numClass)
+          || Elements.isNumberOrStringSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else if (identical(element, compiler.doubleClass)) {
+        // We let the JS semantics decide for that check. Currently
+        // the code we emit will always return true.
+        return node;
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    } else if (expressionType.isDouble()) {
+      if (identical(element, compiler.doubleClass)
+          || identical(element, compiler.numClass)
+          || Elements.isNumberOrStringSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else if (identical(element, compiler.intClass)) {
+        // We let the JS semantics decide for that check. Currently
+        // the code we emit will return true for a double that can be
+        // represented as a 31-bit integer and for -0.0.
+        return node;
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    } else if (expressionType.isNumber()) {
+      if (identical(element, compiler.numClass)) {
+        return graph.addConstantBool(true, constantSystem);
+      }
+      // We cannot just return false, because the expression may be of
+      // type int or double.
+    } else if (expressionType.isString()) {
+      if (identical(element, compiler.stringClass)
+               || Elements.isStringOnlySupertype(element, compiler)
+               || Elements.isNumberOrStringSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    } else if (expressionType.isArray()) {
+      if (identical(element, compiler.listClass)
+          || Elements.isListSupertype(element, compiler)) {
+        return graph.addConstantBool(true, constantSystem);
+      } else {
+        return graph.addConstantBool(false, constantSystem);
+      }
+    // TODO(karlklose): remove the hasTypeArguments check.
+    } else if (expressionType.isUseful()
+               && !expressionType.canBeNull()
+               && !RuntimeTypeInformation.hasTypeArguments(type)) {
+      DartType receiverType = expressionType.computeType(compiler);
+      if (receiverType != null) {
+        if (!receiverType.isMalformed &&
+            !type.isMalformed &&
+            compiler.types.isSubtype(receiverType, type)) {
+          return graph.addConstantBool(true, constantSystem);
+        } else if (expressionType.isExact()) {
+          return graph.addConstantBool(false, constantSystem);
+        }
+      }
+    }
+    return node;
+  }
+
+  HInstruction visitTypeConversion(HTypeConversion node) {
+    HInstruction value = node.inputs[0];
+    DartType type = types[node].computeType(compiler);
+    if (identical(type.element, compiler.dynamicClass)
+        || identical(type.element, compiler.objectClass)) {
+      return value;
+    }
+    if (types[value].canBeNull() && node.isBooleanConversionCheck) {
+      return node;
+    }
+    HType combinedType = types[value].intersection(types[node], compiler);
+    return (combinedType == types[value]) ? value : node;
+  }
+
+  Element findConcreteFieldForDynamicAccess(HInstruction receiver,
+                                            Selector selector) {
+    HType receiverType = types[receiver];
+    if (!receiverType.isUseful()) return null;
+    if (receiverType.canBeNull()) return null;
+    DartType type = receiverType.computeType(compiler);
+    if (type == null) return null;
+    return compiler.world.locateSingleField(type, selector);
+  }
+
+  HInstruction visitFieldGet(HFieldGet node) {
+    if (node.element == backend.jsArrayLength) {
+      if (node.receiver is HInvokeStatic) {
+        // Try to recognize the length getter with input
+        // [:new List.fixedLength(int):].
+        HInvokeStatic call = node.receiver;
+        if (isFixedSizeListConstructor(call)) {
+          return call.inputs[1];
+        }
+      }
+    }
+    return node;
+  }
+
+  HInstruction visitInvokeDynamicGetter(HInvokeDynamicGetter node) {
+    if (node.isInterceptorCall) return handleInterceptorCall(node);
+
+    Element field =
+        findConcreteFieldForDynamicAccess(node.receiver, node.selector);
+    if (field == null) return node;
+
+    Modifiers modifiers = field.modifiers;
+    bool isFinalOrConst = modifiers.isFinal() || modifiers.isConst();
+    if (!compiler.resolverWorld.hasInvokedSetter(field, compiler)) {
+      // If no setter is ever used for this field it is only initialized in the
+      // initializer list.
+      isFinalOrConst = true;
+    }
+    HFieldGet result = new HFieldGet(
+        field, node.inputs[0], isAssignable: !isFinalOrConst);
+    HType type = backend.optimisticFieldType(field);
+    if (type != null) {
+      result.guaranteedType = type;
+      backend.registerFieldTypesOptimization(
+          work.element, field, result.guaranteedType);
+    }
+    return result;
+  }
+
+  HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
+    if (node.isInterceptorCall) return handleInterceptorCall(node);
+
+    Element field =
+        findConcreteFieldForDynamicAccess(node.receiver, node.selector);
+    if (field == null || !field.isAssignable()) return node;
+    HInstruction value = node.inputs[1];
+    if (compiler.enableTypeAssertions) {
+      HInstruction other = value.convertType(
+          compiler,
+          field.computeType(compiler),
+          HTypeConversion.CHECKED_MODE_CHECK);
+      if (other != value) {
+        node.block.addBefore(node, other);
+        value = other;
+      }
+    }
+    return new HFieldSet(field, node.inputs[0], value);
+  }
+
+  HInstruction visitStringConcat(HStringConcat node) {
+    DartString folded = const LiteralDartString("");
+    for (int i = 0; i < node.inputs.length; i++) {
+      HInstruction part = node.inputs[i];
+      if (!part.isConstant()) return node;
+      HConstant constant = part;
+      if (!constant.constant.isPrimitive()) return node;
+      PrimitiveConstant primitive = constant.constant;
+      folded = new DartString.concat(folded, primitive.toDartString());
+    }
+    return graph.addConstant(constantSystem.createString(folded, node.node));
+  }
+
+  HInstruction visitInterceptor(HInterceptor node) {
+    if (node.isConstant()) return node;
+    HInstruction constant = tryComputeConstantInterceptor(
+        node.inputs[0], node.interceptedClasses);
+    if (constant == null) return node;
+    return constant;
+  }
+
+  HInstruction tryComputeConstantInterceptor(HInstruction input,
+                                             Set<ClassElement> intercepted) {
+    HType type = types[input];
+    ClassElement constantInterceptor;
+    if (type.isInteger()) {
+      constantInterceptor = backend.jsIntClass;
+    } else if (type.isDouble()) {
+      constantInterceptor = backend.jsDoubleClass;
+    } else if (type.isBoolean()) {
+      constantInterceptor = backend.jsBoolClass;
+    } else if (type.isString()) {
+      constantInterceptor = backend.jsStringClass;
+    } else if (type.isArray()) {
+      constantInterceptor = backend.jsArrayClass;
+    } else if (type.isNull()) {
+      constantInterceptor = backend.jsNullClass;
+    } else if (type.isNumber()) {
+      // If the method being intercepted is not defined in [int] or
+      // [double] we can safely use the number interceptor.
+      if (!intercepted.contains(compiler.intClass)
+          && !intercepted.contains(compiler.doubleClass)) {
+        constantInterceptor = backend.jsNumberClass;
+      }
+    }
+
+    if (constantInterceptor == null) return null;
+    if (constantInterceptor == work.element.getEnclosingClass()) {
+      return graph.thisInstruction;
+    }
+
+    Constant constant = new ConstructedConstant(
+        constantInterceptor.computeType(compiler), <Constant>[]);
+    return graph.addConstant(constant);
+  }
+
+  HInstruction visitOneShotInterceptor(HOneShotInterceptor node) {
+    HInstruction newInstruction = handleInterceptorCall(node);
+    if (newInstruction != node) return newInstruction;
+
+    HInstruction constant = tryComputeConstantInterceptor(
+        node.inputs[1], node.interceptedClasses);
+
+    if (constant == null) return node;
+
+    Selector selector = node.selector;
+    // TODO(ngeoffray): make one shot interceptors know whether
+    // they have side effects.
+    if (selector.isGetter()) {
+      HInstruction res = new HInvokeDynamicGetter(
+          selector, node.element, constant, false);
+      res.inputs.add(node.inputs[1]);
+      return res;
+    } else if (node.selector.isSetter()) {
+      HInstruction res = new HInvokeDynamicSetter(
+          selector, node.element, constant, node.inputs[1], false);
+      res.inputs.add(node.inputs[2]);
+      return res;
+    } else {
+      List<HInstruction> inputs = new List<HInstruction>.from(node.inputs);
+      inputs[0] = constant;
+      return new HInvokeDynamicMethod(selector, inputs, true);
+    }
+  }
+}
+
+class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase {
+  final HTypeMap types;
+  final Set<HInstruction> boundsChecked;
+  final CodegenWorkItem work;
+  final JavaScriptBackend backend;
+  final String name = "SsaCheckInserter";
+  HGraph graph;
+
+  SsaCheckInserter(this.backend,
+                   this.work,
+                   this.types,
+                   this.boundsChecked);
+
+  void visitGraph(HGraph graph) {
+    this.graph = graph;
+    visitDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      instruction = instruction.accept(this);
+      instruction = next;
+    }
+  }
+
+  HBoundsCheck insertBoundsCheck(HInstruction node,
+                                 HInstruction receiver,
+                                 HInstruction index) {
+    bool isAssignable = !receiver.isFixedArray(types);
+    HFieldGet length = new HFieldGet(
+        backend.jsArrayLength, receiver, isAssignable: isAssignable);
+    length.guaranteedType = HType.INTEGER;
+    types[length] = HType.INTEGER;
+    node.block.addBefore(node, length);
+
+    HBoundsCheck check = new HBoundsCheck(index, length);
+    node.block.addBefore(node, check);
+    boundsChecked.add(node);
+    return check;
+  }
+
+  HIntegerCheck insertIntegerCheck(HInstruction node, HInstruction value) {
+    HIntegerCheck check = new HIntegerCheck(value);
+    node.block.addBefore(node, check);
+    Set<HInstruction> dominatedUsers = value.dominatedUsers(node);
+    for (HInstruction user in dominatedUsers) {
+      user.changeUse(value, check);
+    }
+    return check;
+  }
+
+  void visitIndex(HIndex node) {
+    if (boundsChecked.contains(node)) return;
+    HInstruction index = node.index;
+    if (!node.index.isInteger(types)) {
+      index = insertIntegerCheck(node, index);
+    }
+    index = insertBoundsCheck(node, node.receiver, index);
+    node.changeUse(node.index, index);
+  }
+
+  void visitIndexAssign(HIndexAssign node) {
+    if (!node.receiver.isMutableArray(types)) return;
+    if (boundsChecked.contains(node)) return;
+    HInstruction index = node.index;
+    if (!node.index.isInteger(types)) {
+      index = insertIntegerCheck(node, index);
+    }
+    index = insertBoundsCheck(node, node.receiver, index);
+    node.changeUse(node.index, index);
+  }
+
+  void visitInvokeDynamicMethod(HInvokeDynamicMethod node) {
+    Element element = node.element;
+    if (node.isInterceptorCall) return;
+    if (element != backend.jsArrayRemoveLast) return;
+    if (boundsChecked.contains(node)) return;
+    insertBoundsCheck(
+        node, node.receiver, graph.addConstantInt(0, backend.constantSystem));
+  }
+}
+
+class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase {
+  final HTypeMap types;
+  final String name = "SsaDeadCodeEliminator";
+
+  SsaDeadCodeEliminator(this.types);
+
+  bool isDeadCode(HInstruction instruction) {
+    return !instruction.hasSideEffects()
+           && !instruction.canThrow()
+           && instruction.usedBy.isEmpty
+           && instruction is !HTypeGuard
+           && instruction is !HParameterValue
+           && instruction is !HLocalSet
+           && !instruction.isControlFlow();
+  }
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    HInstruction instruction = block.last;
+    while (instruction != null) {
+      var previous = instruction.previous;
+      if (isDeadCode(instruction)) block.remove(instruction);
+      instruction = previous;
+    }
+  }
+}
+
+class SsaDeadPhiEliminator implements OptimizationPhase {
+  final String name = "SsaDeadPhiEliminator";
+
+  void visitGraph(HGraph graph) {
+    final List<HPhi> worklist = <HPhi>[];
+    // A set to keep track of the live phis that we found.
+    final Set<HPhi> livePhis = new Set<HPhi>();
+
+    // Add to the worklist all live phis: phis referenced by non-phi
+    // instructions.
+    for (final block in graph.blocks) {
+      block.forEachPhi((HPhi phi) {
+        for (final user in phi.usedBy) {
+          if (user is !HPhi) {
+            worklist.add(phi);
+            livePhis.add(phi);
+            break;
+          }
+        }
+      });
+    }
+
+    // Process the worklist by propagating liveness to phi inputs.
+    while (!worklist.isEmpty) {
+      HPhi phi = worklist.removeLast();
+      for (final input in phi.inputs) {
+        if (input is HPhi && !livePhis.contains(input)) {
+          worklist.add(input);
+          livePhis.add(input);
+        }
+      }
+    }
+
+    // Remove phis that are not live.
+    // Traverse in reverse order to remove phis with no uses before the
+    // phis that they might use.
+    // NOTICE: Doesn't handle circular references, but we don't currently
+    // create any.
+    List<HBasicBlock> blocks = graph.blocks;
+    for (int i = blocks.length - 1; i >= 0; i--) {
+      HBasicBlock block = blocks[i];
+      HPhi current = block.phis.first;
+      HPhi next = null;
+      while (current != null) {
+        next = current.next;
+        if (!livePhis.contains(current)
+            // TODO(ahe): Not sure the following is correct.
+            && current.usedBy.isEmpty) {
+          block.removePhi(current);
+        }
+        current = next;
+      }
+    }
+  }
+}
+
+class SsaRedundantPhiEliminator implements OptimizationPhase {
+  final String name = "SsaRedundantPhiEliminator";
+
+  void visitGraph(HGraph graph) {
+    final List<HPhi> worklist = <HPhi>[];
+
+    // Add all phis in the worklist.
+    for (final block in graph.blocks) {
+      block.forEachPhi((HPhi phi) => worklist.add(phi));
+    }
+
+    while (!worklist.isEmpty) {
+      HPhi phi = worklist.removeLast();
+
+      // If the phi has already been processed, continue.
+      if (!phi.isInBasicBlock()) continue;
+
+      // Find if the inputs of the phi are the same instruction.
+      // The builder ensures that phi.inputs[0] cannot be the phi
+      // itself.
+      assert(!identical(phi.inputs[0], phi));
+      HInstruction candidate = phi.inputs[0];
+      for (int i = 1; i < phi.inputs.length; i++) {
+        HInstruction input = phi.inputs[i];
+        // If the input is the phi, the phi is still candidate for
+        // elimination.
+        if (!identical(input, candidate) && !identical(input, phi)) {
+          candidate = null;
+          break;
+        }
+      }
+
+      // If the inputs are not the same, continue.
+      if (candidate == null) continue;
+
+      // Because we're updating the users of this phi, we may have new
+      // phis candidate for elimination. Add phis that used this phi
+      // to the worklist.
+      for (final user in phi.usedBy) {
+        if (user is HPhi) worklist.add(user);
+      }
+      phi.block.rewrite(phi, candidate);
+      phi.block.removePhi(phi);
+    }
+  }
+}
+
+class SsaGlobalValueNumberer implements OptimizationPhase {
+  final String name = "SsaGlobalValueNumberer";
+  final Compiler compiler;
+  final HTypeMap types;
+  final Set<int> visited;
+
+  List<int> blockChangesFlags;
+  List<int> loopChangesFlags;
+
+  SsaGlobalValueNumberer(this.compiler, this.types) : visited = new Set<int>();
+
+  void visitGraph(HGraph graph) {
+    computeChangesFlags(graph);
+    moveLoopInvariantCode(graph);
+    visitBasicBlock(graph.entry, new ValueSet());
+  }
+
+  void moveLoopInvariantCode(HGraph graph) {
+    for (int i = graph.blocks.length - 1; i >= 0; i--) {
+      HBasicBlock block = graph.blocks[i];
+      if (block.isLoopHeader()) {
+        int changesFlags = loopChangesFlags[block.id];
+        HLoopInformation info = block.loopInformation;
+        // Iterate over all blocks of this loop. Note that blocks in
+        // inner loops are not visited here, but we know they
+        // were visited before because we are iterating in post-order.
+        // So instructions that are GVN'ed in an inner loop are in their
+        // loop entry, and [info.blocks] contains this loop entry.
+        for (HBasicBlock other in info.blocks) {
+          moveLoopInvariantCodeFromBlock(other, block, changesFlags);
+        }
+      }
+    }
+  }
+
+  void moveLoopInvariantCodeFromBlock(HBasicBlock block,
+                                      HBasicBlock loopHeader,
+                                      int changesFlags) {
+    assert(block.parentLoopHeader == loopHeader);
+    HBasicBlock preheader = loopHeader.predecessors[0];
+    int dependsFlags = HInstruction.computeDependsOnFlags(changesFlags);
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      if (instruction.useGvn()
+          && (instruction is !HCheck)
+          && (instruction.flags & dependsFlags) == 0) {
+        bool loopInvariantInputs = true;
+        List<HInstruction> inputs = instruction.inputs;
+        for (int i = 0, length = inputs.length; i < length; i++) {
+          if (isInputDefinedAfterDominator(inputs[i], preheader)) {
+            loopInvariantInputs = false;
+            break;
+          }
+        }
+
+        // If the inputs are loop invariant, we can move the
+        // instruction from the current block to the pre-header block.
+        if (loopInvariantInputs) {
+          block.detach(instruction);
+          preheader.moveAtExit(instruction);
+        }
+      }
+      int oldChangesFlags = changesFlags;
+      changesFlags |= instruction.getChangesFlags();
+      if (oldChangesFlags != changesFlags) {
+        dependsFlags = HInstruction.computeDependsOnFlags(changesFlags);
+      }
+      instruction = next;
+    }
+  }
+
+  bool isInputDefinedAfterDominator(HInstruction input,
+                                    HBasicBlock dominator) {
+    return input.block.id > dominator.id;
+  }
+
+  void visitBasicBlock(HBasicBlock block, ValueSet values) {
+    HInstruction instruction = block.first;
+    if (block.isLoopHeader()) {
+      int flags = loopChangesFlags[block.id];
+      values.kill(flags);
+    }
+    while (instruction != null) {
+      HInstruction next = instruction.next;
+      int flags = instruction.getChangesFlags();
+      assert(flags == 0 || !instruction.useGvn());
+      values.kill(flags);
+      if (instruction.useGvn()) {
+        HInstruction other = values.lookup(instruction);
+        if (other != null) {
+          assert(other.gvnEquals(instruction) && instruction.gvnEquals(other));
+          block.rewriteWithBetterUser(instruction, other);
+          block.remove(instruction);
+        } else {
+          values.add(instruction);
+        }
+      }
+      instruction = next;
+    }
+
+    List<HBasicBlock> dominatedBlocks = block.dominatedBlocks;
+    for (int i = 0, length = dominatedBlocks.length; i < length; i++) {
+      HBasicBlock dominated = dominatedBlocks[i];
+      // No need to copy the value set for the last child.
+      ValueSet successorValues = (i == length - 1) ? values : values.copy();
+      // If we have no values in our set, we do not have to kill
+      // anything. Also, if the range of block ids from the current
+      // block to the dominated block is empty, there is no blocks on
+      // any path from the current block to the dominated block so we
+      // don't have to do anything either.
+      assert(block.id < dominated.id);
+      if (!successorValues.isEmpty && block.id + 1 < dominated.id) {
+        visited.clear();
+        int changesFlags = getChangesFlagsForDominatedBlock(block, dominated);
+        successorValues.kill(changesFlags);
+      }
+      visitBasicBlock(dominated, successorValues);
+    }
+  }
+
+  void computeChangesFlags(HGraph graph) {
+    // Create the changes flags lists. Make sure to initialize the
+    // loop changes flags list to zero so we can use bitwise or when
+    // propagating loop changes upwards.
+    final int length = graph.blocks.length;
+    blockChangesFlags = new List<int>.fixedLength(length);
+    loopChangesFlags = new List<int>.fixedLength(length);
+    for (int i = 0; i < length; i++) loopChangesFlags[i] = 0;
+
+    // Run through all the basic blocks in the graph and fill in the
+    // changes flags lists.
+    for (int i = length - 1; i >= 0; i--) {
+      final HBasicBlock block = graph.blocks[i];
+      final int id = block.id;
+
+      // Compute block changes flags for the block.
+      int changesFlags = 0;
+      HInstruction instruction = block.first;
+      while (instruction != null) {
+        changesFlags |= instruction.getChangesFlags();
+        instruction = instruction.next;
+      }
+      assert(blockChangesFlags[id] == null);
+      blockChangesFlags[id] = changesFlags;
+
+      // Loop headers are part of their loop, so update the loop
+      // changes flags accordingly.
+      if (block.isLoopHeader()) {
+        loopChangesFlags[id] |= changesFlags;
+      }
+
+      // Propagate loop changes flags upwards.
+      HBasicBlock parentLoopHeader = block.parentLoopHeader;
+      if (parentLoopHeader != null) {
+        loopChangesFlags[parentLoopHeader.id] |= (block.isLoopHeader())
+            ? loopChangesFlags[id]
+            : changesFlags;
+      }
+    }
+  }
+
+  int getChangesFlagsForDominatedBlock(HBasicBlock dominator,
+                                       HBasicBlock dominated) {
+    int changesFlags = 0;
+    List<HBasicBlock> predecessors = dominated.predecessors;
+    for (int i = 0, length = predecessors.length; i < length; i++) {
+      HBasicBlock block = predecessors[i];
+      int id = block.id;
+      // If the current predecessor block is on the path from the
+      // dominator to the dominated, it must have an id that is in the
+      // range from the dominator to the dominated.
+      if (dominator.id < id && id < dominated.id && !visited.contains(id)) {
+        visited.add(id);
+        changesFlags |= blockChangesFlags[id];
+        // Loop bodies might not be on the path from dominator to dominated,
+        // but they can invalidate values.
+        changesFlags |= loopChangesFlags[id];
+        changesFlags |= getChangesFlagsForDominatedBlock(dominator, block);
+      }
+    }
+    return changesFlags;
+  }
+}
+
+// This phase merges equivalent instructions on different paths into
+// one instruction in a dominator block. It runs through the graph
+// post dominator order and computes a ValueSet for each block of
+// instructions that can be moved to a dominator block. These
+// instructions are the ones that:
+// 1) can be used for GVN, and
+// 2) do not use definitions of their own block.
+//
+// A basic block looks at its sucessors and finds the intersection of
+// these computed ValueSet. It moves all instructions of the
+// intersection into its own list of instructions.
+class SsaCodeMotion extends HBaseVisitor implements OptimizationPhase {
+  final String name = "SsaCodeMotion";
+
+  List<ValueSet> values;
+
+  void visitGraph(HGraph graph) {
+    values = new List<ValueSet>.fixedLength(graph.blocks.length);
+    for (int i = 0; i < graph.blocks.length; i++) {
+      values[graph.blocks[i].id] = new ValueSet();
+    }
+    visitPostDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    List<HBasicBlock> successors = block.successors;
+
+    // Phase 1: get the ValueSet of all successors (if there are more than one),
+    // compute the intersection and move the instructions of the intersection
+    // into this block.
+    if (successors.length > 1) {
+      ValueSet instructions = values[successors[0].id];
+      for (int i = 1; i < successors.length; i++) {
+        ValueSet other = values[successors[i].id];
+        instructions = instructions.intersection(other);
+      }
+
+      if (!instructions.isEmpty) {
+        List<HInstruction> list = instructions.toList();
+        for (HInstruction instruction in list) {
+          // Move the instruction to the current block.
+          instruction.block.detach(instruction);
+          block.moveAtExit(instruction);
+          // Go through all successors and rewrite their instruction
+          // to the shared one.
+          for (final successor in successors) {
+            HInstruction toRewrite = values[successor.id].lookup(instruction);
+            if (toRewrite != instruction) {
+              successor.rewriteWithBetterUser(toRewrite, instruction);
+              successor.remove(toRewrite);
+            }
+          }
+        }
+      }
+    }
+
+    // Don't try to merge instructions to a dominator if we have
+    // multiple predecessors.
+    if (block.predecessors.length != 1) return;
+
+    // Phase 2: Go through all instructions of this block and find
+    // which instructions can be moved to a dominator block.
+    ValueSet set_ = values[block.id];
+    HInstruction instruction = block.first;
+    int flags = 0;
+    while (instruction != null) {
+      int dependsFlags = HInstruction.computeDependsOnFlags(flags);
+      flags |= instruction.getChangesFlags();
+
+      HInstruction current = instruction;
+      instruction = instruction.next;
+
+      // TODO(ngeoffray): this check is needed because we currently do
+      // not have flags to express 'Gvn'able', but not movable.
+      if (current is HCheck) continue;
+      if (!current.useGvn()) continue;
+      if ((current.flags & dependsFlags) != 0) continue;
+
+      bool canBeMoved = true;
+      for (final HInstruction input in current.inputs) {
+        if (input.block == block) {
+          canBeMoved = false;
+          break;
+        }
+      }
+      if (!canBeMoved) continue;
+
+      // This is safe because we are running after GVN.
+      // TODO(ngeoffray): ensure GVN has been run.
+      set_.add(current);
+    }
+  }
+}
+
+class SsaTypeConversionInserter extends HBaseVisitor
+    implements OptimizationPhase {
+  final String name = "SsaTypeconversionInserter";
+  final Compiler compiler;
+
+  SsaTypeConversionInserter(this.compiler);
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+
+  // Update users of [input] that are dominated by [:dominator.first:]
+  // to use [newInput] instead.
+  void changeUsesDominatedBy(HBasicBlock dominator,
+                             HInstruction input,
+                             HType convertedType) {
+    Set<HInstruction> dominatedUsers = input.dominatedUsers(dominator.first);
+    if (dominatedUsers.isEmpty) return;
+
+    HTypeConversion newInput = new HTypeConversion(convertedType, input);
+    dominator.addBefore(dominator.first, newInput);
+    dominatedUsers.forEach((HInstruction user) {
+      user.changeUse(input, newInput);
+    });
+  }
+
+  void visitIs(HIs instruction) {
+    HInstruction input = instruction.expression;
+    HType convertedType =
+        new HType.fromBoundedType(instruction.typeExpression, compiler);
+
+    List<HInstruction> ifUsers = <HInstruction>[];
+    List<HInstruction> notIfUsers = <HInstruction>[];
+
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HIf) {
+        ifUsers.add(user);
+      } else if (user is HNot) {
+        for (HInstruction notUser in user.usedBy) {
+          if (notUser is HIf) notIfUsers.add(notUser);
+        }
+      }
+    }
+
+    if (ifUsers.isEmpty && notIfUsers.isEmpty) return;
+
+    for (HIf ifUser in ifUsers) {
+      changeUsesDominatedBy(ifUser.thenBlock, input, convertedType);
+      // TODO(ngeoffray): Also change uses for the else block on a HType
+      // that knows it is not of a specific Type.
+    }
+
+    for (HIf ifUser in notIfUsers) {
+      changeUsesDominatedBy(ifUser.elseBlock, input, convertedType);
+      // TODO(ngeoffray): Also change uses for the then block on a HType
+      // that knows it is not of a specific Type.
+    }
+  }
+}
+
+
+// Analyze the constructors to see if some fields will always have a specific
+// type after construction. If this is the case we can ignore the type given
+// by the field initializer. This is especially useful when the field
+// initializer is initializing the field to null.
+class SsaConstructionFieldTypes
+    extends HBaseVisitor implements OptimizationPhase {
+  final JavaScriptBackend backend;
+  final CodegenWorkItem work;
+  final HTypeMap types;
+  final String name = "SsaConstructionFieldTypes";
+  final Set<HInstruction> thisUsers;
+  final Set<Element> allSetters;
+  final Map<HBasicBlock, Map<Element, HType>> blockFieldSetters;
+  bool thisExposed = false;
+  HGraph currentGraph;
+  Map<Element, HType> currentFieldSetters;
+
+  SsaConstructionFieldTypes(JavaScriptBackend this.backend,
+         CodegenWorkItem this.work,
+         HTypeMap this.types)
+      : thisUsers = new Set<HInstruction>(),
+        allSetters = new Set<Element>(),
+        blockFieldSetters = new Map<HBasicBlock, Map<Element, HType>>();
+
+  void visitGraph(HGraph graph) {
+    currentGraph = graph;
+    if (!work.element.isGenerativeConstructorBody() &&
+        !work.element.isGenerativeConstructor()) return;
+    visitDominatorTree(graph);
+    if (work.element.isGenerativeConstructor()) {
+      backend.registerConstructor(work.element);
+    }
+  }
+
+  visitBasicBlock(HBasicBlock block) {
+    if (block.predecessors.length == 0) {
+      // Create a new empty map for the first block.
+      currentFieldSetters = new Map<Element, HType>();
+    } else {
+      // Build a map which intersects the fields from all predecessors. For
+      // each field in this intersection it unions the types.
+      currentFieldSetters =
+          new Map.from(blockFieldSetters[block.predecessors[0]]);
+      // Loop headers are the only nodes with back edges.
+      if (!block.isLoopHeader()) {
+        for (int i = 1; i < block.predecessors.length; i++) {
+          Map<Element, HType> predecessorsFieldSetters =
+              blockFieldSetters[block.predecessors[i]];
+          Map<Element, HType> newFieldSetters = new Map<Element, HType>();
+          predecessorsFieldSetters.forEach((Element element, HType type) {
+            HType currentType = currentFieldSetters[element];
+            if (currentType != null) {
+              newFieldSetters[element] =
+                  currentType.union(type, backend.compiler);
+            }
+          });
+          currentFieldSetters = newFieldSetters;
+        }
+      } else {
+        assert(block.predecessors.length <= 2);
+      }
+    }
+    block.forEachPhi((HPhi phi) => phi.accept(this));
+    block.forEachInstruction(
+        (HInstruction instruction) => instruction.accept(this));
+    assert(currentFieldSetters != null);
+    blockFieldSetters[block] = currentFieldSetters;
+  }
+
+  visitInstruction(HInstruction instruction) {
+    // All instructions not explicitly handled below will flag the this
+    // exposure if using this.
+    thisExposed = thisExposed || thisUsers.contains(instruction);
+  }
+
+  visitPhi(HPhi phi) {
+    if (thisUsers.contains(phi)) {
+      thisUsers.addAll(phi.usedBy);
+    }
+  }
+
+  visitThis(HThis instruction) {
+    // Collect all users of this in a set to make the this exposed check simple
+    // and cheap.
+    thisUsers.addAll(instruction.usedBy);
+  }
+
+  visitFieldGet(HInstruction _) {
+    // The field get instruction is allowed to use this.
+  }
+
+  visitForeignNew(HForeignNew node) {
+    // The HForeignNew instruction is used in the generative constructor to
+    // initialize all fields in newly created objects. The fields are
+    // initialized to the value present in the initializer list or set to null
+    // if not otherwise initialized.
+    // Here we handle members in superclasses as well, as the handling of
+    // the generative constructor bodies will ensure, that the initializer
+    // type will not be used if the field is in any of these.
+    int j = 0;
+    node.element.forEachInstanceField(
+        (ClassElement enclosingClass, Element element) {
+          backend.registerFieldInitializer(element, types[node.inputs[j]]);
+          j++;
+        },
+        includeBackendMembers: false,
+        includeSuperMembers: true);
+  }
+
+  visitFieldSet(HFieldSet node) {
+    Element field = node.element;
+    HInstruction value = node.value;
+    HType type = types[value];
+    // [HFieldSet] is also used for variables in try/catch.
+    if (field.isField()) allSetters.add(field);
+    // Don't handle fields defined in superclasses. Given that the field is
+    // always added to the [allSetters] set, setting a field defined in a
+    // superclass will get an inferred type of UNKNOWN.
+    if (identical(work.element.getEnclosingClass(), field.getEnclosingClass()) &&
+        value.hasGuaranteedType()) {
+      currentFieldSetters[field] = type;
+    }
+  }
+
+  visitExit(HExit node) {
+    // If this has been exposed then we cannot say anything about types after
+    // construction.
+    if (!thisExposed) {
+      // Register the known field types.
+      currentFieldSetters.forEach((Element element, HType type) {
+        backend.registerFieldConstructor(element, type);
+        allSetters.remove(element);
+      });
+    }
+
+    // For other fields having setters in the generative constructor body, set
+    // the type to UNKNOWN to avoid relying on the type set in the initializer
+    // list.
+    allSetters.forEach((Element element) {
+      backend.registerFieldConstructor(element, HType.UNKNOWN);
+    });
+  }
+}
+
+/**
+ * This phase specializes dominated uses of a call, where the call
+ * can give us some type information of what the receiver might be.
+ * For example, after a call to [:a.foo():], if [:foo:] is only
+ * in class [:A:], a can be of type [:A:].
+ */
+class SsaReceiverSpecialization extends HBaseVisitor
+    implements OptimizationPhase {
+  final String name = "SsaReceiverSpecialization";
+  final Compiler compiler;
+
+  SsaReceiverSpecialization(this.compiler);
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+  void visitInterceptor(HInterceptor interceptor) {
+    HInstruction receiver = interceptor.receiver;
+    JavaScriptBackend backend = compiler.backend;
+    for (var user in receiver.usedBy) {
+      if (user is HInterceptor && interceptor.dominates(user)) {
+        Set<ClassElement> otherIntercepted = user.interceptedClasses;
+        // If the dominated interceptor intercepts the int class or
+        // the double class, we make sure these classes are also being
+        // intercepted by the dominating interceptor. Otherwise, the
+        // dominating interceptor could just intercept the number
+        // class and therefore not implement the methods in the int or
+        // double class.
+        if (otherIntercepted.contains(backend.jsIntClass)
+            || otherIntercepted.contains(backend.jsDoubleClass)) {
+          interceptor.interceptedClasses.addAll(user.interceptedClasses);
+        }
+        user.interceptedClasses = interceptor.interceptedClasses;
+      }
+    }
+  }
+
+  // TODO(ngeoffray): Also implement it for non-intercepted calls.
+}
+
+/**
+ * This phase replaces all interceptors that are used only once with
+ * one-shot interceptors. It saves code size and makes the receiver of
+ * an intercepted call a candidate for being generated at use site.
+ */
+class SsaSimplifyInterceptors extends HBaseVisitor
+    implements OptimizationPhase {
+  final String name = "SsaSimplifyInterceptors";
+  final ConstantSystem constantSystem;
+  HGraph graph;
+
+  SsaSimplifyInterceptors(this.constantSystem);
+
+  void visitGraph(HGraph graph) {
+    this.graph = graph;
+    visitDominatorTree(graph);
+  }
+
+  void visitInterceptor(HInterceptor node) {
+    if (node.usedBy.length != 1) return;
+    // [HBailoutTarget] instructions might have the interceptor as
+    // input. In such situation we let the dead code analyzer find out
+    // the interceptor is not needed.
+    if (node.usedBy[0] is !HInvokeDynamic) return;
+
+    HInvokeDynamic user = node.usedBy[0];
+
+    // If [node] was loop hoisted, we keep the interceptor.
+    if (!user.hasSameLoopHeaderAs(node)) return;
+
+    // Replace the user with a [HOneShotInterceptor].
+    HConstant nullConstant = graph.addConstantNull(constantSystem);
+    List<HInstruction> inputs = new List<HInstruction>.from(user.inputs);
+    inputs[0] = nullConstant;
+    HOneShotInterceptor interceptor = new HOneShotInterceptor(
+        user.selector, inputs, node.interceptedClasses);
+    interceptor.sourcePosition = user.sourcePosition;
+    interceptor.sourceElement = user.sourceElement;
+
+    HBasicBlock block = user.block;
+    block.addAfter(user, interceptor);
+    block.rewrite(user, interceptor);
+    block.remove(user);
+
+    // The interceptor will be removed in the dead code elimination
+    // phase. Note that removing it here would not work because of how
+    // the [visitBasicBlock] is implemented.
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart
new file mode 100644
index 0000000..13de8b6
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart
@@ -0,0 +1,43 @@
+// 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.
+
+library ssa;
+
+import 'dart:collection';
+
+import '../closure.dart';
+import '../js/js.dart' as js;
+import '../dart2jslib.dart' hide Selector;
+import '../dart_types.dart';
+import '../source_file.dart';
+import '../source_map_builder.dart';
+import '../elements/elements.dart';
+import '../js_backend/js_backend.dart';
+import '../native_handler.dart' as native;
+import '../tree/tree.dart';
+import '../types/types.dart';
+import '../universe/universe.dart';
+import '../util/util.dart';
+import '../util/characters.dart';
+
+import '../scanner/scannerlib.dart'
+    show PartialFunctionElement, Token, PLUS_TOKEN;
+
+import '../elements/modelx.dart'
+    show ElementX,
+         ConstructorBodyElementX;
+
+part 'bailout.dart';
+part 'builder.dart';
+part 'codegen.dart';
+part 'codegen_helpers.dart';
+part 'invoke_dynamic_specializers.dart';
+part 'nodes.dart';
+part 'optimize.dart';
+part 'types.dart';
+part 'types_propagation.dart';
+part 'validate.dart';
+part 'variable_allocator.dart';
+part 'value_range_analyzer.dart';
+part 'value_set.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart
new file mode 100644
index 0000000..7a8f6bc
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart
@@ -0,0 +1,563 @@
+// 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.
+
+library tracer;
+
+import 'dart:io';
+import 'ssa.dart';
+import '../js_backend/js_backend.dart';
+import '../dart2jslib.dart';
+
+const bool GENERATE_SSA_TRACE = false;
+const String SSA_TRACE_FILTER = null;
+
+class HTracer extends HGraphVisitor implements Tracer {
+  JavaScriptItemCompilationContext context;
+  int indent = 0;
+  final RandomAccessFile output;
+  final bool enabled = GENERATE_SSA_TRACE;
+  bool traceActive = false;
+
+  HTracer([String path = "dart.cfg"])
+      : output = GENERATE_SSA_TRACE ? new File(path).openSync(FileMode.WRITE)
+                                    : null;
+
+  void close() {
+    if (enabled) output.closeSync();
+  }
+
+  void traceCompilation(String methodName,
+                        JavaScriptItemCompilationContext compilationContext) {
+    if (!enabled) return;
+    this.context = compilationContext;
+    traceActive =
+        SSA_TRACE_FILTER == null || methodName.contains(SSA_TRACE_FILTER);
+    if (!traceActive) return;
+    tag("compilation", () {
+      printProperty("name", methodName);
+      printProperty("method", methodName);
+      printProperty("date", new DateTime.now().millisecondsSinceEpoch);
+    });
+  }
+
+  void traceGraph(String name, HGraph graph) {
+    if (!traceActive) return;
+    tag("cfg", () {
+      printProperty("name", name);
+      visitDominatorTree(graph);
+    });
+  }
+
+  void addPredecessors(HBasicBlock block) {
+    if (block.predecessors.isEmpty) {
+      printEmptyProperty("predecessors");
+    } else {
+      addIndent();
+      add("predecessors");
+      for (HBasicBlock predecessor in block.predecessors) {
+        add(' "B${predecessor.id}"');
+      }
+      add("\n");
+    }
+  }
+
+  void addSuccessors(HBasicBlock block) {
+    if (block.successors.isEmpty) {
+      printEmptyProperty("successors");
+    } else {
+      addIndent();
+      add("successors");
+      for (HBasicBlock successor in block.successors) {
+        add(' "B${successor.id}"');
+      }
+      add("\n");
+    }
+  }
+
+  void addInstructions(HInstructionStringifier stringifier,
+                       HInstructionList list) {
+    HTypeMap types = context.types;
+    for (HInstruction instruction = list.first;
+         instruction != null;
+         instruction = instruction.next) {
+      int bci = 0;
+      int uses = instruction.usedBy.length;
+      String changes = instruction.hasSideEffects() ? '!' : ' ';
+      String depends = instruction.dependsOnSomething() ? '?' : '';
+      addIndent();
+      String temporaryId = stringifier.temporaryId(instruction);
+      String instructionString = stringifier.visit(instruction);
+      add("$bci $uses $temporaryId $instructionString $changes $depends <|@\n");
+    }
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    HInstructionStringifier stringifier =
+        new HInstructionStringifier(context, block);
+    assert(block.id != null);
+    tag("block", () {
+      printProperty("name", "B${block.id}");
+      printProperty("from_bci", -1);
+      printProperty("to_bci", -1);
+      addPredecessors(block);
+      addSuccessors(block);
+      printEmptyProperty("xhandlers");
+      printEmptyProperty("flags");
+      if (block.dominator != null) {
+        printProperty("dominator", "B${block.dominator.id}");
+      }
+      tag("states", () {
+        tag("locals", () {
+          printProperty("size", 0);
+          printProperty("method", "None");
+          block.forEachPhi((phi) {
+            String phiId = stringifier.temporaryId(phi);
+            StringBuffer inputIds = new StringBuffer();
+            for (int i = 0; i < phi.inputs.length; i++) {
+              inputIds.add(stringifier.temporaryId(phi.inputs[i]));
+              inputIds.add(" ");
+            }
+            println("${phi.id} $phiId [ $inputIds]");
+          });
+        });
+      });
+      tag("HIR", () {
+        addInstructions(stringifier, block.phis);
+        addInstructions(stringifier, block);
+      });
+    });
+  }
+
+  void tag(String tagName, Function f) {
+    println("begin_$tagName");
+    indent++;
+    f();
+    indent--;
+    println("end_$tagName");
+  }
+
+  void println(String string) {
+    addIndent();
+    add(string);
+    add("\n");
+  }
+
+  void printEmptyProperty(String propertyName) {
+    println(propertyName);
+  }
+
+  void printProperty(String propertyName, var value) {
+    if (value is num) {
+      println("$propertyName $value");
+    } else {
+      println('$propertyName "$value"');
+    }
+  }
+
+  void add(String string) {
+    output.writeStringSync(string);
+  }
+
+  void addIndent() {
+    for (int i = 0; i < indent; i++) {
+      add("  ");
+    }
+  }
+}
+
+class HInstructionStringifier implements HVisitor<String> {
+  JavaScriptItemCompilationContext context;
+  HBasicBlock currentBlock;
+
+  HInstructionStringifier(this.context, this.currentBlock);
+
+  visit(HInstruction node) => node.accept(this);
+
+  String temporaryId(HInstruction instruction) {
+    String prefix;
+    HType type = context.types[instruction];
+    if (!type.isPrimitive()) {
+      prefix = 'U';
+    } else {
+      if (type == HType.MUTABLE_ARRAY) {
+        prefix = 'm';
+      } else if (type == HType.READABLE_ARRAY) {
+        prefix = 'a';
+      } else if (type == HType.EXTENDABLE_ARRAY) {
+        prefix = 'e';
+      } else if (type == HType.BOOLEAN) {
+        prefix = 'b';
+      } else if (type == HType.INTEGER) {
+        prefix = 'i';
+      } else if (type == HType.DOUBLE) {
+        prefix = 'd';
+      } else if (type == HType.NUMBER) {
+        prefix = 'n';
+      } else if (type == HType.STRING) {
+        prefix = 's';
+      } else if (type == HType.UNKNOWN) {
+        prefix = 'v';
+      } else if (type == HType.CONFLICTING) {
+        prefix = 'c';
+      } else if (type == HType.INDEXABLE_PRIMITIVE) {
+        prefix = 'r';
+      } else if (type == HType.NULL) {
+        prefix = 'u';
+      } else {
+        prefix = 'x';
+      }
+    }
+    return "$prefix${instruction.id}";
+  }
+
+  String visitBailoutTarget(HBailoutTarget node) {
+    StringBuffer envBuffer = new StringBuffer();
+    List<HInstruction> inputs = node.inputs;
+    for (int i = 0; i < inputs.length; i++) {
+      envBuffer.add(" ${temporaryId(inputs[i])}");
+    }
+    String on = node.isEnabled ? "enabled" : "disabled";
+    return "BailoutTarget($on): id: ${node.state} env: $envBuffer";
+  }
+
+  String visitBoolify(HBoolify node) {
+    return "Boolify: ${temporaryId(node.inputs[0])}";
+  }
+
+  String handleInvokeBinary(HInvokeBinary node, String op) {
+    String left = temporaryId(node.left);
+    String right= temporaryId(node.right);
+    return '$left $op $right';
+  }
+
+  String visitAdd(HAdd node) => handleInvokeBinary(node, '+');
+
+  String visitBitAnd(HBitAnd node) => handleInvokeBinary(node, '&');
+
+  String visitBitNot(HBitNot node) {
+    String operand = temporaryId(node.operand);
+    return "~$operand";
+  }
+
+  String visitBitOr(HBitOr node) => handleInvokeBinary(node, '|');
+
+  String visitBitXor(HBitXor node) => handleInvokeBinary(node, '^');
+
+  String visitBoundsCheck(HBoundsCheck node) {
+    String lengthId = temporaryId(node.length);
+    String indexId = temporaryId(node.index);
+    return "Bounds check: length = $lengthId, index = $indexId";
+  }
+
+  String visitBreak(HBreak node) {
+    HBasicBlock target = currentBlock.successors[0];
+    if (node.label != null) {
+      return "Break ${node.label.labelName}: (B${target.id})";
+    }
+    return "Break: (B${target.id})";
+  }
+
+  String visitConstant(HConstant constant) => "Constant ${constant.constant}";
+
+  String visitContinue(HContinue node) {
+    HBasicBlock target = currentBlock.successors[0];
+    if (node.label != null) {
+      return "Continue ${node.label.labelName}: (B${target.id})";
+    }
+    return "Continue: (B${target.id})";
+  }
+
+  String visitDivide(HDivide node) => handleInvokeBinary(node, '/');
+
+  String visitExit(HExit node) => "exit";
+
+  String visitFieldGet(HFieldGet node) {
+    String fieldName = node.element.name.slowToString();
+    return 'field get ${temporaryId(node.receiver)}.$fieldName';
+  }
+
+  String visitFieldSet(HFieldSet node) {
+    String valueId = temporaryId(node.value);
+    String fieldName = node.element.name.slowToString();
+    return 'field set ${temporaryId(node.receiver)}.$fieldName to $valueId';
+  }
+
+  String visitLocalGet(HLocalGet node) {
+    String localName = node.element.name.slowToString();
+    return 'local get ${temporaryId(node.local)}.$localName';
+  }
+
+  String visitLocalSet(HLocalSet node) {
+    String valueId = temporaryId(node.value);
+    String localName = node.element.name.slowToString();
+    return 'local set ${temporaryId(node.local)}.$localName to $valueId';
+  }
+
+  String visitGoto(HGoto node) {
+    HBasicBlock target = currentBlock.successors[0];
+    return "Goto: (B${target.id})";
+  }
+
+  String visitGreater(HGreater node) => handleInvokeBinary(node, '>');
+  String visitGreaterEqual(HGreaterEqual node) {
+    handleInvokeBinary(node, '>=');
+  }
+  String visitIdentity(HIdentity node) => handleInvokeBinary(node, '===');
+
+  String visitIf(HIf node) {
+    HBasicBlock thenBlock = currentBlock.successors[0];
+    HBasicBlock elseBlock = currentBlock.successors[1];
+    String conditionId = temporaryId(node.inputs[0]);
+    return "If ($conditionId): (B${thenBlock.id}) else (B${elseBlock.id})";
+  }
+
+  String visitGenericInvoke(String invokeType, String functionName,
+                            List<HInstruction> arguments) {
+    StringBuffer argumentsString = new StringBuffer();
+    for (int i = 0; i < arguments.length; i++) {
+      if (i != 0) argumentsString.add(", ");
+      argumentsString.add(temporaryId(arguments[i]));
+    }
+    return "$invokeType: $functionName($argumentsString)";
+  }
+
+  String visitIndex(HIndex node) {
+    String receiver = temporaryId(node.receiver);
+    String index = temporaryId(node.index);
+    return "Index: $receiver[$index]";
+  }
+
+  String visitIndexAssign(HIndexAssign node) {
+    String receiver = temporaryId(node.receiver);
+    String index = temporaryId(node.index);
+    String value = temporaryId(node.value);
+    return "IndexAssign: $receiver[$index] = $value";
+  }
+
+  String visitIntegerCheck(HIntegerCheck node) {
+    String value = temporaryId(node.value);
+    return "Integer check: $value";
+  }
+
+  String visitInterceptor(HInterceptor node) {
+    String value = temporaryId(node.inputs[0]);
+    return "Intercept: $value";
+  }
+
+  String visitInvokeClosure(HInvokeClosure node)
+      => visitInvokeDynamic(node, "closure");
+
+  String visitInvokeDynamic(HInvokeDynamic invoke, String kind) {
+    String receiver = temporaryId(invoke.receiver);
+    String name = invoke.selector.name.slowToString();
+    String target = "($kind) $receiver.$name";
+    int offset = HInvoke.ARGUMENTS_OFFSET;
+    List arguments =
+        invoke.inputs.getRange(offset, invoke.inputs.length - offset);
+    return visitGenericInvoke("Invoke", target, arguments);
+  }
+
+  String visitInvokeDynamicMethod(HInvokeDynamicMethod node)
+      => visitInvokeDynamic(node, "method");
+  String visitInvokeDynamicGetter(HInvokeDynamicGetter node)
+      => visitInvokeDynamic(node, "get");
+  String visitInvokeDynamicSetter(HInvokeDynamicSetter node)
+      => visitInvokeDynamic(node, "set");
+
+  String visitInvokeStatic(HInvokeStatic invoke) {
+    String target = temporaryId(invoke.target);
+    int offset = HInvoke.ARGUMENTS_OFFSET;
+    List arguments =
+        invoke.inputs.getRange(offset, invoke.inputs.length - offset);
+    return visitGenericInvoke("Invoke", target, arguments);
+  }
+
+  String visitInvokeSuper(HInvokeSuper invoke) {
+    String target = temporaryId(invoke.target);
+    int offset = HInvoke.ARGUMENTS_OFFSET + 1;
+    List arguments =
+        invoke.inputs.getRange(offset, invoke.inputs.length - offset);
+    return visitGenericInvoke("Invoke super", target, arguments);
+  }
+
+  String visitForeign(HForeign foreign) {
+    return visitGenericInvoke("Foreign", "${foreign.code}", foreign.inputs);
+  }
+
+  String visitForeignNew(HForeignNew node) {
+    return visitGenericInvoke("New",
+                              "${node.element.name.slowToString()}",
+                              node.inputs);
+  }
+
+  String visitLess(HLess node) => handleInvokeBinary(node, '<');
+  String visitLessEqual(HLessEqual node) => handleInvokeBinary(node, '<=');
+
+  String visitLiteralList(HLiteralList node) {
+    StringBuffer elementsString = new StringBuffer();
+    for (int i = 0; i < node.inputs.length; i++) {
+      if (i != 0) elementsString.add(", ");
+      elementsString.add(temporaryId(node.inputs[i]));
+    }
+    return "Literal list: [$elementsString]";
+  }
+
+  String visitLoopBranch(HLoopBranch branch) {
+    HBasicBlock bodyBlock = currentBlock.successors[0];
+    HBasicBlock exitBlock = currentBlock.successors[1];
+    String conditionId = temporaryId(branch.inputs[0]);
+    return "While ($conditionId): (B${bodyBlock.id}) then (B${exitBlock.id})";
+  }
+
+  String visitMultiply(HMultiply node) => handleInvokeBinary(node, '*');
+
+  String visitNegate(HNegate node) {
+    String operand = temporaryId(node.operand);
+    return "-$operand";
+  }
+
+  String visitNot(HNot node) => "Not: ${temporaryId(node.inputs[0])}";
+
+  String visitParameterValue(HParameterValue node) {
+    return "p${node.sourceElement.name.slowToString()}";
+  }
+
+  String visitLocalValue(HLocalValue node) {
+    return "l${node.sourceElement.name.slowToString()}";
+  }
+
+  String visitPhi(HPhi phi) {
+    StringBuffer buffer = new StringBuffer();
+    buffer.add("Phi(");
+    for (int i = 0; i < phi.inputs.length; i++) {
+      if (i > 0) buffer.add(", ");
+      buffer.add(temporaryId(phi.inputs[i]));
+    }
+    buffer.add(")");
+    return buffer.toString();
+  }
+
+  String visitReturn(HReturn node) => "Return ${temporaryId(node.inputs[0])}";
+
+  String visitShiftLeft(HShiftLeft node) => handleInvokeBinary(node, '<<');
+
+  String visitStatic(HStatic node)
+      => "Static ${node.element.name.slowToString()}";
+
+  String visitLazyStatic(HLazyStatic node)
+      => "LazyStatic ${node.element.name.slowToString()}";
+
+  String visitOneShotInterceptor(HOneShotInterceptor node)
+      => visitInvokeDynamic(node, "one shot interceptor");
+
+  String visitStaticStore(HStaticStore node) {
+    String lhs = node.element.name.slowToString();
+    return "Static $lhs = ${temporaryId(node.inputs[0])}";
+  }
+
+  String visitStringConcat(HStringConcat node) {
+    var leftId = temporaryId(node.left);
+    var rightId = temporaryId(node.right);
+    return "StringConcat: $leftId + $rightId";
+  }
+
+  String visitSubtract(HSubtract node) => handleInvokeBinary(node, '-');
+
+  String visitSwitch(HSwitch node) {
+    StringBuffer buf = new StringBuffer();
+    buf.add("Switch: (");
+    buf.add(temporaryId(node.inputs[0]));
+    buf.add(") ");
+    for (int i = 1; i < node.inputs.length; i++) {
+      buf.add(temporaryId(node.inputs[i]));
+      buf.add(": B");
+      buf.add(node.block.successors[i - 1].id);
+      buf.add(", ");
+    }
+    buf.add("default: B");
+    buf.add(node.block.successors.last.id);
+    return buf.toString();
+  }
+
+  String visitThis(HThis node) => "this";
+
+  String visitThrow(HThrow node) => "Throw ${temporaryId(node.inputs[0])}";
+
+  String visitExitTry(HExitTry node) {
+    return "Exit try";
+  }
+
+  String visitTry(HTry node) {
+    List<HBasicBlock> successors = currentBlock.successors;
+    String tryBlock = 'B${successors[0].id}';
+    String catchBlock = 'none';
+    if (node.catchBlock != null) {
+      catchBlock = 'B${successors[1].id}';
+    }
+
+    String finallyBlock = 'none';
+    if (node.finallyBlock != null) {
+      finallyBlock = 'B${node.finallyBlock.id}';
+    }
+
+    return "Try: $tryBlock, Catch: $catchBlock, Finally: $finallyBlock, "
+        "Join: B${successors.last.id}";
+  }
+
+  String visitTypeGuard(HTypeGuard node) {
+    String type;
+    HType guardedType = node.guardedType;
+    if (guardedType == HType.MUTABLE_ARRAY) {
+      type = "mutable_array";
+    } else if (guardedType == HType.READABLE_ARRAY) {
+      type = "readable_array";
+    } else if (guardedType == HType.EXTENDABLE_ARRAY) {
+      type = "extendable_array";
+    } else if (guardedType == HType.BOOLEAN) {
+      type = "bool";
+    } else if (guardedType == HType.INTEGER) {
+      type = "integer";
+    } else if (guardedType == HType.DOUBLE) {
+      type = "double";
+    } else if (guardedType == HType.NUMBER) {
+      type = "number";
+    } else if (guardedType == HType.STRING) {
+      type = "string";
+    } else if (guardedType == HType.INDEXABLE_PRIMITIVE) {
+      type = "string_or_array";
+    } else if (guardedType == HType.UNKNOWN) {
+      type = 'unknown';
+    } else {
+      throw new CompilerCancelledException('Unexpected type guard: $type');
+    }
+    HInstruction guarded = node.guarded;
+    HInstruction bailoutTarget = node.bailoutTarget;
+    StringBuffer envBuffer = new StringBuffer();
+    List<HInstruction> inputs = node.inputs;
+    assert(inputs.length >= 2);
+    assert(inputs[0] == guarded);
+    assert(inputs[1] == bailoutTarget);
+    for (int i = 2; i < inputs.length; i++) {
+      envBuffer.add(" ${temporaryId(inputs[i])}");
+    }
+    String on = node.isEnabled ? "enabled" : "disabled";
+    String guardedId = temporaryId(node.guarded);
+    String bailoutId = temporaryId(node.bailoutTarget);
+    return "TypeGuard($on): $guardedId is $type bailout: $bailoutId "
+           "env: $envBuffer";
+  }
+
+  String visitIs(HIs node) {
+    String type = node.typeExpression.toString();
+    return "TypeTest: ${temporaryId(node.expression)} is $type";
+  }
+
+  String visitTypeConversion(HTypeConversion node) {
+    return "TypeConversion: ${temporaryId(node.checkedInput)} to ${node.type}";
+  }
+
+  String visitRangeConversion(HRangeConversion node) {
+    return "RangeConversion: ${node.checkedInput}";
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart
new file mode 100644
index 0000000..b6d4746
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart
@@ -0,0 +1,997 @@
+// 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.
+
+part of ssa;
+
+abstract class HType {
+  const HType();
+
+  /**
+   * Returns an [HType] that represents [type] and all types that have
+   * [type] as supertype.
+   */
+  factory HType.fromBoundedType(DartType type,
+                                Compiler compiler,
+                                [bool canBeNull = false]) {
+    Element element = type.element;
+    if (element.kind == ElementKind.TYPE_VARIABLE) {
+      // TODO(ngeoffray): Replace object type with [type].
+      return new HBoundedPotentialPrimitiveType(
+          compiler.objectClass.computeType(compiler), canBeNull, true);
+    }
+
+    if (element == compiler.intClass) {
+      return canBeNull ? HType.INTEGER_OR_NULL : HType.INTEGER;
+    } else if (element == compiler.numClass) {
+      return canBeNull ? HType.NUMBER_OR_NULL : HType.NUMBER;
+    } else if (element == compiler.doubleClass) {
+      return canBeNull ? HType.DOUBLE_OR_NULL : HType.DOUBLE;
+    } else if (element == compiler.stringClass) {
+      return canBeNull ? HType.STRING_OR_NULL : HType.STRING;
+    } else if (element == compiler.boolClass) {
+      return canBeNull ? HType.BOOLEAN_OR_NULL : HType.BOOLEAN;
+    } else if (element == compiler.nullClass) {
+      return HType.NULL;
+    } else if (element == compiler.listClass
+        || Elements.isListSupertype(element, compiler)) {
+      return new HBoundedPotentialPrimitiveArray(type, canBeNull);
+    } else if (Elements.isNumberOrStringSupertype(element, compiler)) {
+      return new HBoundedPotentialPrimitiveNumberOrString(type, canBeNull);
+    } else if (Elements.isStringOnlySupertype(element, compiler)) {
+      return new HBoundedPotentialPrimitiveString(type, canBeNull);
+    } else if (element == compiler.objectClass) {
+      return new HBoundedPotentialPrimitiveType(
+          compiler.objectClass.computeType(compiler), canBeNull, true);
+    } else {
+      return canBeNull ? new HBoundedType.withNull(type)
+                       : new HBoundedType.nonNull(type);
+    }
+  }
+
+  static const HType CONFLICTING = const HConflictingType();
+  static const HType UNKNOWN = const HUnknownType();
+  static const HType BOOLEAN = const HBooleanType();
+  static const HType NUMBER = const HNumberType();
+  static const HType INTEGER = const HIntegerType();
+  static const HType DOUBLE = const HDoubleType();
+  static const HType INDEXABLE_PRIMITIVE = const HIndexablePrimitiveType();
+  static const HType STRING = const HStringType();
+  static const HType READABLE_ARRAY = const HReadableArrayType();
+  static const HType MUTABLE_ARRAY = const HMutableArrayType();
+  static const HType FIXED_ARRAY = const HFixedArrayType();
+  static const HType EXTENDABLE_ARRAY = const HExtendableArrayType();
+  static const HType NULL = const HNullType();
+
+  static const HType BOOLEAN_OR_NULL = const HBooleanOrNullType();
+  static const HType NUMBER_OR_NULL = const HNumberOrNullType();
+  static const HType INTEGER_OR_NULL = const HIntegerOrNullType();
+  static const HType DOUBLE_OR_NULL = const HDoubleOrNullType();
+  static const HType STRING_OR_NULL = const HStringOrNullType();
+
+  bool isConflicting() => identical(this, CONFLICTING);
+  bool isUnknown() => identical(this, UNKNOWN);
+  bool isNull() => false;
+  bool isBoolean() => false;
+  bool isNumber() => false;
+  bool isInteger() => false;
+  bool isDouble() => false;
+  bool isString() => false;
+  bool isBooleanOrNull() => false;
+  bool isNumberOrNull() => false;
+  bool isIntegerOrNull() => false;
+  bool isDoubleOrNull() => false;
+  bool isStringOrNull() => false;
+  bool isIndexablePrimitive() => false;
+  bool isFixedArray() => false;
+  bool isReadableArray() => false;
+  bool isMutableArray() => false;
+  bool isExtendableArray() => false;
+  bool isPrimitive() => false;
+  bool isExact() => false;
+  bool isPrimitiveOrNull() => false;
+  bool isTop() => false;
+
+  bool canBePrimitive() => false;
+  bool canBeNull() => false;
+
+  /** A type is useful it is not unknown, not conflicting, and not null. */
+  bool isUseful() => !isUnknown() && !isConflicting() && !isNull();
+  /** Alias for isReadableArray. */
+  bool isArray() => isReadableArray();
+
+  DartType computeType(Compiler compiler);
+
+  /**
+   * The intersection of two types is the intersection of its values. For
+   * example:
+   *   * INTEGER.intersect(NUMBER) => INTEGER.
+   *   * DOUBLE.intersect(INTEGER) => CONFLICTING.
+   *   * MUTABLE_ARRAY.intersect(READABLE_ARRAY) => MUTABLE_ARRAY.
+   *
+   * When there is no predefined type to represent the intersection returns
+   * [CONFLICTING].
+   *
+   * An intersection with [UNKNOWN] returns the non-UNKNOWN type. An
+   * intersection with [CONFLICTING] returns [CONFLICTING].
+   */
+  HType intersection(HType other, Compiler compiler);
+
+  /**
+   * The union of two types is the union of its values. For example:
+   *   * INTEGER.union(NUMBER) => NUMBER.
+   *   * DOUBLE.union(INTEGER) => NUMBER.
+   *   * MUTABLE_ARRAY.union(READABLE_ARRAY) => READABLE_ARRAY.
+   *
+   * When there is no predefined type to represent the union returns
+   * [UNKNOWN].
+   *
+   * A union with [UNKNOWN] returns [UNKNOWN].
+   * A union of [CONFLICTING] with any other types returns the other type.
+   */
+  HType union(HType other, Compiler compiler);
+}
+
+/** Used to represent [HType.UNKNOWN] and [HType.CONFLICTING]. */
+abstract class HAnalysisType extends HType {
+  final String name;
+  const HAnalysisType(this.name);
+  String toString() => name;
+
+  DartType computeType(Compiler compiler) => null;
+}
+
+class HUnknownType extends HAnalysisType {
+  const HUnknownType() : super("unknown");
+  bool canBePrimitive() => true;
+  bool canBeNull() => true;
+
+  HType union(HType other, Compiler compiler) => this;
+  HType intersection(HType other, Compiler compiler) => other;
+}
+
+class HConflictingType extends HAnalysisType {
+  const HConflictingType() : super("conflicting");
+  bool canBePrimitive() => true;
+  bool canBeNull() => true;
+
+  HType union(HType other, Compiler compiler) => other;
+  HType intersection(HType other, Compiler compiler) => this;
+}
+
+abstract class HPrimitiveType extends HType {
+  const HPrimitiveType();
+  bool isPrimitive() => true;
+  bool canBePrimitive() => true;
+  bool isPrimitiveOrNull() => true;
+}
+
+class HNullType extends HPrimitiveType {
+  const HNullType();
+  bool canBeNull() => true;
+  bool isNull() => true;
+  String toString() => 'null';
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsNullClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isString()) return HType.STRING_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER_OR_NULL;
+    if (other.isDouble()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isBoolean()) return HType.BOOLEAN_OR_NULL;
+    // TODO(ngeoffray): Deal with the type of null more generally.
+    if (other.isReadableArray()) return other.union(this, compiler);
+    if (!other.canBeNull()) return HType.UNKNOWN;
+    return other;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isUnknown()) return HType.NULL;
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (!other.canBeNull()) return HType.CONFLICTING;
+    return HType.NULL;
+  }
+}
+
+abstract class HPrimitiveOrNullType extends HType {
+  const HPrimitiveOrNullType();
+  bool canBePrimitive() => true;
+  bool canBeNull() => true;
+  bool isPrimitiveOrNull() => true;
+}
+
+class HBooleanOrNullType extends HPrimitiveOrNullType {
+  const HBooleanOrNullType();
+  String toString() => "boolean or null";
+  bool isBooleanOrNull() => true;
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsBoolClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.BOOLEAN_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL;
+    if (other.isBoolean()) return HType.BOOLEAN_OR_NULL;
+    if (other.isNull()) return HType.BOOLEAN_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.BOOLEAN_OR_NULL;
+    if (other.isBoolean()) return HType.BOOLEAN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.BOOLEAN;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HBooleanType extends HPrimitiveType {
+  const HBooleanType();
+  bool isBoolean() => true;
+  bool isBooleanOrNull() => true;
+  String toString() => "boolean";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsBoolClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.BOOLEAN;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isBoolean()) return HType.BOOLEAN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL;
+    if (other.isNull()) return HType.BOOLEAN_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.BOOLEAN;
+    if (other.isBooleanOrNull()) return HType.BOOLEAN;
+    if (other.isBoolean()) return HType.BOOLEAN;
+    return HType.CONFLICTING;
+  }
+}
+
+class HNumberOrNullType extends HPrimitiveOrNullType {
+  const HNumberOrNullType();
+  bool isNumberOrNull() => true;
+  String toString() => "number or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsNumberClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.NUMBER_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.NUMBER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.NUMBER_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.NUMBER;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HNumberType extends HPrimitiveType {
+  const HNumberType();
+  bool isNumber() => true;
+  bool isNumberOrNull() => true;
+  String toString() => "number";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsNumberClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.NUMBER;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.NUMBER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.NUMBER;
+    if (other.isNumber()) return other;
+    if (other.isIntegerOrNull()) return HType.INTEGER;
+    if (other.isDoubleOrNull()) return HType.DOUBLE;
+    if (other.isNumberOrNull()) return HType.NUMBER;
+    return HType.CONFLICTING;
+  }
+}
+
+class HIntegerOrNullType extends HNumberOrNullType {
+  const HIntegerOrNullType();
+  bool isIntegerOrNull() => true;
+  String toString() => "integer or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsIntClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.INTEGER_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.INTEGER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.INTEGER_OR_NULL;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isDouble()) return HType.CONFLICTING;
+    if (other.isDoubleOrNull()) return HType.NULL;
+    if (other.isNumber()) return HType.INTEGER;
+    if (other.isNumberOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.INTEGER;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HIntegerType extends HNumberType {
+  const HIntegerType();
+  bool isInteger() => true;
+  bool isIntegerOrNull() => true;
+  String toString() => "integer";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsIntClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.INTEGER;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.INTEGER_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.INTEGER;
+    if (other.isIntegerOrNull()) return HType.INTEGER;
+    if (other.isInteger()) return HType.INTEGER;
+    if (other.isDouble()) return HType.CONFLICTING;
+    if (other.isDoubleOrNull()) return HType.CONFLICTING;
+    if (other.isNumber()) return HType.INTEGER;
+    if (other.isNumberOrNull()) return HType.INTEGER;
+    return HType.CONFLICTING;
+  }
+}
+
+class HDoubleOrNullType extends HNumberOrNullType {
+  const HDoubleOrNullType();
+  bool isDoubleOrNull() => true;
+  String toString() => "double or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsDoubleClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.DOUBLE_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isDouble()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER_OR_NULL;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.DOUBLE_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.DOUBLE_OR_NULL;
+    if (other.isInteger()) return HType.CONFLICTING;
+    if (other.isIntegerOrNull()) return HType.NULL;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.DOUBLE;
+    if (other.isNumberOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.DOUBLE;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HDoubleType extends HNumberType {
+  const HDoubleType();
+  bool isDouble() => true;
+  bool isDoubleOrNull() => true;
+  String toString() => "double";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsDoubleClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.DOUBLE;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL;
+    if (other.isNumber()) return HType.NUMBER;
+    if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL;
+    if (other.isNull()) return HType.DOUBLE_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.DOUBLE;
+    if (other.isIntegerOrNull()) return HType.CONFLICTING;
+    if (other.isInteger()) return HType.CONFLICTING;
+    if (other.isDouble()) return HType.DOUBLE;
+    if (other.isDoubleOrNull()) return HType.DOUBLE;
+    if (other.isNumber()) return HType.DOUBLE;
+    if (other.isNumberOrNull()) return HType.DOUBLE;
+    return HType.CONFLICTING;
+  }
+}
+
+class HIndexablePrimitiveType extends HPrimitiveType {
+  const HIndexablePrimitiveType();
+  bool isIndexablePrimitive() => true;
+  String toString() => "indexable";
+
+  DartType computeType(Compiler compiler) {
+    // TODO(ngeoffray): Represent union types.
+    return null;
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.INDEXABLE_PRIMITIVE;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveString) {
+      // TODO(ngeoffray): Represent union types.
+      return HType.UNKNOWN;
+    }
+    if (other is HBoundedPotentialPrimitiveArray) {
+      // TODO(ngeoffray): Represent union types.
+      return HType.UNKNOWN;
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.INDEXABLE_PRIMITIVE;
+    if (other.isIndexablePrimitive()) return other;
+    if (other is HBoundedPotentialPrimitiveString) return HType.STRING;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HStringOrNullType extends HPrimitiveOrNullType {
+  const HStringOrNullType();
+  bool isStringOrNull() => true;
+  String toString() => "String or null";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsStringClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.STRING_OR_NULL;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isString()) return HType.STRING_OR_NULL;
+    if (other.isStringOrNull()) return HType.STRING_OR_NULL;
+    if (other.isIndexablePrimitive()) {
+      // We don't have a type that represents the nullable indexable
+      // primitive.
+      return HType.UNKNOWN;
+    }
+    if (other is HBoundedPotentialPrimitiveString) {
+      if (other.canBeNull()) {
+        return other;
+      } else {
+        HBoundedType boundedType = other;
+        return new HBoundedPotentialPrimitiveString(boundedType.type, true);
+      }
+    }
+    if (other.isNull()) return HType.STRING_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.STRING_OR_NULL;
+    if (other.isString()) return HType.STRING;
+    if (other.isStringOrNull()) return HType.STRING_OR_NULL;
+    if (other.isArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.STRING;
+    if (other is HBoundedPotentialPrimitiveString) {
+      return other.canBeNull() ? HType.STRING_OR_NULL : HType.STRING;
+    }
+    if (other.isTop()) {
+      return other.canBeNull() ? this : HType.STRING;
+    }
+    if (other.canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+}
+
+class HStringType extends HIndexablePrimitiveType {
+  const HStringType();
+  bool isString() => true;
+  bool isStringOrNull() => true;
+  String toString() => "String";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsStringClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.STRING;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isString()) return HType.STRING;
+    if (other.isStringOrNull()) return HType.STRING_OR_NULL;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveString) return other;
+    if (other.isNull()) return HType.STRING_OR_NULL;
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.STRING;
+    if (other.isString()) return HType.STRING;
+    if (other.isArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.STRING;
+    if (other.isStringOrNull()) return HType.STRING;
+    if (other is HBoundedPotentialPrimitiveString) return HType.STRING;
+    return HType.CONFLICTING;
+  }
+}
+
+class HReadableArrayType extends HIndexablePrimitiveType {
+  const HReadableArrayType();
+  bool isReadableArray() => true;
+  String toString() => "readable array";
+
+  DartType computeType(Compiler compiler) {
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsArrayClass.computeType(compiler);
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.READABLE_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be readable array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.READABLE_ARRAY;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isReadableArray()) return other;
+    if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HMutableArrayType extends HReadableArrayType {
+  const HMutableArrayType();
+  bool isMutableArray() => true;
+  String toString() => "mutable array";
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.MUTABLE_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isMutableArray()) return HType.MUTABLE_ARRAY;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be mutable array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.MUTABLE_ARRAY;
+    if (other.isMutableArray()) return other;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.MUTABLE_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.MUTABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HFixedArrayType extends HMutableArrayType {
+  const HFixedArrayType();
+  bool isFixedArray() => true;
+  String toString() => "fixed array";
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.FIXED_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isFixedArray()) return HType.FIXED_ARRAY;
+    if (other.isMutableArray()) return HType.MUTABLE_ARRAY;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be fixed array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.FIXED_ARRAY;
+    if (other.isFixedArray()) return HType.FIXED_ARRAY;
+    if (other.isExtendableArray()) return HType.CONFLICTING;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.FIXED_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.FIXED_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HExtendableArrayType extends HMutableArrayType {
+  const HExtendableArrayType();
+  bool isExtendableArray() => true;
+  String toString() => "extendable array";
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.EXTENDABLE_ARRAY;
+    if (other.isUnknown()) return HType.UNKNOWN;
+    if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY;
+    if (other.isMutableArray()) return HType.MUTABLE_ARRAY;
+    if (other.isReadableArray()) return HType.READABLE_ARRAY;
+    if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE;
+    if (other is HBoundedPotentialPrimitiveArray) return other;
+    if (other.isNull()) {
+      // TODO(ngeoffray): This should be extendable array or null.
+      return new HBoundedPotentialPrimitiveArray(
+          compiler.listClass.computeType(compiler), true);
+    }
+    return HType.UNKNOWN;
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isUnknown()) return HType.EXTENDABLE_ARRAY;
+    if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isFixedArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.EXTENDABLE_ARRAY;
+    if (other is HBoundedPotentialPrimitiveArray) return HType.EXTENDABLE_ARRAY;
+    return HType.CONFLICTING;
+  }
+}
+
+class HBoundedType extends HType {
+  final DartType type;
+  final bool _canBeNull;
+  final bool _isExact;
+
+  String toString() {
+    return 'BoundedType($type, canBeNull: $_canBeNull, isExact: $_isExact)';
+  }
+
+  bool canBeNull() => _canBeNull;
+
+  bool isExact() => _isExact;
+
+  const HBoundedType(DartType this.type,
+                     [bool canBeNull = false, isExact = false])
+      : _canBeNull = canBeNull, _isExact = isExact;
+  const HBoundedType.exact(DartType type) : this(type, false, true);
+  const HBoundedType.withNull(DartType type) : this(type, true, false);
+  const HBoundedType.nonNull(DartType type) : this(type);
+
+  DartType computeType(Compiler compiler) => type;
+
+  Element lookupMember(SourceString name) {
+    if (!isExact()) return null;
+    ClassElement classElement = type.element;
+    return classElement.lookupMember(name);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    assert(!(isExact() && canBeNull()));
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isNull()) return canBeNull() ? HType.NULL : HType.CONFLICTING;
+
+    if (other is HBoundedType) {
+      HBoundedType temp = other;
+      if (identical(this.type, temp.type)) {
+        // If the types are the same, we return the [HBoundedType]
+        // that has the most restrictive representation: if it's exact
+        // (eg cannot be a subtype), and if it cannot be null.
+        if (isExact()) {
+          return this;
+        } else if (other.isExact()) {
+          return other;
+        } else if (canBeNull()) {
+          return other;
+        } else {
+          return this;
+        }
+      // If one type is a subtype of the other, we return the former,
+      // which is the narrower type.
+      } else if (!type.isMalformed && !other.type.isMalformed) {
+        if (compiler.types.isSubtype(type, other.type)) {
+          return this;
+        } else if (compiler.types.isSubtype(other.type, type)) {
+          return other;
+        }
+      }
+    }
+    if (other.isUnknown()) return this;
+    if (other.canBeNull() && canBeNull()) return HType.NULL;
+    return HType.CONFLICTING;
+  }
+
+  bool operator ==(HType other) {
+    if (other is !HBoundedType) return false;
+    HBoundedType bounded = other;
+    return (identical(type, bounded.type)
+            && identical(canBeNull(), bounded.canBeNull())
+            && identical(isExact(), other.isExact()));
+  }
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedType.withNull(type);
+      }
+    }
+    if (other is HBoundedType) {
+      HBoundedType temp = other;
+      if (!identical(type, temp.type)) return HType.UNKNOWN;
+      if (isExact()) return other;
+      if (other.isExact()) return this;
+      return canBeNull() ? this : other;
+    }
+    if (other.isConflicting()) return this;
+    return HType.UNKNOWN;
+  }
+}
+
+class HBoundedPotentialPrimitiveType extends HBoundedType {
+  final bool _isObject;
+  const HBoundedPotentialPrimitiveType(DartType type,
+                                       bool canBeNull,
+                                       this._isObject)
+      : super(type, canBeNull, false);
+
+  String toString() {
+    return 'BoundedPotentialPrimitiveType($type, canBeNull: $_canBeNull)';
+  }
+
+  bool canBePrimitive() => true;
+  bool isTop() => _isObject;
+
+  HType union(HType other, Compiler compiler) {
+    if (isTop()) {
+      // The union of the top type and another type is the top type.
+      if (!canBeNull() && other.canBeNull()) {
+        return new HBoundedPotentialPrimitiveType(type, true, true);
+      } else {
+        return this;
+      }
+    } else {
+      return super.union(other, compiler);
+    }
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (isTop()) {
+      // The intersection of the top type and any other type is the other type.
+      // TODO(ngeoffray): Also update the canBeNull information.
+      return other;
+    } else {
+      return super.intersection(other, compiler);
+    }
+  }
+}
+
+class HBoundedPotentialPrimitiveNumberOrString
+    extends HBoundedPotentialPrimitiveType {
+  const HBoundedPotentialPrimitiveNumberOrString(DartType type, bool canBeNull)
+      : super(type, canBeNull, false);
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isNumber()) return this;
+    if (other.isNumberOrNull()) {
+      if (canBeNull()) return this;
+      return new HBoundedPotentialPrimitiveNumberOrString(type, true);
+    }
+
+    if (other.isString()) return this;
+    if (other.isStringOrNull()) {
+      if (canBeNull()) return this;
+      return new HBoundedPotentialPrimitiveNumberOrString(type, true);
+    }
+
+    if (other.isNull()) {
+      if (canBeNull()) return this;
+      return new HBoundedPotentialPrimitiveNumberOrString(type, true);
+    }
+
+    return super.union(other, compiler);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isNumber()) return other;
+    if (other.isNumberOrNull()) {
+      if (!canBeNull()) return HType.NUMBER;
+      return other;
+    }
+    if (other.isString()) return other;
+    if (other.isStringOrNull()) {
+      if (!canBeNull()) return HType.STRING;
+      return other;
+    }
+    return super.intersection(other, compiler);
+  }
+}
+
+class HBoundedPotentialPrimitiveArray extends HBoundedPotentialPrimitiveType {
+  const HBoundedPotentialPrimitiveArray(DartType type, bool canBeNull)
+      : super(type, canBeNull, false);
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isString()) return HType.UNKNOWN;
+    if (other.isReadableArray()) return this;
+    // TODO(ngeoffray): implement union types.
+    if (other.isIndexablePrimitive()) return HType.UNKNOWN;
+    if (other.isNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedPotentialPrimitiveArray(type, true);
+      }
+    }
+    return super.union(other, compiler);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isString()) return HType.CONFLICTING;
+    if (other.isReadableArray()) return other;
+    if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY;
+    return super.intersection(other, compiler);
+  }
+}
+
+class HBoundedPotentialPrimitiveString extends HBoundedPotentialPrimitiveType {
+  const HBoundedPotentialPrimitiveString(DartType type, bool canBeNull)
+      : super(type, canBeNull, false);
+
+  bool isPrimitiveOrNull() => true;
+
+  HType union(HType other, Compiler compiler) {
+    if (other.isString()) return this;
+    if (other.isStringOrNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedPotentialPrimitiveString(type, true);
+      }
+    }
+    if (other.isNull()) {
+      if (canBeNull()) {
+        return this;
+      } else {
+        return new HBoundedPotentialPrimitiveString(type, true);
+      }
+    }
+    // TODO(ngeoffray): implement union types.
+    if (other.isIndexablePrimitive()) return HType.UNKNOWN;
+    return super.union(other, compiler);
+  }
+
+  HType intersection(HType other, Compiler compiler) {
+    if (other.isConflicting()) return HType.CONFLICTING;
+    if (other.isString()) return HType.STRING;
+    if (other.isStringOrNull()) {
+      return canBeNull() ? HType.STRING_OR_NULL : HType.STRING;
+    }
+    if (other.isReadableArray()) return HType.CONFLICTING;
+    if (other.isIndexablePrimitive()) return HType.STRING;
+    return super.intersection(other, compiler);
+  }
+}
+
+class HTypeMap {
+  // Approximately 85% of methods in the sample "swarm" have less than
+  // 32 instructions.
+  static const int INITIAL_SIZE = 32;
+
+  List<HType> _list = new List<HType>()..length = INITIAL_SIZE;
+
+  operator [](HInstruction instruction) {
+    HType result;
+    if (instruction.id < _list.length) result = _list[instruction.id];
+    if (result == null) return instruction.guaranteedType;
+    return result;
+  }
+
+  operator []=(HInstruction instruction, HType value) {
+    int length = _list.length;
+    int id = instruction.id;
+    if (length <= id) {
+      if (id + 1 < length * 2) {
+        _list.length = length * 2;
+      } else {
+        _list.length = id + 1;
+      }
+    }
+    _list[id] = value;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart
new file mode 100644
index 0000000..ee0f1a1
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart
@@ -0,0 +1,210 @@
+// 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.
+
+part of ssa;
+
+class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase {
+
+  final Map<int, HInstruction> workmap;
+  final List<int> worklist;
+  final Map<HInstruction, Function> pendingOptimizations;
+  final HTypeMap types;
+
+  final Compiler compiler;
+  String get name => 'type propagator';
+
+  SsaTypePropagator(this.compiler, this.types)
+      : workmap = new Map<int, HInstruction>(),
+        worklist = new List<int>(),
+        pendingOptimizations = new Map<HInstruction, Function>();
+
+  HType computeType(HInstruction instruction) {
+    if (instruction.hasGuaranteedType()) return instruction.guaranteedType;
+    return instruction.computeTypeFromInputTypes(types, compiler);
+  }
+
+  // Re-compute and update the type of the instruction. Returns
+  // whether or not the type was changed.
+  bool updateType(HInstruction instruction) {
+    // The [updateType] method is invoked when one of the inputs of
+    // the instruction changes its type. That gives us a new
+    // opportunity to consider this instruction for optimizations.
+    considerForArgumentTypeOptimization(instruction);
+    // Compute old and new types.
+    HType oldType = types[instruction];
+    HType newType = computeType(instruction);
+    // We unconditionally replace the propagated type with the new type. The
+    // computeType must make sure that we eventually reach a stable state.
+    types[instruction] = newType;
+    return oldType != newType;
+  }
+
+  void considerForArgumentTypeOptimization(HInstruction instruction) {
+    // Update the pending optimizations map based on the potentially
+    // new types of the operands. If the operand types no longer allow
+    // us to optimize, we remove the pending optimization.
+    if (instruction is !HInvokeDynamicMethod) return;
+    HInvokeDynamicMethod invoke = instruction;
+    if (instruction.specializer is !BinaryArithmeticSpecializer) return;
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isNumber(types) && !right.isNumber(types)) {
+      pendingOptimizations[instruction] = () {
+        // This callback function is invoked after we're done
+        // propagating types. The types shouldn't have changed.
+        assert(left.isNumber(types) && !right.isNumber(types));
+        convertInput(instruction, right, HType.NUMBER);
+      };
+    } else {
+      pendingOptimizations.remove(instruction);
+    }
+  }
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+    processWorklist();
+  }
+
+  visitBasicBlock(HBasicBlock block) {
+    if (block.isLoopHeader()) {
+      block.forEachPhi((HPhi phi) {
+        // Set the initial type for the phi. We're not using the type
+        // the phi thinks it has because new optimizations may imply
+        // changing it.
+        // In theory we would need to mark
+        // the type of all other incoming edges as "unitialized" and take this
+        // into account when doing the propagation inside the phis. Just
+        // setting the propagated type is however easier.
+        types[phi] = types[phi.inputs[0]];
+        addToWorkList(phi);
+      });
+    } else {
+      block.forEachPhi((HPhi phi) {
+        if (updateType(phi)) {
+          addDependentInstructionsToWorkList(phi);
+        }
+      });
+    }
+
+    HInstruction instruction = block.first;
+    while (instruction != null) {
+      if (updateType(instruction)) {
+        addDependentInstructionsToWorkList(instruction);
+      }
+      instruction = instruction.next;
+    }
+  }
+
+  void processWorklist() {
+    do {
+      while (!worklist.isEmpty) {
+        int id = worklist.removeLast();
+        HInstruction instruction = workmap[id];
+        assert(instruction != null);
+        workmap.remove(id);
+        if (updateType(instruction)) {
+          addDependentInstructionsToWorkList(instruction);
+        }
+      }
+      // While processing the optimizable arithmetic instructions, we
+      // may discover better type information for dominated users of
+      // replaced operands, so we may need to take another stab at
+      // emptying the worklist afterwards.
+      processPendingOptimizations();
+    } while (!worklist.isEmpty);
+  }
+
+  void addDependentInstructionsToWorkList(HInstruction instruction) {
+    for (int i = 0, length = instruction.usedBy.length; i < length; i++) {
+      // The non-speculative type propagator only propagates types forward. We
+      // thus only need to add the users of the [instruction] to the list.
+      addToWorkList(instruction.usedBy[i]);
+    }
+  }
+
+  void addToWorkList(HInstruction instruction) {
+    final int id = instruction.id;
+    if (!workmap.containsKey(id)) {
+      worklist.add(id);
+      workmap[id] = instruction;
+    }
+  }
+
+  void processPendingOptimizations() {
+    pendingOptimizations.forEach((instruction, action) => action());
+    pendingOptimizations.clear();
+  }
+
+  void convertInput(HInstruction instruction, HInstruction input, HType type) {
+    HTypeConversion converted =
+        new HTypeConversion.argumentTypeCheck(type, input);
+    instruction.block.addBefore(instruction, converted);
+    Set<HInstruction> dominatedUsers = input.dominatedUsers(instruction);
+    for (HInstruction user in dominatedUsers) {
+      user.changeUse(input, converted);
+      addToWorkList(user);
+    }
+  }
+}
+
+class SsaSpeculativeTypePropagator extends SsaTypePropagator {
+  final String name = 'speculative type propagator';
+  SsaSpeculativeTypePropagator(Compiler compiler, HTypeMap types)
+      : super(compiler, types);
+
+  void addDependentInstructionsToWorkList(HInstruction instruction) {
+    // The speculative type propagator propagates types forward and backward.
+    // Not only do we need to add the users of the [instruction] to the list.
+    // We also need to add the inputs fo the [instruction], since they might
+    // want to propagate the desired outgoing type.
+    for (int i = 0, length = instruction.usedBy.length; i < length; i++) {
+      addToWorkList(instruction.usedBy[i]);
+    }
+    for (int i = 0, length = instruction.inputs.length; i < length; i++) {
+      addToWorkList(instruction.inputs[i]);
+    }
+  }
+
+  HType computeDesiredType(HInstruction instruction) {
+    HType desiredType = HType.UNKNOWN;
+    for (final user in instruction.usedBy) {
+      HType userType =
+          user.computeDesiredTypeForInput(instruction, types, compiler);
+      // Mainly due to the "if (true)" added by hackAroundPossiblyAbortingBody
+      // in builder.dart uninitialized variables will propagate a type of null
+      // which will result in a conflicting type when combined with a primitive
+      // type. Avoid this to improve generated code.
+      // TODO(sgjesse): Reconcider this when hackAroundPossiblyAbortingBody
+      // has been removed.
+      if (desiredType.isPrimitive() && userType == HType.NULL) continue;
+      desiredType = desiredType.intersection(userType, compiler);
+      // No need to continue if two users disagree on the type.
+      if (desiredType.isConflicting()) break;
+    }
+    return desiredType;
+  }
+
+  HType computeType(HInstruction instruction) {
+    // Once we are in a conflicting state don't update the type anymore.
+    HType oldType = types[instruction];
+    if (oldType.isConflicting()) return oldType;
+
+    HType newType = super.computeType(instruction);
+    // [computeDesiredType] goes to all usedBys and lets them compute their
+    // desired type. By setting the [newType] here we give them more context to
+    // work with.
+    types[instruction] = newType;
+    HType desiredType = computeDesiredType(instruction);
+    // If the desired type is conflicting just return the computed type.
+    if (desiredType.isConflicting()) return newType;
+    // TODO(ngeoffray): Allow speculative optimizations on
+    // non-primitive types?
+    if (!desiredType.isPrimitive()) return newType;
+    return newType.intersection(desiredType, compiler);
+  }
+
+  // Do not use speculative argument type optimization for now.
+  void considerForArgumentTypeOptimization(HInstruction instruction) { }
+
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart
new file mode 100644
index 0000000..5e9598e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart
@@ -0,0 +1,181 @@
+// Copyright (c) 2011, 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.
+
+part of ssa;
+
+class HValidator extends HInstructionVisitor {
+  bool isValid = true;
+  HGraph graph;
+
+  void visitGraph(HGraph visitee) {
+    graph = visitee;
+    visitDominatorTree(visitee);
+  }
+
+  void markInvalid(String reason) {
+    print(reason);
+    isValid = false;
+  }
+
+  // Note that during construction of the Ssa graph the basic blocks are
+  // not required to be valid yet.
+  void visitBasicBlock(HBasicBlock block) {
+    currentBlock = block;
+    if (!isValid) return;  // Don't need to continue if we are already invalid.
+
+    // Test that the last instruction is a branching instruction and that the
+    // basic block contains the branch-target.
+    if (block.first == null || block.last == null) {
+      markInvalid("empty block");
+    }
+    if (block.last is !HControlFlow) {
+      markInvalid("block ends with non-tail node.");
+    }
+    if (block.last is HIf && block.successors.length != 2) {
+      markInvalid("If node without two successors");
+    }
+    if (block.last is HConditionalBranch && block.successors.length != 2) {
+      markInvalid("Conditional node without two successors");
+    }
+    if (block.last is HGoto && block.successors.length != 1) {
+      markInvalid("Goto node with not exactly one successor");
+    }
+    if (block.last is HJump && block.successors.length != 1) {
+      markInvalid("Break or continue node without one successor");
+    }
+    if (block.last is HReturn &&
+        (block.successors.length != 1 || !block.successors[0].isExitBlock())) {
+      markInvalid("Return node with > 1 succesor or not going to exit-block");
+    }
+    if (block.last is HExit && !block.successors.isEmpty) {
+      markInvalid("Exit block with successor");
+    }
+    if (block.last is HThrow && !block.successors.isEmpty) {
+      markInvalid("Throw block with successor");
+    }
+
+    if (block.successors.isEmpty &&
+        block.last is !HThrow &&
+        !block.isExitBlock()) {
+      markInvalid("Non-exit or throw block without successor");
+    }
+
+    // Check that successors ids are always higher than the current one.
+    // TODO(floitsch): this is, of course, not true for back-branches.
+    if (block.id == null) markInvalid("block without id");
+    for (HBasicBlock successor in block.successors) {
+      if (!isValid) break;
+      if (successor.id == null) markInvalid("successor without id");
+      if (successor.id <= block.id && !successor.isLoopHeader()) {
+        markInvalid("successor with lower id, but not a loop-header");
+      }
+    }
+
+    // Check that the entries in the dominated-list are sorted.
+    int lastId = 0;
+    for (HBasicBlock dominated in block.dominatedBlocks) {
+      if (!isValid) break;
+      if (!identical(dominated.dominator, block)) {
+        markInvalid("dominated block not pointing back");
+      }
+      if (dominated.id == null || dominated.id <= lastId) {
+        markInvalid("dominated.id == null or dominated has <= id");
+      }
+      lastId = dominated.id;
+    }
+
+    if (!isValid) return;
+    block.forEachPhi(visitInstruction);
+
+    // Check that the blocks of the parameters of a phi are dominating the
+    // corresponding predecessor block. Note that a block dominates
+    // itself.
+    block.forEachPhi((HPhi phi) {
+      for (int i = 0; i < phi.inputs.length; i++) {
+        HInstruction input = phi.inputs[i];
+        if (!input.block.dominates(block.predecessors[i])) {
+          markInvalid("Definition does not dominate use");
+        }
+      }
+    });
+
+    // Check that the blocks of the inputs of an instruction dominate the
+    // instruction's block.
+    block.forEachInstruction((HInstruction instruction) {
+      for (HInstruction input in instruction.inputs) {
+        if (!input.block.dominates(block)) {
+          markInvalid("Definition does not dominate use");
+        }
+      }
+    });
+
+    super.visitBasicBlock(block);
+  }
+
+  /** Returns how often [instruction] is contained in [instructions]. */
+  static int countInstruction(List<HInstruction> instructions,
+                              HInstruction instruction) {
+    int result = 0;
+    for (int i = 0; i < instructions.length; i++) {
+      if (identical(instructions[i], instruction)) result++;
+    }
+    return result;
+  }
+
+  /**
+   * Returns true if the predicate returns true for every instruction in the
+   * list. The argument to [f] is an instruction with the count of how often
+   * it appeared in the list [instructions].
+   */
+  static bool everyInstruction(List<HInstruction> instructions, Function f) {
+    var copy = new List<HInstruction>.from(instructions);
+    // TODO(floitsch): there is currently no way to sort HInstructions before
+    // we have assigned an ID. The loop is therefore O(n^2) for now.
+    for (int i = 0; i < copy.length; i++) {
+      var current = copy[i];
+      if (current == null) continue;
+      int count = 1;
+      for (int j = i + 1; j < copy.length; j++) {
+        if (identical(copy[j], current)) {
+          copy[j] = null;
+          count++;
+        }
+      }
+      if (!f(current, count)) return false;
+    }
+    return true;
+  }
+
+  void visitInstruction(HInstruction instruction) {
+    // Verifies that we are in the use list of our inputs.
+    bool hasCorrectInputs() {
+      bool inBasicBlock = instruction.isInBasicBlock();
+      return everyInstruction(instruction.inputs, (input, count) {
+        if (inBasicBlock) {
+          return countInstruction(input.usedBy, instruction) == count;
+        } else {
+          return countInstruction(input.usedBy, instruction) == 0;
+        }
+      });
+    }
+
+    // Verifies that all our uses have us in their inputs.
+    bool hasCorrectUses() {
+      if (!instruction.isInBasicBlock()) return true;
+      return everyInstruction(instruction.usedBy, (use, count) {
+        return countInstruction(use.inputs, instruction) == count;
+      });
+    }
+
+    if (!identical(instruction.block, currentBlock)) {
+      markInvalid("Instruction in wrong block");
+    }
+    if (!hasCorrectInputs()) {
+      markInvalid("Incorrect inputs");
+    }
+    if (!hasCorrectUses()) {
+      markInvalid("Incorrect uses");
+    }
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart
new file mode 100644
index 0000000..c28d5c4
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart
@@ -0,0 +1,996 @@
+// 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.
+
+part of ssa;
+
+
+class ValueRangeInfo {
+  final ConstantSystem constantSystem;
+
+  IntValue intZero;
+  IntValue intOne;
+
+  ValueRangeInfo(this.constantSystem) {
+    intZero = newIntValue(0);
+    intOne = newIntValue(1);
+  }
+
+  Value newIntValue(int value) {
+    return new IntValue(value, this);
+  }
+
+  Value newInstructionValue(HInstruction instruction) {
+    return new InstructionValue(instruction, this);
+  }
+
+  Value newLengthValue(HInstruction instruction) {
+    return new LengthValue(instruction, this);
+  }
+
+  Value newAddValue(Value left, Value right) {
+    return new AddValue(left, right, this);
+  }
+
+  Value newSubtractValue(Value left, Value right) {
+    return new SubtractValue(left, right, this);
+  }
+
+  Value newNegateValue(Value value) {
+    return new NegateValue(value, this);
+  }
+
+  Range newRange(Value low, Value up) {
+    return new Range(low, up, this);
+  }
+
+  Range newUnboundRange() {
+    return new Range.unbound(this);
+  }
+
+  Range newNormalizedRange(Value low, Value up) {
+    return new Range.normalize(low, up, this);
+  }
+}
+
+/**
+ * A [Value] represents both symbolic values like the value of a
+ * parameter, or the length of an array, and concrete values, like
+ * constants.
+ */
+abstract class Value {
+  final ValueRangeInfo info;
+  const Value([this.info = null]);
+
+  Value operator +(Value other) => const UnknownValue();
+  Value operator -(Value other) => const UnknownValue();
+  Value operator -()  => const UnknownValue();
+  Value operator &(Value other) => const UnknownValue();
+
+  Value min(Value other) {
+    if (this == other) return this;
+    if (other == const MinIntValue()) return other;
+    if (other == const MaxIntValue()) return this;
+    Value value = this - other;
+    if (value.isPositive) return other;
+    if (value.isNegative) return this;
+    return const UnknownValue();
+  }
+
+  Value max(Value other) {
+    if (this == other) return this;
+    if (other == const MinIntValue()) return this;
+    if (other == const MaxIntValue()) return other;
+    Value value = this - other;
+    if (value.isPositive) return this;
+    if (value.isNegative) return other;
+    return const UnknownValue();
+  }
+
+  bool get isNegative => false;
+  bool get isPositive => false;
+  bool get isZero => false;
+}
+
+/**
+ * An [IntValue] contains a constant integer value.
+ */
+class IntValue extends Value {
+  final int value;
+
+  const IntValue(this.value, info) : super(info);
+
+  Value operator +(other) {
+    if (other.isZero) return this;
+    if (other is !IntValue) return other + this;
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.add.fold(
+        constantSystem.createInt(value), constantSystem.createInt(other.value));
+    if (!constant.isInt()) return const UnknownValue();
+    return info.newIntValue(constant.value);
+  }
+
+  Value operator -(other) {
+    if (other.isZero) return this;
+    if (other is !IntValue) return -other + this;
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.subtract.fold(
+        constantSystem.createInt(value), constantSystem.createInt(other.value));
+    if (!constant.isInt()) return const UnknownValue();
+    return info.newIntValue(constant.value);
+  }
+
+  Value operator -() {
+    if (isZero) return this;
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.negate.fold(
+        constantSystem.createInt(value));
+    if (!constant.isInt()) return const UnknownValue();
+    return info.newIntValue(constant.value);
+  }
+
+  Value operator &(other) {
+    if (other is !IntValue) return const UnknownValue();
+    ConstantSystem constantSystem = info.constantSystem;
+    var constant = constantSystem.bitAnd.fold(
+        constantSystem.createInt(value), constantSystem.createInt(other.value));
+    return info.newIntValue(constant.value);
+  }
+
+  Value min(other) {
+    if (other is !IntValue) return other.min(this);
+    return this.value < other.value ? this : other;
+  }
+
+  Value max(other) {
+    if (other is !IntValue) return other.max(this);
+    return this.value < other.value ? other : this;
+  }
+
+  bool operator ==(other) {
+    if (other is !IntValue) return false;
+    return this.value == other.value;
+  }
+
+  String toString() => 'IntValue $value';
+  bool get isNegative => value < 0;
+  bool get isPositive => value >= 0;
+  bool get isZero => value == 0;
+}
+
+/**
+ * The [MaxIntValue] represents the maximum value an integer can have,
+ * which is currently +infinity.
+ */
+class MaxIntValue extends Value {
+  const MaxIntValue() : super(null);
+  Value operator +(Value other) => this;
+  Value operator -(Value other) => this;
+  Value operator -() => const MinIntValue();
+  Value min(Value other) => other;
+  Value max(Value other) => this;
+  String toString() => 'Max';
+  bool get isNegative => false;
+  bool get isPositive => true;
+}
+
+/**
+ * The [MinIntValue] represents the minimum value an integer can have,
+ * which is currently -infinity.
+ */
+class MinIntValue extends Value {
+  const MinIntValue() : super(null);
+  Value operator +(Value other) => this;
+  Value operator -(Value other) => this;
+  Value operator -() => const MaxIntValue();
+  Value min(Value other) => this;
+  Value max(Value other) => other;
+  String toString() => 'Min';
+  bool get isNegative => true;
+  bool get isPositive => false;
+}
+
+/**
+ * The [UnknownValue] is the sentinel in our analysis to mark an
+ * operation that could not be done because of too much complexity.
+ */
+class UnknownValue extends Value {
+  const UnknownValue() : super(null);
+  Value operator +(Value other) => const UnknownValue();
+  Value operator -(Value other) => const UnknownValue();
+  Value operator -() => const UnknownValue();
+  Value min(Value other) => const UnknownValue();
+  Value max(Value other) => const UnknownValue();
+  bool get isNegative => false;
+  bool get isPositive => false;
+  String toString() => 'Unknown';
+}
+
+/**
+ * A symbolic value representing an [HInstruction].
+ */
+class InstructionValue extends Value {
+  final HInstruction instruction;
+  InstructionValue(this.instruction, info) : super(info);
+
+  bool operator ==(other) {
+    if (other is !InstructionValue) return false;
+    return this.instruction == other.instruction;
+  }
+
+  Value operator +(Value other) {
+    if (other.isZero) return this;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newSubtractValue(this, -other);
+      }
+      return info.newAddValue(this, other);
+    }
+    if (other is InstructionValue) {
+      return info.newAddValue(this, other);
+    }
+    return other + this;
+  }
+
+  Value operator -(Value other) {
+    if (other.isZero) return this;
+    if (this == other) return info.intZero;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newAddValue(this, -other);
+      }
+      return info.newSubtractValue(this, other);
+    }
+    if (other is InstructionValue) {
+      return info.newSubtractValue(this, other);
+    }
+    return -other + this;
+  }
+
+  Value operator -() {
+    return info.newNegateValue(this);
+  }
+
+  bool get isNegative => false;
+  bool get isPositive => false;
+
+  String toString() => 'Instruction: $instruction';
+}
+
+/**
+ * Special value for instructions that represent the length of an
+ * array. The difference with an [InstructionValue] is that we know
+ * the value is positive.
+ */
+class LengthValue extends InstructionValue {
+  LengthValue(HInstruction instruction, info) : super(instruction, info);
+  bool get isPositive => true;
+  String toString() => 'Length: $instruction';
+}
+
+/**
+ * Represents a binary operation on two [Value], where the operation
+ * did not yield a canonical value.
+ */
+class BinaryOperationValue extends Value {
+  final Value left;
+  final Value right;
+  BinaryOperationValue(this.left, this.right, info) : super(info);
+}
+
+class AddValue extends BinaryOperationValue {
+  AddValue(left, right, info) : super(left, right, info);
+
+  bool operator ==(other) {
+    if (other is !AddValue) return false;
+    return (left == other.left && right == other.right)
+      || (left == other.right && right == other.left);
+  }
+
+  Value operator -() => -left - right;
+
+  Value operator +(Value other) {
+    if (other.isZero) return this;
+    Value value = left + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value + right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = right + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left + value;
+    }
+    return const UnknownValue();
+  }
+
+  Value operator -(Value other) {
+    if (other.isZero) return this;
+    Value value = left - other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value + right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = right - other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left + value;
+    }
+    return const UnknownValue();
+  }
+
+  bool get isNegative => left.isNegative && right.isNegative;
+  bool get isPositive => left.isPositive && right.isPositive;
+  String toString() => '$left + $right';
+}
+
+class SubtractValue extends BinaryOperationValue {
+  SubtractValue(left, right, info) : super(left, right, info);
+
+  bool operator ==(other) {
+    if (other is !SubtractValue) return false;
+    return left == other.left && right == other.right;
+  }
+
+  Value operator -() => right - left;
+
+  Value operator +(Value other) {
+    if (other.isZero) return this;
+    Value value = left + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value - right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = other - right;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left + value;
+    }
+    return const UnknownValue();
+  }
+
+  Value operator -(Value other) {
+    if (other.isZero) return this;
+    Value value = left - other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return value - right;
+    }
+    // If the result is not simple enough, we try the same approach
+    // with [right].
+    value = right + other;
+    if (value != const UnknownValue() && value is! BinaryOperationValue) {
+      return left - value;
+    }
+    return const UnknownValue();
+  }
+
+  bool get isNegative => left.isNegative && right.isPositive;
+  bool get isPositive => left.isPositive && right.isNegative;
+  String toString() => '$left - $right';
+}
+
+class NegateValue extends Value {
+  final Value value;
+  NegateValue(this.value, info) : super(info);
+
+  bool operator ==(other) {
+    if (other is !NegateValue) return false;
+    return value == other.value;
+  }
+
+  Value operator +(other) {
+    if (other.isZero) return this;
+    if (other == value) return info.intZero;
+    if (other is NegateValue) return this - other.value;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newSubtractValue(this, -other);
+      }
+      return info.newSubtractValue(other, value);
+    }
+    if (other is InstructionValue) {
+      return info.newSubtractValue(other, value);
+    }
+    return other - value;
+  }
+
+  Value operator &(Value other) => const UnknownValue();
+
+  Value operator -(other) {
+    if (other.isZero) return this;
+    if (other is IntValue) {
+      if (other.isNegative) {
+        return info.newSubtractValue(-other, value);
+      }
+      return info.newSubtractValue(this, other);
+    }
+    if (other is InstructionValue) {
+      return info.newSubtractValue(this, other);
+    }
+    if (other is NegateValue) return this + other.value;
+    return -other - value;
+  }
+
+  Value operator -() => value;
+
+  bool get isNegative => value.isPositive;
+  bool get isPositive => value.isNegative;
+  String toString() => '-$value';
+}
+
+/**
+ * A [Range] represents the possible integer values an instruction
+ * can have, from its [lower] bound to its [upper] bound, both
+ * included.
+ */
+class Range {
+  final Value lower;
+  final Value upper;
+  final ValueRangeInfo info;
+  Range(this.lower, this.upper, this.info);
+
+  Range.unbound(info) : this(const MinIntValue(), const MaxIntValue(), info);
+
+  /**
+   * Checks if the given values are unknown, and creates a
+   * range that does not have any unknown values.
+   */
+  Range.normalize(Value low, Value up, info) : this(
+      low == const UnknownValue() ? const MinIntValue() : low,
+      up == const UnknownValue() ? const MaxIntValue() : up,
+      info);
+
+  Range union(Range other) {
+    return info.newNormalizedRange(
+        lower.min(other.lower), upper.max(other.upper));
+  }
+
+  intersection(Range other) {
+    Value low = lower.max(other.lower);
+    Value up = upper.min(other.upper);
+    // If we could not compute max or min, pick a value in the two
+    // ranges, with priority to [IntValue]s because they are simpler.
+    if (low == const UnknownValue()) {
+      if (lower is IntValue) low = lower;
+      else if (other.lower is IntValue) low = other.lower;
+      else low = lower;
+    }
+    if (up == const UnknownValue()) {
+      if (upper is IntValue) up = upper;
+      else if (other.upper is IntValue) up = other.upper;
+      else up = upper;
+    }
+    return info.newRange(low, up);
+  }
+
+  Range operator +(Range other) {
+    return info.newNormalizedRange(lower + other.lower, upper + other.upper);
+  }
+
+  Range operator -(Range other) {
+    return info.newNormalizedRange(lower - other.upper, upper - other.lower);
+  }
+
+  Range operator -() {
+    return info.newNormalizedRange(-upper, -lower);
+  }
+
+  Range operator &(Range other) {
+    if (isSingleValue
+        && other.isSingleValue
+        && lower is IntValue
+        && other.lower is IntValue) {
+      return info.newRange(lower & other.lower, upper & other.upper);
+    }
+    if (isPositive && other.isPositive) {
+      Value up = upper.min(other.upper);
+      if (up == const UnknownValue()) {
+        // If we could not find a trivial bound, just try to use the
+        // one that is an int.
+        up = upper is IntValue ? upper : other.upper;
+        // Make sure we get the same upper bound, whether it's a & b
+        // or b & a.
+        if (up is! IntValue && upper != other.upper) up = const MaxIntValue();
+      }
+      return info.newRange(info.intZero, up);
+    } else if (isPositive) {
+      return info.newRange(info.intZero, upper);
+    } else if (other.isPositive) {
+      return info.newRange(info.intZero, other.upper);
+    } else {
+      return info.newUnboundRange();
+    }
+  }
+
+  bool operator ==(other) {
+    if (other is! Range) return false;
+    return other.lower == lower && other.upper == upper;
+  }
+
+  bool operator <(Range other) {
+    return upper != other.lower && upper.min(other.lower) == upper;
+  }
+
+  bool operator >(Range other) {
+    return lower != other.upper && lower.max(other.upper) == lower;
+  }
+
+  bool operator <=(Range other) {
+    return upper.min(other.lower) == upper;
+  }
+
+  bool operator >=(Range other) {
+    return lower.max(other.upper) == lower;
+  }
+
+  bool get isNegative => upper.isNegative;
+  bool get isPositive => lower.isPositive;
+  bool get isSingleValue => lower == upper;
+
+  String toString() => '[$lower, $upper]';
+}
+
+/**
+ * Visits the graph in dominator order, and computes value ranges for
+ * integer instructions. While visiting the graph, this phase also
+ * removes unnecessary bounds checks, and comparisons that are proven
+ * to be true or false.
+ */
+class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
+  String get name => 'SSA value range builder';
+
+  /**
+   * List of [HRangeConversion] instructions created by the phase. We
+   * save them here in order to remove them once the phase is done.
+   */
+  final List<HRangeConversion> conversions = <HRangeConversion>[];
+
+  /**
+   * Value ranges for integer instructions. This map gets populated by
+   * the dominator tree visit.
+   */
+  final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>();
+
+  final ConstantSystem constantSystem;
+  final HTypeMap types;
+  final ValueRangeInfo info;
+
+  CodegenWorkItem work;
+  HGraph graph;
+
+  SsaValueRangeAnalyzer(constantSystem, this.types, this.work)
+      : info = new ValueRangeInfo(constantSystem),
+        this.constantSystem = constantSystem;
+
+  void visitGraph(HGraph graph) {
+    this.graph = graph;
+    visitDominatorTree(graph);
+    // We remove the range conversions after visiting the graph so
+    // that the graph does not get polluted with these instructions
+    // only necessary for this phase.
+    removeRangeConversion();
+  }
+
+  void removeRangeConversion() {
+    conversions.forEach((HRangeConversion instruction) {
+      instruction.block.rewrite(instruction, instruction.inputs[0]);;
+      instruction.block.remove(instruction);
+    });
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+
+    void visit(HInstruction instruction) {
+      Range range = instruction.accept(this);
+      if (instruction.isInteger(types)) {
+        assert(range != null);
+        ranges[instruction] = range;
+      }
+    }
+
+    block.forEachPhi(visit);
+    block.forEachInstruction(visit);
+  }
+
+  Range visitInstruction(HInstruction instruction) {
+    return info.newUnboundRange();
+  }
+
+  Range visitParameterValue(HParameterValue parameter) {
+    if (!parameter.isInteger(types)) return info.newUnboundRange();
+    Value value = info.newInstructionValue(parameter);
+    return info.newRange(value, value);
+  }
+
+  Range visitPhi(HPhi phi) {
+    if (!phi.isInteger(types)) return info.newUnboundRange();
+    if (phi.block.isLoopHeader()) {
+      Range range = tryInferLoopPhiRange(phi);
+      if (range == null) return info.newUnboundRange();
+      return range;
+    }
+
+    Range range = ranges[phi.inputs[0]];
+    for (int i = 1; i < phi.inputs.length; i++) {
+      range = range.union(ranges[phi.inputs[i]]);
+    }
+    return range;
+  }
+
+  Range tryInferLoopPhiRange(HPhi phi) {
+    HInstruction update = phi.inputs[1];
+    return update.accept(new LoopUpdateRecognizer(phi, ranges, types, info));
+  }
+
+  Range visitConstant(HConstant constant) {
+    if (!constant.isInteger(types)) return info.newUnboundRange();
+    IntConstant constantInt = constant.constant;
+    Value value = info.newIntValue(constantInt.value);
+    return info.newRange(value, value);
+  }
+
+  Range visitFieldGet(HFieldGet fieldGet) {
+    if (!fieldGet.isInteger(types)) return info.newUnboundRange();
+    if (!fieldGet.receiver.isIndexablePrimitive(types)) {
+      return visitInstruction(fieldGet);
+    }
+    LengthValue value = info.newLengthValue(fieldGet);
+    // We know this range is above zero. To simplify the analysis, we
+    // put the zero value as the lower bound of this range. This
+    // allows to easily remove the second bound check in the following
+    // expression: a[1] + a[0].
+    return info.newRange(info.intZero, value);
+  }
+
+  Range visitBoundsCheck(HBoundsCheck check) {
+    // Save the next instruction, in case the check gets removed.
+    HInstruction next = check.next;
+    Range indexRange = ranges[check.index];
+    Range lengthRange = ranges[check.length];
+
+    // Check if the index is strictly below the upper bound of the length
+    // range.
+    Value maxIndex = lengthRange.upper - info.intOne;
+    bool belowLength = maxIndex != const MaxIntValue()
+        && indexRange.upper.min(maxIndex) == indexRange.upper;
+
+    // Check if the index is strictly below the lower bound of the length
+    // range.
+    belowLength = belowLength
+        || (indexRange.upper != lengthRange.lower
+            && indexRange.upper.min(lengthRange.lower) == indexRange.upper);
+    if (indexRange.isPositive && belowLength) {
+      check.block.rewrite(check, check.index);
+      check.block.remove(check);
+    } else if (indexRange.isNegative || lengthRange < indexRange) {
+      check.staticChecks = HBoundsCheck.ALWAYS_FALSE;
+      // The check is always false, and whatever instruction it
+      // dominates is dead code.
+      return indexRange;
+    } else if (indexRange.isPositive) {
+      check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO;
+    } else if (belowLength) {
+      check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH;
+    }
+
+    if (indexRange.isPositive) {
+      // If the test passes, we know the lower bound of the length is
+      // greater or equal than the lower bound of the index.
+      Value low = lengthRange.lower.max(indexRange.lower);
+      if (low != const UnknownValue()) {
+        HInstruction instruction =
+            createRangeConversion(next, check.length);
+        ranges[instruction] = info.newRange(low, lengthRange.upper);
+      }
+    }
+
+    if (!belowLength) {
+      // Update the range of the index if using the maximum index
+      // narrows it.
+      Range newIndexRange = indexRange.intersection(
+          info.newRange(info.intZero, maxIndex));
+      if (indexRange == newIndexRange) return indexRange;
+      HInstruction instruction = createRangeConversion(next, check.index);
+      ranges[instruction] = newIndexRange;
+      return newIndexRange;
+    }
+
+    return indexRange;
+  }
+
+  Range visitRelational(HRelational relational) {
+    HInstruction right = relational.right;
+    HInstruction left = relational.left;
+    if (!left.isInteger(types)) return info.newUnboundRange();
+    if (!right.isInteger(types)) return info.newUnboundRange();
+    BinaryOperation operation = relational.operation(constantSystem);
+    Range rightRange = ranges[relational.right];
+    Range leftRange = ranges[relational.left];
+
+    if (relational is HIdentity) {
+      handleEqualityCheck(relational);
+    } else if (operation.apply(leftRange, rightRange)) {
+      relational.block.rewrite(
+          relational, graph.addConstantBool(true, constantSystem));
+      relational.block.remove(relational);
+    } else if (reverseOperation(operation).apply(leftRange, rightRange)) {
+      relational.block.rewrite(
+          relational, graph.addConstantBool(false, constantSystem));
+      relational.block.remove(relational);
+    }
+    return info.newUnboundRange();
+  }
+
+  void handleEqualityCheck(HRelational node) {
+    Range right = ranges[node.right];
+    Range left = ranges[node.left];
+    if (left.isSingleValue && right.isSingleValue && left == right) {
+      node.block.rewrite(
+          node, graph.addConstantBool(true, constantSystem));
+      node.block.remove(node);
+    }
+  }
+
+  Range handleBinaryOperation(HBinaryArithmetic instruction) {
+    if (!instruction.isInteger(types)) return info.newUnboundRange();
+    return instruction.operation(constantSystem).apply(
+        ranges[instruction.left], ranges[instruction.right]);
+  }
+
+  Range visitAdd(HAdd add) {
+    return handleBinaryOperation(add);
+  }
+
+  Range visitSubtract(HSubtract sub) {
+    return handleBinaryOperation(sub);
+  }
+
+  Range visitBitAnd(HBitAnd node) {
+    if (!node.isInteger(types)) return info.newUnboundRange();
+    HInstruction right = node.right;
+    HInstruction left = node.left;
+    if (left.isInteger(types) && right.isInteger(types)) {
+      return ranges[left] & ranges[right];
+    }
+
+    Range tryComputeRange(HInstruction instruction) {
+      Range range = ranges[instruction];
+      if (range.isPositive) {
+        return info.newRange(info.intZero, range.upper);
+      } else if (range.isNegative) {
+        return info.newRange(range.lower, info.intZero);
+      }
+      return info.newUnboundRange();
+    }
+
+    if (left.isInteger(types)) {
+      return tryComputeRange(left);
+    } else if (right.isInteger(types)) {
+      return tryComputeRange(right);
+    }
+    return info.newUnboundRange();
+  }
+
+  Range visitCheck(HCheck instruction) {
+    if (ranges[instruction.checkedInput] == null) {
+      return info.newUnboundRange();
+    }
+    return ranges[instruction.checkedInput];
+  }
+
+  HInstruction createRangeConversion(HInstruction cursor,
+                                     HInstruction instruction) {
+    HRangeConversion newInstruction = new HRangeConversion(instruction);
+    conversions.add(newInstruction);
+    cursor.block.addBefore(cursor, newInstruction);
+    // Update the users of the instruction dominated by [cursor] to
+    // use the new instruction, that has an narrower range.
+    Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor);
+    for (HInstruction user in dominatedUsers) {
+      user.changeUse(instruction, newInstruction);
+    }
+    return newInstruction;
+  }
+
+  static BinaryOperation reverseOperation(BinaryOperation operation) {
+    if (operation == const LessOperation()) {
+      return const GreaterEqualOperation();
+    } else if (operation == const LessEqualOperation()) {
+      return const GreaterOperation();
+    } else if (operation == const GreaterOperation()) {
+      return const LessEqualOperation();
+    } else if (operation == const GreaterEqualOperation()) {
+      return const LessOperation();
+    } else {
+      return null;
+    }
+  }
+
+  Range computeConstrainedRange(BinaryOperation operation,
+                                Range leftRange,
+                                Range rightRange) {
+    Range range;
+    if (operation == const LessOperation()) {
+      range = info.newRange(
+          const MinIntValue(), rightRange.upper - info.intOne);
+    } else if (operation == const LessEqualOperation()) {
+      range = info.newRange(const MinIntValue(), rightRange.upper);
+    } else if (operation == const GreaterOperation()) {
+      range = info.newRange(
+          rightRange.lower + info.intOne, const MaxIntValue());
+    } else if (operation == const GreaterEqualOperation()) {
+      range = info.newRange(rightRange.lower, const MaxIntValue());
+    } else {
+      range = info.newUnboundRange();
+    }
+    return range.intersection(leftRange);
+  }
+
+  Range visitConditionalBranch(HConditionalBranch branch) {
+    var condition = branch.condition;
+    // TODO(ngeoffray): Handle complex conditions.
+    if (condition is !HRelational) return info.newUnboundRange();
+    if (condition is HIdentity) return info.newUnboundRange();
+    HInstruction right = condition.right;
+    HInstruction left = condition.left;
+    if (!left.isInteger(types)) return info.newUnboundRange();
+    if (!right.isInteger(types)) return info.newUnboundRange();
+
+    Range rightRange = ranges[right];
+    Range leftRange = ranges[left];
+    Operation operation = condition.operation(constantSystem);
+    Operation reverse = reverseOperation(operation);
+    // Only update the true branch if this block is the only
+    // predecessor.
+    if (branch.trueBranch.predecessors.length == 1) {
+      assert(branch.trueBranch.predecessors[0] == branch.block);
+      // Update the true branch to use narrower ranges for [left] and
+      // [right].
+      Range range = computeConstrainedRange(operation, leftRange, rightRange);
+      if (leftRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.trueBranch.first, left);
+        ranges[instruction] = range;
+      }
+
+      range = computeConstrainedRange(reverse, rightRange, leftRange);
+      if (rightRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.trueBranch.first, right);
+        ranges[instruction] = range;
+      }
+    }
+
+    // Only update the false branch if this block is the only
+    // predecessor.
+    if (branch.falseBranch.predecessors.length == 1) {
+      assert(branch.falseBranch.predecessors[0] == branch.block);
+      // Update the false branch to use narrower ranges for [left] and
+      // [right].
+      Range range = computeConstrainedRange(reverse, leftRange, rightRange);
+      if (leftRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.falseBranch.first, left);
+        ranges[instruction] = range;
+      }
+
+      range = computeConstrainedRange(operation, rightRange, leftRange);
+      if (rightRange != range) {
+        HInstruction instruction =
+            createRangeConversion(branch.falseBranch.first, right);
+        ranges[instruction] = range;
+      }
+    }
+
+    return info.newUnboundRange();
+  }
+
+  Range visitRangeConversion(HRangeConversion conversion) {
+    return ranges[conversion];
+  }
+}
+
+/**
+ * Recognizes a number of patterns in a loop update instruction and
+ * tries to infer a range for the loop phi.
+ */
+class LoopUpdateRecognizer extends HBaseVisitor {
+  final HPhi loopPhi;
+  final Map<HInstruction, Range> ranges;
+  final HTypeMap types;
+  final ValueRangeInfo info;
+  LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types, this.info);
+
+  Range visitAdd(HAdd operation) {
+    Range range = getRangeForRecognizableOperation(operation);
+    if (range == null) return info.newUnboundRange();
+    Range initial = ranges[loopPhi.inputs[0]];
+    if (range.isPositive) {
+      return info.newRange(initial.lower, const MaxIntValue());
+    } else if (range.isNegative) {
+      return info.newRange(const MinIntValue(), initial.upper);
+    }
+    return info.newUnboundRange();
+  }
+
+  Range visitSubtract(HSubtract operation) {
+    Range range = getRangeForRecognizableOperation(operation);
+    if (range == null) return info.newUnboundRange();
+    Range initial = ranges[loopPhi.inputs[0]];
+    if (range.isPositive) {
+      return info.newRange(const MinIntValue(), initial.upper);
+    } else if (range.isNegative) {
+      return info.newRange(initial.lower, const MaxIntValue());
+    }
+    return info.newUnboundRange();
+  }
+
+  Range visitPhi(HPhi phi) {
+    Range phiRange;
+    for (HInstruction input in phi.inputs) {
+      HInstruction instruction = unwrap(input);
+      // If one of the inputs is the loop phi, then we're only
+      // interested in the other inputs: a loop phi feeding itself means
+      // it is not being updated.
+      if (instruction == loopPhi) continue;
+
+      // If another loop phi is involved, it's too complex to analyze.
+      if (instruction is HPhi && instruction.block.isLoopHeader()) return null;
+
+      Range inputRange = instruction.accept(this);
+      if (inputRange == null) return null;
+      if (phiRange == null) {
+        phiRange = inputRange;
+      } else {
+        phiRange = phiRange.union(inputRange);
+      }
+    }
+    return phiRange;
+  }
+
+  /**
+   * If [operation] is recognizable, returns the inferred range.
+   * Otherwise returns [null].
+   */
+  Range getRangeForRecognizableOperation(HBinaryArithmetic operation) {
+    if (!operation.left.isInteger(types)) return null;
+    if (!operation.right.isInteger(types)) return null;
+    HInstruction left = unwrap(operation.left);
+    HInstruction right = unwrap(operation.right);
+    // We only recognize operations that operate on the loop phi.
+    bool isLeftLoopPhi = (left == loopPhi);
+    bool isRightLoopPhi = (right == loopPhi);
+    if (!isLeftLoopPhi && !isRightLoopPhi) return null;
+
+    var other = isLeftLoopPhi ? right : left;
+    // If the analysis already computed range for the update, use it.
+    if (ranges[other] != null) return ranges[other];
+
+    // We currently only handle constants in updates if the
+    // update does not have a range.
+    if (other.isConstant()) {
+      Value value = info.newIntValue(other.constant.value);
+      return info.newRange(value, value);
+    }
+    return null;
+  }
+
+  /**
+   * [HCheck] instructions may check the loop phi. Since we only
+   * recognize updates on the loop phi, we must [unwrap] the [HCheck]
+   * instruction to check if it references the loop phi.
+   */
+  HInstruction unwrap(instruction) {
+    if (instruction is HCheck) return unwrap(instruction.checkedInput);
+    // [HPhi] might have two different [HCheck] instructions as
+    // inputs, checking the same instruction.
+    if (instruction is HPhi && !instruction.block.isLoopHeader()) {
+      HInstruction result = unwrap(instruction.inputs[0]);
+      for (int i = 1; i < instruction.inputs.length; i++) {
+        if (result != unwrap(instruction.inputs[i])) return instruction;
+      }
+      return result;
+    }
+    return instruction;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart
new file mode 100644
index 0000000..d565de7
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart
@@ -0,0 +1,157 @@
+// 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.
+
+part of ssa;
+
+class ValueSet {
+  int size = 0;
+  List<HInstruction> table;
+  ValueSetNode collisions;
+  ValueSet() : table = new List<HInstruction>.fixedLength(8);
+
+  bool get isEmpty => size == 0;
+  int get length => size;
+
+  void add(HInstruction instruction) {
+    assert(lookup(instruction) == null);
+    int hashCode = instruction.gvnHashCode();
+    int capacity = table.length;
+    // Resize when half of the hash table is in use.
+    if (size >= capacity >> 1) {
+      capacity = capacity << 1;
+      resize(capacity);
+    }
+    // Try to insert in the hash table first.
+    int index = hashCode % capacity;
+    if (table[index] == null) {
+      table[index] = instruction;
+    } else {
+      collisions = new ValueSetNode(instruction, hashCode, collisions);
+    }
+    size++;
+  }
+
+  HInstruction lookup(HInstruction instruction) {
+    int hashCode = instruction.gvnHashCode();
+    int index = hashCode % table.length;
+    // Look in the hash table.
+    HInstruction probe = table[index];
+    if (probe != null && probe.gvnEquals(instruction)) return probe;
+    // Look in the collisions list.
+    for (ValueSetNode node = collisions; node != null; node = node.next) {
+      if (node.hashCode == hashCode) {
+        HInstruction cached = node.value;
+        if (cached.gvnEquals(instruction)) return cached;
+      }
+    }
+    return null;
+  }
+
+  void kill(int flags) {
+    if (flags == 0) return;
+    int depends = HInstruction.computeDependsOnFlags(flags);
+    // Kill in the hash table.
+    for (int index = 0, length = table.length; index < length; index++) {
+      HInstruction instruction = table[index];
+      if (instruction != null && (instruction.flags & depends) != 0) {
+        table[index] = null;
+        size--;
+      }
+    }
+    // Kill in the collisions list.
+    ValueSetNode previous = null;
+    ValueSetNode current = collisions;
+    while (current != null) {
+      ValueSetNode next = current.next;
+      HInstruction cached = current.value;
+      if ((cached.flags & depends) != 0) {
+        if (previous == null) {
+          collisions = next;
+        } else {
+          previous.next = next;
+        }
+        size--;
+      } else {
+        previous = current;
+      }
+      current = next;
+    }
+  }
+
+  ValueSet copy() {
+    return copyTo(new ValueSet(), table, collisions);
+  }
+
+  List<HInstruction> toList() {
+    return copyTo(<HInstruction>[], table, collisions);
+  }
+
+  // Copy the instructions in value set defined by [table] and
+  // [collisions] into [other] and returns [other]. The copy is done
+  // by iterating through the hash table and the collisions list and
+  // calling [:other.add:].
+  static copyTo(var other, List<HInstruction> table, ValueSetNode collisions) {
+    // Copy elements from the hash table.
+    for (int index = 0, length = table.length; index < length; index++) {
+      HInstruction instruction = table[index];
+      if (instruction != null) other.add(instruction);
+    }
+    // Copy elements from the collision list.
+    ValueSetNode current = collisions;
+    while (current != null) {
+      // TODO(kasperl): Maybe find a way of reusing the hash code
+      // rather than recomputing it every time.
+      other.add(current.value);
+      current = current.next;
+    }
+    return other;
+  }
+
+  ValueSet intersection(ValueSet other) {
+    if (size > other.size) return other.intersection(this);
+    ValueSet result = new ValueSet();
+    // Look in the hash table.
+    for (int index = 0, length = table.length; index < length; index++) {
+      HInstruction instruction = table[index];
+      if (instruction != null && other.lookup(instruction) != null) {
+        result.add(instruction);
+      }
+    }
+    // Look in the collision list.
+    ValueSetNode current = collisions;
+    while (current != null) {
+      HInstruction value = current.value;
+      if (other.lookup(value) != null) {
+        result.add(value);
+      }
+      current = current.next;
+    }
+    return result;
+  }
+
+  void resize(int capacity) {
+    var oldSize = size;
+    var oldTable = table;
+    var oldCollisions = collisions;
+    // Reset the table with a bigger capacity.
+    assert(capacity > table.length);
+    size = 0;
+    table = new List<HInstruction>.fixedLength(capacity);
+    collisions = null;
+    // Add the old instructions to the new table.
+    copyTo(this, oldTable, oldCollisions);
+    // Make sure we preserved all elements and that no resizing
+    // happened as part of this resizing.
+    assert(size == oldSize);
+    assert(table.length == capacity);
+  }
+}
+
+class ValueSetNode {
+  final HInstruction value;
+  final int hash;
+  int get hashCode => hash;
+  ValueSetNode next;
+  ValueSetNode(this.value, this.hash, this.next);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart
new file mode 100644
index 0000000..34f960d
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart
@@ -0,0 +1,669 @@
+// 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.
+
+part of ssa;
+
+/**
+ * The [LiveRange] class covers a range where an instruction is live.
+ */
+class LiveRange {
+  final int start;
+  // [end] is not final because it can be updated due to loops.
+  int end;
+  LiveRange(this.start, this.end) {
+    assert(start <= end);
+  }
+
+  String toString() => '[$start $end[';
+}
+
+/**
+ * The [LiveInterval] class contains the list of ranges where an
+ * instruction is live.
+ */
+class LiveInterval {
+  /**
+   * The id where the instruction is defined.
+   */
+  int start;
+  final List<LiveRange> ranges;
+  LiveInterval() : ranges = <LiveRange>[];
+
+  /**
+   * Update all ranges that are contained in [from, to[ to
+   * die at [to].
+   */
+  void loopUpdate(int from, int to) {
+    for (LiveRange range in ranges) {
+      if (from <= range.start && range.end < to) {
+        range.end = to;
+      }
+    }
+  }
+
+  /**
+   * Add a new range to this interval.
+   */
+  void add(LiveRange interval) {
+    ranges.add(interval);
+  }
+
+  /**
+   * Returns true if one of the ranges of this interval dies at [at].
+   */
+  bool diesAt(int at) {
+    for (LiveRange range in ranges) {
+      if (range.end == at) return true;
+    }
+    return false;
+  }
+
+  String toString() {
+    List<String> res = new List<String>();
+    for (final interval in ranges) res.add(interval.toString());
+    return '(${Strings.join(res, ', ')})';
+  }
+}
+
+/**
+ * The [LiveEnvironment] class contains the liveIn set of a basic
+ * block. A liveIn set of a block contains the instructions that are
+ * live when entering that block.
+ */
+class LiveEnvironment {
+  /**
+   * The instruction id where the basic block starts. See
+   * [SsaLiveIntervalBuilder.instructionId].
+   */
+  int startId;
+
+  /**
+   * The instruction id where the basic block ends.
+   */
+  final int endId;
+
+  /**
+   * Loop markers that will be updated once the loop header is
+   * visited. The liveIn set of the loop header will be merged into this
+   * environment. [loopMarkers] is a mapping from block header to the
+   * end instruction id of the loop exit block.
+   */
+  final Map<HBasicBlock, int> loopMarkers;
+
+  /**
+   * The instructions that are live in this basic block. The values of
+   * the map contain the instruction ids where the instructions die.
+   * It will be used when adding a range to the live interval of an
+   * instruction.
+   */
+  final Map<HInstruction, int> liveInstructions;
+
+  /**
+   * Map containing the live intervals of instructions.
+   */
+  final Map<HInstruction, LiveInterval> liveIntervals;
+
+  LiveEnvironment(this.liveIntervals, this.endId)
+    : liveInstructions = new Map<HInstruction, int>(),
+      loopMarkers = new Map<HBasicBlock, int>();
+
+  /**
+   * Remove an instruction from the liveIn set. This method also
+   * updates the live interval of [instruction] to contain the new
+   * range: [id, / id contained in [liveInstructions] /].
+   */
+  void remove(HInstruction instruction, int id) {
+    // Special case the HCheck instruction to have the same live
+    // interval as the instruction it is checking.
+    if (instruction is HCheck) {
+      var input = instruction.checkedInput;
+      while (input is HCheck) input = input.checkedInput;
+      liveIntervals.putIfAbsent(input, () => new LiveInterval());
+      // Unconditionally force the live interval of the HCheck to
+      // be the live interval of the instruction it is checking.
+      liveIntervals[instruction] = liveIntervals[input];
+    } else {
+      LiveInterval range = liveIntervals.putIfAbsent(
+          instruction, () => new LiveInterval());
+      int lastId = liveInstructions[instruction];
+      // If [lastId] is null, then this instruction is not being used.
+      range.add(new LiveRange(id, lastId == null ? id : lastId));
+      // The instruction is defined at [id].
+      range.start = id;
+    }
+    liveInstructions.remove(instruction);
+  }
+
+  /**
+   * Add [instruction] to the liveIn set. If the instruction is not
+   * already in the set, we save the id where it dies.
+   */
+  void add(HInstruction instruction, int userId) {
+    // Note that we are visiting the graph in post-dominator order, so
+    // the first time we see a variable is when it dies.
+    liveInstructions.putIfAbsent(instruction, () => userId);
+    if (instruction is HCheck) {
+      // Special case the HCheck instruction to mark the actual
+      // checked instruction live.
+      var input = instruction.checkedInput;
+      while (input is HCheck) input = input.checkedInput;
+      liveInstructions.putIfAbsent(input, () => userId);
+    }
+  }
+
+  /**
+   * Merge this environment with [other]. Update the end id of
+   * instructions in case they are different between this and [other].
+   */
+  void mergeWith(LiveEnvironment other) {
+    other.liveInstructions.forEach((HInstruction instruction, int existingId) {
+      // If both environments have the same instruction id of where
+      // [instruction] dies, there is no need to update the live
+      // interval of [instruction]. For example the if block and the
+      // else block have the same end id for an instruction that is
+      // being used in the join block and defined before the if/else.
+      if (existingId == endId) return;
+      LiveInterval range = liveIntervals.putIfAbsent(
+          instruction, () => new LiveInterval());
+      range.add(new LiveRange(other.startId, existingId));
+      liveInstructions[instruction] = endId;
+    });
+    other.loopMarkers.forEach((k, v) { loopMarkers[k] = v; });
+  }
+
+  void addLoopMarker(HBasicBlock header, int id) {
+    assert(!loopMarkers.containsKey(header));
+    loopMarkers[header] = id;
+  }
+
+  void removeLoopMarker(HBasicBlock header) {
+    assert(loopMarkers.containsKey(header));
+    loopMarkers.remove(header);
+  }
+
+  bool get isEmpty => liveInstructions.isEmpty && loopMarkers.isEmpty;
+  bool contains(HInstruction instruction) =>
+      liveInstructions.containsKey(instruction);
+  String toString() => liveInstructions.toString();
+}
+
+/**
+ * Builds the live intervals of each instruction. The algorithm visits
+ * the graph post-dominator tree to find the last uses of an
+ * instruction, and computes the liveIns of each basic block.
+ */
+class SsaLiveIntervalBuilder extends HBaseVisitor {
+  final Compiler compiler;
+  final Set<HInstruction> generateAtUseSite;
+
+  /**
+   * A counter to assign start and end ids to live ranges. The initial
+   * value is not relevant. Note that instructionId goes downward to ease
+   * reasoning about live ranges (the first instruction of a graph has
+   * the lowest id).
+   */
+  int instructionId = 0;
+
+  /**
+   * The liveIns of basic blocks.
+   */
+  final Map<HBasicBlock, LiveEnvironment> liveInstructions;
+
+  /**
+   * The live intervals of instructions.
+   */
+  final Map<HInstruction, LiveInterval> liveIntervals;
+
+  SsaLiveIntervalBuilder(this.compiler, this.generateAtUseSite)
+    : liveInstructions = new Map<HBasicBlock, LiveEnvironment>(),
+      liveIntervals = new Map<HInstruction, LiveInterval>();
+
+  void visitGraph(HGraph graph) {
+    visitPostDominatorTree(graph);
+    if (!liveInstructions[graph.entry].isEmpty) {
+      compiler.internalError('LiveIntervalBuilder',
+          node: compiler.currentElement.parseNode(compiler));
+    }
+  }
+
+  void markInputsAsLiveInEnvironment(HInstruction instruction,
+                                     LiveEnvironment environment) {
+    for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+      markAsLiveInEnvironment(instruction.inputs[i], environment);
+    }
+  }
+
+  void markAsLiveInEnvironment(HInstruction instruction,
+                               LiveEnvironment environment) {
+    if (environment.contains(instruction)) return;
+    environment.add(instruction, instructionId);
+    // HPhis are treated specially.
+    if (generateAtUseSite.contains(instruction) && instruction is !HPhi) {
+      markInputsAsLiveInEnvironment(instruction, environment);
+    }
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    LiveEnvironment environment =
+        new LiveEnvironment(liveIntervals, instructionId);
+
+    // Add to the environment the liveIn of its successor, as well as
+    // the inputs of the phis of the successor that flow from this block.
+    for (int i = 0; i < block.successors.length; i++) {
+      HBasicBlock successor = block.successors[i];
+      LiveEnvironment successorEnv = liveInstructions[successor];
+      if (successorEnv != null) {
+        environment.mergeWith(successorEnv);
+      } else {
+        environment.addLoopMarker(successor, instructionId);
+      }
+
+      int index = successor.predecessors.indexOf(block);
+      for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) {
+        markAsLiveInEnvironment(phi.inputs[index], environment);
+      }
+    }
+
+    // Iterate over all instructions to remove an instruction from the
+    // environment and add its inputs.
+    HInstruction instruction = block.last;
+    while (instruction != null) {
+      environment.remove(instruction, instructionId);
+      markInputsAsLiveInEnvironment(instruction, environment);
+      instruction = instruction.previous;
+      instructionId--;
+    }
+
+    // We just remove the phis from the environment. The inputs of the
+    // phis will be put in the environment of the predecessors.
+    for (HPhi phi = block.phis.first; phi != null; phi = phi.next) {
+      environment.remove(phi, instructionId);
+    }
+
+    // Save the liveInstructions of that block.
+    environment.startId = instructionId + 1;
+    liveInstructions[block] = environment;
+
+    // If the block is a loop header, we can remove the loop marker,
+    // because it will just recompute the loop phis.
+    // We also check if this loop header has any back edges. If not,
+    // we know there is no loop marker for it.
+    if (block.isLoopHeader() && block.predecessors.length > 1) {
+      updateLoopMarker(block);
+    }
+  }
+
+  void updateLoopMarker(HBasicBlock header) {
+    LiveEnvironment env = liveInstructions[header];
+    int lastId = env.loopMarkers[header];
+    // Update all instructions that are liveIns in [header] to have a
+    // range that covers the loop.
+    env.liveInstructions.forEach((HInstruction instruction, int id) {
+      LiveInterval range = env.liveIntervals.putIfAbsent(
+          instruction, () => new LiveInterval());
+      range.loopUpdate(env.startId, lastId);
+      env.liveInstructions[instruction] = lastId;
+    });
+
+    env.removeLoopMarker(header);
+
+    // Update all liveIns set to contain the liveIns of [header].
+    liveInstructions.forEach((HBasicBlock block, LiveEnvironment other) {
+      if (other.loopMarkers.containsKey(header)) {
+        env.liveInstructions.forEach((HInstruction instruction, int id) {
+          other.liveInstructions[instruction] = id;
+        });
+        other.removeLoopMarker(header);
+        env.loopMarkers.forEach((k, v) { other.loopMarkers[k] = v; });
+      }
+    });
+  }
+}
+
+/**
+ * Represents a copy from one instruction to another. The codegen
+ * also uses this class to represent a copy from one variable to
+ * another.
+ */
+class Copy {
+  final source;
+  final destination;
+  Copy(this.source, this.destination);
+  String toString() => '$destination <- $source';
+}
+
+/**
+ * A copy handler contains the copies that a basic block needs to do
+ * after executing all its instructions.
+ */
+class CopyHandler {
+  /**
+   * The copies from an instruction to a phi of the successor.
+   */
+  final List<Copy> copies;
+
+  /**
+   * Assignments from an instruction that does not need a name (e.g. a
+   * constant) to the phi of a successor.
+   */
+  final List<Copy> assignments;
+
+  CopyHandler()
+    : copies = new List<Copy>(),
+      assignments = new List<Copy>();
+
+  void addCopy(HInstruction source, HInstruction destination) {
+    copies.add(new Copy(source, destination));
+  }
+
+  void addAssignment(HInstruction source, HInstruction destination) {
+    assignments.add(new Copy(source, destination));
+  }
+
+  String toString() => 'Copies: $copies, assignments: $assignments';
+  bool get isEmpty => copies.isEmpty && assignments.isEmpty;
+}
+
+/**
+ * Contains the mapping between instructions and their names for code
+ * generation, as well as the [CopyHandler] for each basic block.
+ */
+class VariableNames {
+  final Map<HInstruction, String> ownName;
+  final Map<HBasicBlock, CopyHandler> copyHandlers;
+
+  // Used to control heuristic that determines how local variables are declared.
+  final Set<String> allUsedNames;
+  /**
+   * Name that is used as a temporary to break cycles in
+   * parallel copies. We make sure this name is not being used
+   * anywhere by reserving it when we allocate names for instructions.
+   */
+  final String swapTemp;
+  /**
+   * Name that is used in bailout code. We make sure this name is not being used
+   * anywhere by reserving it when we allocate names for instructions.
+   */
+  final String stateName;
+
+  String getSwapTemp() {
+    allUsedNames.add(swapTemp);
+    return swapTemp;
+  }
+
+  VariableNames()
+    : ownName = new Map<HInstruction, String>(),
+      copyHandlers = new Map<HBasicBlock, CopyHandler>(),
+      allUsedNames = new Set<String>(),
+      swapTemp = computeFreshWithPrefix("t"),
+      stateName = computeFreshWithPrefix("state");
+
+  int get numberOfVariables => allUsedNames.length;
+
+  /** Returns a fresh variable with the given prefix. */
+  static String computeFreshWithPrefix(String prefix) {
+    String name = '${prefix}0';
+    int i = 1;
+    return name;
+  }
+
+  String getName(HInstruction instruction) {
+    return ownName[instruction];
+  }
+
+  CopyHandler getCopyHandler(HBasicBlock block) {
+    return copyHandlers[block];
+  }
+
+  void addNameUsed(String name) => allUsedNames.add(name);
+
+  bool hasName(HInstruction instruction) => ownName.containsKey(instruction);
+
+  void addCopy(HBasicBlock block, HInstruction source, HPhi destination) {
+    CopyHandler handler =
+        copyHandlers.putIfAbsent(block, () => new CopyHandler());
+    handler.addCopy(source, destination);
+  }
+
+  void addAssignment(HBasicBlock block, HInstruction source, HPhi destination) {
+    CopyHandler handler =
+        copyHandlers.putIfAbsent(block, () => new CopyHandler());
+    handler.addAssignment(source, destination);
+  }
+}
+
+/**
+ * Allocates variable names for instructions, making sure they don't collide.
+ */
+class VariableNamer {
+  final VariableNames names;
+  final Compiler compiler;
+  final Set<String> usedNames;
+  final List<String> freeTemporaryNames;
+  int temporaryIndex = 0;
+  static final RegExp regexp = new RegExp('t[0-9]+');
+
+  VariableNamer(LiveEnvironment environment,
+                this.names,
+                this.compiler)
+    : usedNames = new Set<String>(),
+      freeTemporaryNames = new List<String>() {
+    // [VariableNames.swapTemp] is used when there is a cycle in a copy handler.
+    // Therefore we make sure no one uses it.
+    usedNames.add(names.swapTemp);
+    // [VariableNames.stateName] is being used throughout a bailout function.
+    // Whenever a bailout-target is reached we set the state-variable to 0. We
+    // must therefore not have any local variable that could clash with the
+    // state variable.
+    // Therefore we make sure no one uses it at any time.
+    usedNames.add(names.stateName);
+
+    // All liveIns instructions must have a name at this point, so we
+    // add them to the list of used names.
+    environment.liveInstructions.forEach((HInstruction instruction, int index) {
+      String name = names.getName(instruction);
+      if (name != null) {
+        usedNames.add(name);
+        names.addNameUsed(name);
+      }
+    });
+  }
+
+  String allocateWithHint(String originalName) {
+    int i = 0;
+    JavaScriptBackend backend = compiler.backend;
+    String name = backend.namer.safeVariableName(originalName);
+    while (usedNames.contains(name)) {
+      name = backend.namer.safeVariableName('$originalName${i++}');
+    }
+    return name;
+  }
+
+  String allocateTemporary() {
+    while (!freeTemporaryNames.isEmpty) {
+      String name = freeTemporaryNames.removeLast();
+      if (!usedNames.contains(name)) return name;
+    }
+    String name = 't${temporaryIndex++}';
+    while (usedNames.contains(name)) name = 't${temporaryIndex++}';
+    return name;
+  }
+
+  HPhi firstPhiUserWithElement(HInstruction instruction) {
+    for (HInstruction user in instruction.usedBy) {
+      if (user is HPhi && user.sourceElement != null) {
+        return user;
+      }
+    }
+    return null;
+  }
+
+  String allocateName(HInstruction instruction) {
+    String name;
+    if (instruction is HCheck) {
+      // Special case this instruction to use the name of its
+      // input if it has one.
+      var temp = instruction;
+      do {
+        temp = temp.checkedInput;
+        name = names.ownName[temp];
+      } while (name == null && temp is HCheck);
+      if (name != null) return addAllocatedName(instruction, name);
+    }
+
+    if (instruction.sourceElement != null) {
+      name = allocateWithHint(instruction.sourceElement.name.slowToString());
+    } else {
+      // We could not find an element for the instruction. If the
+      // instruction is used by a phi, try to use the name of the phi.
+      // Otherwise, just allocate a temporary name.
+      HPhi phi = firstPhiUserWithElement(instruction);
+      if (phi != null) {
+        name = allocateWithHint(phi.sourceElement.name.slowToString());
+      } else {
+        name = allocateTemporary();
+      }
+    }
+    return addAllocatedName(instruction, name);
+  }
+
+  String addAllocatedName(HInstruction instruction, String name) {
+    usedNames.add(name);
+    names.addNameUsed(name);
+    names.ownName[instruction] = name;
+    return name;
+  }
+
+  /**
+   * Frees [instruction]'s name so it can be used for other instructions.
+   */
+  void freeName(HInstruction instruction) {
+    String ownName = names.ownName[instruction];
+    if (ownName != null) {
+      // We check if we have already looked for temporary names
+      // because if we haven't, chances are the temporary we allocate
+      // in this block can match a phi with the same name in the
+      // successor block.
+      if (temporaryIndex != 0 && regexp.hasMatch(ownName)) {
+        freeTemporaryNames.addLast(ownName);
+      }
+      usedNames.remove(ownName);
+    }
+  }
+}
+
+/**
+ * Visits all blocks in the graph, sets names to instructions, and
+ * creates the [CopyHandler] for each block. This class needs to have
+ * the liveIns set as well as all the live intervals of instructions.
+ * It visits the graph in dominator order, so that at each entry of a
+ * block, the instructions in its liveIns set have names.
+ *
+ * When visiting a block, it goes through all instructions. For each
+ * instruction, it frees the names of the inputs that die at that
+ * instruction, and allocates a name to the instruction. For each phi,
+ * it adds a copy to the CopyHandler of the corresponding predecessor.
+ */
+class SsaVariableAllocator extends HBaseVisitor {
+
+  final Compiler compiler;
+  final Map<HBasicBlock, LiveEnvironment> liveInstructions;
+  final Map<HInstruction, LiveInterval> liveIntervals;
+  final Set<HInstruction> generateAtUseSite;
+
+  final VariableNames names;
+
+  SsaVariableAllocator(this.compiler,
+                       this.liveInstructions,
+                       this.liveIntervals,
+                       this.generateAtUseSite)
+    : this.names = new VariableNames();
+
+  void visitGraph(HGraph graph) {
+    visitDominatorTree(graph);
+  }
+
+  void visitBasicBlock(HBasicBlock block) {
+    VariableNamer namer = new VariableNamer(
+        liveInstructions[block], names, compiler);
+
+    block.forEachPhi((HPhi phi) {
+      handlePhi(phi, namer);
+    });
+
+    block.forEachInstruction((HInstruction instruction) {
+      handleInstruction(instruction, namer);
+    });
+  }
+
+  /**
+   * Returns whether [instruction] needs a name. Instructions that
+   * have no users or that are generated at use site does not need a name.
+   */
+  bool needsName(HInstruction instruction) {
+    if (instruction is HThis) return false;
+    if (instruction is HParameterValue) return true;
+    if (instruction.usedBy.isEmpty) return false;
+    if (generateAtUseSite.contains(instruction)) return false;
+    // A [HCheck] instruction that has control flow needs a name only if its
+    // checked input needs a name (e.g. a check [HConstant] does not
+    // need a name).
+    if (instruction is HCheck && instruction.isControlFlow()) {
+      HCheck check = instruction;
+      return needsName(instruction.checkedInput);
+    }
+    return true;
+  }
+
+  /**
+   * Returns whether [instruction] dies at the instruction [at].
+   */
+  bool diesAt(HInstruction instruction, HInstruction at) {
+    LiveInterval atInterval = liveIntervals[at];
+    LiveInterval instructionInterval = liveIntervals[instruction];
+    int start = atInterval.start;
+    return instructionInterval.diesAt(start);
+  }
+
+  void handleInstruction(HInstruction instruction, VariableNamer namer) {
+    // TODO(ager): We cannot perform this check to free names for
+    // HCheck instructions because they are special cased to have the
+    // same live intervals as the instruction they are checking. This
+    // includes sharing the start id with the checked
+    // input. Therefore, for HCheck(checkedInput, otherInput) we would
+    // end up checking that otherInput dies not here, but at the
+    // location of checkedInput. We should preserve the start id for
+    // the check instruction.
+    if (instruction is! HCheck) {
+      for (int i = 0, len = instruction.inputs.length; i < len; i++) {
+        HInstruction input = instruction.inputs[i];
+        // If [input] has a name, and its use here is the last use, free
+        // its name.
+        if (needsName(input) && diesAt(input, instruction)) {
+          namer.freeName(input);
+        }
+      }
+    }
+
+    if (needsName(instruction)) {
+      namer.allocateName(instruction);
+    }
+  }
+
+  void handlePhi(HPhi phi, VariableNamer namer) {
+    if (!needsName(phi)) return;
+
+    for (int i = 0; i < phi.inputs.length; i++) {
+      HInstruction input = phi.inputs[i];
+      HBasicBlock predecessor = phi.block.predecessors[i];
+      if (!needsName(input)) {
+        names.addAssignment(predecessor, input, phi);
+      } else {
+        names.addCopy(predecessor, input, phi);
+      }
+    }
+
+    namer.allocateName(phi);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart b/pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart
new file mode 100644
index 0000000..ef294bd
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart
@@ -0,0 +1,214 @@
+// 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.
+
+// Check the validity of string literals.
+
+library stringvalidator;
+
+import "dart:collection";
+
+import "dart2jslib.dart";
+import "tree/tree.dart";
+import "elements/elements.dart";
+import "util/characters.dart";
+import "scanner/scannerlib.dart" show Token;
+
+class StringValidator {
+  final DiagnosticListener listener;
+
+  StringValidator(this.listener);
+
+  DartString validateQuotedString(Token token) {
+    SourceString source = token.value;
+    StringQuoting quoting = quotingFromString(source);
+    int leftQuote = quoting.leftQuoteLength;
+    int rightQuote = quoting.rightQuoteLength;
+    SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote);
+    return validateString(token,
+                          token.charOffset + leftQuote,
+                          content,
+                          quoting);
+  }
+
+  DartString validateInterpolationPart(Token token, StringQuoting quoting,
+                                       {bool isFirst: false,
+                                        bool isLast: false}) {
+    SourceString source = token.value;
+    int leftQuote = 0;
+    int rightQuote = 0;
+    if (isFirst) leftQuote = quoting.leftQuoteLength;
+    if (isLast) rightQuote = quoting.rightQuoteLength;
+    SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote);
+    return validateString(token,
+                          token.charOffset + leftQuote,
+                          content,
+                          quoting);
+  }
+
+  static StringQuoting quotingFromString(SourceString sourceString) {
+    Iterator<int> source = sourceString.iterator;
+    bool raw = false;
+    int quoteLength = 1;
+    source.moveNext();
+    int quoteChar = source.current;
+    if (quoteChar == $r) {
+      raw = true;
+      source.moveNext();
+      quoteChar = source.current;
+    }
+    assert(quoteChar == $SQ || quoteChar == $DQ);
+    // String has at least one quote. Check it if has three.
+    // If it only have two, the string must be an empty string literal,
+    // and end after the second quote.
+    bool multiline = false;
+    if (source.moveNext() && source.current == quoteChar && source.moveNext()) {
+      int code = source.current;
+      assert(code == quoteChar);  // If not, there is a bug in the parser.
+      quoteLength = 3;
+      // Check if a multiline string starts with a newline (CR, LF or CR+LF).
+      if (source.moveNext()) {
+        code = source.current;
+        if (code == $CR) {
+          quoteLength += 1;
+          if (source.moveNext() && source.current == $LF) {
+            quoteLength += 1;
+          }
+        } else if (code == $LF) {
+          quoteLength += 1;
+        }
+      }
+    }
+    return StringQuoting.getQuoting(quoteChar, raw, quoteLength);
+  }
+
+  void stringParseError(String message, Token token, int offset) {
+    listener.cancel("$message @ $offset", token : token);
+  }
+
+  /**
+   * Validates the escape sequences and special characters of a string literal.
+   * Returns a DartString if valid, and null if not.
+   */
+  DartString validateString(Token token,
+                            int startOffset,
+                            SourceString string,
+                            StringQuoting quoting) {
+    // We need to check for invalid x and u escapes, for line
+    // terminators in non-multiline strings, and for invalid Unicode
+    // scalar values (either directly or as u-escape values).  We also check
+    // for unpaired UTF-16 surrogates.
+    int length = 0;
+    int index = startOffset;
+    bool containsEscape = false;
+    bool previousWasLeadSurrogate = false;
+    bool invalidUtf16 = false;
+    for(HasNextIterator<int> iter = new HasNextIterator(string.iterator);
+        iter.hasNext;
+        length++) {
+      index++;
+      int code = iter.next();
+      if (code == $BACKSLASH) {
+        if (quoting.raw) continue;
+        containsEscape = true;
+        if (!iter.hasNext) {
+          stringParseError("Incomplete escape sequence",token, index);
+          return null;
+        }
+        index++;
+        code = iter.next();
+        if (code == $x) {
+          for (int i = 0; i < 2; i++) {
+            if (!iter.hasNext) {
+              stringParseError("Incomplete escape sequence", token, index);
+              return null;
+            }
+            index++;
+            code = iter.next();
+            if (!isHexDigit(code)) {
+              stringParseError("Invalid character in escape sequence",
+                               token, index);
+              return null;
+            }
+          }
+          // A two-byte hex escape can't generate an invalid value.
+          continue;
+        } else if (code == $u) {
+          int escapeStart = index - 1;
+          index++;
+          code = iter.hasNext ? iter.next() : 0;
+          int value = 0;
+          if (code == $OPEN_CURLY_BRACKET) {
+            // expect 1-6 hex digits.
+            int count = 0;
+            while (iter.hasNext) {
+              code = iter.next();
+              index++;
+              if (code == $CLOSE_CURLY_BRACKET) {
+                break;
+              }
+              if (!isHexDigit(code)) {
+                stringParseError("Invalid character in escape sequence",
+                                 token, index);
+                return null;
+              }
+              count++;
+              value = value * 16 + hexDigitValue(code);
+            }
+            if (code != $CLOSE_CURLY_BRACKET || count == 0 || count > 6) {
+              int errorPosition = index - count;
+              if (count > 6) errorPosition += 6;
+              stringParseError("Invalid character in escape sequence",
+                               token, errorPosition);
+              return null;
+            }
+          } else {
+            // Expect four hex digits, including the one just read.
+            for (int i = 0; i < 4; i++) {
+              if (i > 0) {
+                if (iter.hasNext) {
+                  index++;
+                  code = iter.next();
+                } else {
+                  code = 0;
+                }
+              }
+              if (!isHexDigit(code)) {
+                stringParseError("Invalid character in escape sequence",
+                                 token, index);
+                return null;
+              }
+              value = value * 16 + hexDigitValue(code);
+            }
+          }
+          code = value;
+        }
+      }
+      if (code >= 0x10000) length++;
+      // This handles both unescaped characters and the value of unicode
+      // escapes.
+      if (previousWasLeadSurrogate) {
+        if (!isUtf16TrailSurrogate(code)) {
+          invalidUtf16 = true;
+          break;
+        }
+        previousWasLeadSurrogate = false;
+      } else if (isUtf16LeadSurrogate(code)) {
+        previousWasLeadSurrogate = true;
+      } else if (!isUnicodeScalarValue(code)) {
+        invalidUtf16 = true;
+        break;
+      }
+    }
+    if (previousWasLeadSurrogate || invalidUtf16) {
+      stringParseError("Invalid Utf16 surrogate", token, index);
+      return null;
+    }
+    // String literal successfully validated.
+    if (quoting.raw || !containsEscape) {
+      // A string without escapes could just as well have been raw.
+      return new DartString.rawString(string, length);
+    }
+    return new DartString.escapedString(string, length);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart
new file mode 100644
index 0000000..ea56771
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart
@@ -0,0 +1,321 @@
+// 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.
+
+library parser;
+
+import 'dart:io';
+import 'dart:scalarlist';
+
+import 'dart:utf';
+
+import '../elements/elements.dart';
+import '../scanner/scanner_implementation.dart';
+import '../scanner/scannerlib.dart';
+import '../tree/tree.dart';
+import '../util/characters.dart';
+import '../source_file.dart';
+import '../ssa/ssa.dart';
+
+import '../../compiler.dart' as api;
+
+part '../diagnostic_listener.dart';
+part '../scanner/byte_array_scanner.dart';
+part '../scanner/byte_strings.dart';
+
+int charCount = 0;
+Stopwatch stopwatch;
+
+void main() {
+  toolMain(new Options().arguments);
+}
+
+void toolMain(List<String> arguments) {
+  filesWithCrashes = [];
+  stopwatch = new Stopwatch();
+  MyOptions options = new MyOptions();
+
+  void printStats() {
+    int kb = (charCount / 1024).round().toInt();
+    String stats =
+        '$classCount classes (${kb}Kb) in ${stopwatch.elapsedMilliseconds}ms';
+    if (errorCount != 0) {
+      stats = '$stats with $errorCount errors';
+    }
+    if (options.diet) {
+      print('Diet parsed $stats.');
+    } else {
+      print('Parsed $stats.');
+    }
+    if (filesWithCrashes.length != 0) {
+      print('The following ${filesWithCrashes.length} files caused a crash:');
+      for (String file in filesWithCrashes) {
+        print(file);
+      }
+    }
+  }
+
+  for (String argument in arguments) {
+    if (argument == "--diet") {
+      options.diet = true;
+      continue;
+    }
+    if (argument == "--throw") {
+      options.throwOnError = true;
+      continue;
+    }
+    if (argument == "--scan-only") {
+      options.scanOnly = true;
+      continue;
+    }
+    if (argument == "--read-only") {
+      options.readOnly = true;
+      continue;
+    }
+    if (argument == "--ast") {
+      options.buildAst = true;
+      continue;
+    }
+    if (argument == "-") {
+      parseFilesFrom(stdin, options, printStats);
+      return;
+    }
+    stopwatch.start();
+    parseFile(argument, options);
+    stopwatch.stop();
+  }
+
+  printStats();
+}
+
+void parseFile(String filename, MyOptions options) {
+  List<int> bytes = read(filename);
+  charCount += bytes.length;
+  if (options.readOnly) return;
+  MySourceFile file = new MySourceFile(filename, bytes);
+  final Listener listener = options.buildAst
+      ? new MyNodeListener(file, options)
+      : new MyListener(file);
+  final Parser parser = options.diet
+      ? new PartialParser(listener)
+      : new Parser(listener);
+  try {
+    Token token = scan(file);
+    if (!options.scanOnly) parser.parseUnit(token);
+  } on ParserError catch (ex) {
+    if (options.throwOnError) {
+      throw;
+    } else {
+      print(ex);
+    }
+  } catch (ex) {
+    print('Error in file: $filename');
+    throw;
+  }
+  if (options.buildAst) {
+    MyNodeListener l = listener;
+    if (!l.nodes.isEmpty) {
+      String message = 'Stack not empty after parsing';
+      print(formatError(message, l.nodes.head.getBeginToken(),
+                        l.nodes.head.getEndToken(), file));
+      throw message;
+    }
+  }
+}
+
+Token scan(MySourceFile source) {
+  Scanner scanner = new ByteArrayScanner(source.rawText);
+  return scanner.tokenize();
+}
+
+var filesWithCrashes;
+
+void parseFilesFrom(InputStream input, MyOptions options, Function whenDone) {
+  void readLine(String line) {
+    stopwatch.start();
+    try {
+      parseFile(line, options);
+    } catch (ex, trace) {
+      filesWithCrashes.add(line);
+      print(ex);
+      print(trace);
+    }
+    stopwatch.stop();
+  }
+  forEachLine(input, readLine, whenDone);
+}
+
+void forEachLine(InputStream input,
+                 void lineHandler(String line),
+                 void closeHandler()) {
+  StringInputStream stringStream = new StringInputStream(input);
+  stringStream.onLine = () {
+    String line;
+    while ((line = stringStream.readLine()) != null) {
+      lineHandler(line);
+    }
+  };
+  stringStream.onClosed = closeHandler;
+}
+
+List<int> read(String filename) {
+  RandomAccessFile file = new File(filename).openSync();
+  bool threw = true;
+  try {
+    int size = file.lengthSync();
+    List<int> bytes = new Uint8List(size + 1);
+    file.readListSync(bytes, 0, size);
+    bytes[size] = $EOF;
+    threw = false;
+    return bytes;
+  } finally {
+    try {
+      file.closeSync();
+    } catch (ex) {
+      if (!threw) throw;
+    }
+  }
+}
+
+int classCount = 0;
+int errorCount = 0;
+
+class MyListener extends Listener {
+  final SourceFile file;
+
+  MyListener(this.file);
+
+  void beginClassDeclaration(Token token) {
+    classCount++;
+  }
+
+  void beginInterface(Token token) {
+    classCount++;
+  }
+
+  void error(String message, Token token) {
+    throw new ParserError(formatError(message, token, token, file));
+  }
+}
+
+String formatError(String message, Token beginToken, Token endToken,
+                   SourceFile file) {
+  ++errorCount;
+  if (beginToken == null) return '${file.filename}: $message';
+  String tokenString = endToken.toString();
+  int begin = beginToken.charOffset;
+  int end = endToken.charOffset + tokenString.length;
+  return file.getLocationMessage(message, begin, end, true, (x) => x);
+}
+
+class MyNodeListener extends NodeListener {
+  MyNodeListener(SourceFile file, MyOptions options)
+    : super(new MyCanceller(file, options), null);
+
+  void beginClassDeclaration(Token token) {
+    classCount++;
+  }
+
+  void beginInterface(Token token) {
+    classCount++;
+  }
+
+  void endClassDeclaration(int interfacesCount, Token beginToken,
+                           Token extendsKeyword, Token implementsKeyword,
+                           Token endToken) {
+    super.endClassDeclaration(interfacesCount, beginToken,
+                              extendsKeyword, implementsKeyword,
+                              endToken);
+    ClassNode node = popNode(); // Discard ClassNode and assert the type.
+  }
+
+  void endInterface(int supertypeCount, Token interfaceKeyword,
+                    Token extendsKeyword, Token endToken) {
+    super.endInterface(supertypeCount, interfaceKeyword, extendsKeyword,
+                       endToken);
+    ClassNode node = popNode(); // Discard ClassNode and assert the type.
+  }
+
+  void endTopLevelFields(int count, Token beginToken, Token endToken) {
+    super.endTopLevelFields(count, beginToken, endToken);
+    VariableDefinitions node = popNode(); // Discard node and assert the type.
+  }
+
+  void endFunctionTypeAlias(Token typedefKeyword, Token endToken) {
+    super.endFunctionTypeAlias(typedefKeyword, endToken);
+    Typedef node = popNode(); // Discard Typedef and assert type type.
+  }
+
+  void endLibraryTag(bool hasPrefix, Token beginToken, Token endToken) {
+    super.endLibraryTag(hasPrefix, beginToken, endToken);
+    ScriptTag node = popNode(); // Discard ScriptTag and assert type type.
+  }
+
+  void log(message) {
+    print(message);
+  }
+}
+
+class MyCanceller implements DiagnosticListener {
+  final SourceFile file;
+  final MyOptions options;
+
+  MyCanceller(this.file, this.options);
+
+  void log(String message) {}
+
+  void cancel(String reason, {node, token, instruction, element}) {
+    Token beginToken;
+    Token endToken;
+    if (token != null) {
+      beginToken = token;
+      endToken = token;
+    } else if (node != null) {
+      beginToken = node.getBeginToken();
+      endToken = node.getEndToken();
+    }
+    String message = formatError(reason, beginToken, endToken, file);
+    if (options.throwOnError) throw new ParserError(message);
+    print(message);
+  }
+}
+
+class MyOptions {
+  bool diet = false;
+  bool throwOnError = false;
+  bool scanOnly = false;
+  bool readOnly = false;
+  bool buildAst = false;
+}
+
+class MySourceFile extends SourceFile {
+  final rawText;
+  var stringText;
+
+  MySourceFile(filename, this.rawText) : super(filename, null);
+
+  String get text {
+    if (rawText is String) {
+      return rawText;
+    } else {
+      if (stringText == null) {
+        stringText = new String.fromCharCodes(rawText);
+        if (stringText.endsWith('\u0000')) {
+          // Strip trailing NUL used by ByteArrayScanner to signal EOF.
+          stringText = stringText.substring(0, stringText.length - 1);
+        }
+      }
+      return stringText;
+    }
+  }
+
+  set text(String newText) {
+    throw "not supported";
+  }
+}
+
+class Mock {
+  const Mock();
+  bool get useColors => true;
+  internalError(message) { throw message.toString(); }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart
new file mode 100644
index 0000000..5a3494d
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart
@@ -0,0 +1,235 @@
+// 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.
+
+part of tree;
+
+/**
+ * The [DartString] type represents a Dart string value as a sequence of Unicode
+ * Scalar Values.
+ * After parsing, any valid [LiteralString] will contain a [DartString]
+ * representing its content after removing quotes and resolving escapes in
+ * its source.
+ */
+abstract class DartString extends Iterable<int> {
+  factory DartString.empty() => const LiteralDartString("");
+  // This is a convenience constructor. If you need a const literal DartString,
+  // use [const LiteralDartString(string)] directly.
+  factory DartString.literal(String string) => new LiteralDartString(string);
+  factory DartString.rawString(SourceString source, int length) =>
+      new RawSourceDartString(source, length);
+  factory DartString.escapedString(SourceString source, int length) =>
+      new EscapedSourceDartString(source, length);
+  factory DartString.concat(DartString first, DartString second) {
+    if (first.isEmpty) return second;
+    if (second.isEmpty) return first;
+    return new ConsDartString(first, second);
+  }
+  const DartString();
+  int get length;
+  bool get isEmpty => length == 0;
+  Iterator<int> get iterator;
+  String slowToString();
+
+  bool operator ==(var other) {
+    if (other is !DartString) return false;
+    DartString otherString = other;
+    if (length != otherString.length) return false;
+    Iterator it1 = iterator;
+    Iterator it2 = otherString.iterator;
+    while (it1.moveNext()) {
+      if (!it2.moveNext()) return false;
+      if (it1.current != it2.current) return false;
+    }
+    return true;
+  }
+  String toString() => "DartString#${length}:${slowToString()}";
+  SourceString get source;
+}
+
+
+/**
+ * A [DartString] where the content is represented by an actual [String].
+ */
+class LiteralDartString extends DartString {
+  final String string;
+  const LiteralDartString(this.string);
+  int get length => string.length;
+  Iterator<int> get iterator => new StringCodeIterator(string);
+  String slowToString() => string;
+  SourceString get source => new StringWrapper(string);
+}
+
+/**
+ * A [DartString] where the content comes from a slice of the program source.
+ */
+abstract class SourceBasedDartString extends DartString {
+  String toStringCache = null;
+  final SourceString source;
+  final int length;
+  SourceBasedDartString(this.source, this.length);
+  Iterator<int> get iterator;
+}
+
+/**
+ * Special case of a [SourceBasedDartString] where we know the source doesn't
+ * contain any escapes.
+ */
+class RawSourceDartString extends SourceBasedDartString {
+  RawSourceDartString(source, length) : super(source, length);
+  Iterator<int> get iterator => source.iterator;
+  String slowToString() {
+    if (toStringCache != null) return toStringCache;
+    toStringCache  = source.slowToString();
+    return toStringCache;
+  }
+}
+
+/**
+ * General case of a [SourceBasedDartString] where the source might contain
+ * escapes.
+ */
+class EscapedSourceDartString extends SourceBasedDartString {
+  EscapedSourceDartString(source, length) : super(source, length);
+  Iterator<int> get iterator {
+    if (toStringCache != null) return new StringCodeIterator(toStringCache);
+    return new StringEscapeIterator(source);
+  }
+  String slowToString() {
+    if (toStringCache != null) return toStringCache;
+    StringBuffer buffer = new StringBuffer();
+    StringEscapeIterator it = new StringEscapeIterator(source);
+    while (it.moveNext()) {
+      buffer.addCharCode(it.current);
+    }
+    toStringCache = buffer.toString();
+    return toStringCache;
+  }
+}
+
+/**
+ * The concatenation of two [DartString]s.
+ */
+class ConsDartString extends DartString {
+  final DartString left;
+  final DartString right;
+  final int length;
+  String toStringCache;
+  ConsDartString(DartString left, DartString right)
+      : this.left = left,
+        this.right = right,
+        length = left.length + right.length;
+
+  Iterator<int> get iterator => new ConsDartStringIterator(this);
+
+  String slowToString() {
+    if (toStringCache != null) return toStringCache;
+    toStringCache = left.slowToString().concat(right.slowToString());
+    return toStringCache;
+  }
+  SourceString get source => new StringWrapper(slowToString());
+}
+
+class ConsDartStringIterator implements Iterator<int> {
+  HasNextIterator<int> currentIterator;
+  DartString right;
+  bool hasNextLookAhead;
+  int _current = null;
+
+  ConsDartStringIterator(ConsDartString cons)
+      : currentIterator = new HasNextIterator<int>(cons.left.iterator),
+        right = cons.right {
+    hasNextLookAhead = currentIterator.hasNext;
+    if (!hasNextLookAhead) {
+      nextPart();
+    }
+  }
+
+  int get current => _current;
+
+  bool moveNext() {
+    if (!hasNextLookAhead) {
+      _current = null;
+      return false;
+    }
+    _current = currentIterator.next();
+    hasNextLookAhead = currentIterator.hasNext;
+    if (!hasNextLookAhead) {
+      nextPart();
+    }
+    return true;
+  }
+  void nextPart() {
+    if (right != null) {
+      currentIterator = new HasNextIterator<int>(right.iterator);
+      right = null;
+      hasNextLookAhead = currentIterator.hasNext;
+    }
+  }
+}
+
+/**
+ *Iterator that returns the actual string contents of a string with escapes.
+ */
+class StringEscapeIterator implements Iterator<int>{
+  final Iterator<int> source;
+  int _current = null;
+
+  StringEscapeIterator(SourceString source) : this.source = source.iterator;
+
+  int get current => _current;
+
+  bool moveNext() {
+    if (!source.moveNext()) {
+      _current = null;
+      return false;
+    }
+    int code = source.current;
+    if (code != $BACKSLASH) {
+      _current = code;
+      return true;
+    }
+    source.moveNext();
+    code = source.current;
+    switch (code) {
+      case $n: _current = $LF; break;
+      case $r: _current = $CR; break;
+      case $t: _current = $TAB; break;
+      case $b: _current = $BS; break;
+      case $f: _current = $FF; break;
+      case $v: _current = $VTAB; break;
+      case $x:
+        source.moveNext();
+        int value = hexDigitValue(source.current);
+        source.moveNext();
+        value = value * 16 + hexDigitValue(source.current);
+        _current = value;
+        break;
+      case $u:
+        int value = 0;
+        source.moveNext();
+        code = source.current;
+        if (code == $OPEN_CURLY_BRACKET) {
+          source.moveNext();
+          while (source.current != $CLOSE_CURLY_BRACKET) {
+            value = value * 16 + hexDigitValue(source.current);
+            source.moveNext();
+          }
+          _current = value;
+          break;
+        }
+        // Four digit hex value.
+        value = hexDigitValue(code);
+        for (int i = 0; i < 3; i++) {
+          source.moveNext();
+          value = value * 16 + hexDigitValue(source.current);
+        }
+        _current = value;
+        break;
+      default:
+        _current = code;
+    }
+    return true;
+  }
+}
+
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart
new file mode 100644
index 0000000..1b3b455
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart
@@ -0,0 +1,2086 @@
+// 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.
+
+part of tree;
+
+abstract class Visitor<R> {
+  const Visitor();
+
+  R visitNode(Node node);
+
+  R visitBlock(Block node) => visitStatement(node);
+  R visitBreakStatement(BreakStatement node) => visitGotoStatement(node);
+  R visitCascade(Cascade node) => visitExpression(node);
+  R visitCascadeReceiver(CascadeReceiver node) => visitExpression(node);
+  R visitCaseMatch(CaseMatch node) => visitNode(node);
+  R visitCatchBlock(CatchBlock node) => visitNode(node);
+  R visitClassNode(ClassNode node) => visitNode(node);
+  R visitCombinator(Combinator node) => visitNode(node);
+  R visitConditional(Conditional node) => visitExpression(node);
+  R visitContinueStatement(ContinueStatement node) => visitGotoStatement(node);
+  R visitDoWhile(DoWhile node) => visitLoop(node);
+  R visitEmptyStatement(EmptyStatement node) => visitStatement(node);
+  R visitExport(Export node) => visitLibraryDependency(node);
+  R visitExpression(Expression node) => visitNode(node);
+  R visitExpressionStatement(ExpressionStatement node) => visitStatement(node);
+  R visitFor(For node) => visitLoop(node);
+  R visitForIn(ForIn node) => visitLoop(node);
+  R visitFunctionDeclaration(FunctionDeclaration node) => visitStatement(node);
+  R visitFunctionExpression(FunctionExpression node) => visitExpression(node);
+  R visitGotoStatement(GotoStatement node) => visitStatement(node);
+  R visitIdentifier(Identifier node) => visitExpression(node);
+  R visitIf(If node) => visitStatement(node);
+  R visitImport(Import node) => visitLibraryDependency(node);
+  R visitLabel(Label node) => visitNode(node);
+  R visitLabeledStatement(LabeledStatement node) => visitStatement(node);
+  R visitLibraryDependency(LibraryDependency node) => visitLibraryTag(node);
+  R visitLibraryName(LibraryName node) => visitLibraryTag(node);
+  R visitLibraryTag(LibraryTag node) => visitNode(node);
+  R visitLiteral(Literal node) => visitExpression(node);
+  R visitLiteralBool(LiteralBool node) => visitLiteral(node);
+  R visitLiteralDouble(LiteralDouble node) => visitLiteral(node);
+  R visitLiteralInt(LiteralInt node) => visitLiteral(node);
+  R visitLiteralList(LiteralList node) => visitExpression(node);
+  R visitLiteralMap(LiteralMap node) => visitExpression(node);
+  R visitLiteralMapEntry(LiteralMapEntry node) => visitNode(node);
+  R visitLiteralNull(LiteralNull node) => visitLiteral(node);
+  R visitLiteralString(LiteralString node) => visitStringNode(node);
+  R visitStringJuxtaposition(StringJuxtaposition node) => visitStringNode(node);
+  R visitLoop(Loop node) => visitStatement(node);
+  R visitMixinApplication(MixinApplication node) => visitNode(node);
+  R visitModifiers(Modifiers node) => visitNode(node);
+  R visitNamedArgument(NamedArgument node) => visitExpression(node);
+  R visitNamedMixinApplication(NamedMixinApplication node) {
+    return visitMixinApplication(node);
+  }
+  R visitNewExpression(NewExpression node) => visitExpression(node);
+  R visitNodeList(NodeList node) => visitNode(node);
+  R visitOperator(Operator node) => visitIdentifier(node);
+  R visitParenthesizedExpression(ParenthesizedExpression node) {
+    return visitExpression(node);
+  }
+  R visitPart(Part node) => visitLibraryTag(node);
+  R visitPartOf(PartOf node) => visitNode(node);
+  R visitPostfix(Postfix node) => visitNodeList(node);
+  R visitPrefix(Prefix node) => visitNodeList(node);
+  R visitReturn(Return node) => visitStatement(node);
+  R visitScriptTag(ScriptTag node) => visitNode(node);
+  R visitSend(Send node) => visitExpression(node);
+  R visitSendSet(SendSet node) => visitSend(node);
+  R visitStatement(Statement node) => visitNode(node);
+  R visitStringNode(StringNode node) => visitExpression(node);
+  R visitStringInterpolation(StringInterpolation node) => visitStringNode(node);
+  R visitStringInterpolationPart(StringInterpolationPart node) {
+    return visitNode(node);
+  }
+  R visitSwitchCase(SwitchCase node) => visitNode(node);
+  R visitSwitchStatement(SwitchStatement node) => visitStatement(node);
+  R visitThrow(Throw node) => visitStatement(node);
+  R visitTryStatement(TryStatement node) => visitStatement(node);
+  R visitTypeAnnotation(TypeAnnotation node) => visitNode(node);
+  R visitTypedef(Typedef node) => visitNode(node);
+  R visitTypeVariable(TypeVariable node) => visitNode(node);
+  R visitVariableDefinitions(VariableDefinitions node) => visitStatement(node);
+  R visitWhile(While node) => visitLoop(node);
+}
+
+Token firstBeginToken(Node first, Node second) {
+  Token token = null;
+  if (first != null) {
+    token = first.getBeginToken();
+  }
+  if (token == null && second != null) {
+    // [token] might be null even when [first] is not, e.g. for empty Modifiers.
+    token = second.getBeginToken();
+  }
+  return token;
+}
+
+/**
+ * A node in a syntax tree.
+ *
+ * The abstract part of "abstract syntax tree" is invalidated when
+ * supporting tools such as code formatting. These tools need concrete
+ * syntax such as parentheses and no constant folding.
+ *
+ * We support these tools by storing additional references back to the
+ * token stream. These references are stored in fields ending with
+ * "Token".
+ */
+abstract class Node extends TreeElementMixin implements Spannable {
+  final int hashCode;
+  static int _HASH_COUNTER = 0;
+
+  Node() : hashCode = ++_HASH_COUNTER;
+
+  accept(Visitor visitor);
+
+  visitChildren(Visitor visitor);
+
+  /**
+   * Returns this node unparsed to Dart source string.
+   */
+  toString() => unparse(this);
+
+  /**
+   * Returns Xml-like tree representation of this node.
+   */
+  toDebugString() {
+    return PrettyPrinter.prettyPrint(this);
+  }
+
+  String getObjectDescription() => super.toString();
+
+  Token getBeginToken();
+
+  Token getEndToken();
+
+  Block asBlock() => null;
+  BreakStatement asBreakStatement() => null;
+  Cascade asCascade() => null;
+  CascadeReceiver asCascadeReceiver() => null;
+  CaseMatch asCaseMatch() => null;
+  CatchBlock asCatchBlock() => null;
+  ClassNode asClassNode() => null;
+  Combinator asCombinator() => null;
+  Conditional asConditional() => null;
+  ContinueStatement asContinueStatement() => null;
+  DoWhile asDoWhile() => null;
+  EmptyStatement asEmptyStatement() => null;
+  Export asExport() => null;
+  Expression asExpression() => null;
+  ExpressionStatement asExpressionStatement() => null;
+  For asFor() => null;
+  ForIn asForIn() => null;
+  FunctionDeclaration asFunctionDeclaration() => null;
+  FunctionExpression asFunctionExpression() => null;
+  Identifier asIdentifier() => null;
+  If asIf() => null;
+  Import asImport() => null;
+  Label asLabel() => null;
+  LabeledStatement asLabeledStatement() => null;
+  LibraryName asLibraryName() => null;
+  LiteralBool asLiteralBool() => null;
+  LiteralDouble asLiteralDouble() => null;
+  LiteralInt asLiteralInt() => null;
+  LiteralList asLiteralList() => null;
+  LiteralMap asLiteralMap() => null;
+  LiteralMapEntry asLiteralMapEntry() => null;
+  LiteralNull asLiteralNull() => null;
+  LiteralString asLiteralString() => null;
+  MixinApplication asMixinApplication() => null;
+  Modifiers asModifiers() => null;
+  NamedArgument asNamedArgument() => null;
+  NamedMixinApplication asNamedMixinApplication() => null;
+  NodeList asNodeList() => null;
+  Operator asOperator() => null;
+  ParenthesizedExpression asParenthesizedExpression() => null;
+  Part asPart() => null;
+  PartOf asPartOf() => null;
+  Return asReturn() => null;
+  ScriptTag asScriptTag() => null;
+  Send asSend() => null;
+  SendSet asSendSet() => null;
+  Statement asStatement() => null;
+  StringInterpolation asStringInterpolation() => null;
+  StringInterpolationPart asStringInterpolationPart() => null;
+  StringJuxtaposition asStringJuxtaposition() => null;
+  StringNode asStringNode() => null;
+  SwitchCase asSwitchCase() => null;
+  SwitchStatement asSwitchStatement() => null;
+  Throw asThrow() => null;
+  TryStatement asTryStatement() => null;
+  TypeAnnotation asTypeAnnotation() => null;
+  TypeVariable asTypeVariable() => null;
+  Typedef asTypedef() => null;
+  VariableDefinitions asVariableDefinitions() => null;
+  While asWhile() => null;
+
+  bool isValidBreakTarget() => false;
+  bool isValidContinueTarget() => false;
+}
+
+class ClassNode extends Node {
+  final Modifiers modifiers;
+  final Identifier name;
+  final Node superclass;
+  final NodeList interfaces;
+  final NodeList typeParameters;
+  final NodeList body;
+
+  // TODO(ahe, karlklose): the default keyword is not recorded.
+  final TypeAnnotation defaultClause;
+
+  final Token beginToken;
+  final Token extendsKeyword;
+  final Token endToken;
+
+  ClassNode(this.modifiers, this.name, this.typeParameters, this.superclass,
+            this.interfaces, this.defaultClause, this.beginToken,
+            this.extendsKeyword, this.body, this.endToken);
+
+  ClassNode asClassNode() => this;
+
+  accept(Visitor visitor) => visitor.visitClassNode(this);
+
+  visitChildren(Visitor visitor) {
+    if (name != null) name.accept(visitor);
+    if (typeParameters != null) typeParameters.accept(visitor);
+    if (superclass != null) superclass.accept(visitor);
+    if (interfaces != null) interfaces.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  bool get isInterface => identical(beginToken.stringValue, 'interface');
+
+  bool get isClass => !isInterface;
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => endToken;
+}
+
+class MixinApplication extends Node {
+  final TypeAnnotation superclass;
+  final NodeList mixins;
+
+  MixinApplication(this.superclass, this.mixins);
+
+  MixinApplication asMixinApplication() => this;
+
+  accept(Visitor visitor) => visitor.visitMixinApplication(this);
+
+  visitChildren(Visitor visitor) {
+    if (superclass != null) superclass.accept(visitor);
+    if (mixins != null) mixins.accept(visitor);
+  }
+
+  Token getBeginToken() => superclass.getBeginToken();
+  Token getEndToken() => mixins.getEndToken();
+}
+
+// TODO(kasperl): Let this share some structure with the typedef for function
+// type aliases?
+class NamedMixinApplication extends Node implements MixinApplication {
+  final Identifier name;
+  final NodeList typeParameters;
+
+  final Modifiers modifiers;
+  final MixinApplication mixinApplication;
+  final NodeList interfaces;
+
+  final Token typedefKeyword;
+  final Token endToken;
+
+  NamedMixinApplication(this.name, this.typeParameters,
+                        this.modifiers, this.mixinApplication, this.interfaces,
+                        this.typedefKeyword, this.endToken);
+
+  TypeAnnotation get superclass => mixinApplication.superclass;
+  NodeList get mixins => mixinApplication.mixins;
+
+  MixinApplication asMixinApplication() => this;
+  NamedMixinApplication asNamedMixinApplication() => this;
+
+  accept(Visitor visitor) => visitor.visitNamedMixinApplication(this);
+
+  visitChildren(Visitor visitor) {
+    name.accept(visitor);
+    if (typeParameters != null) typeParameters.accept(visitor);
+    if (modifiers != null) modifiers.accept(visitor);
+    if (interfaces != null) interfaces.accept(visitor);
+    mixinApplication.accept(visitor);
+  }
+
+  Token getBeginToken() => typedefKeyword;
+  Token getEndToken() => endToken;
+}
+
+abstract class Expression extends Node {
+  Expression();
+
+  Expression asExpression() => this;
+
+  // TODO(ahe): make class abstract instead of adding an abstract method.
+  accept(Visitor visitor);
+}
+
+abstract class Statement extends Node {
+  Statement();
+
+  Statement asStatement() => this;
+
+  // TODO(ahe): make class abstract instead of adding an abstract method.
+  accept(Visitor visitor);
+
+  bool isValidBreakTarget() => true;
+}
+
+/**
+ * A message send aka method invocation. In Dart, most operations can
+ * (and should) be considered as message sends. Getters and setters
+ * are just methods with a special syntax. Consequently, we model
+ * property access, assignment, operators, and method calls with this
+ * one node.
+ */
+class Send extends Expression {
+  final Node receiver;
+  final Node selector;
+  final NodeList argumentsNode;
+  Link<Node> get arguments => argumentsNode.nodes;
+
+  Send([this.receiver, this.selector, this.argumentsNode]);
+  Send.postfix(this.receiver, this.selector, [Node argument = null])
+      : argumentsNode = (argument == null)
+        ? new Postfix()
+        : new Postfix.singleton(argument);
+  Send.prefix(this.receiver, this.selector, [Node argument = null])
+      : argumentsNode = (argument == null)
+        ? new Prefix()
+        : new Prefix.singleton(argument);
+
+  Send asSend() => this;
+
+  accept(Visitor visitor) => visitor.visitSend(this);
+
+  visitChildren(Visitor visitor) {
+    if (receiver != null) receiver.accept(visitor);
+    if (selector != null) selector.accept(visitor);
+    if (argumentsNode != null) argumentsNode.accept(visitor);
+  }
+
+  int argumentCount() {
+    return (argumentsNode == null) ? -1 : argumentsNode.slowLength();
+  }
+
+  bool get isSuperCall {
+    return receiver != null &&
+           receiver.asIdentifier() != null &&
+           receiver.asIdentifier().isSuper();
+  }
+  bool get isOperator => selector is Operator;
+  bool get isPropertyAccess => argumentsNode == null;
+  bool get isFunctionObjectInvocation => selector == null;
+  bool get isPrefix => argumentsNode is Prefix;
+  bool get isPostfix => argumentsNode is Postfix;
+  bool get isCall => !isOperator && !isPropertyAccess;
+  bool get isIndex =>
+      isOperator && identical(selector.asOperator().source.stringValue, '[]');
+  bool get isLogicalAnd =>
+      isOperator && identical(selector.asOperator().source.stringValue, '&&');
+  bool get isLogicalOr =>
+      isOperator && identical(selector.asOperator().source.stringValue, '||');
+  bool get isParameterCheck =>
+      isOperator && identical(selector.asOperator().source.stringValue, '?');
+
+  Token getBeginToken() {
+    if (isPrefix && !isIndex) return selector.getBeginToken();
+    return firstBeginToken(receiver, selector);
+  }
+
+  Token getEndToken() {
+    if (isPrefix) {
+      if (receiver != null) return receiver.getEndToken();
+      if (selector != null) return selector.getEndToken();
+      return null;
+    }
+    if (!isPostfix && argumentsNode != null) {
+      return argumentsNode.getEndToken();
+    }
+    if (selector != null) return selector.getEndToken();
+    return receiver.getBeginToken();
+  }
+
+  Send copyWithReceiver(Node newReceiver) {
+    assert(receiver == null);
+    return new Send(newReceiver, selector, argumentsNode);
+  }
+}
+
+class Postfix extends NodeList {
+  Postfix() : super(null, const Link<Node>());
+  Postfix.singleton(Node argument) : super.singleton(argument);
+}
+
+class Prefix extends NodeList {
+  Prefix() : super(null, const Link<Node>());
+  Prefix.singleton(Node argument) : super.singleton(argument);
+}
+
+class SendSet extends Send {
+  final Operator assignmentOperator;
+  SendSet(receiver, selector, this.assignmentOperator, argumentsNode)
+    : super(receiver, selector, argumentsNode);
+  SendSet.postfix(receiver,
+                  selector,
+                  this.assignmentOperator,
+                  [Node argument = null])
+      : super.postfix(receiver, selector, argument);
+  SendSet.prefix(receiver,
+                 selector,
+                 this.assignmentOperator,
+                 [Node argument = null])
+      : super.prefix(receiver, selector, argument);
+
+  SendSet asSendSet() => this;
+
+  accept(Visitor visitor) => visitor.visitSendSet(this);
+
+  visitChildren(Visitor visitor) {
+    super.visitChildren(visitor);
+    if (assignmentOperator != null) assignmentOperator.accept(visitor);
+  }
+
+  Send copyWithReceiver(Node newReceiver) {
+    assert(receiver == null);
+    return new SendSet(newReceiver, selector, assignmentOperator,
+                       argumentsNode);
+  }
+
+  Token getBeginToken() {
+    if (isPrefix) return assignmentOperator.getBeginToken();
+    return super.getBeginToken();
+  }
+
+  Token getEndToken() {
+    if (isPostfix) return assignmentOperator.getEndToken();
+    return super.getEndToken();
+  }
+}
+
+class NewExpression extends Expression {
+  /** The token NEW or CONST */
+  final Token newToken;
+
+  // Note: we expect that send.receiver is null.
+  final Send send;
+
+  NewExpression([this.newToken, this.send]);
+
+  accept(Visitor visitor) => visitor.visitNewExpression(this);
+
+  visitChildren(Visitor visitor) {
+    if (send != null) send.accept(visitor);
+  }
+
+  bool isConst() {
+    return identical(newToken.stringValue, 'const')
+        || identical(newToken.stringValue, '@');
+  }
+
+  Token getBeginToken() => newToken;
+
+  Token getEndToken() => send.getEndToken();
+}
+
+class NodeList extends Node {
+  final Link<Node> nodes;
+  final Token beginToken;
+  final Token endToken;
+  final SourceString delimiter;
+  bool get isEmpty => nodes.isEmpty;
+
+  NodeList([this.beginToken, this.nodes, this.endToken, this.delimiter]);
+
+  Iterator<Node> get iterator => nodes.iterator;
+
+  NodeList.singleton(Node node) : this(null, const Link<Node>().prepend(node));
+  NodeList.empty() : this(null, const Link<Node>());
+
+  NodeList asNodeList() => this;
+
+  int slowLength() {
+    int result = 0;
+    for (Link<Node> cursor = nodes; !cursor.isEmpty; cursor = cursor.tail) {
+      result++;
+    }
+    return result;
+  }
+
+  accept(Visitor visitor) => visitor.visitNodeList(this);
+
+  visitChildren(Visitor visitor) {
+    if (nodes == null) return;
+    for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) {
+      if (link.head != null) link.head.accept(visitor);
+    }
+  }
+
+  Token getBeginToken() {
+    if (beginToken != null) return beginToken;
+     if (nodes != null) {
+       for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) {
+         if (link.head.getBeginToken() != null) {
+           return link.head.getBeginToken();
+         }
+         if (link.head.getEndToken() != null) {
+           return link.head.getEndToken();
+         }
+       }
+     }
+    return endToken;
+  }
+
+  Token getEndToken() {
+    if (endToken != null) return endToken;
+    if (nodes != null) {
+      Link<Node> link = nodes;
+      if (link.isEmpty) return beginToken;
+      while (!link.tail.isEmpty) link = link.tail;
+      if (link.head.getEndToken() != null) return link.head.getEndToken();
+      if (link.head.getBeginToken() != null) return link.head.getBeginToken();
+    }
+    return beginToken;
+  }
+}
+
+class Block extends Statement {
+  final NodeList statements;
+
+  Block(this.statements);
+
+  Block asBlock() => this;
+
+  accept(Visitor visitor) => visitor.visitBlock(this);
+
+  visitChildren(Visitor visitor) {
+    if (statements != null) statements.accept(visitor);
+  }
+
+  Token getBeginToken() => statements.getBeginToken();
+
+  Token getEndToken() => statements.getEndToken();
+}
+
+class If extends Statement {
+  final ParenthesizedExpression condition;
+  final Statement thenPart;
+  final Statement elsePart;
+
+  final Token ifToken;
+  final Token elseToken;
+
+  If(this.condition, this.thenPart, this.elsePart,
+     this.ifToken, this.elseToken);
+
+  If asIf() => this;
+
+  bool get hasElsePart => elsePart != null;
+
+  void validate() {
+    // TODO(ahe): Check that condition has size one.
+  }
+
+  accept(Visitor visitor) => visitor.visitIf(this);
+
+  visitChildren(Visitor visitor) {
+    if (condition != null) condition.accept(visitor);
+    if (thenPart != null) thenPart.accept(visitor);
+    if (elsePart != null) elsePart.accept(visitor);
+  }
+
+  Token getBeginToken() => ifToken;
+
+  Token getEndToken() {
+    if (elsePart == null) return thenPart.getEndToken();
+    return elsePart.getEndToken();
+  }
+}
+
+class Conditional extends Expression {
+  final Expression condition;
+  final Expression thenExpression;
+  final Expression elseExpression;
+
+  final Token questionToken;
+  final Token colonToken;
+
+  Conditional(this.condition, this.thenExpression,
+              this.elseExpression, this.questionToken, this.colonToken);
+
+  Conditional asConditional() => this;
+
+  accept(Visitor visitor) => visitor.visitConditional(this);
+
+  visitChildren(Visitor visitor) {
+    condition.accept(visitor);
+    thenExpression.accept(visitor);
+    elseExpression.accept(visitor);
+  }
+
+  Token getBeginToken() => condition.getBeginToken();
+
+  Token getEndToken() => elseExpression.getEndToken();
+}
+
+class For extends Loop {
+  /** Either a variable declaration or an expression. */
+  final Node initializer;
+  /** Either an expression statement or an empty statement. */
+  final Statement conditionStatement;
+  final NodeList update;
+
+  final Token forToken;
+
+  For(this.initializer, this.conditionStatement, this.update, body,
+      this.forToken) : super(body);
+
+  For asFor() => this;
+
+  Expression get condition {
+    if (conditionStatement is ExpressionStatement) {
+      return conditionStatement.asExpressionStatement().expression;
+    } else {
+      return null;
+    }
+  }
+
+  accept(Visitor visitor) => visitor.visitFor(this);
+
+  visitChildren(Visitor visitor) {
+    if (initializer != null) initializer.accept(visitor);
+    if (conditionStatement != null) conditionStatement.accept(visitor);
+    if (update != null) update.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  Token getBeginToken() => forToken;
+
+  Token getEndToken() {
+    return body.getEndToken();
+  }
+}
+
+class FunctionDeclaration extends Statement {
+  final FunctionExpression function;
+
+  FunctionDeclaration(this.function);
+
+  FunctionDeclaration asFunctionDeclaration() => this;
+
+  accept(Visitor visitor) => visitor.visitFunctionDeclaration(this);
+
+  visitChildren(Visitor visitor) => function.accept(visitor);
+
+  Token getBeginToken() => function.getBeginToken();
+  Token getEndToken() => function.getEndToken();
+}
+
+class FunctionExpression extends Expression {
+  final Node name;
+
+  /**
+   * List of VariableDefinitions or NodeList.
+   *
+   * A NodeList can only occur at the end and holds named parameters.
+   */
+  final NodeList parameters;
+
+  final Statement body;
+  final TypeAnnotation returnType;
+  final Modifiers modifiers;
+  final NodeList initializers;
+
+  final Token getOrSet;
+
+  FunctionExpression(this.name, this.parameters, this.body, this.returnType,
+                     this.modifiers, this.initializers, this.getOrSet) {
+    assert(modifiers != null);
+  }
+
+  FunctionExpression asFunctionExpression() => this;
+
+  accept(Visitor visitor) => visitor.visitFunctionExpression(this);
+
+  visitChildren(Visitor visitor) {
+    if (modifiers != null) modifiers.accept(visitor);
+    if (returnType != null) returnType.accept(visitor);
+    if (name != null) name.accept(visitor);
+    if (parameters != null) parameters.accept(visitor);
+    if (initializers != null) initializers.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  bool hasBody() => body.asEmptyStatement() == null;
+
+  bool hasEmptyBody() {
+    Block block = body.asBlock();
+    if (block == null) return false;
+    return block.statements.isEmpty;
+  }
+
+  Token getBeginToken() {
+    Token token = firstBeginToken(modifiers, returnType);
+    if (token != null) return token;
+    if (getOrSet != null) return getOrSet;
+    return firstBeginToken(name, parameters);
+  }
+
+  Token getEndToken() {
+    Token token = (body == null) ? null : body.getEndToken();
+    token = (token == null) ? parameters.getEndToken() : token;
+    return (token == null) ? name.getEndToken() : token;
+  }
+}
+
+typedef void DecodeErrorHandler(Token token, var error);
+
+abstract class Literal<T> extends Expression {
+  final Token token;
+  final DecodeErrorHandler handler;
+
+  Literal(Token this.token, DecodeErrorHandler this.handler);
+
+  T get value;
+
+  visitChildren(Visitor visitor) {}
+
+  Token getBeginToken() => token;
+
+  Token getEndToken() => token;
+}
+
+class LiteralInt extends Literal<int> {
+  LiteralInt(Token token, DecodeErrorHandler handler) : super(token, handler);
+
+  LiteralInt asLiteralInt() => this;
+
+  int get value {
+    try {
+      Token valueToken = token;
+      if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next;
+      return int.parse(valueToken.value.slowToString());
+    } on FormatException catch (ex) {
+      (this.handler)(token, ex);
+    }
+  }
+
+  accept(Visitor visitor) => visitor.visitLiteralInt(this);
+}
+
+class LiteralDouble extends Literal<double> {
+  LiteralDouble(Token token, DecodeErrorHandler handler)
+    : super(token, handler);
+
+  LiteralDouble asLiteralDouble() => this;
+
+  double get value {
+    try {
+      Token valueToken = token;
+      if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next;
+      return double.parse(valueToken.value.slowToString());
+    } on FormatException catch (ex) {
+      (this.handler)(token, ex);
+    }
+  }
+
+  accept(Visitor visitor) => visitor.visitLiteralDouble(this);
+}
+
+class LiteralBool extends Literal<bool> {
+  LiteralBool(Token token, DecodeErrorHandler handler) : super(token, handler);
+
+  LiteralBool asLiteralBool() => this;
+
+  bool get value {
+    if (identical(token.stringValue, 'true')) return true;
+    if (identical(token.stringValue, 'false')) return false;
+    (this.handler)(token, "not a bool ${token.value}");
+  }
+
+  accept(Visitor visitor) => visitor.visitLiteralBool(this);
+}
+
+
+class StringQuoting {
+  static const StringQuoting SINGLELINE_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 1);
+  static const StringQuoting RAW_SINGLELINE_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 1);
+  static const StringQuoting MULTILINE_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 3);
+  static const StringQuoting RAW_MULTILINE_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 3);
+  static const StringQuoting MULTILINE_NL_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 4);
+  static const StringQuoting RAW_MULTILINE_NL_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 4);
+  static const StringQuoting MULTILINE_NL2_DQ =
+      const StringQuoting($DQ, raw: false, leftQuoteLength: 5);
+  static const StringQuoting RAW_MULTILINE_NL2_DQ =
+      const StringQuoting($DQ, raw: true, leftQuoteLength: 5);
+  static const StringQuoting SINGLELINE_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 1);
+  static const StringQuoting RAW_SINGLELINE_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 1);
+  static const StringQuoting MULTILINE_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 3);
+  static const StringQuoting RAW_MULTILINE_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 3);
+  static const StringQuoting MULTILINE_NL_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 4);
+  static const StringQuoting RAW_MULTILINE_NL_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 4);
+  static const StringQuoting MULTILINE_NL2_SQ =
+      const StringQuoting($SQ, raw: false, leftQuoteLength: 5);
+  static const StringQuoting RAW_MULTILINE_NL2_SQ =
+      const StringQuoting($SQ, raw: true, leftQuoteLength: 5);
+
+
+  static const List<StringQuoting> mapping = const <StringQuoting>[
+    SINGLELINE_DQ,
+    RAW_SINGLELINE_DQ,
+    MULTILINE_DQ,
+    RAW_MULTILINE_DQ,
+    MULTILINE_NL_DQ,
+    RAW_MULTILINE_NL_DQ,
+    MULTILINE_NL2_DQ,
+    RAW_MULTILINE_NL2_DQ,
+    SINGLELINE_SQ,
+    RAW_SINGLELINE_SQ,
+    MULTILINE_SQ,
+    RAW_MULTILINE_SQ,
+    MULTILINE_NL_SQ,
+    RAW_MULTILINE_NL_SQ,
+    MULTILINE_NL2_SQ,
+    RAW_MULTILINE_NL2_SQ
+  ];
+  final bool raw;
+  final int leftQuoteCharCount;
+  final int quote;
+  const StringQuoting(this.quote, {bool raw, int leftQuoteLength})
+      : this.raw = raw, this.leftQuoteCharCount = leftQuoteLength;
+  String get quoteChar => identical(quote, $DQ) ? '"' : "'";
+
+  int get leftQuoteLength => (raw ? 1 : 0) + leftQuoteCharCount;
+  int get rightQuoteLength => (leftQuoteCharCount > 2) ? 3 : 1;
+  static StringQuoting getQuoting(int quote, bool raw, int quoteLength) {
+    int index = quoteLength - 1;
+    if (quoteLength > 2) index -= 1;
+    return mapping[(raw ? 1 : 0) + index * 2 + (identical(quote, $SQ) ? 8 : 0)];
+  }
+}
+
+/**
+  * Superclass for classes representing string literals.
+  */
+abstract class StringNode extends Expression {
+  DartString get dartString;
+  bool get isInterpolation;
+
+  StringNode asStringNode() => this;
+}
+
+class LiteralString extends StringNode {
+  final Token token;
+  /** Non-null on validated string literals. */
+  final DartString dartString;
+
+  LiteralString(this.token, this.dartString);
+
+  LiteralString asLiteralString() => this;
+
+  void visitChildren(Visitor visitor) {}
+
+  bool get isInterpolation => false;
+  bool isValidated() => dartString != null;
+
+  Token getBeginToken() => token;
+  Token getEndToken() => token;
+
+  accept(Visitor visitor) => visitor.visitLiteralString(this);
+}
+
+class LiteralNull extends Literal<SourceString> {
+  LiteralNull(Token token) : super(token, null);
+
+  LiteralNull asLiteralNull() => this;
+
+  SourceString get value => null;
+
+  accept(Visitor visitor) => visitor.visitLiteralNull(this);
+}
+
+class LiteralList extends Expression {
+  final NodeList typeArguments;
+  final NodeList elements;
+
+  final Token constKeyword;
+
+  LiteralList(this.typeArguments, this.elements, this.constKeyword);
+
+  bool isConst() => constKeyword != null;
+
+  LiteralList asLiteralList() => this;
+  accept(Visitor visitor) => visitor.visitLiteralList(this);
+
+  visitChildren(Visitor visitor) {
+    if (typeArguments != null) typeArguments.accept(visitor);
+    elements.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    if (constKeyword != null) return constKeyword;
+    return firstBeginToken(typeArguments, elements);
+  }
+
+  Token getEndToken() => elements.getEndToken();
+}
+
+class Identifier extends Expression {
+  final Token token;
+
+  SourceString get source => token.value;
+
+  Identifier(Token this.token);
+
+  bool isThis() => identical(source.stringValue, 'this');
+
+  bool isSuper() => identical(source.stringValue, 'super');
+
+  Identifier asIdentifier() => this;
+
+  accept(Visitor visitor) => visitor.visitIdentifier(this);
+
+  visitChildren(Visitor visitor) {}
+
+  Token getBeginToken() => token;
+
+  Token getEndToken() => token;
+}
+
+class Operator extends Identifier {
+  Operator(Token token) : super(token);
+
+  Operator asOperator() => this;
+
+  accept(Visitor visitor) => visitor.visitOperator(this);
+}
+
+class Return extends Statement {
+  final Node expression;
+  final Token beginToken;
+  final Token endToken;
+
+  Return(this.beginToken, this.endToken, this.expression);
+
+  Return asReturn() => this;
+
+  bool get hasExpression => expression != null;
+
+  bool get isRedirectingFactoryBody => beginToken.stringValue == '=';
+
+  accept(Visitor visitor) => visitor.visitReturn(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() {
+    if (endToken == null) return expression.getEndToken();
+    return endToken;
+  }
+}
+
+class ExpressionStatement extends Statement {
+  final Expression expression;
+  final Token endToken;
+
+  ExpressionStatement(this.expression, this.endToken);
+
+  ExpressionStatement asExpressionStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitExpressionStatement(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => endToken;
+}
+
+class Throw extends Statement {
+  final Expression expression;
+
+  final Token throwToken;
+  final Token endToken;
+
+  Throw(this.expression, this.throwToken, this.endToken);
+
+  Throw asThrow() => this;
+
+  accept(Visitor visitor) => visitor.visitThrow(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => throwToken;
+
+  Token getEndToken() => endToken;
+}
+
+class TypeAnnotation extends Node {
+  final Expression typeName;
+  final NodeList typeArguments;
+
+  TypeAnnotation(Expression this.typeName, NodeList this.typeArguments);
+
+  TypeAnnotation asTypeAnnotation() => this;
+
+  accept(Visitor visitor) => visitor.visitTypeAnnotation(this);
+
+  visitChildren(Visitor visitor) {
+    typeName.accept(visitor);
+    if (typeArguments != null) typeArguments.accept(visitor);
+  }
+
+  Token getBeginToken() => typeName.getBeginToken();
+
+  Token getEndToken() => typeName.getEndToken();
+}
+
+class TypeVariable extends Node {
+  final Identifier name;
+  final TypeAnnotation bound;
+  TypeVariable(Identifier this.name, TypeAnnotation this.bound);
+
+  accept(Visitor visitor) => visitor.visitTypeVariable(this);
+
+  visitChildren(Visitor visitor) {
+    name.accept(visitor);
+    if (bound != null) {
+      bound.accept(visitor);
+    }
+  }
+
+  TypeVariable asTypeVariable() => this;
+
+  Token getBeginToken() => name.getBeginToken();
+
+  Token getEndToken() {
+    return (bound != null) ? bound.getEndToken() : name.getEndToken();
+  }
+}
+
+class VariableDefinitions extends Statement {
+  final TypeAnnotation type;
+  final Modifiers modifiers;
+  final NodeList definitions;
+  VariableDefinitions(this.type, this.modifiers, this.definitions) {
+    assert(modifiers != null);
+  }
+
+  VariableDefinitions asVariableDefinitions() => this;
+
+  accept(Visitor visitor) => visitor.visitVariableDefinitions(this);
+
+  visitChildren(Visitor visitor) {
+    if (type != null) type.accept(visitor);
+    if (definitions != null) definitions.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    var token = firstBeginToken(modifiers, type);
+    if (token == null) {
+      token = definitions.getBeginToken();
+    }
+    return token;
+  }
+
+  Token getEndToken() => definitions.getEndToken();
+}
+
+abstract class Loop extends Statement {
+  Expression get condition;
+  final Statement body;
+
+  Loop(this.body);
+
+  bool isValidContinueTarget() => true;
+}
+
+class DoWhile extends Loop {
+  final Token doKeyword;
+  final Token whileKeyword;
+  final Token endToken;
+
+  final Expression condition;
+
+  DoWhile(Statement body, Expression this.condition,
+          Token this.doKeyword, Token this.whileKeyword, Token this.endToken)
+    : super(body);
+
+  DoWhile asDoWhile() => this;
+
+  accept(Visitor visitor) => visitor.visitDoWhile(this);
+
+  visitChildren(Visitor visitor) {
+    if (condition != null) condition.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  Token getBeginToken() => doKeyword;
+
+  Token getEndToken() => endToken;
+}
+
+class While extends Loop {
+  final Token whileKeyword;
+  final Expression condition;
+
+  While(Expression this.condition, Statement body,
+        Token this.whileKeyword) : super(body);
+
+  While asWhile() => this;
+
+  accept(Visitor visitor) => visitor.visitWhile(this);
+
+  visitChildren(Visitor visitor) {
+    if (condition != null) condition.accept(visitor);
+    if (body != null) body.accept(visitor);
+  }
+
+  Token getBeginToken() => whileKeyword;
+
+  Token getEndToken() => body.getEndToken();
+}
+
+class ParenthesizedExpression extends Expression {
+  final Expression expression;
+  final BeginGroupToken beginToken;
+
+  ParenthesizedExpression(Expression this.expression,
+                          BeginGroupToken this.beginToken);
+
+  ParenthesizedExpression asParenthesizedExpression() => this;
+
+  accept(Visitor visitor) => visitor.visitParenthesizedExpression(this);
+
+  visitChildren(Visitor visitor) {
+    if (expression != null) expression.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => beginToken.endGroup;
+}
+
+/** Representation of modifiers such as static, abstract, final, etc. */
+class Modifiers extends Node {
+  /**
+   * Pseudo-constant for empty modifiers.
+   */
+  static final Modifiers EMPTY = new Modifiers(new NodeList.empty());
+
+  /* TODO(ahe): The following should be validated relating to modifiers:
+   * 1. The nodes must come in a certain order.
+   * 2. The keywords "var" and "final" may not be used at the same time.
+   * 3. The keywords "abstract" and "external" may not be used at the same time.
+   * 4. The type of an element must be null if isVar() is true.
+   */
+
+  final NodeList nodes;
+  /** Bit pattern to easy check what modifiers are present. */
+  final int flags;
+
+  static const int FLAG_STATIC = 1;
+  static const int FLAG_ABSTRACT = FLAG_STATIC << 1;
+  static const int FLAG_FINAL = FLAG_ABSTRACT << 1;
+  static const int FLAG_VAR = FLAG_FINAL << 1;
+  static const int FLAG_CONST = FLAG_VAR << 1;
+  static const int FLAG_FACTORY = FLAG_CONST << 1;
+  static const int FLAG_EXTERNAL = FLAG_FACTORY << 1;
+
+  Modifiers(NodeList nodes) : this.withFlags(nodes, computeFlags(nodes.nodes));
+
+  Modifiers.withFlags(this.nodes, this.flags);
+
+  static int computeFlags(Link<Node> nodes) {
+    int flags = 0;
+    for (; !nodes.isEmpty; nodes = nodes.tail) {
+      String value = nodes.head.asIdentifier().source.stringValue;
+      if (identical(value, 'static')) flags |= FLAG_STATIC;
+      else if (identical(value, 'abstract')) flags |= FLAG_ABSTRACT;
+      else if (identical(value, 'final')) flags |= FLAG_FINAL;
+      else if (identical(value, 'var')) flags |= FLAG_VAR;
+      else if (identical(value, 'const')) flags |= FLAG_CONST;
+      else if (identical(value, 'factory')) flags |= FLAG_FACTORY;
+      else if (identical(value, 'external')) flags |= FLAG_EXTERNAL;
+      else throw 'internal error: ${nodes.head}';
+    }
+    return flags;
+  }
+
+  Node findModifier(String modifier) {
+    Link<Node> nodeList = nodes.nodes;
+    for (; !nodeList.isEmpty; nodeList = nodeList.tail) {
+      String value = nodeList.head.asIdentifier().source.stringValue;
+      if(identical(value, modifier)) {
+        return nodeList.head;
+      }
+    }
+    return null;
+  }
+
+  Modifiers asModifiers() => this;
+  Token getBeginToken() => nodes.getBeginToken();
+  Token getEndToken() => nodes.getEndToken();
+  accept(Visitor visitor) => visitor.visitModifiers(this);
+  visitChildren(Visitor visitor) => nodes.accept(visitor);
+
+  bool isStatic() => (flags & FLAG_STATIC) != 0;
+  bool isAbstract() => (flags & FLAG_ABSTRACT) != 0;
+  bool isFinal() => (flags & FLAG_FINAL) != 0;
+  bool isVar() => (flags & FLAG_VAR) != 0;
+  bool isConst() => (flags & FLAG_CONST) != 0;
+  bool isFactory() => (flags & FLAG_FACTORY) != 0;
+  bool isExternal() => (flags & FLAG_EXTERNAL) != 0;
+
+  Node getStatic() => findModifier('static');
+
+  /**
+   * Use this to check if the declaration is either explicitly or implicitly
+   * final.
+   */
+  bool isFinalOrConst() => isFinal() || isConst();
+
+  String toString() {
+    LinkBuilder<String> builder = new LinkBuilder<String>();
+    if (isStatic()) builder.addLast('static');
+    if (isAbstract()) builder.addLast('abstract');
+    if (isFinal()) builder.addLast('final');
+    if (isVar()) builder.addLast('var');
+    if (isConst()) builder.addLast('const');
+    if (isFactory()) builder.addLast('factory');
+    if (isExternal()) builder.addLast('external');
+    StringBuffer buffer = new StringBuffer();
+    builder.toLink().printOn(buffer, ', ');
+    return buffer.toString();
+  }
+}
+
+class StringInterpolation extends StringNode {
+  final LiteralString string;
+  final NodeList parts;
+
+  StringInterpolation(this.string, this.parts);
+
+  StringInterpolation asStringInterpolation() => this;
+
+  DartString get dartString => null;
+  bool get isInterpolation => true;
+
+  accept(Visitor visitor) => visitor.visitStringInterpolation(this);
+
+  visitChildren(Visitor visitor) {
+    string.accept(visitor);
+    parts.accept(visitor);
+  }
+
+  Token getBeginToken() => string.getBeginToken();
+  Token getEndToken() => parts.getEndToken();
+}
+
+class StringInterpolationPart extends Node {
+  final Expression expression;
+  final LiteralString string;
+
+  StringInterpolationPart(this.expression, this.string);
+
+  StringInterpolationPart asStringInterpolationPart() => this;
+
+  accept(Visitor visitor) => visitor.visitStringInterpolationPart(this);
+
+  visitChildren(Visitor visitor) {
+    expression.accept(visitor);
+    string.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => string.getEndToken();
+}
+
+/**
+ * A class representing juxtaposed string literals.
+ * The string literals can be both plain literals and string interpolations.
+ */
+class StringJuxtaposition extends StringNode {
+  final Expression first;
+  final Expression second;
+
+  /**
+   * Caches the check for whether this juxtaposition contains a string
+   * interpolation
+   */
+  bool isInterpolationCache = null;
+
+  /**
+   * Caches a Dart string representation of the entire juxtaposition's
+   * content. Only juxtapositions that don't (transitively) contains
+   * interpolations have a static representation.
+   */
+  DartString dartStringCache = null;
+
+  StringJuxtaposition(this.first, this.second);
+
+  StringJuxtaposition asStringJuxtaposition() => this;
+
+  bool get isInterpolation {
+    if (isInterpolationCache == null) {
+      isInterpolationCache = (first.accept(const IsInterpolationVisitor()) ||
+                          second.accept(const IsInterpolationVisitor()));
+    }
+    return isInterpolationCache;
+  }
+
+  /**
+   * Retrieve a single DartString that represents this entire juxtaposition
+   * of string literals.
+   * Should only be called if [isInterpolation] returns false.
+   */
+  DartString get dartString {
+    if (isInterpolation) {
+      throw new SpannableAssertionFailure(
+          this, "Getting dartString on interpolation;");
+    }
+    if (dartStringCache == null) {
+      DartString firstString = first.accept(const GetDartStringVisitor());
+      DartString secondString = second.accept(const GetDartStringVisitor());
+      if (firstString == null || secondString == null) {
+        return null;
+      }
+      dartStringCache = new DartString.concat(firstString, secondString);
+    }
+    return dartStringCache;
+  }
+
+  accept(Visitor visitor) => visitor.visitStringJuxtaposition(this);
+
+  void visitChildren(Visitor visitor) {
+    first.accept(visitor);
+    second.accept(visitor);
+  }
+
+  Token getBeginToken() => first.getBeginToken();
+
+  Token getEndToken() => second.getEndToken();
+}
+
+class EmptyStatement extends Statement {
+  final Token semicolonToken;
+
+  EmptyStatement(this.semicolonToken);
+
+  EmptyStatement asEmptyStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitEmptyStatement(this);
+
+  visitChildren(Visitor visitor) {}
+
+  Token getBeginToken() => semicolonToken;
+
+  Token getEndToken() => semicolonToken;
+}
+
+class LiteralMap extends Expression {
+  final NodeList typeArguments;
+  final NodeList entries;
+
+  final Token constKeyword;
+
+  LiteralMap(this.typeArguments, this.entries, this.constKeyword);
+
+  bool isConst() => constKeyword != null;
+
+  LiteralMap asLiteralMap() => this;
+
+  accept(Visitor visitor) => visitor.visitLiteralMap(this);
+
+  visitChildren(Visitor visitor) {
+    if (typeArguments != null) typeArguments.accept(visitor);
+    entries.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    if (constKeyword != null) return constKeyword;
+    return firstBeginToken(typeArguments, entries);
+  }
+
+  Token getEndToken() => entries.getEndToken();
+}
+
+class LiteralMapEntry extends Node {
+  final Expression key;
+  final Expression value;
+
+  final Token colonToken;
+
+  LiteralMapEntry(this.key, this.colonToken, this.value);
+
+  LiteralMapEntry asLiteralMapEntry() => this;
+
+  accept(Visitor visitor) => visitor.visitLiteralMapEntry(this);
+
+  visitChildren(Visitor visitor) {
+    key.accept(visitor);
+    value.accept(visitor);
+  }
+
+  Token getBeginToken() => key.getBeginToken();
+
+  Token getEndToken() => value.getEndToken();
+}
+
+class NamedArgument extends Expression {
+  final Identifier name;
+  final Expression expression;
+
+  final Token colonToken;
+
+  NamedArgument(this.name, this.colonToken, this.expression);
+
+  NamedArgument asNamedArgument() => this;
+
+  accept(Visitor visitor) => visitor.visitNamedArgument(this);
+
+  visitChildren(Visitor visitor) {
+    name.accept(visitor);
+    expression.accept(visitor);
+  }
+
+  Token getBeginToken() => name.getBeginToken();
+
+  Token getEndToken() => expression.getEndToken();
+}
+
+class SwitchStatement extends Statement {
+  final ParenthesizedExpression parenthesizedExpression;
+  final NodeList cases;
+
+  final Token switchKeyword;
+
+  SwitchStatement(this.parenthesizedExpression, this.cases,
+                  this.switchKeyword);
+
+  SwitchStatement asSwitchStatement() => this;
+
+  Expression get expression => parenthesizedExpression.expression;
+
+  accept(Visitor visitor) => visitor.visitSwitchStatement(this);
+
+  visitChildren(Visitor visitor) {
+    parenthesizedExpression.accept(visitor);
+    cases.accept(visitor);
+  }
+
+  Token getBeginToken() => switchKeyword;
+
+  Token getEndToken() => cases.getEndToken();
+}
+
+class CaseMatch extends Node {
+  final Token caseKeyword;
+  final Expression expression;
+  final Token colonToken;
+  CaseMatch(this.caseKeyword, this.expression, this.colonToken);
+
+  CaseMatch asCaseMatch() => this;
+  Token getBeginToken() => caseKeyword;
+  Token getEndToken() => colonToken;
+  accept(Visitor visitor) => visitor.visitCaseMatch(this);
+  visitChildren(Visitor visitor) => expression.accept(visitor);
+}
+
+class SwitchCase extends Node {
+  // The labels and case patterns are collected in [labelsAndCases].
+  // The default keyword, if present, is collected in [defaultKeyword].
+  // Any actual switch case must have at least one 'case' or 'default'
+  // clause.
+  // Notice: The labels and cases can occur interleaved in the source.
+  // They are separated here, since the order is irrelevant to the meaning
+  // of the switch.
+
+  /** List of [Label] and [CaseMatch] nodes. */
+  final NodeList labelsAndCases;
+  /** A "default" keyword token, if applicable. */
+  final Token defaultKeyword;
+  /** List of statements, the body of the case. */
+  final NodeList statements;
+
+  final Token startToken;
+
+  SwitchCase(this.labelsAndCases, this.defaultKeyword,
+             this.statements, this.startToken);
+
+  SwitchCase asSwitchCase() => this;
+
+  bool get isDefaultCase => defaultKeyword != null;
+
+  bool isValidContinueTarget() => true;
+
+  accept(Visitor visitor) => visitor.visitSwitchCase(this);
+
+  visitChildren(Visitor visitor) {
+    labelsAndCases.accept(visitor);
+    statements.accept(visitor);
+  }
+
+  Token getBeginToken() {
+    return startToken;
+  }
+
+  Token getEndToken() {
+    if (statements.nodes.isEmpty) {
+      // All cases must have at least one expression or be the default.
+      if (defaultKeyword != null) {
+        // The colon after 'default'.
+        return defaultKeyword.next;
+      }
+      // The colon after the last expression.
+      return labelsAndCases.getEndToken();
+    } else {
+      return statements.getEndToken();
+    }
+  }
+}
+
+abstract class GotoStatement extends Statement {
+  final Identifier target;
+  final Token keywordToken;
+  final Token semicolonToken;
+
+  GotoStatement(this.target, this.keywordToken, this.semicolonToken);
+
+  visitChildren(Visitor visitor) {
+    if (target != null) target.accept(visitor);
+  }
+
+  Token getBeginToken() => keywordToken;
+
+  Token getEndToken() => semicolonToken;
+
+  // TODO(ahe): make class abstract instead of adding an abstract method.
+  accept(Visitor visitor);
+}
+
+class BreakStatement extends GotoStatement {
+  BreakStatement(Identifier target, Token keywordToken, Token semicolonToken)
+    : super(target, keywordToken, semicolonToken);
+
+  BreakStatement asBreakStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitBreakStatement(this);
+}
+
+class ContinueStatement extends GotoStatement {
+  ContinueStatement(Identifier target, Token keywordToken, Token semicolonToken)
+    : super(target, keywordToken, semicolonToken);
+
+  ContinueStatement asContinueStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitContinueStatement(this);
+}
+
+class ForIn extends Loop {
+  final Node declaredIdentifier;
+  final Expression expression;
+
+  final Token forToken;
+  final Token inToken;
+
+  ForIn(this.declaredIdentifier, this.expression,
+        Statement body, this.forToken, this.inToken) : super(body);
+
+  Expression get condition => null;
+
+  ForIn asForIn() => this;
+
+  accept(Visitor visitor) => visitor.visitForIn(this);
+
+  visitChildren(Visitor visitor) {
+    declaredIdentifier.accept(visitor);
+    expression.accept(visitor);
+    body.accept(visitor);
+  }
+
+  Token getBeginToken() => forToken;
+
+  Token getEndToken() => body.getEndToken();
+}
+
+class Label extends Node {
+  final Identifier identifier;
+  final Token colonToken;
+
+  Label(this.identifier, this.colonToken);
+
+  String slowToString() => identifier.source.slowToString();
+
+  Label asLabel() => this;
+
+  accept(Visitor visitor) => visitor.visitLabel(this);
+
+  void visitChildren(Visitor visitor) {
+    identifier.accept(visitor);
+  }
+
+  Token getBeginToken() => identifier.token;
+  Token getEndToken() => colonToken;
+}
+
+class LabeledStatement extends Statement {
+  final NodeList labels;
+  final Statement statement;
+
+  LabeledStatement(this.labels, this.statement);
+
+  LabeledStatement asLabeledStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitLabeledStatement(this);
+
+  visitChildren(Visitor visitor) {
+    labels.accept(visitor);
+    statement.accept(visitor);
+  }
+
+  Token getBeginToken() => labels.getBeginToken();
+
+  Token getEndToken() => statement.getEndToken();
+
+  bool isValidContinueTarget() => statement.isValidContinueTarget();
+
+  Node getBody() => statement;
+}
+
+class ScriptTag extends Node {
+  final Identifier tag;
+  final StringNode argument;
+  final Identifier prefixIdentifier;
+  final StringNode prefix;
+
+  final Token beginToken;
+  final Token endToken;
+
+  ScriptTag(this.tag, this.argument, this.prefixIdentifier, this.prefix,
+            this.beginToken, this.endToken);
+
+  bool isImport() => tag.source == const SourceString("import");
+  bool isSource() => tag.source == const SourceString("source");
+  bool isLibrary() => tag.source == const SourceString("library");
+
+  ScriptTag asScriptTag() => this;
+
+  accept(Visitor visitor) => visitor.visitScriptTag(this);
+
+  visitChildren(Visitor visitor) {
+    tag.accept(visitor);
+    argument.accept(visitor);
+    if (prefixIdentifier != null) prefixIdentifier.accept(visitor);
+    if (prefix != null) prefix.accept(visitor);
+  }
+
+  Token getBeginToken() => beginToken;
+
+  Token getEndToken() => endToken;
+
+  LibraryTag toLibraryTag() {
+    if (isImport()) {
+      Identifier prefixNode;
+      if (prefix != null) {
+        SourceString source = prefix.dartString.source;
+        Token prefixToken = prefix.getBeginToken();
+        Token token = new StringToken.fromSource(IDENTIFIER_INFO, source,
+                                                 prefixToken.charOffset);
+        token.next = prefixToken.next;
+        prefixNode = new Identifier(token);
+      }
+      return new Import(tag.token, argument, prefixNode, null, null);
+    } else if (isLibrary()) {
+      return new LibraryName(tag.token, argument, null);
+    } else if (isSource()) {
+      return new Part(tag.token, argument, null);
+    } else {
+      throw 'Unknown script tag ${tag.token.slowToString()}';
+    }
+  }
+}
+
+abstract class LibraryTag extends Node {
+  final Link<MetadataAnnotation> metadata;
+
+  LibraryTag(this.metadata);
+
+  bool get isLibraryName => false;
+  bool get isImport => false;
+  bool get isExport => false;
+  bool get isPart => false;
+  bool get isPartOf => false;
+}
+
+class LibraryName extends LibraryTag {
+  final Expression name;
+
+  final Token libraryKeyword;
+
+  LibraryName(this.libraryKeyword,
+              this.name,
+              Link<MetadataAnnotation> metadata)
+    : super(metadata);
+
+  bool get isLibraryName => true;
+
+  LibraryName asLibraryName() => this;
+
+  accept(Visitor visitor) => visitor.visitLibraryName(this);
+
+  visitChildren(Visitor visitor) => name.accept(visitor);
+
+  Token getBeginToken() => libraryKeyword;
+
+  Token getEndToken() => name.getEndToken().next;
+}
+
+/**
+ * This tag describes a dependency between one library and the exported
+ * identifiers of another library. The other library is specified by the [uri].
+ * Combinators filter away some identifiers from the other library.
+ */
+abstract class LibraryDependency extends LibraryTag {
+  final StringNode uri;
+  final NodeList combinators;
+
+  LibraryDependency(this.uri,
+                    this.combinators,
+                    Link<MetadataAnnotation> metadata)
+    : super(metadata);
+}
+
+/**
+ * An [:import:] library tag.
+ *
+ * An import tag is dependency on another library where the exported identifiers
+ * are put into the import scope of the importing library. The import scope is
+ * only visible inside the library.
+ */
+class Import extends LibraryDependency {
+  final Identifier prefix;
+  final Token importKeyword;
+
+  Import(this.importKeyword, StringNode uri,
+         this.prefix, NodeList combinators,
+         Link<MetadataAnnotation> metadata)
+      : super(uri, combinators, metadata);
+
+  bool get isImport => true;
+
+  Import asImport() => this;
+
+  Token get asKeyword => prefix == null ? null : uri.getEndToken().next;
+
+  accept(Visitor visitor) => visitor.visitImport(this);
+
+  visitChildren(Visitor visitor) {
+    uri.accept(visitor);
+    if (prefix != null) prefix.accept(visitor);
+    if (combinators != null) combinators.accept(visitor);
+  }
+
+  Token getBeginToken() => importKeyword;
+
+  Token getEndToken() {
+    if (combinators != null) return combinators.getEndToken().next;
+    if (prefix != null) return prefix.getEndToken().next;
+    return uri.getEndToken().next;
+  }
+}
+
+/**
+ * An [:export:] library tag.
+ *
+ * An export tag is dependency on another library where the exported identifiers
+ * are put into the export scope of the exporting library. The export scope is
+ * not visible inside the library.
+ */
+class Export extends LibraryDependency {
+  final Token exportKeyword;
+
+  Export(this.exportKeyword,
+         StringNode uri,
+         NodeList combinators,
+         Link<MetadataAnnotation> metadata)
+      : super(uri, combinators, metadata);
+
+  bool get isExport => true;
+
+  Export asExport() => this;
+
+  accept(Visitor visitor) => visitor.visitExport(this);
+
+  visitChildren(Visitor visitor) {
+    uri.accept(visitor);
+    if (combinators != null) combinators.accept(visitor);
+  }
+
+  Token getBeginToken() => exportKeyword;
+
+  Token getEndToken() {
+    if (combinators != null) return combinators.getEndToken().next;
+    return uri.getEndToken().next;
+  }
+}
+
+class Part extends LibraryTag {
+  final StringNode uri;
+
+  final Token partKeyword;
+
+  Part(this.partKeyword, this.uri, Link<MetadataAnnotation> metadata)
+    : super(metadata);
+
+  bool get isPart => true;
+
+  Part asPart() => this;
+
+  accept(Visitor visitor) => visitor.visitPart(this);
+
+  visitChildren(Visitor visitor) => uri.accept(visitor);
+
+  Token getBeginToken() => partKeyword;
+
+  Token getEndToken() => uri.getEndToken().next;
+}
+
+class PartOf extends Node {
+  final Expression name;
+
+  final Token partKeyword;
+
+  final Link<MetadataAnnotation> metadata;
+
+  PartOf(this.partKeyword, this.name, this.metadata);
+
+  Token get ofKeyword => partKeyword.next;
+
+  bool get isPartOf => true;
+
+  PartOf asPartOf() => this;
+
+  accept(Visitor visitor) => visitor.visitPartOf(this);
+
+  visitChildren(Visitor visitor) => name.accept(visitor);
+
+  Token getBeginToken() => partKeyword;
+
+  Token getEndToken() => name.getEndToken().next;
+}
+
+class Combinator extends Node {
+  final NodeList identifiers;
+
+  final Token keywordToken;
+
+  Combinator(this.identifiers, this.keywordToken);
+
+  bool get isShow => identical(keywordToken.stringValue, 'show');
+
+  bool get isHide => identical(keywordToken.stringValue, 'hide');
+
+  Combinator asCombinator() => this;
+
+  accept(Visitor visitor) => visitor.visitCombinator(this);
+
+  visitChildren(Visitor visitor) => identifiers.accept(visitor);
+
+  Token getBeginToken() => keywordToken;
+
+  Token getEndToken() => identifiers.getEndToken();
+}
+
+class Typedef extends Node {
+  final TypeAnnotation returnType;
+  final Identifier name;
+  final NodeList typeParameters;
+  final NodeList formals;
+
+  final Token typedefKeyword;
+  final Token endToken;
+
+  Typedef(this.returnType, this.name, this.typeParameters, this.formals,
+          this.typedefKeyword, this.endToken);
+
+  Typedef asTypedef() => this;
+
+  accept(Visitor visitor) => visitor.visitTypedef(this);
+
+  visitChildren(Visitor visitor) {
+    if (returnType != null) returnType.accept(visitor);
+    name.accept(visitor);
+    if (typeParameters != null) typeParameters.accept(visitor);
+    formals.accept(visitor);
+  }
+
+  Token getBeginToken() => typedefKeyword;
+
+  Token getEndToken() => endToken;
+}
+
+class TryStatement extends Statement {
+  final Block tryBlock;
+  final NodeList catchBlocks;
+  final Block finallyBlock;
+
+  final Token tryKeyword;
+  final Token finallyKeyword;
+
+  TryStatement(this.tryBlock, this.catchBlocks, this.finallyBlock,
+               this.tryKeyword, this.finallyKeyword);
+
+  TryStatement asTryStatement() => this;
+
+  accept(Visitor visitor) => visitor.visitTryStatement(this);
+
+  visitChildren(Visitor visitor) {
+    tryBlock.accept(visitor);
+    catchBlocks.accept(visitor);
+    if (finallyBlock != null) finallyBlock.accept(visitor);
+  }
+
+  Token getBeginToken() => tryKeyword;
+
+  Token getEndToken() {
+    if (finallyBlock != null) return finallyBlock.getEndToken();
+    if (!catchBlocks.isEmpty) return catchBlocks.getEndToken();
+    return tryBlock.getEndToken();
+  }
+}
+
+class Cascade extends Expression {
+  final Expression expression;
+  Cascade(this.expression);
+
+  Cascade asCascade() => this;
+  accept(Visitor visitor) => visitor.visitCascade(this);
+
+  void visitChildren(Visitor visitor) {
+    expression.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => expression.getEndToken();
+}
+
+class CascadeReceiver extends Expression {
+  final Expression expression;
+  final Token cascadeOperator;
+  CascadeReceiver(this.expression, this.cascadeOperator);
+
+  CascadeReceiver asCascadeReceiver() => this;
+  accept(Visitor visitor) => visitor.visitCascadeReceiver(this);
+
+  void visitChildren(Visitor visitor) {
+    expression.accept(visitor);
+  }
+
+  Token getBeginToken() => expression.getBeginToken();
+
+  Token getEndToken() => expression.getEndToken();
+}
+
+class CatchBlock extends Node {
+  final TypeAnnotation type;
+  final NodeList formals;
+  final Block block;
+
+  final Token onKeyword;
+  final Token catchKeyword;
+
+  CatchBlock(this.type, this.formals, this.block,
+             this.onKeyword, this.catchKeyword);
+
+  CatchBlock asCatchBlock() => this;
+
+  accept(Visitor visitor) => visitor.visitCatchBlock(this);
+
+  Node get exception {
+    if (formals == null || formals.nodes.isEmpty) return null;
+    VariableDefinitions declarations = formals.nodes.head;
+    return declarations.definitions.nodes.head;
+  }
+
+  Node get trace {
+    if (formals == null || formals.nodes.isEmpty) return null;
+    Link<Node> declarations = formals.nodes.tail;
+    if (declarations.isEmpty) return null;
+    VariableDefinitions head = declarations.head;
+    return head.definitions.nodes.head;
+  }
+
+  visitChildren(Visitor visitor) {
+    if (type != null) type.accept(visitor);
+    if (formals != null) formals.accept(visitor);
+    block.accept(visitor);
+  }
+
+  Token getBeginToken() => onKeyword != null ? onKeyword : catchKeyword;
+
+  Token getEndToken() => block.getEndToken();
+}
+
+class Initializers {
+  static bool isSuperConstructorCall(Send node) {
+    return (node.receiver == null &&
+            node.selector.asIdentifier() != null &&
+            node.selector.asIdentifier().isSuper()) ||
+           (node.receiver != null &&
+            node.receiver.asIdentifier() != null &&
+            node.receiver.asIdentifier().isSuper() &&
+            node.selector.asIdentifier() != null);
+  }
+
+  static bool isConstructorRedirect(Send node) {
+    return (node.receiver == null &&
+            node.selector.asIdentifier() != null &&
+            node.selector.asIdentifier().isThis()) ||
+           (node.receiver != null &&
+            node.receiver.asIdentifier() != null &&
+            node.receiver.asIdentifier().isThis() &&
+            node.selector.asIdentifier() != null);
+  }
+}
+
+class GetDartStringVisitor extends Visitor<DartString> {
+  const GetDartStringVisitor();
+  DartString visitNode(Node node) => null;
+  DartString visitStringJuxtaposition(StringJuxtaposition node)
+      => node.dartString;
+  DartString visitLiteralString(LiteralString node) => node.dartString;
+}
+
+class IsInterpolationVisitor extends Visitor<bool> {
+  const IsInterpolationVisitor();
+  bool visitNode(Node node) => false;
+  bool visitStringInterpolation(StringInterpolation node) => true;
+  bool visitStringJuxtaposition(StringJuxtaposition node)
+      => node.isInterpolation;
+}
+
+/**
+ * If the given node is a send set, it visits its initializer (first
+ * argument).
+ *
+ * TODO(ahe): This method is controversial, the team needs to discuss
+ * if top-level methods are acceptable and what naming conventions to
+ * use.
+ */
+initializerDo(Node node, f(Node node)) {
+  SendSet send = node.asSendSet();
+  if (send != null) return f(send.arguments.head);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart
new file mode 100644
index 0000000..129c637
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart
@@ -0,0 +1,480 @@
+// 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.
+
+part of tree;
+
+/**
+ * Pretty-prints Node tree in XML-like format.
+ *
+ * TODO(smok): Add main() to run from command-line to print out tree for given
+ * .dart file.
+ */
+class PrettyPrinter implements Visitor {
+
+  /** String used to represent one level of indent. */
+  static const String INDENT = "  ";
+
+  StringBuffer sb;
+  Link<String> tagStack;
+
+  PrettyPrinter() :
+      sb = new StringBuffer(),
+      tagStack = const Link<String>();
+
+  void pushTag(String tag) {
+    tagStack = tagStack.prepend(tag);
+  }
+
+  String popTag() {
+    assert(!tagStack.isEmpty);
+    String tag = tagStack.head;
+    tagStack = tagStack.tail;
+    return tag;
+  }
+
+  /**
+   * Adds given string to result string.
+   */
+  void add(SourceString string) {
+    string.printOn(sb);
+  }
+
+  void addBeginAndEndTokensToParams(Node node, Map params) {
+    params['getBeginToken'] = tokenToStringOrNull(node.getBeginToken());
+    params['getEndToken'] = tokenToStringOrNull(node.getEndToken());
+  }
+
+  /**
+   * Adds given node type to result string.
+   * The method "opens" the node, meaning that all output after calling
+   * this method and before calling closeNode() will represent contents
+   * of given node.
+   */
+  void openNode(Node node, String type, [Map params]) {
+    if (params == null) params = new Map();
+    addCurrentIndent();
+    sb.add("<");
+    addBeginAndEndTokensToParams(node, params);
+    addTypeWithParams(type, params);
+    sb.add(">\n");
+    pushTag(type);
+  }
+
+  /**
+   * Adds given node to result string.
+   */
+  void openAndCloseNode(Node node, String type, [Map params]) {
+    if (params == null) params = new Map();
+    addCurrentIndent();
+    sb.add("<");
+    addBeginAndEndTokensToParams(node, params);
+    addTypeWithParams(type, params);
+    sb.add("/>\n");
+  }
+
+  /**
+   * Closes current node type.
+   */
+  void closeNode() {
+    String tag = popTag();
+    addCurrentIndent();
+    sb.add("</");
+    addTypeWithParams(tag);
+    sb.add(">\n");
+  }
+
+  void addTypeWithParams(String type, [Map params]) {
+    if (params == null) params = new Map();
+    sb.add("${type}");
+    params.forEach((k, v) {
+      String value;
+      if (v != null) {
+        value = v
+            .replaceAll("<", "&lt;")
+            .replaceAll(">", "&gt;")
+            .replaceAll('"', "'");
+      } else {
+        value = "[null]";
+      }
+      sb.add(' $k="$value"');
+    });
+  }
+
+  void addCurrentIndent() {
+    tagStack.forEach((_) { sb.add(INDENT); });
+  }
+
+  /**
+   * Pretty-prints given node tree into string.
+   */
+  static String prettyPrint(Node node) {
+    var p = new PrettyPrinter();
+    node.accept(p);
+    return p.sb.toString();
+  }
+
+  visitNodeWithChildren(Node node, String type) {
+    openNode(node, type);
+    node.visitChildren(this);
+    closeNode();
+  }
+
+  visitBlock(Block node) {
+    visitNodeWithChildren(node, "Block");
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    visitNodeWithChildren(node, "BreakStatement");
+  }
+
+  visitCascade(Cascade node) {
+    visitNodeWithChildren(node, "Cascade");
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visitNodeWithChildren(node, "CascadeReceiver");
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    visitNodeWithChildren(node, "CaseMatch");
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    visitNodeWithChildren(node, "CatchBlock");
+  }
+
+  visitClassNode(ClassNode node) {
+    openNode(node, "ClassNode", {
+      "extendsKeyword" : tokenToStringOrNull(node.extendsKeyword)
+    });
+    visitChildNode(node.name, "name");
+    visitChildNode(node.superclass, "superclass");
+    visitChildNode(node.interfaces, "interfaces");
+    visitChildNode(node.typeParameters, "typeParameters");
+    visitChildNode(node.defaultClause, "defaultClause");
+    closeNode();
+  }
+
+  visitConditional(Conditional node) {
+    visitNodeWithChildren(node, "Conditional");
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    visitNodeWithChildren(node, "ContinueStatement");
+  }
+
+  visitDoWhile(DoWhile node) {
+    visitNodeWithChildren(node, "DoWhile");
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    visitNodeWithChildren(node, "EmptyStatement");
+  }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    visitNodeWithChildren(node, "ExpressionStatement");
+  }
+
+  visitFor(For node) {
+    visitNodeWithChildren(node, "For");
+  }
+
+  visitForIn(ForIn node) {
+    visitNodeWithChildren(node, "ForIn");
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    visitNodeWithChildren(node, "FunctionDeclaration");
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    openNode(node, "FunctionExpression", {
+      "getOrSet" : tokenToStringOrNull(node.getOrSet)
+    });
+    visitChildNode(node.modifiers, "modifiers");
+    visitChildNode(node.returnType, "returnType");
+    visitChildNode(node.name, "name");
+    visitChildNode(node.parameters, "parameters");
+    visitChildNode(node.initializers, "initializers");
+    visitChildNode(node.body, "body");
+    closeNode();
+  }
+
+  visitIdentifier(Identifier node) {
+    openAndCloseNode(node, "Identifier", {"token" : node.token.slowToString()});
+  }
+
+  visitIf(If node) {
+    visitNodeWithChildren(node, "If");
+  }
+
+  visitLabel(Label node) {
+    visitNodeWithChildren(node, "Label");
+  }
+
+  visitLabeledStatement(LabeledStatement node) {
+    visitNodeWithChildren(node, "LabeledStatement");
+  }
+
+  // Custom.
+  printLiteral(Literal node, String type) {
+    openAndCloseNode(node, type, {"value" : node.value.toString()});
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    printLiteral(node, "LiteralBool");
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    printLiteral(node, "LiteralDouble");
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    printLiteral(node, "LiteralInt");
+  }
+
+  /** Returns token string value or [null] if token is [null]. */
+  tokenToStringOrNull(Token token) => token == null ? null : token.stringValue;
+
+  visitLiteralList(LiteralList node) {
+    openNode(node, "LiteralList", {
+      "constKeyword" : tokenToStringOrNull(node.constKeyword)
+    });
+    visitChildNode(node.typeArguments, "typeArguments");
+    visitChildNode(node.elements, "elements");
+    closeNode();
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    visitNodeWithChildren(node, "LiteralMap");
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    visitNodeWithChildren(node, "LiteralMapEntry");
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    printLiteral(node, "LiteralNull");
+  }
+
+  visitLiteralString(LiteralString node) {
+    openAndCloseNode(node, "LiteralString",
+        {"value" : node.token.slowToString()});
+  }
+
+  visitMixinApplication(MixinApplication node) {
+    visitNodeWithChildren(node, "MixinApplication");
+  }
+
+  visitModifiers(Modifiers node) {
+    visitNodeWithChildren(node, "Modifiers");
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visitNodeWithChildren(node, "NamedArgument");
+  }
+
+  visitNamedMixinApplication(NamedMixinApplication node) {
+    visitNodeWithChildren(node, "NamedMixinApplication");
+  }
+
+  visitNewExpression(NewExpression node) {
+    visitNodeWithChildren(node, "NewExpression");
+  }
+
+  visitNodeList(NodeList node) {
+    var params = {
+        "delimiter" :
+            node.delimiter != null ? node.delimiter.stringValue : null
+    };
+    if (node.nodes.toList().length == 0) {
+      openAndCloseNode(node, "NodeList", params);
+    } else {
+      openNode(node, "NodeList", params);
+      node.visitChildren(this);
+      closeNode();
+    }
+  }
+
+  visitOperator(Operator node) {
+    openAndCloseNode(node, "Operator", {"value" : node.token.slowToString()});
+  }
+
+  visitParenthesizedExpression(ParenthesizedExpression node) {
+    visitNodeWithChildren(node, "ParenthesizedExpression");
+  }
+
+  visitReturn(Return node) {
+    openNode(node, "Return");
+    visitChildNode(node.expression, "expression");
+    closeNode();
+  }
+
+  visitScriptTag(ScriptTag node) {
+    visitNodeWithChildren(node, "ScriptTag");
+  }
+
+  visitChildNode(Node node, String fieldName) {
+    if (node == null) return;
+    addCurrentIndent();
+    sb.add("<$fieldName>\n");
+    pushTag(fieldName);
+    node.accept(this);
+    popTag();
+    addCurrentIndent();
+    sb.add("</$fieldName>\n");
+  }
+
+  openSendNodeWithFields(Send node, String type) {
+    openNode(node, type, {
+        "isPrefix" : "${node.isPrefix}",
+        "isPostfix" : "${node.isPostfix}",
+        "isIndex" : "${node.isIndex}"
+    });
+    visitChildNode(node.receiver, "receiver");
+    visitChildNode(node.selector, "selector");
+    visitChildNode(node.argumentsNode, "argumentsNode");
+  }
+
+  visitSend(Send node) {
+    openSendNodeWithFields(node, "Send");
+    closeNode();
+  }
+
+  visitSendSet(SendSet node) {
+    openSendNodeWithFields(node, "SendSet");
+    visitChildNode(node.assignmentOperator, "assignmentOperator");
+    closeNode();
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    visitNodeWithChildren(node, "StringInterpolation");
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    visitNodeWithChildren(node, "StringInterpolationPart");
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    visitNodeWithChildren(node, "StringJuxtaposition");
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    visitNodeWithChildren(node, "SwitchCase");
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    visitNodeWithChildren(node, "SwitchStatement");
+  }
+
+  visitThrow(Throw node) {
+    visitNodeWithChildren(node, "Throw");
+  }
+
+  visitTryStatement(TryStatement node) {
+    visitNodeWithChildren(node, "TryStatement");
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    openNode(node, "TypeAnnotation");
+    visitChildNode(node.typeName, "typeName");
+    visitChildNode(node.typeArguments, "typeArguments");
+    closeNode();
+  }
+
+  visitTypedef(Typedef node) {
+    visitNodeWithChildren(node, "Typedef");
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    openNode(node, "TypeVariable");
+    visitChildNode(node.name, "name");
+    visitChildNode(node.bound, "bound");
+    closeNode();
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    openNode(node, "VariableDefinitions");
+    visitChildNode(node.type, "type");
+    visitChildNode(node.modifiers, "modifiers");
+    visitChildNode(node.definitions, "definitions");
+    closeNode();
+  }
+
+  visitWhile(While node) {
+    visitNodeWithChildren(node, "While");
+  }
+
+  visitNode(Node node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitCombinator(Combinator node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitExport(Export node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitExpression(Expression node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitGotoStatement(GotoStatement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitImport(Import node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLibraryDependency(Node node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLibraryName(LibraryName node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLibraryTag(LibraryTag node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLiteral(Literal node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitLoop(Loop node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPart(Part node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPartOf(PartOf node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPostfix(Postfix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitPrefix(Prefix node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitStatement(Statement node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  visitStringNode(StringNode node) {
+    unimplemented('visitNode', node: node);
+  }
+
+  unimplemented(String message, {Node node}) {
+    throw message;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart
new file mode 100644
index 0000000..3faa36c
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart
@@ -0,0 +1,22 @@
+// 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.
+
+library tree;
+
+import 'dart:math';
+import 'dart:collection';
+
+import '../scanner/scannerlib.dart';
+import '../util/util.dart';
+import '../util/characters.dart';
+
+import '../resolution/secret_tree_element.dart' show TreeElementMixin;
+
+import '../elements/elements.dart' show MetadataAnnotation;
+
+part 'dartstring.dart';
+part 'nodes.dart';
+part 'prettyprint.dart';
+part 'unparser.dart';
+part 'visitors.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart
new file mode 100644
index 0000000..9c20f2d
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart
@@ -0,0 +1,627 @@
+// 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.
+
+part of tree;
+
+String unparse(Node node) {
+  Unparser unparser = new Unparser();
+  unparser.unparse(node);
+  return unparser.result;
+}
+
+class Unparser implements Visitor {
+  final StringBuffer sb;
+
+  String get result => sb.toString();
+
+  Unparser() : sb = new StringBuffer();
+
+  void add(SourceString string) {
+    string.printOn(sb);
+  }
+
+  void addToken(Token token) {
+    if (token == null) return;
+    add(token.value);
+    if (identical(token.kind, KEYWORD_TOKEN)
+        || identical(token.kind, IDENTIFIER_TOKEN)) {
+      sb.add(' ');
+    }
+  }
+
+  unparse(Node node) { visit(node); }
+
+  visit(Node node) {
+    if (node != null) node.accept(this);
+  }
+
+  visitBlock(Block node) {
+    visit(node.statements);
+  }
+
+  visitCascade(Cascade node) {
+    visit(node.expression);
+  }
+
+  visitCascadeReceiver(CascadeReceiver node) {
+    visit(node.expression);
+  }
+
+  unparseClassWithBody(ClassNode node, Iterable<Node> members) {
+    addToken(node.beginToken);
+    if (node.beginToken.stringValue == 'abstract') {
+      addToken(node.beginToken.next);
+    }
+    visit(node.name);
+    if (node.typeParameters != null) {
+      visit(node.typeParameters);
+    }
+    if (node.extendsKeyword != null) {
+      sb.add(' ');
+      addToken(node.extendsKeyword);
+      visit(node.superclass);
+    }
+    if (!node.interfaces.isEmpty) {
+      sb.add(' ');
+      visit(node.interfaces);
+    }
+    if (node.defaultClause != null) {
+      sb.add(' default ');
+      visit(node.defaultClause);
+    }
+    sb.add('{');
+    for (final member in members) {
+      visit(member);
+    }
+    sb.add('}');
+  }
+
+  visitClassNode(ClassNode node) {
+    unparseClassWithBody(node, node.body.nodes);
+  }
+
+  visitMixinApplication(MixinApplication node) {
+    visit(node.superclass);
+    sb.add(' with ');
+    visit(node.mixins);
+  }
+
+  visitNamedMixinApplication(NamedMixinApplication node) {
+    sb.add('typedef ');
+    visit(node.name);
+    if (node.typeParameters != null) {
+      visit(node.typeParameters);
+    }
+    sb.add(' = ');
+    if (!node.modifiers.nodes.isEmpty) {
+      visit(node.modifiers);
+      sb.add(' ');
+    }
+    visit(node.mixinApplication);
+    if (node.interfaces != null) {
+      sb.add(' implements ');
+      visit(node.interfaces);
+    }
+    sb.add(';');
+  }
+
+  visitConditional(Conditional node) {
+    visit(node.condition);
+    add(node.questionToken.value);
+    visit(node.thenExpression);
+    add(node.colonToken.value);
+    visit(node.elseExpression);
+  }
+
+  visitExpressionStatement(ExpressionStatement node) {
+    visit(node.expression);
+    add(node.endToken.value);
+  }
+
+  visitFor(For node) {
+    add(node.forToken.value);
+    sb.add('(');
+    visit(node.initializer);
+    sb.add(';');
+    visit(node.conditionStatement);
+    visit(node.update);
+    sb.add(')');
+    visit(node.body);
+  }
+
+  visitFunctionDeclaration(FunctionDeclaration node) {
+    visit(node.function);
+  }
+
+  void unparseFunctionName(Node name) {
+    // TODO(antonm): that's a workaround as currently FunctionExpression
+    // names are modelled with Send and it emits operator[] as only
+    // operator, without [] which are expected to be emitted with
+    // arguments.
+    if (name is Send) {
+      Send send = name;
+      assert(send is !SendSet);
+      if (!send.isOperator) {
+        // Looks like a factory method.
+        visit(send.receiver);
+        sb.add('.');
+      } else {
+        visit(send.receiver);
+        Identifier identifier = send.selector.asIdentifier();
+        if (identical(identifier.token.kind, KEYWORD_TOKEN)) {
+          sb.add(' ');
+        } else if (identifier.source == const SourceString('negate')) {
+          // TODO(ahe): Remove special case for negate.
+          sb.add(' ');
+        }
+      }
+      visit(send.selector);
+    } else {
+      visit(name);
+    }
+  }
+
+  visitFunctionExpression(FunctionExpression node) {
+    if (!node.modifiers.nodes.isEmpty) {
+      visit(node.modifiers);
+      sb.add(' ');
+    }
+    if (node.returnType != null) {
+      visit(node.returnType);
+      sb.add(' ');
+    }
+    if (node.getOrSet != null) {
+      add(node.getOrSet.value);
+      sb.add(' ');
+    }
+    unparseFunctionName(node.name);
+    visit(node.parameters);
+    visit(node.initializers);
+    visit(node.body);
+  }
+
+  visitIdentifier(Identifier node) {
+    add(node.token.value);
+  }
+
+  visitIf(If node) {
+    add(node.ifToken.value);
+    visit(node.condition);
+    visit(node.thenPart);
+    if (node.hasElsePart) {
+      add(node.elseToken.value);
+      if (node.elsePart is !Block) sb.add(' ');
+      visit(node.elsePart);
+    }
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    add(node.token.value);
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    add(node.token.value);
+    // -Lit is represented as a send.
+    if (node.token.kind == PLUS_TOKEN) add(node.token.next.value);
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    add(node.token.value);
+    // -Lit is represented as a send.
+    if (node.token.kind == PLUS_TOKEN) add(node.token.next.value);
+  }
+
+  visitLiteralString(LiteralString node) {
+    add(node.token.value);
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    visit(node.first);
+    sb.add(" ");
+    visit(node.second);
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    add(node.token.value);
+  }
+
+  visitNewExpression(NewExpression node) {
+    addToken(node.newToken);
+    visit(node.send);
+  }
+
+  visitLiteralList(LiteralList node) {
+    if (node.constKeyword != null) add(node.constKeyword.value);
+    visit(node.typeArguments);
+    visit(node.elements);
+    // If list is empty, emit space after [] to disambiguate cases like []==[].
+    if (node.elements.isEmpty) sb.add(' ');
+  }
+
+  visitModifiers(Modifiers node) => node.visitChildren(this);
+
+  /**
+   * Unparses given NodeList starting from specific node.
+   */
+  unparseNodeListFrom(NodeList node, Link<Node> from) {
+    if (from.isEmpty) return;
+    String delimiter = (node.delimiter == null) ? "" : "${node.delimiter}";
+    visit(from.head);
+    for (Link link = from.tail; !link.isEmpty; link = link.tail) {
+      sb.add(delimiter);
+      visit(link.head);
+    }
+  }
+
+  visitNodeList(NodeList node) {
+    addToken(node.beginToken);
+    if (node.nodes != null) {
+      unparseNodeListFrom(node, node.nodes);
+    }
+    if (node.endToken != null) add(node.endToken.value);
+  }
+
+  visitOperator(Operator node) {
+    visitIdentifier(node);
+  }
+
+  visitReturn(Return node) {
+    if (node.isRedirectingFactoryBody) {
+      sb.add(' ');
+    }
+    add(node.beginToken.value);
+    if (node.hasExpression && node.beginToken.stringValue != '=>') {
+      sb.add(' ');
+    }
+    visit(node.expression);
+    if (node.endToken != null) add(node.endToken.value);
+  }
+
+  unparseSendReceiver(Send node, {bool spacesNeeded: false}) {
+    if (node.receiver == null) return;
+    visit(node.receiver);
+    CascadeReceiver asCascadeReceiver = node.receiver.asCascadeReceiver();
+    if (asCascadeReceiver != null) {
+      add(asCascadeReceiver.cascadeOperator.value);
+    } else if (node.selector.asOperator() == null) {
+      sb.add('.');
+    } else if (spacesNeeded) {
+      sb.add(' ');
+    }
+  }
+
+  visitSend(Send node) {
+    Operator op = node.selector.asOperator();
+    String opString = op != null ? op.source.stringValue : null;
+    bool spacesNeeded = identical(opString, 'is') || identical(opString, 'as');
+
+    if (node.isPrefix) visit(node.selector);
+    unparseSendReceiver(node, spacesNeeded: spacesNeeded);
+    if (!node.isPrefix && !node.isIndex) visit(node.selector);
+    if (spacesNeeded) sb.add(' ');
+    // Also add a space for sequences like x + +1 and y - -y.
+    // TODO(ahe): remove case for '+' when we drop the support for it.
+    if (node.argumentsNode != null && (identical(opString, '-')
+        || identical(opString, '+'))) {
+      Token beginToken = node.argumentsNode.getBeginToken();
+      if (beginToken != null && identical(beginToken.stringValue, opString)) {
+        sb.add(' ');
+      }
+    }
+    visit(node.argumentsNode);
+  }
+
+  visitSendSet(SendSet node) {
+    if (node.isPrefix) {
+      sb.add(' ');
+      visit(node.assignmentOperator);
+    }
+    unparseSendReceiver(node);
+    if (node.isIndex) {
+      sb.add('[');
+      visit(node.arguments.head);
+      sb.add(']');
+      if (!node.isPrefix) visit(node.assignmentOperator);
+      unparseNodeListFrom(node.argumentsNode, node.argumentsNode.nodes.tail);
+    } else {
+      visit(node.selector);
+      if (!node.isPrefix) {
+        visit(node.assignmentOperator);
+        if (node.assignmentOperator.source.slowToString() != '=') sb.add(' ');
+      }
+      visit(node.argumentsNode);
+    }
+  }
+
+  visitThrow(Throw node) {
+    add(node.throwToken.value);
+    if (node.expression != null) {
+      sb.add(' ');
+      visit(node.expression);
+    }
+    node.endToken.value.printOn(sb);
+  }
+
+  visitTypeAnnotation(TypeAnnotation node) {
+    visit(node.typeName);
+    visit(node.typeArguments);
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    visit(node.name);
+    if (node.bound != null) {
+      sb.add(' extends ');
+      visit(node.bound);
+    }
+  }
+
+  visitVariableDefinitions(VariableDefinitions node) {
+    visit(node.modifiers);
+    if (!node.modifiers.nodes.isEmpty) {
+      sb.add(' ');
+    }
+    if (node.type != null) {
+      visit(node.type);
+      sb.add(' ');
+    }
+    visit(node.definitions);
+  }
+
+  visitDoWhile(DoWhile node) {
+    add(node.doKeyword.value);
+    if (node.body is !Block) sb.add(' ');
+    visit(node.body);
+    add(node.whileKeyword.value);
+    visit(node.condition);
+    sb.add(node.endToken.value);
+  }
+
+  visitWhile(While node) {
+    addToken(node.whileKeyword);
+    visit(node.condition);
+    visit(node.body);
+  }
+
+  visitParenthesizedExpression(ParenthesizedExpression node) {
+    add(node.getBeginToken().value);
+    visit(node.expression);
+    add(node.getEndToken().value);
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    visit(node.string);
+    visit(node.parts);
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    sb.add('\${'); // TODO(ahe): Preserve the real tokens.
+    visit(node.expression);
+    sb.add('}');
+    visit(node.string);
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    add(node.semicolonToken.value);
+  }
+
+  visitGotoStatement(GotoStatement node) {
+    add(node.keywordToken.value);
+    if (node.target != null) {
+      sb.add(' ');
+      visit(node.target);
+    }
+    add(node.semicolonToken.value);
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    visitGotoStatement(node);
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    visitGotoStatement(node);
+  }
+
+  visitForIn(ForIn node) {
+    add(node.forToken.value);
+    sb.add('(');
+    visit(node.declaredIdentifier);
+    sb.add(' ');
+    addToken(node.inToken);
+    visit(node.expression);
+    sb.add(')');
+    visit(node.body);
+  }
+
+  visitLabel(Label node) {
+    visit(node.identifier);
+    add(node.colonToken.value);
+   }
+
+  visitLabeledStatement(LabeledStatement node) {
+    visit(node.labels);
+    visit(node.statement);
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    if (node.constKeyword != null) add(node.constKeyword.value);
+    if (node.typeArguments != null) visit(node.typeArguments);
+    visit(node.entries);
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    visit(node.key);
+    add(node.colonToken.value);
+    visit(node.value);
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    visit(node.name);
+    add(node.colonToken.value);
+    visit(node.expression);
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    addToken(node.switchKeyword);
+    visit(node.parenthesizedExpression);
+    visit(node.cases);
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    visit(node.labelsAndCases);
+    if (node.isDefaultCase) {
+      sb.add('default:');
+    }
+    visit(node.statements);
+  }
+
+  unparseImportTag(String uri, [String prefix]) {
+    final suffix = prefix == null ? '' : ' as $prefix';
+    sb.add('import "$uri"$suffix;');
+  }
+
+  visitScriptTag(ScriptTag node) {
+    add(node.beginToken.value);
+    visit(node.tag);
+    sb.add('(');
+    visit(node.argument);
+    if (node.prefixIdentifier != null) {
+      visit(node.prefixIdentifier);
+      sb.add(':');
+      visit(node.prefix);
+    }
+    sb.add(')');
+    add(node.endToken.value);
+  }
+
+  visitTryStatement(TryStatement node) {
+    addToken(node.tryKeyword);
+    visit(node.tryBlock);
+    visit(node.catchBlocks);
+    if (node.finallyKeyword != null) {
+      addToken(node.finallyKeyword);
+      visit(node.finallyBlock);
+    }
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    add(node.caseKeyword.value);
+    sb.add(" ");
+    visit(node.expression);
+    add(node.colonToken.value);
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    addToken(node.onKeyword);
+    if (node.type != null) {
+      visit(node.type);
+      sb.add(' ');
+    }
+    addToken(node.catchKeyword);
+    visit(node.formals);
+    visit(node.block);
+  }
+
+  visitTypedef(Typedef node) {
+    addToken(node.typedefKeyword);
+    if (node.returnType != null) {
+      visit(node.returnType);
+      sb.add(' ');
+    }
+    visit(node.name);
+    if (node.typeParameters != null) {
+      visit(node.typeParameters);
+    }
+    visit(node.formals);
+    add(node.endToken.value);
+  }
+
+  visitLibraryName(LibraryName node) {
+    addToken(node.libraryKeyword);
+    node.visitChildren(this);
+    add(node.getEndToken().value);
+  }
+
+  visitImport(Import node) {
+    addToken(node.importKeyword);
+    visit(node.uri);
+    if (node.prefix != null) {
+      sb.add(' ');
+      addToken(node.asKeyword);
+      visit(node.prefix);
+    }
+    if (node.combinators != null) {
+      sb.add(' ');
+      visit(node.combinators);
+    }
+    add(node.getEndToken().value);
+  }
+
+  visitExport(Export node) {
+    addToken(node.exportKeyword);
+    visit(node.uri);
+    if (node.combinators != null) {
+      sb.add(' ');
+      visit(node.combinators);
+    }
+    add(node.getEndToken().value);
+  }
+
+  visitPart(Part node) {
+    addToken(node.partKeyword);
+    visit(node.uri);
+    add(node.getEndToken().value);
+  }
+
+  visitPartOf(PartOf node) {
+    addToken(node.partKeyword);
+    addToken(node.ofKeyword);
+    visit(node.name);
+    add(node.getEndToken().value);
+  }
+
+  visitCombinator(Combinator node) {
+    addToken(node.keywordToken);
+    visit(node.identifiers);
+  }
+
+  visitNode(Node node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitExpression(Expression node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLibraryTag(LibraryTag node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLibraryDependency(Node node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLiteral(Literal node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitLoop(Loop node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitPostfix(Postfix node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitPrefix(Prefix node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitStatement(Statement node) {
+    throw 'internal error'; // Should not be called.
+  }
+
+  visitStringNode(StringNode node) {
+    throw 'internal error'; // Should not be called.
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart
new file mode 100644
index 0000000..9096cff
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2011, 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.
+
+part of tree;
+
+/**
+ * This visitor takes another visitor and applies it to every
+ * node in the tree. There is currently no way to control the
+ * traversal.
+ */
+class TraversingVisitor extends Visitor {
+  final Visitor visitor;
+
+  TraversingVisitor(Visitor this.visitor);
+
+  visitNode(Node node) {
+    node.accept(visitor);
+    node.visitChildren(this);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart
new file mode 100644
index 0000000..6a3b93f
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2011, 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.
+
+part of dart2js;
+
+class TreeValidatorTask extends CompilerTask {
+  TreeValidatorTask(Compiler compiler) : super(compiler);
+
+  void validate(Node tree) {
+    assert(check(tree));
+  }
+
+  bool check(Node tree) {
+    List<InvalidNodeError> errors = [];
+    void report(node, message) {
+      final error = new InvalidNodeError(node, message);
+      errors.add(error);
+      compiler.reportWarning(node, message);
+    };
+    final validator = new ValidatorVisitor(report);
+    tree.accept(new TraversingVisitor(validator));
+
+    return errors.isEmpty;
+  }
+}
+
+class ValidatorVisitor extends Visitor {
+  final Function reportInvalidNode;
+
+  ValidatorVisitor(Function this.reportInvalidNode);
+
+  expect(Node node, bool test, [message]) {
+    if (!test) reportInvalidNode(node, message);
+  }
+
+  visitNode(Node node) {}
+
+  visitSendSet(SendSet node) {
+    final selector = node.selector;
+    final name = node.assignmentOperator.source.stringValue;
+    final arguments = node.arguments;
+
+    expect(node, arguments != null);
+    expect(node, selector is Identifier, 'selector is not assignable');
+    if (identical(name, '++') || identical(name, '--')) {
+      expect(node, node.assignmentOperator is Operator);
+      if (node.isIndex) {
+        expect(node.arguments.tail.head, node.arguments.tail.isEmpty);
+      } else {
+        expect(node.arguments.head, node.arguments.isEmpty);
+      }
+    } else {
+      expect(node, !node.arguments.isEmpty);
+    }
+  }
+
+  visitReturn(Return node) {
+    if (!node.isRedirectingFactoryBody && node.hasExpression) {
+      // We allow non-expression expressions in Return nodes, but only when
+      // using them to hold redirecting factory constructors.
+      expect(node, node.expression.asExpression() != null);
+    }
+  }
+}
+
+class InvalidNodeError {
+  final Node node;
+  final String message;
+  InvalidNodeError(this.node, [this.message]);
+
+  toString() {
+    String nodeString = node.toDebugString();
+    String result = 'invalid node: $nodeString';
+    if (message != null) result = '$result ($message)';
+    return result;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart b/pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart
new file mode 100644
index 0000000..01fa607
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart
@@ -0,0 +1,751 @@
+// 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.
+
+part of dart2js;
+
+class TypeCheckerTask extends CompilerTask {
+  TypeCheckerTask(Compiler compiler) : super(compiler);
+  String get name => "Type checker";
+
+  static const bool LOG_FAILURES = false;
+
+  void check(Node tree, TreeElements elements) {
+    measure(() {
+      Visitor visitor =
+          new TypeCheckerVisitor(compiler, elements, compiler.types);
+      try {
+        tree.accept(visitor);
+      } on CancelTypeCheckException catch (e) {
+        if (LOG_FAILURES) {
+          // Do not warn about unimplemented features; log message instead.
+          compiler.log("'${e.node}': ${e.reason}");
+        }
+      }
+    });
+  }
+}
+
+class CancelTypeCheckException {
+  final Node node;
+  final String reason;
+
+  CancelTypeCheckException(this.node, this.reason);
+}
+
+class TypeCheckerVisitor implements Visitor<DartType> {
+  final Compiler compiler;
+  final TreeElements elements;
+  final Types types;
+
+  Node lastSeenNode;
+  DartType expectedReturnType;
+  ClassElement currentClass;
+
+  Link<DartType> cascadeTypes = const Link<DartType>();
+
+  DartType intType;
+  DartType doubleType;
+  DartType boolType;
+  DartType stringType;
+  DartType objectType;
+  DartType listType;
+
+  TypeCheckerVisitor(this.compiler, this.elements, this.types) {
+    intType = compiler.intClass.computeType(compiler);
+    doubleType = compiler.doubleClass.computeType(compiler);
+    boolType = compiler.boolClass.computeType(compiler);
+    stringType = compiler.stringClass.computeType(compiler);
+    objectType = compiler.objectClass.computeType(compiler);
+    listType = compiler.listClass.computeType(compiler);
+  }
+
+  DartType fail(node, [reason]) {
+    String message = 'cannot type-check';
+    if (reason != null) {
+      message = '$message: $reason';
+    }
+    throw new CancelTypeCheckException(node, message);
+  }
+
+  reportTypeWarning(Node node, MessageKind kind, [Map arguments = const {}]) {
+    compiler.reportWarning(node, new TypeWarning(kind, arguments));
+  }
+
+  // TODO(karlklose): remove these functions.
+  DartType unhandledStatement() => StatementType.NOT_RETURNING;
+  DartType unhandledExpression() => types.dynamicType;
+
+  DartType analyzeNonVoid(Node node) {
+    DartType type = analyze(node);
+    if (type == types.voidType) {
+      reportTypeWarning(node, MessageKind.VOID_EXPRESSION);
+    }
+    return type;
+  }
+
+  DartType analyzeWithDefault(Node node, DartType defaultValue) {
+    return node != null ? analyze(node) : defaultValue;
+  }
+
+  DartType analyze(Node node) {
+    if (node == null) {
+      final String error = 'internal error: unexpected node: null';
+      if (lastSeenNode != null) {
+        fail(null, error);
+      } else {
+        compiler.cancel(error);
+      }
+    } else {
+      lastSeenNode = node;
+    }
+    DartType result = node.accept(this);
+    // TODO(karlklose): record type?
+    if (result == null) {
+      fail(node, 'internal error: type is null');
+    }
+    return result;
+  }
+
+  /**
+   * Check if a value of type t can be assigned to a variable,
+   * parameter or return value of type s.
+   */
+  checkAssignable(Node node, DartType s, DartType t) {
+    if (!types.isAssignable(s, t)) {
+      reportTypeWarning(node, MessageKind.NOT_ASSIGNABLE,
+                        {'fromType': s, 'toType': t});
+    }
+  }
+
+  checkCondition(Expression condition) {
+    checkAssignable(condition, boolType, analyze(condition));
+  }
+
+  void pushCascadeType(DartType type) {
+    cascadeTypes = cascadeTypes.prepend(type);
+  }
+
+  DartType popCascadeType() {
+    DartType type = cascadeTypes.head;
+    cascadeTypes = cascadeTypes.tail;
+    return type;
+  }
+
+  DartType visitBlock(Block node) {
+    return analyze(node.statements);
+  }
+
+  DartType visitCascade(Cascade node) {
+    analyze(node.expression);
+    return popCascadeType();
+  }
+
+  DartType visitCascadeReceiver(CascadeReceiver node) {
+    DartType type = analyze(node.expression);
+    pushCascadeType(type);
+    return type;
+  }
+
+  DartType visitClassNode(ClassNode node) {
+    fail(node);
+  }
+
+  DartType visitMixinApplication(MixinApplication node) {
+    fail(node);
+  }
+
+  DartType visitNamedMixinApplication(NamedMixinApplication node) {
+    fail(node);
+  }
+
+  DartType visitDoWhile(DoWhile node) {
+    StatementType bodyType = analyze(node.body);
+    checkCondition(node.condition);
+    return bodyType.join(StatementType.NOT_RETURNING);
+  }
+
+  DartType visitExpressionStatement(ExpressionStatement node) {
+    analyze(node.expression);
+    return StatementType.NOT_RETURNING;
+  }
+
+  /** Dart Programming Language Specification: 11.5.1 For Loop */
+  DartType visitFor(For node) {
+    analyzeWithDefault(node.initializer, StatementType.NOT_RETURNING);
+    checkCondition(node.condition);
+    analyzeWithDefault(node.update, StatementType.NOT_RETURNING);
+    StatementType bodyType = analyze(node.body);
+    return bodyType.join(StatementType.NOT_RETURNING);
+  }
+
+  DartType visitFunctionDeclaration(FunctionDeclaration node) {
+    analyze(node.function);
+    return StatementType.NOT_RETURNING;
+  }
+
+  DartType visitFunctionExpression(FunctionExpression node) {
+    DartType type;
+    DartType returnType;
+    DartType previousType;
+    final FunctionElement element = elements[node];
+    if (Elements.isUnresolved(element)) return types.dynamicType;
+    if (identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR) ||
+        identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY)) {
+      type = types.dynamicType;
+      returnType = types.voidType;
+    } else {
+      FunctionType functionType = computeType(element);
+      returnType = functionType.returnType;
+      type = functionType;
+    }
+    DartType previous = expectedReturnType;
+    expectedReturnType = returnType;
+    if (element.isMember()) currentClass = element.getEnclosingClass();
+    StatementType bodyType = analyze(node.body);
+    if (returnType != types.voidType && returnType != types.dynamicType
+        && bodyType != StatementType.RETURNING) {
+      MessageKind kind;
+      if (bodyType == StatementType.MAYBE_RETURNING) {
+        kind = MessageKind.MAYBE_MISSING_RETURN;
+      } else {
+        kind = MessageKind.MISSING_RETURN;
+      }
+      reportTypeWarning(node.name, kind);
+    }
+    expectedReturnType = previous;
+    return type;
+  }
+
+  DartType visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      return currentClass.computeType(compiler);
+    } else {
+      // This is an identifier of a formal parameter.
+      return types.dynamicType;
+    }
+  }
+
+  DartType visitIf(If node) {
+    checkCondition(node.condition);
+    StatementType thenType = analyze(node.thenPart);
+    StatementType elseType = node.hasElsePart ? analyze(node.elsePart)
+                                              : StatementType.NOT_RETURNING;
+    return thenType.join(elseType);
+  }
+
+  DartType visitLoop(Loop node) {
+    return unhandledStatement();
+  }
+
+  DartType lookupMethodType(Node node, ClassElement classElement,
+                            SourceString name) {
+    Element member = classElement.lookupLocalMember(name);
+    if (member == null) {
+      classElement.ensureResolved(compiler);
+      for (Link<DartType> supertypes = classElement.allSupertypes;
+           !supertypes.isEmpty && member == null;
+           supertypes = supertypes.tail) {
+        ClassElement lookupTarget = supertypes.head.element;
+        member = lookupTarget.lookupLocalMember(name);
+      }
+    }
+    if (member != null && member.kind == ElementKind.FUNCTION) {
+      return computeType(member);
+    }
+    reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND,
+                      {'className': classElement.name, 'methodName': name});
+    return types.dynamicType;
+  }
+
+  // TODO(johnniwinther): Provide the element from which the type came in order
+  // to give better error messages.
+  void analyzeArguments(Send send, DartType type) {
+    Link<Node> arguments = send.arguments;
+    if (type == null || identical(type, types.dynamicType)) {
+      while(!arguments.isEmpty) {
+        analyze(arguments.head);
+        arguments = arguments.tail;
+      }
+    } else {
+      FunctionType funType = type;
+      Link<DartType> parameterTypes = funType.parameterTypes;
+      Link<DartType> optionalParameterTypes = funType.optionalParameterTypes;
+      while (!arguments.isEmpty) {
+        Node argument = arguments.head;
+        NamedArgument namedArgument = argument.asNamedArgument();
+        if (namedArgument != null) {
+          argument = namedArgument.expression;
+          SourceString argumentName = namedArgument.name.source;
+          DartType namedParameterType =
+              funType.getNamedParameterType(argumentName);
+          if (namedParameterType == null) {
+            // TODO(johnniwinther): Provide better information on the called
+            // function.
+            reportTypeWarning(argument, MessageKind.NAMED_ARGUMENT_NOT_FOUND,
+                {'argumentName': argumentName});
+
+            analyze(argument);
+          } else {
+            checkAssignable(argument, namedParameterType, analyze(argument));
+          }
+        } else {
+          if (parameterTypes.isEmpty) {
+            if (optionalParameterTypes.isEmpty) {
+              // TODO(johnniwinther): Provide better information on the
+              // called function.
+              reportTypeWarning(argument, MessageKind.ADDITIONAL_ARGUMENT);
+
+              analyze(argument);
+            } else {
+              checkAssignable(argument, optionalParameterTypes.head,
+                              analyze(argument));
+              optionalParameterTypes = optionalParameterTypes.tail;
+            }
+          } else {
+            checkAssignable(argument, parameterTypes.head, analyze(argument));
+            parameterTypes = parameterTypes.tail;
+          }
+        }
+        arguments = arguments.tail;
+      }
+      if (!parameterTypes.isEmpty) {
+        // TODO(johnniwinther): Provide better information on the called
+        // function.
+        reportTypeWarning(send, MessageKind.MISSING_ARGUMENT,
+            {'argumentType': parameterTypes.head});
+      }
+    }
+  }
+
+  DartType visitSend(Send node) {
+    Element element = elements[node];
+
+    if (Elements.isClosureSend(node, element)) {
+      // TODO(karlklose): Finish implementation.
+      return types.dynamicType;
+    }
+
+    Identifier selector = node.selector.asIdentifier();
+    String name = selector.source.stringValue;
+
+    if (node.isOperator && identical(name, 'is')) {
+      analyze(node.receiver);
+      return boolType;
+    } else if (node.isOperator) {
+      final Node firstArgument = node.receiver;
+      final DartType firstArgumentType = analyze(node.receiver);
+      final arguments = node.arguments;
+      final Node secondArgument = arguments.isEmpty ? null : arguments.head;
+      final DartType secondArgumentType =
+          analyzeWithDefault(secondArgument, null);
+
+      if (identical(name, '+') || identical(name, '=') || identical(name, '-')
+          || identical(name, '*') || identical(name, '/') || identical(name, '%')
+          || identical(name, '~/') || identical(name, '|') || identical(name, '&')
+          || identical(name, '^') || identical(name, '~')|| identical(name, '<<')
+          || identical(name, '>>') || identical(name, '[]')) {
+        return types.dynamicType;
+      } else if (identical(name, '<') || identical(name, '>') || identical(name, '<=')
+                 || identical(name, '>=') || identical(name, '==') || identical(name, '!=')
+                 || identical(name, '===') || identical(name, '!==')) {
+        return boolType;
+      } else if (identical(name, '||') || identical(name, '&&') || identical(name, '!')) {
+        checkAssignable(firstArgument, boolType, firstArgumentType);
+        if (!arguments.isEmpty) {
+          // TODO(karlklose): check number of arguments in validator.
+          checkAssignable(secondArgument, boolType, secondArgumentType);
+        }
+        return boolType;
+      }
+      fail(selector, 'unexpected operator ${name}');
+
+    } else if (node.isPropertyAccess) {
+      if (node.receiver != null) {
+        // TODO(karlklose): we cannot handle fields.
+        return unhandledExpression();
+      }
+      if (element == null) return types.dynamicType;
+      return computeType(element);
+
+    } else if (node.isFunctionObjectInvocation) {
+      fail(node.receiver, 'function object invocation unimplemented');
+
+    } else {
+      FunctionType computeFunType() {
+        if (node.receiver != null) {
+          DartType receiverType = analyze(node.receiver);
+          if (receiverType.element == compiler.dynamicClass) return null;
+          if (receiverType == null) {
+            fail(node.receiver, 'receivertype is null');
+          }
+          if (identical(receiverType.element.kind, ElementKind.GETTER)) {
+            FunctionType getterType  = receiverType;
+            receiverType = getterType.returnType;
+          }
+          ElementKind receiverKind = receiverType.element.kind;
+          if (identical(receiverKind, ElementKind.TYPEDEF)) {
+            // TODO(karlklose): handle typedefs.
+            return null;
+          }
+          if (identical(receiverKind, ElementKind.TYPE_VARIABLE)) {
+            // TODO(karlklose): handle type variables.
+            return null;
+          }
+          if (!identical(receiverKind, ElementKind.CLASS)) {
+            fail(node.receiver, 'unexpected receiver kind: ${receiverKind}');
+          }
+          ClassElement classElement = receiverType.element;
+          // TODO(karlklose): substitute type arguments.
+          DartType memberType =
+            lookupMethodType(selector, classElement, selector.source);
+          if (identical(memberType.element, compiler.dynamicClass)) return null;
+          return memberType;
+        } else {
+          if (Elements.isUnresolved(element)) {
+            fail(node, 'unresolved ${node.selector}');
+          } else if (identical(element.kind, ElementKind.FUNCTION)) {
+            return computeType(element);
+          } else if (element.isForeign(compiler)) {
+            return null;
+          } else if (identical(element.kind, ElementKind.VARIABLE)
+                     || identical(element.kind, ElementKind.FIELD)) {
+            // TODO(karlklose): handle object invocations.
+            return null;
+          } else {
+            fail(node, 'unexpected element kind ${element.kind}');
+          }
+        }
+      }
+      FunctionType funType = computeFunType();
+      analyzeArguments(node, funType);
+      return (funType != null) ? funType.returnType : types.dynamicType;
+    }
+  }
+
+  visitSendSet(SendSet node) {
+    Identifier selector = node.selector;
+    final name = node.assignmentOperator.source.stringValue;
+    if (identical(name, '++') || identical(name, '--')) {
+      final Element element = elements[node.selector];
+      final DartType receiverType = computeType(element);
+      // TODO(karlklose): this should be the return type instead of int.
+      return node.isPrefix ? intType : receiverType;
+    } else {
+      DartType targetType = computeType(elements[node]);
+      Node value = node.arguments.head;
+      checkAssignable(value, targetType, analyze(value));
+      return targetType;
+    }
+  }
+
+  DartType visitLiteralInt(LiteralInt node) {
+    return intType;
+  }
+
+  DartType visitLiteralDouble(LiteralDouble node) {
+    return doubleType;
+  }
+
+  DartType visitLiteralBool(LiteralBool node) {
+    return boolType;
+  }
+
+  DartType visitLiteralString(LiteralString node) {
+    return stringType;
+  }
+
+  DartType visitStringJuxtaposition(StringJuxtaposition node) {
+    analyze(node.first);
+    analyze(node.second);
+    return stringType;
+  }
+
+  DartType visitLiteralNull(LiteralNull node) {
+    return types.dynamicType;
+  }
+
+  DartType visitNewExpression(NewExpression node) {
+    Element element = elements[node.send];
+    analyzeArguments(node.send, computeType(element));
+    return analyze(node.send.selector);
+  }
+
+  DartType visitLiteralList(LiteralList node) {
+    return listType;
+  }
+
+  DartType visitNodeList(NodeList node) {
+    DartType type = StatementType.NOT_RETURNING;
+    bool reportedDeadCode = false;
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      DartType nextType = analyze(link.head);
+      if (type == StatementType.RETURNING) {
+        if (!reportedDeadCode) {
+          reportTypeWarning(link.head, MessageKind.UNREACHABLE_CODE);
+          reportedDeadCode = true;
+        }
+      } else if (type == StatementType.MAYBE_RETURNING){
+        if (nextType == StatementType.RETURNING) {
+          type = nextType;
+        }
+      } else {
+        type = nextType;
+      }
+    }
+    return type;
+  }
+
+  DartType visitOperator(Operator node) {
+    fail(node, 'internal error');
+  }
+
+  /** Dart Programming Language Specification: 11.10 Return */
+  DartType visitReturn(Return node) {
+    if (identical(node.getBeginToken().stringValue, 'native')) {
+      return StatementType.RETURNING;
+    }
+    if (node.isRedirectingFactoryBody) {
+      // TODO(lrn): Typecheck the body. It must refer to the constructor
+      // of a subtype.
+      return StatementType.RETURNING;
+    }
+
+    final expression = node.expression;
+    final isVoidFunction = (identical(expectedReturnType, types.voidType));
+
+    // Executing a return statement return e; [...] It is a static type warning
+    // if the type of e may not be assigned to the declared return type of the
+    // immediately enclosing function.
+    if (expression != null) {
+      final expressionType = analyze(expression);
+      if (isVoidFunction
+          && !types.isAssignable(expressionType, types.voidType)) {
+        reportTypeWarning(expression, MessageKind.RETURN_VALUE_IN_VOID);
+      } else {
+        checkAssignable(expression, expectedReturnType, expressionType);
+      }
+
+    // Let f be the function immediately enclosing a return statement of the
+    // form 'return;' It is a static warning if both of the following conditions
+    // hold:
+    // - f is not a generative constructor.
+    // - The return type of f may not be assigned to void.
+    } else if (!types.isAssignable(expectedReturnType, types.voidType)) {
+      reportTypeWarning(node, MessageKind.RETURN_NOTHING,
+                        {'returnType': expectedReturnType});
+    }
+    return StatementType.RETURNING;
+  }
+
+  DartType visitThrow(Throw node) {
+    if (node.expression != null) analyze(node.expression);
+    return StatementType.RETURNING;
+  }
+
+  DartType computeType(Element element) {
+    if (Elements.isUnresolved(element)) return types.dynamicType;
+    DartType result = element.computeType(compiler);
+    return (result != null) ? result : types.dynamicType;
+  }
+
+  DartType visitTypeAnnotation(TypeAnnotation node) {
+    return elements.getType(node);
+  }
+
+  visitTypeVariable(TypeVariable node) {
+    return types.dynamicType;
+  }
+
+  DartType visitVariableDefinitions(VariableDefinitions node) {
+    DartType type = analyzeWithDefault(node.type, types.dynamicType);
+    if (type == types.voidType) {
+      reportTypeWarning(node.type, MessageKind.VOID_VARIABLE);
+      type = types.dynamicType;
+    }
+    for (Link<Node> link = node.definitions.nodes; !link.isEmpty;
+         link = link.tail) {
+      Node initialization = link.head;
+      compiler.ensure(initialization is Identifier
+                      || initialization is Send);
+      if (initialization is Send) {
+        DartType initializer = analyzeNonVoid(link.head);
+        checkAssignable(node, type, initializer);
+      }
+    }
+    return StatementType.NOT_RETURNING;
+  }
+
+  DartType visitWhile(While node) {
+    checkCondition(node.condition);
+    StatementType bodyType = analyze(node.body);
+    Expression cond = node.condition.asParenthesizedExpression().expression;
+    if (cond.asLiteralBool() != null && cond.asLiteralBool().value == true) {
+      // If the condition is a constant boolean expression denoting true,
+      // control-flow always enters the loop body.
+      // TODO(karlklose): this should be StatementType.RETURNING unless there
+      // is a break in the loop body that has the loop or a label outside the
+      // loop as a target.
+      return bodyType;
+    } else {
+      return bodyType.join(StatementType.NOT_RETURNING);
+    }
+  }
+
+  DartType visitParenthesizedExpression(ParenthesizedExpression node) {
+    return analyze(node.expression);
+  }
+
+  DartType visitConditional(Conditional node) {
+    checkCondition(node.condition);
+    DartType thenType = analyzeNonVoid(node.thenExpression);
+    DartType elseType = analyzeNonVoid(node.elseExpression);
+    if (types.isSubtype(thenType, elseType)) {
+      return thenType;
+    } else if (types.isSubtype(elseType, thenType)) {
+      return elseType;
+    } else {
+      return objectType;
+    }
+  }
+
+  DartType visitModifiers(Modifiers node) {}
+
+  visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    return stringType;
+  }
+
+  visitStringInterpolationPart(StringInterpolationPart node) {
+    node.visitChildren(this);
+    return stringType;
+  }
+
+  visitEmptyStatement(EmptyStatement node) {
+    return StatementType.NOT_RETURNING;
+  }
+
+  visitBreakStatement(BreakStatement node) {
+    return StatementType.NOT_RETURNING;
+  }
+
+  visitContinueStatement(ContinueStatement node) {
+    return StatementType.NOT_RETURNING;
+  }
+
+  visitForIn(ForIn node) {
+    analyze(node.expression);
+    StatementType bodyType = analyze(node.body);
+    return bodyType.join(StatementType.NOT_RETURNING);
+  }
+
+  visitLabel(Label node) { }
+
+  visitLabeledStatement(LabeledStatement node) {
+    return node.statement.accept(this);
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    return unhandledExpression();
+  }
+
+  visitLiteralMapEntry(LiteralMapEntry node) {
+    return unhandledExpression();
+  }
+
+  visitNamedArgument(NamedArgument node) {
+    return unhandledExpression();
+  }
+
+  visitSwitchStatement(SwitchStatement node) {
+    return unhandledStatement();
+  }
+
+  visitSwitchCase(SwitchCase node) {
+    return unhandledStatement();
+  }
+
+  visitCaseMatch(CaseMatch node) {
+    return unhandledStatement();
+  }
+
+  visitTryStatement(TryStatement node) {
+    return unhandledStatement();
+  }
+
+  visitScriptTag(ScriptTag node) {
+    return unhandledExpression();
+  }
+
+  visitCatchBlock(CatchBlock node) {
+    return unhandledStatement();
+  }
+
+  visitTypedef(Typedef node) {
+    return unhandledStatement();
+  }
+
+  DartType visitNode(Node node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitCombinator(Combinator node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitExport(Export node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitExpression(Expression node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitGotoStatement(GotoStatement node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitImport(Import node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLibraryName(LibraryName node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLibraryTag(LibraryTag node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLiteral(Literal node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPart(Part node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPartOf(PartOf node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPostfix(Postfix node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitPrefix(Prefix node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitStatement(Statement node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitStringNode(StringNode node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+
+  DartType visitLibraryDependency(LibraryDependency node) {
+    compiler.unimplemented('visitNode', node: node);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart
new file mode 100644
index 0000000..6c05203
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart
@@ -0,0 +1,1678 @@
+// 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.
+
+part of types;
+
+class CancelTypeInferenceException {
+  final Node node;
+  final String reason;
+
+  CancelTypeInferenceException(this.node, this.reason);
+}
+
+/**
+ * A singleton concrete type. More precisely, a [BaseType] is one of the
+ * following:
+ *
+ *   - a non-asbtract class like [: int :] or [: Uri :] but not [: List :]
+ *   - the null base type
+ *   - the unknown base type
+ */
+abstract class BaseType {
+  bool isClass();
+  bool isUnknown();
+  bool isNull();
+}
+
+/**
+ * A non-asbtract class like [: int :] or [: Uri :] but not [: List :].
+ */
+class ClassBaseType implements BaseType {
+  final ClassElement element;
+
+  ClassBaseType(this.element);
+
+  bool operator ==(BaseType other) {
+    if (identical(this, other)) return true;
+    if (other is! ClassBaseType) return false;
+    return element == other.element;
+  }
+  int get hashCode => element.hashCode;
+  String toString() => element.name.slowToString();
+  bool isClass() => true;
+  bool isUnknown() => false;
+  bool isNull() => false;
+}
+
+/**
+ * The unknown base type.
+ */
+class UnknownBaseType implements BaseType {
+  const UnknownBaseType();
+  bool operator ==(BaseType other) => other is UnknownBaseType;
+  int get hashCode => 0;
+  bool isClass() => false;
+  bool isUnknown() => true;
+  bool isNull() => false;
+  toString() => "unknown";
+}
+
+/**
+ * The null base type.
+ */
+class NullBaseType implements BaseType {
+  const NullBaseType();
+  bool operator ==(BaseType other) => other is NullBaseType;
+  int get hashCode => 1;
+  bool isClass() => false;
+  bool isUnknown() => false;
+  bool isNull() => true;
+  toString() => "null";
+}
+
+/**
+ * An immutable set of base types, like [: {int, bool} :] or the unknown
+ * concrete type.
+ */
+abstract class ConcreteType {
+  factory ConcreteType.empty() {
+    return new UnionType(new Set<BaseType>());
+  }
+
+  /**
+   * The singleton constituted of the unknown base type is the unknown concrete
+   * type.
+   */
+  factory ConcreteType.singleton(int maxConcreteTypeSize, BaseType baseType) {
+    if (baseType.isUnknown() || maxConcreteTypeSize < 1) {
+      return new UnknownConcreteType();
+    }
+    Set<BaseType> singletonSet = new Set<BaseType>();
+    singletonSet.add(baseType);
+    return new UnionType(singletonSet);
+  }
+
+  factory ConcreteType.unknown() {
+    return const UnknownConcreteType();
+  }
+
+  ConcreteType union(int maxConcreteTypeSize, ConcreteType other);
+  bool isUnkown();
+  bool isEmpty();
+  Set<BaseType> get baseTypes;
+
+  /**
+   * Returns the unique element of [: this :] if [: this :] is a singleton,
+   * null otherwise.
+   */
+  ClassElement getUniqueType();
+}
+
+/**
+ * The unkown concrete type: it is absorbing for the union.
+ */
+class UnknownConcreteType implements ConcreteType {
+  const UnknownConcreteType();
+  bool isUnkown() => true;
+  bool isEmpty() => false;
+  bool operator ==(ConcreteType other) => identical(this, other);
+  Set<BaseType> get baseTypes =>
+      new Set<BaseType>.from([const UnknownBaseType()]);
+  int get hashCode => 0;
+  ConcreteType union(int maxConcreteTypeSize, ConcreteType other) => this;
+  ClassElement getUniqueType() => null;
+  toString() => "unknown";
+}
+
+/**
+ * An immutable set of base types, like [: {int, bool} :].
+ */
+class UnionType implements ConcreteType {
+  final Set<BaseType> baseTypes;
+
+  /**
+   * The argument should NOT be mutated later. Do not call directly, use
+   * ConcreteType.singleton instead.
+   */
+  UnionType(this.baseTypes);
+
+  bool isUnkown() => false;
+  bool isEmpty() => baseTypes.isEmpty;
+
+  bool operator ==(ConcreteType other) {
+    if (other is! UnionType) return false;
+    if (baseTypes.length != other.baseTypes.length) return false;
+    return baseTypes.containsAll(other.baseTypes);
+  }
+
+  int get hashCode {
+    int result = 1;
+    for (final baseType in baseTypes) {
+      result = 31 * result + baseType.hashCode;
+    }
+    return result;
+  }
+
+  // TODO(polux): Collapse {num, int, ...}, {num, double, ...} and
+  // {int, double,...} into {num, ...} as an optimization. It will require
+  // UnionType to know about these class elements, which is cumbersome because
+  // there are no nested classes. We need factory methods instead.
+  ConcreteType union(int maxConcreteTypeSize, ConcreteType other) {
+    if (other.isUnkown()) {
+      return const UnknownConcreteType();
+    }
+    UnionType otherUnion = other;  // cast
+    Set<BaseType> newBaseTypes = new Set<BaseType>.from(baseTypes);
+    newBaseTypes.addAll(otherUnion.baseTypes);
+    return newBaseTypes.length > maxConcreteTypeSize
+        ? const UnknownConcreteType()
+        : new UnionType(newBaseTypes);
+  }
+
+  ClassElement getUniqueType() {
+    if (baseTypes.length == 1) {
+      var iterator = baseTypes.iterator;
+      iterator.moveNext();
+      BaseType uniqueBaseType = iterator.current;
+      if (uniqueBaseType.isClass()) {
+        ClassBaseType uniqueClassType = uniqueBaseType;
+        return uniqueClassType.element;
+      }
+    }
+    return null;
+  }
+
+  String toString() => baseTypes.toString();
+}
+
+/**
+ * The cartesian product of concrete types: an iterable of [BaseTypeTuple]s. For
+ * instance, the cartesian product of the concrete types [: {A, B} :] and
+ * [: {C, D} :] is an itearble whose iterators will yield [: (A, C) :],
+ * [: (A, D) :], [: (B, C) :] and finally [: (B, D) :].
+ */
+class ConcreteTypeCartesianProduct
+    extends Iterable<ConcreteTypesEnvironment> {
+  final ConcreteTypesInferrer inferrer;
+  final ClassElement typeOfThis;
+  final Map<Element, ConcreteType> concreteTypes;
+  ConcreteTypeCartesianProduct(this.inferrer, this.typeOfThis,
+                               this.concreteTypes);
+  Iterator get iterator => concreteTypes.isEmpty
+      ? [new ConcreteTypesEnvironment(inferrer, new ClassBaseType(typeOfThis))]
+            .iterator
+      : new ConcreteTypeCartesianProductIterator(inferrer,
+            new ClassBaseType(typeOfThis), concreteTypes);
+  String toString() {
+    List<ConcreteTypesEnvironment> cartesianProduct =
+        new List<ConcreteTypesEnvironment>.from(this);
+    return cartesianProduct.toString();
+  }
+}
+
+/**
+ * An helper class for [ConcreteTypeCartesianProduct].
+ */
+class ConcreteTypeCartesianProductIterator
+    implements Iterator<ConcreteTypesEnvironment> {
+  final ConcreteTypesInferrer inferrer;
+  final BaseType baseTypeOfThis;
+  final Map<Element, ConcreteType> concreteTypes;
+  final Map<Element, BaseType> nextValues;
+  final Map<Element, Iterator> state;
+  int size = 1;
+  int counter = 0;
+  ConcreteTypesEnvironment _current;
+
+  ConcreteTypeCartesianProductIterator(this.inferrer, this.baseTypeOfThis,
+      Map<Element, ConcreteType> concreteTypes)
+      : this.concreteTypes = concreteTypes,
+        nextValues = new Map<Element, BaseType>(),
+        state = new Map<Element, Iterator>() {
+    if (concreteTypes.isEmpty) {
+      size = 0;
+      return;
+    }
+    for (final e in concreteTypes.keys) {
+      final baseTypes = concreteTypes[e].baseTypes;
+      size *= baseTypes.length;
+    }
+  }
+
+  ConcreteTypesEnvironment get current => _current;
+
+  ConcreteTypesEnvironment takeSnapshot() {
+    Map<Element, ConcreteType> result = new Map<Element, ConcreteType>();
+    nextValues.forEach((k, v) {
+      result[k] = inferrer.singletonConcreteType(v);
+    });
+    return new ConcreteTypesEnvironment.of(inferrer, result, baseTypeOfThis);
+  }
+
+  bool moveNext() {
+    if (counter >= size) {
+      _current = null;
+      return false;
+    }
+    Element keyToIncrement = null;
+    for (final key in concreteTypes.keys) {
+      final iterator = state[key];
+      if (iterator != null && iterator.moveNext()) {
+        nextValues[key] = state[key].current;
+        break;
+      }
+      Iterator newIterator = concreteTypes[key].baseTypes.iterator;
+      state[key] = newIterator;
+      newIterator.moveNext();
+      nextValues[key] = newIterator.current;
+    }
+    counter++;
+    _current = takeSnapshot();
+    return true;
+  }
+}
+
+/**
+ * [BaseType] Constants.
+ */
+class BaseTypes {
+  final ClassBaseType intBaseType;
+  final ClassBaseType doubleBaseType;
+  final ClassBaseType numBaseType;
+  final ClassBaseType boolBaseType;
+  final ClassBaseType stringBaseType;
+  final ClassBaseType listBaseType;
+  final ClassBaseType mapBaseType;
+  final ClassBaseType objectBaseType;
+  final ClassBaseType typeBaseType;
+
+  static _getNativeListClass(Compiler compiler) {
+    // TODO(polux): switch to other implementations on other backends
+    JavaScriptBackend backend = compiler.backend;
+    return backend.jsArrayClass;
+  }
+
+  BaseTypes(Compiler compiler) :
+    intBaseType = new ClassBaseType(compiler.intClass),
+    doubleBaseType = new ClassBaseType(compiler.doubleClass),
+    numBaseType = new ClassBaseType(compiler.numClass),
+    boolBaseType = new ClassBaseType(compiler.boolClass),
+    stringBaseType = new ClassBaseType(compiler.stringClass),
+    // in the Javascript backend, lists are implemented by JsArray
+    listBaseType = new ClassBaseType(_getNativeListClass(compiler)),
+    mapBaseType = new ClassBaseType(compiler.mapClass),
+    objectBaseType = new ClassBaseType(compiler.objectClass),
+    typeBaseType = new ClassBaseType(compiler.typeClass);
+}
+
+/**
+ * A method-local immutable mapping from variables to their inferred
+ * [ConcreteTypes]. Each visitor owns one.
+ */
+class ConcreteTypesEnvironment {
+  final ConcreteTypesInferrer inferrer;
+  final Map<Element, ConcreteType> environment;
+  final BaseType typeOfThis;
+
+  ConcreteTypesEnvironment(this.inferrer, [this.typeOfThis]) :
+    this.environment = new Map<Element, ConcreteType>();
+  ConcreteTypesEnvironment.of(this.inferrer, this.environment, this.typeOfThis);
+
+  ConcreteType lookupType(Element element) => environment[element];
+  ConcreteType lookupTypeOfThis() {
+    return (typeOfThis == null)
+        ? null
+        : inferrer.singletonConcreteType(typeOfThis);
+  }
+
+  ConcreteTypesEnvironment put(Element element, ConcreteType type) {
+    Map<Element, ConcreteType> newMap =
+        new Map<Element, ConcreteType>.from(environment);
+    newMap[element] = type;
+    return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis);
+  }
+
+  ConcreteTypesEnvironment join(ConcreteTypesEnvironment other) {
+    if (typeOfThis != other.typeOfThis) {
+      throw "trying to join incompatible environments";
+    }
+    Map<Element, ConcreteType> newMap =
+        new Map<Element, ConcreteType>.from(environment);
+    other.environment.forEach((element, type) {
+      ConcreteType currentType = newMap[element];
+      if (element == null) {
+        newMap[element] = type;
+      } else {
+        newMap[element] = inferrer.union(currentType, type);
+      }
+    });
+    return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis);
+  }
+
+  bool operator ==(ConcreteTypesEnvironment other) {
+    if (other is! ConcreteTypesEnvironment) return false;
+    if (typeOfThis != other.typeOfThis) return false;
+    if (environment.length != other.environment.length) return false;
+    for (Element key in environment.keys) {
+      if (!other.environment.containsKey(key)
+          || (environment[key] != other.environment[key])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  int get hashCode {
+    int result = (typeOfThis != null) ? typeOfThis.hashCode : 1;
+    environment.forEach((element, concreteType) {
+      result = 31 * (31 * result + element.hashCode) +
+          concreteType.hashCode;
+    });
+    return result;
+  }
+
+  String toString() => "{ this: $typeOfThis, env: ${environment.toString()} }";
+}
+
+/**
+ * A work item for the type inference queue.
+ */
+class InferenceWorkItem {
+  FunctionElement method;
+  ConcreteTypesEnvironment environment;
+  InferenceWorkItem(this.method, this.environment);
+
+  toString() => "{ method = ${method.name.slowToString()}, "
+                "environment = $environment }";
+}
+
+/**
+ * A task which conservatively infers a [ConcreteType] for each sub expression
+ * of the program. The entry point is [analyzeMain].
+ */
+class ConcreteTypesInferrer {
+  static final bool LOG_FAILURES = true;
+
+  final String name = "Type inferrer";
+
+  final Compiler compiler;
+
+  /**
+   * When true, the string literal [:"__dynamic_for_test":] is inferred to
+   * have the unknown type.
+   */
+  // TODO(polux): get rid of this hack once we have a natural way of inferring
+  // the unknown type.
+  bool testMode = false;
+
+  /**
+   * Constants representing builtin base types. Initialized in [initialize]
+   * and not in the constructor because the compiler elements are not yet
+   * populated.
+   */
+  BaseTypes baseTypes;
+
+  /**
+   * Constant representing [:ConcreteList#[]:] where [:ConcreteList:] is the
+   * concrete implmentation of lists for the selected backend.
+   */
+  FunctionElement listIndex;
+
+  /**
+   * Constant representing [:ConcreteList#[]=:] where [:ConcreteList:] is the
+   * concrete implmentation of lists for the selected backend.
+   */
+  FunctionElement listIndexSet;
+
+  /**
+   * Constant representing [:List():].
+   */
+  FunctionElement listConstructor;
+
+  /**
+   * A cache from (function x argument base types) to concrete types,
+   * used to memoize [analyzeMonoSend]. Another way of seeing [cache] is as a
+   * map from [FunctionElement]s to "templates" in the sense of "The Cartesian
+   * Product Algorithm - Simple and Precise Type Inference of Parametric
+   * Polymorphism" by Ole Agesen.
+   */
+  final Map<FunctionElement, Map<ConcreteTypesEnvironment, ConcreteType>> cache;
+
+  /** A map from expressions to their inferred concrete types. */
+  final Map<Node, ConcreteType> inferredTypes;
+
+  /** A map from fields to their inferred concrete types. */
+  final Map<Element, ConcreteType> inferredFieldTypes;
+
+  /** The work queue consumed by [analyzeMain]. */
+  final Queue<InferenceWorkItem> workQueue;
+
+  /** [: callers[f] :] is the list of [: f :]'s possible callers. */
+  final Map<FunctionElement, Set<FunctionElement>> callers;
+
+  /** [: readers[field] :] is the list of [: field :]'s possible readers. */
+  final Map<Element, Set<FunctionElement>> readers;
+
+  /** The inferred type of elements stored in Lists. */
+  ConcreteType listElementType;
+
+  /**
+   * A map from parameters to their inferred concrete types. It plays no role
+   * in the analysis, it is write only.
+   */
+  final Map<VariableElement, ConcreteType> inferredParameterTypes;
+
+  ConcreteTypesInferrer(Compiler compiler)
+      : this.compiler = compiler,
+        cache = new Map<FunctionElement,
+            Map<ConcreteTypesEnvironment, ConcreteType>>(),
+        inferredTypes = new Map<Node, ConcreteType>(),
+        inferredFieldTypes = new Map<Element, ConcreteType>(),
+        inferredParameterTypes = new Map<VariableElement, ConcreteType>(),
+        workQueue = new Queue<InferenceWorkItem>(),
+        callers = new Map<FunctionElement, Set<FunctionElement>>(),
+        readers = new Map<Element, Set<FunctionElement>>(),
+        listElementType = new ConcreteType.empty() {
+    unknownConcreteType = new ConcreteType.unknown();
+    emptyConcreteType = new ConcreteType.empty();
+  }
+
+  /**
+   * Populates [cache] with ad hoc rules like:
+   *
+   *     {int} + {int}    -> {int}
+   *     {int} + {double} -> {num}
+   *     {int} + {num}    -> {double}
+   *     ...
+   */
+  populateCacheWithBuiltinRules() {
+    // Builds the environment that would be looked up if we were to analyze
+    // o.method(arg) where o has concrete type {receiverType} and arg has
+    // concrete type {argumentType}.
+    ConcreteTypesEnvironment makeEnvironment(BaseType receiverType,
+                                             FunctionElement method,
+                                             BaseType argumentType) {
+      ArgumentsTypes argumentsTypes = new ArgumentsTypes(
+          [singletonConcreteType(argumentType)],
+          new Map());
+      Map<Element, ConcreteType> argumentMap =
+          associateArguments(method, argumentsTypes);
+      return new ConcreteTypesEnvironment.of(this, argumentMap, receiverType);
+    }
+
+    // Adds the rule {receiverType}.method({argumentType}) -> {returnType}
+    // to cache.
+    void rule(ClassBaseType receiverType, String method,
+              BaseType argumentType, BaseType returnType) {
+      // The following line shouldn't be needed but the mock compiler doesn't
+      // resolve num for some reason.
+      receiverType.element.ensureResolved(compiler);
+      FunctionElement methodElement =
+          receiverType.element.lookupMember(new SourceString(method));
+      ConcreteTypesEnvironment environment =
+          makeEnvironment(receiverType, methodElement, argumentType);
+      Map<ConcreteTypesEnvironment, ConcreteType> map =
+          cache.containsKey(methodElement)
+              ? cache[methodElement]
+              : new Map<ConcreteTypesEnvironment, ConcreteType>();
+      map[environment] = singletonConcreteType(returnType);
+      cache[methodElement] = map;
+    }
+
+    // The hardcoded typing rules.
+    final ClassBaseType int = baseTypes.intBaseType;
+    final ClassBaseType double = baseTypes.doubleBaseType;
+    final ClassBaseType num = baseTypes.numBaseType;
+    for (String method in ['+', '*', '-']) {
+      rule(int, method, int, int);
+      rule(int, method, double, num);
+      rule(int, method, num, num);
+
+      rule(double, method, double, double);
+      rule(double, method, int, num);
+      rule(double, method, num, num);
+
+      rule(num, method, int, num);
+      rule(num, method, double, num);
+      rule(num, method, num, num);
+    }
+  }
+
+  // --- utility methods ---
+
+  /** The unknown concrete type */
+  ConcreteType unknownConcreteType;
+
+  /** The empty concrete type */
+  ConcreteType emptyConcreteType;
+
+  /** Creates a singleton concrete type containing [baseType]. */
+  ConcreteType singletonConcreteType(BaseType baseType) {
+    return new ConcreteType.singleton(compiler.maxConcreteTypeSize, baseType);
+  }
+
+  /** Returns the union of its two arguments */
+  ConcreteType union(ConcreteType concreteType1, ConcreteType concreteType2) {
+    return concreteType1.union(compiler.maxConcreteTypeSize, concreteType2);
+  }
+
+  /**
+   * Returns all the members with name [methodName].
+   */
+  List<Element> getMembersByName(SourceString methodName) {
+    // TODO(polux): memoize?
+    var result = new List<Element>();
+    for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) {
+      Element elem = cls.lookupLocalMember(methodName);
+      if (elem != null) {
+        result.add(elem);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Sets the concrete type associated to [node] to the union of the inferred
+   * concrete type so far and [type].
+   */
+  void augmentInferredType(Node node, ConcreteType type) {
+    ConcreteType currentType = inferredTypes[node];
+    inferredTypes[node] = (currentType == null)
+        ? type
+        : union(currentType, type);
+  }
+
+  /**
+   * Returns the current inferred concrete type of [field].
+   */
+  ConcreteType getFieldType(Element field) {
+    ConcreteType result = inferredFieldTypes[field];
+    return (result == null) ? emptyConcreteType : result;
+  }
+
+  /**
+   * Sets the concrete type associated to [field] to the union of the inferred
+   * concrete type so far and [type].
+   */
+  void augmentFieldType(Element field, ConcreteType type) {
+    ConcreteType oldType = inferredFieldTypes[field];
+    ConcreteType newType = (oldType != null)
+        ? union(oldType, type)
+        : type;
+    if (oldType != newType) {
+      inferredFieldTypes[field] = newType;
+      final fieldReaders = readers[field];
+      if (fieldReaders != null) {
+        for (final reader in fieldReaders) {
+          final readerInstances = cache[reader];
+          if (readerInstances != null) {
+            readerInstances.forEach((environment, _) {
+              workQueue.addLast(new InferenceWorkItem(reader, environment));
+            });
+          }
+        }
+      }
+    }
+  }
+
+  /// Augment the inferred type of elements stored in Lists.
+  void augmentListElementType(ConcreteType type) {
+    ConcreteType newType = union(listElementType, type);
+    if (newType != listElementType) {
+      invalidateCallers(listIndex);
+      listElementType = newType;
+    }
+  }
+
+  /**
+   * Sets the concrete type associated to [parameter] to the union of the
+   * inferred concrete type so far and [type].
+   */
+  void augmentParameterType(VariableElement parameter, ConcreteType type) {
+    ConcreteType oldType = inferredParameterTypes[parameter];
+    inferredParameterTypes[parameter] =
+        (oldType == null) ? type : union(oldType, type);
+  }
+
+  /**
+   * Add [caller] to the set of [callee]'s callers.
+   */
+  void addCaller(FunctionElement callee, FunctionElement caller) {
+    Set<FunctionElement> current = callers[callee];
+    if (current != null) {
+      current.add(caller);
+    } else {
+      Set<FunctionElement> newSet = new Set<FunctionElement>();
+      newSet.add(caller);
+      callers[callee] = newSet;
+    }
+  }
+
+  /**
+   * Add [reader] to the set of [field]'s readers.
+   */
+  void addReader(Element field, FunctionElement reader) {
+    Set<FunctionElement> current = readers[field];
+    if (current != null) {
+      current.add(reader);
+    } else {
+      Set<FunctionElement> newSet = new Set<FunctionElement>();
+      newSet.add(reader);
+      readers[field] = newSet;
+    }
+  }
+
+  /**
+   * Add callers of [function] to the workqueue.
+   */
+  void invalidateCallers(FunctionElement function) {
+    Set<FunctionElement> methodCallers = callers[function];
+    if (methodCallers == null) return;
+    for (FunctionElement caller in methodCallers) {
+      Map<ConcreteTypesEnvironment, ConcreteType> callerInstances =
+          cache[caller];
+      if (callerInstances != null) {
+        callerInstances.forEach((environment, _) {
+          workQueue.addLast(
+              new InferenceWorkItem(caller, environment));
+        });
+      }
+    }
+  }
+
+  // -- query --
+
+  /**
+   * Get the inferred concrete type of [node].
+   */
+  ConcreteType getConcreteTypeOfNode(Node node) => inferredTypes[node];
+
+  /**
+   * Get the inferred concrete type of [parameter].
+   */
+  ConcreteType getConcreteTypeOfParameter(VariableElement parameter) {
+    return inferredParameterTypes[parameter];
+  }
+
+  // --- analysis ---
+
+  /**
+   * Returns the concrete type returned by [function] given arguments of
+   * concrete types [argumentsTypes]. If [function] is static then
+   * [receiverType] must be null, else [function] must be a member of the class
+   * of [receiverType].
+   */
+  ConcreteType getSendReturnType(FunctionElement function,
+                                 ClassElement receiverType,
+                                 ArgumentsTypes argumentsTypes) {
+    ConcreteType result = emptyConcreteType;
+    Map<Element, ConcreteType> argumentMap =
+        associateArguments(function, argumentsTypes);
+    // if the association failed, this send will never occur or will fail
+    if (argumentMap == null) {
+      return emptyConcreteType;
+    }
+
+    argumentMap.forEach(augmentParameterType);
+    ConcreteTypeCartesianProduct product =
+        new ConcreteTypeCartesianProduct(this, receiverType, argumentMap);
+    for (ConcreteTypesEnvironment environment in product) {
+      result = union(result,
+                     getMonomorphicSendReturnType(function, environment));
+    }
+    return result;
+  }
+
+  /**
+   * Given a method signature and a list of concrete types, builds a map from
+   * formals to their corresponding concrete types. Returns null if the
+   * association is impossible (for instance: too many arguments).
+   */
+  Map<Element, ConcreteType> associateArguments(FunctionElement function,
+                                                ArgumentsTypes argumentsTypes) {
+    final Map<Element, ConcreteType> result = new Map<Element, ConcreteType>();
+    final FunctionSignature signature = function.computeSignature(compiler);
+
+    // guard 1: too many arguments
+    if (argumentsTypes.length > signature.parameterCount) {
+      return null;
+    }
+    // guard 2: not enough arguments
+    if (argumentsTypes.positional.length < signature.requiredParameterCount) {
+      return null;
+    }
+    // guard 3: too many positional arguments
+    if (signature.optionalParametersAreNamed &&
+        argumentsTypes.positional.length > signature.requiredParameterCount) {
+      return null;
+    }
+
+    handleLeftoverOptionalParameter(Element parameter) {
+      // TODO(polux): use default value whenever available
+      // TODO(polux): add a marker to indicate whether an argument was provided
+      //     in order to handle "?parameter" tests
+      result[parameter] = singletonConcreteType(const NullBaseType());
+    }
+
+    final Iterator<ConcreteType> remainingPositionalArguments =
+        argumentsTypes.positional.iterator;
+    // we attach each positional parameter to its corresponding positional
+    // argument
+    for (Link<Element> requiredParameters = signature.requiredParameters;
+        !requiredParameters.isEmpty;
+        requiredParameters = requiredParameters.tail) {
+      final Element requiredParameter = requiredParameters.head;
+      // we know moveNext() succeeds because of guard 2
+      remainingPositionalArguments.moveNext();
+      result[requiredParameter] = remainingPositionalArguments.current;
+    }
+    if (signature.optionalParametersAreNamed) {
+      // we build a map out of the remaining named parameters
+      Link<Element> remainingOptionalParameters = signature.optionalParameters;
+      final Map<SourceString, Element> leftOverNamedParameters =
+          new Map<SourceString, Element>();
+      for (;
+           !remainingOptionalParameters.isEmpty;
+           remainingOptionalParameters = remainingOptionalParameters.tail) {
+        final Element namedParameter = remainingOptionalParameters.head;
+        leftOverNamedParameters[namedParameter.name] = namedParameter;
+      }
+      // we attach the named arguments to their corresponding optional
+      // parameters
+      for (Identifier identifier in argumentsTypes.named.keys) {
+        final ConcreteType concreteType = argumentsTypes.named[identifier];
+        SourceString source = identifier.source;
+        final Element namedParameter = leftOverNamedParameters[source];
+        // unexisting or already used named parameter
+        if (namedParameter == null) return null;
+        result[namedParameter] = concreteType;
+        leftOverNamedParameters.remove(source);
+      }
+      leftOverNamedParameters.forEach((_, Element parameter) {
+        handleLeftoverOptionalParameter(parameter);
+      });
+    } else { // optional parameters are positional
+      // we attach the remaining positional arguments to their corresponding
+      // optional parameters
+      Link<Element> remainingOptionalParameters = signature.optionalParameters;
+      while (remainingPositionalArguments.moveNext()) {
+        final Element optionalParameter = remainingOptionalParameters.head;
+        result[optionalParameter] = remainingPositionalArguments.current;
+        // we know tail is defined because of guard 1
+        remainingOptionalParameters = remainingOptionalParameters.tail;
+      }
+      for (;
+           !remainingOptionalParameters.isEmpty;
+           remainingOptionalParameters = remainingOptionalParameters.tail) {
+        handleLeftoverOptionalParameter(remainingOptionalParameters.head);
+      }
+    }
+    return result;
+  }
+
+  ConcreteType getMonomorphicSendReturnType(
+      FunctionElement function,
+      ConcreteTypesEnvironment environment) {
+    ConcreteType specialType = getSpecialCaseReturnType(function, environment);
+    if (specialType != null) return specialType;
+
+    Map<ConcreteTypesEnvironment, ConcreteType> template = cache[function];
+    if (template == null) {
+      template = new Map<ConcreteTypesEnvironment, ConcreteType>();
+      cache[function] = template;
+    }
+    ConcreteType type = template[environment];
+    if (type != null) {
+      return type;
+    } else {
+      workQueue.addLast(
+        new InferenceWorkItem(function, environment));
+      // in case of a constructor, optimize by returning the class
+      return emptyConcreteType;
+    }
+  }
+
+  /**
+   * Handles external methods that cannot be cached because they depend on some
+   * other state of [ConcreteTypesInferrer] like [:List#[]:] and
+   * [:List#[]=:]. Returns null if [function] and [environment] don't form a
+   * special case
+   */
+  ConcreteType getSpecialCaseReturnType(FunctionElement function,
+                                        ConcreteTypesEnvironment environment) {
+    if (function == listIndex) {
+      ConcreteType indexType = environment.lookupType(
+          listIndex.functionSignature.requiredParameters.head);
+      if (!indexType.baseTypes.contains(baseTypes.intBaseType)) {
+        return new ConcreteType.empty();
+      }
+      return listElementType;
+    } else if (function == listIndexSet) {
+      Link<Element> parameters =
+          listIndexSet.functionSignature.requiredParameters;
+      ConcreteType indexType = environment.lookupType(parameters.head);
+      if (!indexType.baseTypes.contains(baseTypes.intBaseType)) {
+        return new ConcreteType.empty();
+      }
+      ConcreteType elementType = environment.lookupType(parameters.tail.head);
+      augmentListElementType(elementType);
+      return new ConcreteType.empty();
+    }
+    return null;
+  }
+
+  ConcreteType analyze(FunctionElement element,
+                       ConcreteTypesEnvironment environment) {
+    return element.isGenerativeConstructor()
+        ? analyzeConstructor(element, environment)
+        : analyzeMethod(element, environment);
+  }
+
+  ConcreteType analyzeMethod(FunctionElement element,
+                             ConcreteTypesEnvironment environment) {
+    TreeElements elements =
+        compiler.enqueuer.resolution.resolvedElements[element];
+    ConcreteType specialResult = handleSpecialMethod(element, environment);
+    if (specialResult != null) return specialResult;
+    FunctionExpression tree = element.parseNode(compiler);
+    if (tree.hasBody()) {
+      Visitor visitor =
+          new TypeInferrerVisitor(elements, element, this, environment);
+      return tree.accept(visitor);
+    } else {
+      // TODO(polux): implement visitForeingCall and always use the
+      // implementation element instead of this hack
+      return new ConcreteType.unknown();
+    }
+  }
+
+  ConcreteType analyzeConstructor(FunctionElement element,
+                                  ConcreteTypesEnvironment environment) {
+    ClassElement enclosingClass = element.enclosingElement;
+    FunctionExpression tree = compiler.parser.parse(element);
+    TreeElements elements =
+        compiler.enqueuer.resolution.resolvedElements[element];
+    Visitor visitor =
+        new TypeInferrerVisitor(elements, element, this, environment);
+
+    // handle initializing formals
+    element.functionSignature.forEachParameter((param) {
+      if (param.kind == ElementKind.FIELD_PARAMETER) {
+        FieldParameterElement fieldParam = param;
+        augmentFieldType(fieldParam.fieldElement,
+            environment.lookupType(param));
+      }
+    });
+
+    // analyze initializers, including a possible call to super or a redirect
+    bool foundSuperOrRedirect = false;
+    if (tree.initializers != null) {
+      // we look for a possible call to super in the initializer list
+      for (final init in tree.initializers) {
+        init.accept(visitor);
+        if (init.asSendSet() == null) {
+          foundSuperOrRedirect = true;
+        }
+      }
+    }
+
+    // if no call to super or redirect has been found, call the default
+    // constructor (if the current class is not Object).
+    if (!foundSuperOrRedirect) {
+      ClassElement superClass = enclosingClass.superclass;
+      if (enclosingClass != compiler.objectClass) {
+        FunctionElement target = superClass.lookupConstructor(
+          new Selector.callDefaultConstructor(enclosingClass.getLibrary()));
+        final superClassConcreteType = singletonConcreteType(
+            new ClassBaseType(enclosingClass));
+        getSendReturnType(target, enclosingClass,
+            new ArgumentsTypes(new List(), new Map()));
+      }
+    }
+
+    tree.accept(visitor);
+    return singletonConcreteType(new ClassBaseType(enclosingClass));
+  }
+
+  /**
+   * Hook that performs side effects on some special method calls (like
+   * [:List(length):]) and possibly returns a concrete type
+   * (like [:{JsArray}:]).
+   */
+  ConcreteType handleSpecialMethod(FunctionElement element,
+                                   ConcreteTypesEnvironment environment) {
+    // When List([length]) is called with some length, we must augment
+    // listElementType with {null}.
+    if (element == listConstructor) {
+      Link<Element> parameters =
+          listConstructor.functionSignature.optionalParameters;
+      ConcreteType lengthType = environment.lookupType(parameters.head);
+      if (lengthType.baseTypes.contains(baseTypes.intBaseType)) {
+        augmentListElementType(singletonConcreteType(new NullBaseType()));
+      }
+      return singletonConcreteType(baseTypes.listBaseType);
+    }
+  }
+
+  /* Initialization code that cannot be run in the constructor because it
+   * requires the compiler's elements to be populated.
+   */
+  void initialize() {
+    baseTypes = new BaseTypes(compiler);
+    ClassElement jsArrayClass = baseTypes.listBaseType.element;
+    listIndex = jsArrayClass.lookupMember(const SourceString('[]'));
+    listIndexSet =
+        jsArrayClass.lookupMember(const SourceString('[]='));
+    listConstructor =
+        compiler.listClass.lookupConstructor(
+            new Selector.callConstructor(const SourceString(''),
+                                         compiler.listClass.getLibrary()));
+  }
+
+  /**
+   * Performs concrete type inference of the code reachable from [element].
+   * Returns [:true:] if and only if analysis succeeded.
+   */
+  bool analyzeMain(Element element) {
+    initialize();
+    cache[element] = new Map<ConcreteTypesEnvironment, ConcreteType>();
+    populateCacheWithBuiltinRules();
+    try {
+      workQueue.addLast(
+          new InferenceWorkItem(element, new ConcreteTypesEnvironment(this)));
+      while (!workQueue.isEmpty) {
+        InferenceWorkItem item = workQueue.removeFirst();
+        ConcreteType concreteType = analyze(item.method, item.environment);
+        var template = cache[item.method];
+        if (template[item.environment] == concreteType) continue;
+        template[item.environment] = concreteType;
+        invalidateCallers(item.method);
+      }
+      return true;
+    } on CancelTypeInferenceException catch(e) {
+      if (LOG_FAILURES) {
+        compiler.log(e.reason);
+      }
+      return false;
+    }
+  }
+
+  /**
+   * Dumps debugging information on the standard output.
+   */
+  void debug() {
+    print("callers :");
+    callers.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("readers :");
+    readers.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("inferredFieldTypes:");
+    inferredFieldTypes.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("inferredParameterTypes:");
+    inferredParameterTypes.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("cache:");
+    cache.forEach((k,v) {
+      print("  $k: $v");
+    });
+    print("inferred expression types: ");
+    inferredTypes.forEach((k,v) {
+      print("  $k: $v");
+    });
+  }
+
+  /**
+   * Fail with a message and abort.
+   */
+  void fail(node, [reason]) {
+    String message = 'cannot infer types';
+    if (reason != null) {
+      message = '$message: $reason';
+    }
+    throw new CancelTypeInferenceException(node, message);
+  }
+}
+
+/**
+ * Represents the concrete types of the arguments of a send, indexed by
+ * position or name.
+ */
+class ArgumentsTypes {
+  final List<ConcreteType> positional;
+  final Map<Identifier, ConcreteType> named;
+  ArgumentsTypes(this.positional, this.named);
+  int get length => positional.length + named.length;
+  toString() => "{ positional = $positional, named = $named }";
+}
+
+/**
+ * The core logic of the type inference algorithm.
+ */
+class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
+  final ConcreteTypesInferrer inferrer;
+
+  final FunctionElement currentMethod;
+  ConcreteTypesEnvironment environment;
+  Node lastSeenNode;
+
+  TypeInferrerVisitor(TreeElements elements, this.currentMethod, this.inferrer,
+                      this.environment)
+      : super(elements);
+
+  ArgumentsTypes analyzeArguments(Link<Node> arguments) {
+    final positional = new List<ConcreteType>();
+    final named = new Map<Identifier, ConcreteType>();
+    for(Link<Node> iterator = arguments;
+        !iterator.isEmpty;
+        iterator = iterator.tail) {
+      Node node = iterator.head;
+      NamedArgument namedArgument = node.asNamedArgument();
+      if (namedArgument != null) {
+        named[namedArgument.name] = analyze(namedArgument.expression);
+      } else {
+        positional.add(analyze(node));
+      }
+    }
+    return new ArgumentsTypes(positional, named);
+  }
+
+  /**
+   * A proxy to accept which does book keeping and error reporting. Returns null
+   * if [node] is a non-returning statement, its inferred concrete type
+   * otherwise.
+   */
+  ConcreteType analyze(Node node) {
+    if (node == null) {
+      final String error = 'internal error: unexpected node: null';
+      inferrer.fail(lastSeenNode, error);
+    } else {
+      lastSeenNode = node;
+    }
+    ConcreteType result = node.accept(this);
+    if (result == null) {
+      inferrer.fail(node, 'internal error: inferred type is null');
+    }
+    inferrer.augmentInferredType(node, result);
+    return result;
+  }
+
+  ConcreteType visitBlock(Block node) {
+    return analyze(node.statements);
+  }
+
+  ConcreteType visitCascade(Cascade node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitCascadeReceiver(CascadeReceiver node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitClassNode(ClassNode node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitDoWhile(DoWhile node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitExpressionStatement(ExpressionStatement node) {
+    analyze(node.expression);
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitFor(For node) {
+    if (node.initializer != null) {
+      analyze(node.initializer);
+    }
+    analyze(node.conditionStatement);
+    ConcreteType result = inferrer.emptyConcreteType;
+    ConcreteTypesEnvironment oldEnvironment;
+    do {
+      oldEnvironment = environment;
+      analyze(node.conditionStatement);
+      analyze(node.body);
+      analyze(node.update);
+      environment = oldEnvironment.join(environment);
+    // TODO(polux): Maybe have a destructive join-method that returns a boolean
+    // value indicating whether something changed to avoid performing this
+    // comparison twice.
+    } while (oldEnvironment != environment);
+    return result;
+  }
+
+  ConcreteType visitFunctionDeclaration(FunctionDeclaration node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitFunctionExpression(FunctionExpression node) {
+    return analyze(node.body);
+  }
+
+  ConcreteType visitIdentifier(Identifier node) {
+    if (node.isThis()) {
+      ConcreteType result = environment.lookupTypeOfThis();
+      if (result == null) {
+        inferrer.fail(node, '"this" has no type');
+      }
+      return result;
+    }
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitIf(If node) {
+    analyze(node.condition);
+    ConcreteType thenType = analyze(node.thenPart);
+    ConcreteTypesEnvironment snapshot = environment;
+    ConcreteType elseType = node.hasElsePart ? analyze(node.elsePart)
+                                             : inferrer.emptyConcreteType;
+    environment = environment.join(snapshot);
+    return inferrer.union(thenType, elseType);
+  }
+
+  ConcreteType visitLoop(Loop node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType analyzeSetElement(Element receiver, ConcreteType argumentType) {
+    environment = environment.put(receiver, argumentType);
+    if (receiver.isField()) {
+      inferrer.augmentFieldType(receiver, argumentType);
+    } else if (receiver.isSetter()){
+      FunctionElement setter = receiver;
+      // TODO(polux): A setter always returns void so there's no need to
+      // invalidate its callers even if it is called with new arguments.
+      // However, if we start to record more than returned types, like
+      // exceptions for instance, we need to do it by uncommenting the following
+      // line.
+      // inferrer.addCaller(setter, currentMethod);
+      inferrer.getSendReturnType(setter, receiver.enclosingElement,
+          new ArgumentsTypes([argumentType], new Map()));
+    }
+    return argumentType;
+  }
+
+  ConcreteType analyzeSetNode(Node receiver, ConcreteType argumentType,
+                              SourceString name) {
+    ConcreteType receiverType = analyze(receiver);
+
+    void augmentField(ClassElement receiverType, Element member) {
+      if (member.isField()) {
+        inferrer.augmentFieldType(member, argumentType);
+      } else if (member.isAbstractField()){
+        AbstractFieldElement abstractField = member;
+        FunctionElement setter = abstractField.setter;
+        // TODO(polux): A setter always returns void so there's no need to
+        // invalidate its callers even if it is called with new arguments.
+        // However, if we start to record more than returned types, like
+        // exceptions for instance, we need to do it by uncommenting the
+        // following line.
+        // inferrer.addCaller(setter, currentMethod);
+        inferrer.getSendReturnType(setter, receiverType,
+            new ArgumentsTypes([argumentType], new Map()));
+      }
+      // since this is a sendSet we ignore non-fields
+    }
+
+    if (receiverType.isUnkown()) {
+      for (Element member in inferrer.getMembersByName(name)) {
+        if (!(member.isField() || member.isAbstractField())) continue;
+        Element cls = member.getEnclosingClass();
+        augmentField(cls, member);
+      }
+    } else {
+      for (BaseType baseReceiverType in receiverType.baseTypes) {
+        if (!baseReceiverType.isClass()) continue;
+        ClassBaseType baseReceiverClassType = baseReceiverType;
+        Element member = baseReceiverClassType.element.lookupMember(name);
+        if (member != null) {
+          augmentField(baseReceiverClassType.element, member);
+        }
+      }
+    }
+    return argumentType;
+  }
+
+  SourceString canonicalizeCompoundOperator(SourceString op) {
+    // TODO(ahe): This class should work on elements or selectors, not
+    // names.  Otherwise, it is repeating work the resolver has
+    // already done (or should have done).  In this case, the problem
+    // is that the resolver is not recording the selectors it is
+    // registering in registerBinaryOperator in
+    // ResolverVisitor.visitSendSet.
+    String stringValue = op.stringValue;
+    if (stringValue == '++') return const SourceString(r'+');
+    else if (stringValue == '--') return const SourceString(r'-');
+    else return Elements.mapToUserOperatorOrNull(op);
+  }
+
+  ConcreteType visitSendSet(SendSet node) {
+    // Operator []= has a different behaviour than other send sets: it is
+    // actually a send whose return type is that of its second argument.
+    if (node.selector.asIdentifier().source.stringValue == '[]') {
+      ConcreteType receiverType = analyze(node.receiver);
+      ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments);
+      analyzeDynamicSend(receiverType, const SourceString('[]='),
+                         argumentsTypes);
+      return argumentsTypes.positional[1];
+    }
+
+    // All other operators have a single argument (++ and -- have an implicit
+    // argument: 1). We will store its type in argumentType.
+    ConcreteType argumentType;
+    SourceString operatorName = node.assignmentOperator.source;
+    SourceString compoundOperatorName =
+        canonicalizeCompoundOperator(node.assignmentOperator.source);
+    // ++, --, +=, -=, ...
+    if (compoundOperatorName != null) {
+      ConcreteType receiverType = visitGetterSend(node);
+      // argumentsTypes is either computed from the actual arguments or [{int}]
+      // in case of ++ or --.
+      ArgumentsTypes argumentsTypes;
+      if (operatorName.stringValue == '++'
+          || operatorName.stringValue == '--') {
+        List<ConcreteType> positionalArguments = <ConcreteType>[
+            inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType)];
+        argumentsTypes = new ArgumentsTypes(positionalArguments, new Map());
+      } else {
+        argumentsTypes = analyzeArguments(node.arguments);
+      }
+      argumentType = analyzeDynamicSend(receiverType, compoundOperatorName,
+                                        argumentsTypes);
+    // The simple assignment case: receiver = argument.
+    } else {
+      argumentType = analyze(node.argumentsNode);
+    }
+
+    Element element = elements[node];
+    if (element != null) {
+      return analyzeSetElement(element, argumentType);
+    } else {
+      return analyzeSetNode(node.receiver, argumentType,
+                            node.selector.asIdentifier().source);
+    }
+  }
+
+  ConcreteType visitLiteralInt(LiteralInt node) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType);
+  }
+
+  ConcreteType visitLiteralDouble(LiteralDouble node) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.doubleBaseType);
+  }
+
+  ConcreteType visitLiteralBool(LiteralBool node) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType);
+  }
+
+  ConcreteType visitLiteralString(LiteralString node) {
+    // TODO(polux): get rid of this hack once we have a natural way of inferring
+    // the unknown type.
+    if (inferrer.testMode
+        && node.dartString.slowToString() == "__dynamic_for_test") {
+      return inferrer.unknownConcreteType;
+    }
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitStringJuxtaposition(StringJuxtaposition node) {
+    analyze(node.first);
+    analyze(node.second);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitLiteralNull(LiteralNull node) {
+    return inferrer.singletonConcreteType(const NullBaseType());
+  }
+
+  ConcreteType visitNewExpression(NewExpression node) {
+    Element constructor = elements[node.send];
+    inferrer.addCaller(constructor, currentMethod);
+    ClassElement cls = constructor.enclosingElement;
+    return inferrer.getSendReturnType(constructor, cls,
+                                      analyzeArguments(node.send.arguments));
+  }
+
+  ConcreteType visitLiteralList(LiteralList node) {
+    ConcreteType elementsType = new ConcreteType.empty();
+    // We compute the union of the types of the list literal's elements.
+    for (Link<Node> link = node.elements.nodes;
+         !link.isEmpty;
+         link = link.tail) {
+      elementsType = inferrer.union(elementsType, analyze(link.head));
+    }
+    inferrer.augmentListElementType(elementsType);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.listBaseType);
+  }
+
+  ConcreteType visitNodeList(NodeList node) {
+    ConcreteType type = inferrer.emptyConcreteType;
+    // The concrete type of a sequence of statements is the union of the
+    // statement's types.
+    for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
+      type = inferrer.union(type, analyze(link.head));
+    }
+    return type;
+  }
+
+  ConcreteType visitOperator(Operator node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitReturn(Return node) {
+    final expression = node.expression;
+    return (expression == null)
+        ? inferrer.singletonConcreteType(const NullBaseType())
+        : analyze(expression);
+  }
+
+  ConcreteType visitThrow(Throw node) {
+    if (node.expression != null) analyze(node.expression);
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitTypeAnnotation(TypeAnnotation node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitTypeVariable(TypeVariable node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitVariableDefinitions(VariableDefinitions node) {
+    for (Link<Node> link = node.definitions.nodes; !link.isEmpty;
+         link = link.tail) {
+      analyze(link.head);
+    }
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitWhile(While node) {
+    analyze(node.condition);
+    ConcreteType result = inferrer.emptyConcreteType;
+    ConcreteTypesEnvironment oldEnvironment;
+    do {
+      oldEnvironment = environment;
+      analyze(node.condition);
+      analyze(node.body);
+      environment = oldEnvironment.join(environment);
+    } while (oldEnvironment != environment);
+    return result;
+  }
+
+  ConcreteType visitParenthesizedExpression(ParenthesizedExpression node) {
+    return analyze(node.expression);
+  }
+
+  ConcreteType visitConditional(Conditional node) {
+    analyze(node.condition);
+    ConcreteType thenType = analyze(node.thenExpression);
+    ConcreteType elseType = analyze(node.elseExpression);
+    return inferrer.union(thenType, elseType);
+  }
+
+  ConcreteType visitModifiers(Modifiers node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitStringInterpolationPart(StringInterpolationPart node) {
+    node.visitChildren(this);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType);
+  }
+
+  ConcreteType visitEmptyStatement(EmptyStatement node) {
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitBreakStatement(BreakStatement node) {
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitContinueStatement(ContinueStatement node) {
+    // TODO(polux): we can be more precise
+    return inferrer.emptyConcreteType;
+  }
+
+  ConcreteType visitForIn(ForIn node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitLabel(Label node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitLabeledStatement(LabeledStatement node) {
+    return analyze(node.statement);
+  }
+
+  ConcreteType visitLiteralMap(LiteralMap node) {
+    visitNodeList(node.entries);
+    return inferrer.singletonConcreteType(inferrer.baseTypes.mapBaseType);
+  }
+
+  ConcreteType visitLiteralMapEntry(LiteralMapEntry node) {
+    // We don't need to visit the key, it's always a string.
+    return analyze(node.value);
+  }
+
+  ConcreteType visitNamedArgument(NamedArgument node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitSwitchStatement(SwitchStatement node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitSwitchCase(SwitchCase node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitCaseMatch(CaseMatch node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitTryStatement(TryStatement node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitScriptTag(ScriptTag node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitCatchBlock(CatchBlock node) {
+    inferrer.fail(node, 'not yet implemented');
+  }
+
+  ConcreteType visitTypedef(Typedef node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitSuperSend(Send node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitOperatorSend(Send node) {
+    SourceString name =
+        canonicalizeMethodName(node.selector.asIdentifier().source);
+    if (name == const SourceString('is')) {
+      return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType);
+    }
+    return visitDynamicSend(node);
+  }
+
+  ConcreteType analyzeFieldRead(Element field) {
+    inferrer.addReader(field, currentMethod);
+    return inferrer.getFieldType(field);
+  }
+
+  ConcreteType analyzeGetterSend(ClassElement receiverType,
+                                 FunctionElement getter) {
+      inferrer.addCaller(getter, currentMethod);
+      return inferrer.getSendReturnType(getter,
+                                        receiverType,
+                                        new ArgumentsTypes([], new Map()));
+  }
+
+  ConcreteType visitGetterSend(Send node) {
+    Element element = elements[node];
+    if (element != null) {
+      // node is a local variable or a field of this
+      ConcreteType result = environment.lookupType(element);
+      if (result != null) {
+        // node is a local variable
+        return result;
+      } else {
+        // node is a field or a getter of this
+        if (element.isField()) {
+          return analyzeFieldRead(element);
+        } else {
+          assert(element.isGetter());
+          ClassElement receiverType = element.enclosingElement;
+          return analyzeGetterSend(receiverType, element);
+        }
+      }
+    } else {
+      // node is a field of not(this)
+      assert(node.receiver != null);
+
+      ConcreteType result = inferrer.emptyConcreteType;
+      void augmentResult(ClassElement baseReceiverType, Element member) {
+        if (member.isField()) {
+          result = inferrer.union(result, analyzeFieldRead(member));
+        } else if (member.isAbstractField()){
+          // call to a getter
+          AbstractFieldElement abstractField = member;
+          result = inferrer.union(
+              result,
+              analyzeGetterSend(baseReceiverType, abstractField.getter));
+        }
+        // since this is a get we ignore non-fields
+      }
+
+      ConcreteType receiverType = analyze(node.receiver);
+      if (receiverType.isUnkown()) {
+        List<Element> members =
+            inferrer.getMembersByName(node.selector.asIdentifier().source);
+        for (Element member in members) {
+          if (!(member.isField() || member.isAbstractField())) continue;
+          Element cls = member.getEnclosingClass();
+          augmentResult(cls, member);
+        }
+      } else {
+        for (BaseType baseReceiverType in receiverType.baseTypes) {
+          if (!baseReceiverType.isNull()) {
+            ClassBaseType classBaseType = baseReceiverType;
+            ClassElement cls = classBaseType.element;
+            Element getterOrField =
+                cls.lookupMember(node.selector.asIdentifier().source);
+            if (getterOrField != null) {
+              augmentResult(cls, getterOrField);
+            }
+          }
+        }
+      }
+      return result;
+    }
+  }
+
+  ConcreteType visitClosureSend(Send node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType analyzeDynamicSend(ConcreteType receiverType,
+                                  SourceString canonicalizedMethodName,
+                                  ArgumentsTypes argumentsTypes) {
+    ConcreteType result = inferrer.emptyConcreteType;
+
+    if (receiverType.isUnkown()) {
+      List<Element> methods =
+          inferrer.getMembersByName(canonicalizedMethodName);
+      for (Element element in methods) {
+        // TODO(polux): when we handle closures, we must handle sends to fields
+        // that are closures.
+        if (!element.isFunction()) continue;
+        FunctionElement method = element;
+        inferrer.addCaller(method, currentMethod);
+        Element cls = method.enclosingElement;
+        result = inferrer.union(
+            result,
+            inferrer.getSendReturnType(method, cls, argumentsTypes));
+      }
+
+    } else {
+      for (BaseType baseReceiverType in receiverType.baseTypes) {
+        if (!baseReceiverType.isNull()) {
+          ClassBaseType classBaseReceiverType = baseReceiverType;
+          ClassElement cls = classBaseReceiverType.element;
+          FunctionElement method = cls.lookupMember(canonicalizedMethodName);
+          if (method != null) {
+            inferrer.addCaller(method, currentMethod);
+            result = inferrer.union(
+                result,
+                inferrer.getSendReturnType(method, cls, argumentsTypes));
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  SourceString canonicalizeMethodName(SourceString name) {
+    // TODO(polux): handle unary-
+    SourceString operatorName =
+        Elements.constructOperatorNameOrNull(name, false);
+    if (operatorName != null) return operatorName;
+    return name;
+  }
+
+  ConcreteType visitDynamicSend(Send node) {
+    ConcreteType receiverType = (node.receiver != null)
+        ? analyze(node.receiver)
+        : inferrer.singletonConcreteType(
+            new ClassBaseType(currentMethod.getEnclosingClass()));
+    SourceString name =
+        canonicalizeMethodName(node.selector.asIdentifier().source);
+    ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments);
+    if (name.stringValue == '!=') {
+      ConcreteType returnType = analyzeDynamicSend(receiverType,
+                                                   const SourceString('=='),
+                                                   argumentsTypes);
+      return returnType.isEmpty()
+          ? returnType
+          : inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType);
+    } else {
+      return analyzeDynamicSend(receiverType, name, argumentsTypes);
+    }
+  }
+
+  ConcreteType visitForeignSend(Send node) {
+    inferrer.fail(node, 'not implemented');
+  }
+
+  ConcreteType visitStaticSend(Send node) {
+    Element element = elements[node];
+    inferrer.addCaller(element, currentMethod);
+    return inferrer.getSendReturnType(element, null,
+        analyzeArguments(node.arguments));
+  }
+
+  void internalError(String reason, {Node node}) {
+    inferrer.fail(node, reason);
+  }
+
+  ConcreteType visitTypeReferenceSend(Send) {
+    return inferrer.singletonConcreteType(inferrer.baseTypes.typeBaseType);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart
new file mode 100644
index 0000000..fdce427
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart
@@ -0,0 +1,260 @@
+// 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.
+
+library types;
+
+import 'dart:collection' show Queue;
+
+import '../dart2jslib.dart' hide Selector;
+import '../js_backend/js_backend.dart' show JavaScriptBackend;
+import '../tree/tree.dart';
+import '../elements/elements.dart';
+import '../util/util.dart';
+import '../universe/universe.dart';
+
+part 'concrete_types_inferrer.dart';
+
+/**
+ * The types task infers guaranteed types globally.
+ */
+class TypesTask extends CompilerTask {
+  final String name = 'Type inference';
+  final Set<Element> untypedElements;
+  final Map<Element, Link<Element>> typedSends;
+  ConcreteTypesInferrer concreteTypesInferrer;
+
+  TypesTask(Compiler compiler)
+    : untypedElements = new Set<Element>(),
+      typedSends = new Map<Element, Link<Element>>(),
+      concreteTypesInferrer = compiler.enableConcreteTypeInference
+          ? new ConcreteTypesInferrer(compiler) : null,
+      super(compiler);
+
+  /**
+   * Called once for each method during the resolution phase of the
+   * compiler.
+   */
+  void analyze(Node node, TreeElements elements) {
+    measure(() {
+      node.accept(new ConcreteTypeInferencer(this, elements));
+    });
+  }
+
+  /**
+   * Called when resolution is complete.
+   */
+  void onResolutionComplete(Element mainElement) {
+    measure(() {
+      if (concreteTypesInferrer != null) {
+        bool success = concreteTypesInferrer.analyzeMain(mainElement);
+        if (!success) {
+          // If the concrete type inference bailed out, we pretend it didn't
+          // happen. In the future we might want to record that it failed but
+          // use the partial results as hints.
+          concreteTypesInferrer = null;
+        }
+      }
+    });
+  }
+
+  /**
+   * Return the (inferred) guaranteed concrete type of [element] or null.
+   */
+  ConcreteType getGuaranteedTypeOfElement(Element element) {
+    return measure(() {
+      if (!element.isParameter()) return null;
+      if (concreteTypesInferrer != null) {
+        ConcreteType guaranteedType = concreteTypesInferrer
+            .getConcreteTypeOfParameter(element);
+        if (guaranteedType != null) return guaranteedType;
+      }
+      Element holder = element.enclosingElement;
+      Link<Element> types = typedSends[holder];
+      if (types == null) return null;
+      if (!holder.isFunction()) return null;
+      if (untypedElements.contains(holder)) return null;
+      FunctionElement function = holder;
+      FunctionSignature signature = function.computeSignature(compiler);
+      for (Element parameter in signature.requiredParameters) {
+        if (types.isEmpty) return null;
+        if (element == parameter) {
+          return new ConcreteType.singleton(compiler.maxConcreteTypeSize,
+                                            new ClassBaseType(types.head));
+        }
+        types = types.tail;
+      }
+      return null;
+    });
+  }
+
+  /**
+   * Return the (inferred) guaranteed concrete type of [node] or null.
+   * [node] must be an AST node of [owner].
+   */
+  ConcreteType getGuaranteedTypeOfNode(Node node, Element owner) {
+    return measure(() {
+      if (concreteTypesInferrer != null) {
+        return concreteTypesInferrer.getConcreteTypeOfNode(node);
+      }
+      return null;
+    });
+  }
+}
+
+/**
+ * Infers concrete types for a single method or expression.
+ */
+class ConcreteTypeInferencer extends Visitor {
+  final TypesTask task;
+  final TreeElements elements;
+  final ClassElement boolClass;
+  final ClassElement doubleClass;
+  final ClassElement intClass;
+  final ClassElement listClass;
+  final ClassElement nullClass;
+  final ClassElement stringClass;
+
+  final Map<Node, ClassElement> concreteTypes;
+
+  ConcreteTypeInferencer(TypesTask task, this.elements)
+    : this.task = task,
+      this.boolClass = task.compiler.boolClass,
+      this.doubleClass = task.compiler.doubleClass,
+      this.intClass = task.compiler.intClass,
+      this.listClass = task.compiler.listClass,
+      this.nullClass = task.compiler.nullClass,
+      this.stringClass = task.compiler.stringClass,
+      this.concreteTypes = new Map<Node, ClassElement>();
+
+  visitNode(Node node) => node.visitChildren(this);
+
+  visitLiteralString(LiteralString node) {
+    recordConcreteType(node, stringClass);
+  }
+
+  visitStringInterpolation(StringInterpolation node) {
+    node.visitChildren(this);
+    recordConcreteType(node, stringClass);
+  }
+
+  visitStringJuxtaposition(StringJuxtaposition node) {
+    node.visitChildren(this);
+    recordConcreteType(node, stringClass);
+  }
+
+  recordConcreteType(Node node, ClassElement cls) {
+    concreteTypes[node] = cls;
+  }
+
+  visitLiteralBool(LiteralBool node) {
+    recordConcreteType(node, boolClass);
+  }
+
+  visitLiteralDouble(LiteralDouble node) {
+    recordConcreteType(node, doubleClass);
+  }
+
+  visitLiteralInt(LiteralInt node) {
+    recordConcreteType(node, intClass);
+  }
+
+  visitLiteralList(LiteralList node) {
+    node.visitChildren(this);
+    recordConcreteType(node, listClass);
+  }
+
+  visitLiteralMap(LiteralMap node) {
+    node.visitChildren(this);
+    // TODO(ahe): map class?
+  }
+
+  visitLiteralNull(LiteralNull node) {
+    recordConcreteType(node, nullClass);
+  }
+
+  Link<Element> computeConcreteSendArguments(Send node) {
+    if (node.argumentsNode == null) return null;
+    if (node.arguments.isEmpty) return const Link<Element>();
+    if (node.receiver != null && concreteTypes[node.receiver] == null) {
+      return null;
+    }
+    LinkBuilder<Element> types = new LinkBuilder<Element>();
+    for (Node argument in node.arguments) {
+      Element type = concreteTypes[argument];
+      if (type == null) return null;
+      types.addLast(type);
+    }
+    return types.toLink();
+  }
+
+  visitSend(Send node) {
+    node.visitChildren(this);
+    Element element = elements[node.selector];
+    if (element == null) return;
+    if (!Elements.isStaticOrTopLevelFunction(element)) return;
+    if (node.argumentsNode == null) {
+      // interest(node, 'closurized method');
+      task.untypedElements.add(element);
+      return;
+    }
+    Link<Element> types = computeConcreteSendArguments(node);
+    if (types != null) {
+      Link<Element> existing = task.typedSends[element];
+      if (existing == null) {
+        task.typedSends[element] = types;
+      } else {
+        // interest(node, 'multiple invocations');
+        Link<Element> lub = computeLubs(existing, types);
+        if (lub == null) {
+          task.untypedElements.add(element);
+        } else {
+          task.typedSends[element] = lub;
+        }
+      }
+    } else {
+      // interest(node, 'dynamically typed invocation');
+      task.untypedElements.add(element);
+    }
+  }
+
+  visitSendSet(SendSet node) {
+    // TODO(ahe): Implement this. For now, overridden to avoid calling
+    // visitSend through super.
+    node.visitChildren(this);
+  }
+
+  void interest(Node node, String note) {
+    var message = MessageKind.GENERIC.message({'text': note});
+    task.compiler.reportWarning(node, message);
+  }
+
+  /**
+   * Computes the pairwise Least Upper Bound (LUB) of the elements of
+   * [a] and [b]. Returns [:null:] if it gives up, or if the lists
+   * aren't the same length.
+   */
+  Link<Element> computeLubs(Link<Element> a, Link<Element> b) {
+    LinkBuilder<Element> lubs = new LinkBuilder<Element>();
+    while (!a.isEmpty && !b.isEmpty) {
+      Element lub = computeLub(a.head, b.head);
+      if (lub == null) return null;
+      lubs.addLast(lub);
+      a = a.tail;
+      b = b.tail;
+    }
+    return (a.isEmpty && b.isEmpty) ? lubs.toLink() : null;
+  }
+
+  /**
+   * Computes the Least Upper Bound (LUB) of [a] and [b]. Returns
+   * [:null:] if it gives up.
+   */
+  Element computeLub(Element a, Element b) {
+    // Fast common case, but also simple initial implementation.
+    if (identical(a, b)) return a;
+
+    // TODO(ahe): Improve the following "computation"...
+    return null;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart
new file mode 100644
index 0000000..23bc782
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart
@@ -0,0 +1,140 @@
+// 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.
+
+part of universe;
+
+// TODO(kasperl): This actually holds getters and setters just fine
+// too and stricly they aren't functions. Maybe this needs a better
+// name -- something like ElementSet seems a bit too generic.
+class FunctionSet extends PartialTypeTree {
+
+  FunctionSet(Compiler compiler) : super(compiler);
+
+  FunctionSetNode newSpecializedNode(ClassElement type)
+      => new FunctionSetNode(type);
+
+  // TODO(kasperl): Allow static members too?
+  void add(Element element) {
+    assert(element.isMember());
+    FunctionSetNode node = findNode(element.getEnclosingClass(), true);
+    node.membersByName[element.name] = element;
+  }
+
+  // TODO(kasperl): Allow static members too?
+  void remove(Element element) {
+    assert(element.isMember());
+    FunctionSetNode node = findNode(element.getEnclosingClass(), false);
+    if (node != null) node.membersByName.remove(element.name);
+  }
+
+  // TODO(kasperl): Allow static members too?
+  bool contains(Element element) {
+    assert(element.isMember());
+    FunctionSetNode node = findNode(element.getEnclosingClass(), false);
+    return (node != null)
+        ? node.membersByName.containsKey(element.name)
+        : false;
+  }
+
+  /**
+   * Returns all elements that may be invoked with the given [selector].
+   */
+  Set<Element> filterBySelector(Selector selector) {
+    // TODO(kasperl): For now, we use a different implementation for
+    // filtering if the tree contains interface subtypes.
+    return containsInterfaceSubtypes
+        ? filterAllBySelector(selector)
+        : filterHierarchyBySelector(selector);
+  }
+
+  /**
+   * Returns whether the set has any element matching the given
+   * [selector].
+   */
+  bool hasAnyElementMatchingSelector(Selector selector) {
+    // TODO(kasperl): For now, we use a different implementation for
+    // filtering if the tree contains interface subtypes.
+    return containsInterfaceSubtypes
+        ? hasAnyInAll(selector)
+        : hasAnyInHierarchy(selector);
+  }
+
+  Set<Element> filterAllBySelector(Selector selector) {
+    Set<Element> result = new Set<Element>();
+    if (root == null) return result;
+    root.visitRecursively((FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      // Since we're running through the entire tree we have to use
+      // the applies method that takes types into account.
+      if (member != null && selector.appliesUnnamed(member, compiler)) {
+        result.add(member);
+      }
+      return true;
+    });
+    return result;
+  }
+
+  Set<Element> filterHierarchyBySelector(Selector selector) {
+    Set<Element> result = new Set<Element>();
+    if (root == null) return result;
+    visitHierarchy(selectorType(selector), (FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      if (member != null && selector.appliesUntyped(member, compiler)) {
+        result.add(member);
+      }
+      return true;
+    });
+    return result;
+  }
+
+  bool hasAnyInAll(Selector selector) {
+    bool result = false;
+    if (root == null) return result;
+    root.visitRecursively((FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      // Since we're running through the entire tree we have to use
+      // the applies method that takes types into account.
+      if (member != null && selector.appliesUnnamed(member, compiler)) {
+        result = true;
+        // End the traversal.
+        return false;
+      }
+      return true;
+    });
+    return result;
+  }
+
+  bool hasAnyInHierarchy(Selector selector) {
+    bool result = false;
+    if (root == null) return result;
+    visitHierarchy(selectorType(selector), (FunctionSetNode node) {
+      Element member = node.membersByName[selector.name];
+      if (member != null && selector.appliesUntyped(member, compiler)) {
+        result = true;
+        // End the traversal.
+        return false;
+      }
+      return true;
+    });
+    return result;
+  }
+
+  void forEach(Function f) {
+    if (root == null) return;
+    root.visitRecursively((FunctionSetNode node) {
+      node.membersByName.forEach(
+          (SourceString _, Element element) => f(element));
+      return true;
+    });
+  }
+}
+
+class FunctionSetNode extends PartialTypeTreeNode {
+
+  final Map<SourceString, Element> membersByName;
+
+  FunctionSetNode(ClassElement type) : super(type),
+      membersByName = new Map<SourceString, Element>();
+
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart
new file mode 100644
index 0000000..f310174
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart
@@ -0,0 +1,190 @@
+// 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.
+
+part of universe;
+
+abstract class PartialTypeTree {
+
+  final Compiler compiler;
+  PartialTypeTreeNode root;
+
+  // TODO(kasperl): This should be final but the VM will not allow
+  // that without making the map a compile-time constant.
+  Map<ClassElement, PartialTypeTreeNode> nodes =
+      new Map<ClassElement, PartialTypeTreeNode>();
+
+  // TODO(kasperl): For now, we keep track of whether or not the tree
+  // contains two classes with a subtype relationship that isn't a
+  // subclass relationship.
+  bool containsInterfaceSubtypes = false;
+
+  // TODO(kasperl): This should be final but the VM will not allow
+  // that without making the set a compile-time constant.
+  Set<ClassElement> unseenInterfaceSubtypes =
+      new Set<ClassElement>();
+
+  PartialTypeTree(this.compiler);
+
+  PartialTypeTreeNode newSpecializedNode(ClassElement type);
+
+  PartialTypeTreeNode newNode(ClassElement type) {
+    PartialTypeTreeNode node = newSpecializedNode(type);
+    nodes[type] = node;
+    if (containsInterfaceSubtypes) return node;
+
+    // Check if the implied interface of the new class is implemented
+    // by another class that is already in the tree.
+    if (unseenInterfaceSubtypes.contains(type)) {
+      containsInterfaceSubtypes = true;
+      unseenInterfaceSubtypes.clear();
+      return node;
+    }
+
+    // Run through all the implied interfaces the class that we're
+    // adding implements and see if any of them are already in the
+    // tree. If so, we have a tree with interface subtypes. If not,
+    // keep track of them so we can deal with it if the interface is
+    // added to the tree later.
+    for (Link link = type.interfaces; !link.isEmpty; link = link.tail) {
+      InterfaceType superType = link.head;
+      ClassElement superTypeElement = superType.element;
+      if (nodes.containsKey(superTypeElement)) {
+        containsInterfaceSubtypes = true;
+        unseenInterfaceSubtypes.clear();
+        break;
+      } else {
+        unseenInterfaceSubtypes.add(superTypeElement);
+      }
+    }
+    return node;
+  }
+
+  // TODO(kasperl): Move this to the Selector class?
+  /**
+   * Returns a [ClassElement] that is an upper bound of the receiver type on
+   * [selector].
+   */
+  ClassElement selectorType(Selector selector) {
+    // TODO(ngeoffray): Should the tree be specialized with DartType?
+    DartType type = selector.receiverType;
+    if (type == null) return compiler.objectClass;
+    // TODO(kasperl): Should [dynamic] return Object?
+    if (identical(type.kind, TypeKind.MALFORMED_TYPE))
+        return compiler.objectClass;
+    // TODO(johnniwinther): Change to use [DartType.unalias].
+    if (type.element.isTypedef()) return compiler.functionClass;
+    return type.element;
+  }
+
+  /**
+   * Finds the tree node corresponding to the given [type]. If [insert]
+   * is true, we always return a node that matches the type by
+   * inserting a new node if necessary. If [insert] is false, we
+   * return null if we cannot find a node that matches the [type].
+   */
+  PartialTypeTreeNode findNode(ClassElement type, bool insert) {
+    if (root == null) {
+      if (!insert) return null;
+      root = newNode(compiler.objectClass);
+    }
+
+    PartialTypeTreeNode current = root;
+    L: while (!identical(current.type, type)) {
+      assert(type.isSubclassOf(current.type));
+
+      // Run through the children. If we find a subtype of the type
+      // we are looking for we go that way. If not, we keep track of
+      // the subtypes so we can move them from being children of the
+      // current node to being children of a new node if we need
+      // to insert that.
+      Link<PartialTypeTreeNode> subtypes = const Link();
+      for (Link link = current.children; !link.isEmpty; link = link.tail) {
+        PartialTypeTreeNode child = link.head;
+        ClassElement childType = child.type;
+        if (type.isSubclassOf(childType)) {
+          assert(subtypes.isEmpty);
+          current = child;
+          continue L;
+        } else if (childType.isSubclassOf(type)) {
+          if (insert) subtypes = subtypes.prepend(child);
+        }
+      }
+
+      // If we are not inserting any nodes, we are done.
+      if (!insert) return null;
+
+      // Create a new node and move the children of the current node
+      // that are subtypes of the type of the new node below the new
+      // node in the hierarchy.
+      PartialTypeTreeNode node = newNode(type);
+      if (!subtypes.isEmpty) {
+        node.children = subtypes;
+        Link<PartialTypeTreeNode> remaining = const Link();
+        for (Link link = current.children; !link.isEmpty; link = link.tail) {
+          PartialTypeTreeNode child = link.head;
+          if (!child.type.isSubclassOf(type)) {
+            remaining = remaining.prepend(child);
+          }
+        }
+        current.children = remaining;
+      }
+
+      // Add the new node as a child node of the current node and return it.
+      current.children = current.children.prepend(node);
+      return node;
+    }
+
+    // We found an exact match. No need to insert new nodes.
+    assert(identical(current.type, type));
+    return current;
+  }
+
+  /**
+   * Visits all superclass and subclass nodes for the given [type]. If
+   * the [visit] function ever returns false, we abort the traversal.
+   */
+  void visitHierarchy(ClassElement type, bool visit(PartialTypeTreeNode node)) {
+    assert(!containsInterfaceSubtypes);
+    PartialTypeTreeNode current = root;
+    L: while (!identical(current.type, type)) {
+      assert(type.isSubclassOf(current.type));
+      if (!visit(current)) return;
+      for (Link link = current.children; !link.isEmpty; link = link.tail) {
+        PartialTypeTreeNode child = link.head;
+        ClassElement childType = child.type;
+        if (type.isSubclassOf(childType)) {
+          current = child;
+          continue L;
+        } else if (childType.isSubclassOf(type)) {
+          if (!child.visitRecursively(visit)) return;
+        }
+      }
+      return;
+    }
+    current.visitRecursively(visit);
+  }
+
+}
+
+class PartialTypeTreeNode {
+
+  final ClassElement type;
+  Link<PartialTypeTreeNode> children;
+
+  PartialTypeTreeNode(this.type) : children = const Link();
+
+  /**
+   * Visits this node and its children recursively. If the visit
+   * callback ever returns false, the visiting stops early.
+   */
+  bool visitRecursively(bool visit(PartialTypeTreeNode node)) {
+    if (!visit(this)) return false;
+    for (Link link = children; !link.isEmpty; link = link.tail) {
+      PartialTypeTreeNode child = link.head;
+      if (!child.visitRecursively(visit)) return false;
+    }
+    return true;
+  }
+
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart
new file mode 100644
index 0000000..cf98a10
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart
@@ -0,0 +1,132 @@
+// 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.
+
+part of universe;
+
+class SelectorMap<T> extends PartialTypeTree {
+
+  SelectorMap(Compiler compiler) : super(compiler);
+
+  SelectorMapNode<T> newSpecializedNode(ClassElement type)
+      => new SelectorMapNode<T>(type);
+
+  T operator [](Selector selector) {
+    SelectorMapNode<T> node = findNode(selectorType(selector), false);
+    if (node == null) return null;
+    Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name];
+    if (selectors == null) return null;
+    for (Link link = selectors; !link.isEmpty; link = link.tail) {
+      SelectorValue<T> existing = link.head;
+      if (existing.selector.equalsUntyped(selector)) return existing.value;
+    }
+    return null;
+  }
+
+  void operator []=(Selector selector, T value) {
+    SelectorMapNode<T> node = findNode(selectorType(selector), true);
+    Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name];
+    if (selectors == null) {
+      // No existing selectors with the given name. Create a new
+      // linked list.
+      SelectorValue<T> head = new SelectorValue<T>(selector, value);
+      node.selectorsByName[selector.name] =
+          new Link<SelectorValue<T>>().prepend(head);
+    } else {
+      // Run through the linked list of selectors with the same name. If
+      // we find one that matches, we update the value in the mapping.
+      for (Link link = selectors; !link.isEmpty; link = link.tail) {
+        SelectorValue<T> existing = link.head;
+        // It is safe to ignore the type here, because all selector
+        // mappings that are stored in a single node have the same type.
+        if (existing.selector.equalsUntyped(selector)) {
+          existing.value = value;
+          return;
+        }
+      }
+      // We could not find an existing mapping for the selector, so
+      // we add a new one to the existing linked list.
+      SelectorValue<T> head = new SelectorValue<T>(selector, value);
+      node.selectorsByName[selector.name] = selectors.prepend(head);
+    }
+  }
+
+  // TODO(kasperl): Share code with the [] operator?
+  bool containsKey(Selector selector) {
+    SelectorMapNode<T> node = findNode(selectorType(selector), false);
+    if (node == null) return false;
+    Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name];
+    if (selectors == null) return false;
+    for (Link link = selectors; !link.isEmpty; link = link.tail) {
+      SelectorValue<T> existing = link.head;
+      if (existing.selector.equalsUntyped(selector)) return true;
+    }
+    return false;
+  }
+
+  /**
+   * Visits all mappings for selectors that may be used to invoke the
+   * given [member] element. If the [visit] function ever returns false,
+   * we abort the traversal early.
+   */
+  void visitMatching(Element member, bool visit(Selector selector, T value)) {
+    assert(member.isMember());
+    if (root == null) return;
+    // TODO(kasperl): For now, we use a different implementation for
+    // visiting if the tree contains interface subtypes.
+    if (containsInterfaceSubtypes) {
+      visitAllMatching(member, visit);
+    } else {
+      visitHierarchyMatching(member, visit);
+    }
+  }
+
+  void visitAllMatching(Element member, bool visit(selector, value)) {
+    root.visitRecursively((SelectorMapNode<T> node) {
+      Link<SelectorValue<T>> selectors = node.selectorsByName[member.name];
+      if (selectors == null) return true;
+      for (Link link = selectors; !link.isEmpty; link = link.tail) {
+        SelectorValue<T> existing = link.head;
+        Selector selector = existing.selector;
+        // Since we're running through the entire tree we have to use
+        // the applies method that takes types into account.
+        if (selector.appliesUnnamed(member, compiler)) {
+          if (!visit(selector, existing.value)) return false;
+        }
+      }
+      return true;
+    });
+  }
+
+  void visitHierarchyMatching(Element member, bool visit(selector, value)) {
+    visitHierarchy(member.getEnclosingClass(), (SelectorMapNode<T> node) {
+      Link<SelectorValue<T>> selectors = node.selectorsByName[member.name];
+      if (selectors == null) return true;
+      for (Link link = selectors; !link.isEmpty; link = link.tail) {
+        SelectorValue<T> existing = link.head;
+        Selector selector = existing.selector;
+        if (selector.appliesUntyped(member, compiler)) {
+          if (!visit(selector, existing.value)) return false;
+        }
+      }
+      return true;
+    });
+  }
+
+}
+
+class SelectorMapNode<T> extends PartialTypeTreeNode {
+
+  final Map<SourceString, Link<SelectorValue<T>>> selectorsByName;
+
+  SelectorMapNode(ClassElement type) : super(type),
+      selectorsByName = new Map<SourceString, Link<SelectorValue<T>>>();
+
+}
+
+class SelectorValue<T> {
+  final Selector selector;
+  T value;
+  SelectorValue(this.selector, this.value);
+  toString() => "$selector -> $value";
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart
new file mode 100644
index 0000000..6b64382
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart
@@ -0,0 +1,495 @@
+// 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.
+
+library universe;
+
+import '../closure.dart';
+import '../elements/elements.dart';
+import '../dart2jslib.dart';
+import '../dart_types.dart';
+import '../tree/tree.dart';
+import '../util/util.dart';
+import '../js/js.dart' as js;
+
+part 'function_set.dart';
+part 'partial_type_tree.dart';
+part 'selector_map.dart';
+
+class Universe {
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements are declaration elements.
+   */
+  // TODO(karlklose): these sets should be merged.
+  final Set<ClassElement> instantiatedClasses;
+  final Set<DartType> instantiatedTypes;
+
+  /**
+   * Documentation wanted -- johnniwinther
+   *
+   * Invariant: Elements are declaration elements.
+   */
+  final Set<FunctionElement> staticFunctionsNeedingGetter;
+  final Map<SourceString, Set<Selector>> invokedNames;
+  final Map<SourceString, Set<Selector>> invokedGetters;
+  final Map<SourceString, Set<Selector>> invokedSetters;
+
+  /**
+   * Fields accessed. Currently only the codegen knows this
+   * information. The resolver is too conservative when seeing a
+   * getter and only registers an invoked getter.
+   */
+  final Map<SourceString, Set<Selector>> fieldGetters;
+
+  /**
+   * Fields set. See comment in [fieldGetters].
+   */
+  final Map<SourceString, Set<Selector>> fieldSetters;
+  final Set<DartType> isChecks;
+
+  Universe() : instantiatedClasses = new Set<ClassElement>(),
+               instantiatedTypes = new Set<DartType>(),
+               staticFunctionsNeedingGetter = new Set<FunctionElement>(),
+               invokedNames = new Map<SourceString, Set<Selector>>(),
+               invokedGetters = new Map<SourceString, Set<Selector>>(),
+               fieldGetters = new Map<SourceString, Set<Selector>>(),
+               fieldSetters = new Map<SourceString, Set<Selector>>(),
+               invokedSetters = new Map<SourceString, Set<Selector>>(),
+               isChecks = new Set<DartType>();
+
+  bool hasMatchingSelector(Set<Selector> selectors,
+                           Element member,
+                           Compiler compiler) {
+    if (selectors == null) return false;
+    for (Selector selector in selectors) {
+      if (selector.appliesUnnamed(member, compiler)) return true;
+    }
+    return false;
+  }
+
+  bool hasInvocation(Element member, Compiler compiler) {
+    return hasMatchingSelector(invokedNames[member.name], member, compiler);
+  }
+
+  bool hasInvokedGetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(invokedGetters[member.name], member, compiler);
+  }
+
+  bool hasInvokedSetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(invokedSetters[member.name], member, compiler);
+  }
+
+  bool hasFieldGetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(fieldGetters[member.name], member, compiler);
+  }
+
+  bool hasFieldSetter(Element member, Compiler compiler) {
+    return hasMatchingSelector(fieldSetters[member.name], member, compiler);
+  }
+}
+
+class SelectorKind {
+  final String name;
+  const SelectorKind(this.name);
+
+  static const SelectorKind GETTER = const SelectorKind('getter');
+  static const SelectorKind SETTER = const SelectorKind('setter');
+  static const SelectorKind CALL = const SelectorKind('call');
+  static const SelectorKind OPERATOR = const SelectorKind('operator');
+  static const SelectorKind INDEX = const SelectorKind('index');
+
+  toString() => name;
+}
+
+class Selector {
+  final SelectorKind kind;
+  final SourceString name;
+  final LibraryElement library; // Library is null for non-private selectors.
+
+  // The numbers of arguments of the selector. Includes named arguments.
+  final int argumentCount;
+  final List<SourceString> namedArguments;
+  final List<SourceString> orderedNamedArguments;
+
+  Selector(
+      this.kind,
+      SourceString name,
+      LibraryElement library,
+      this.argumentCount,
+      [List<SourceString> namedArguments = const <SourceString>[]])
+    : this.name = name,
+      this.library = name.isPrivate() ? library : null,
+      this.namedArguments = namedArguments,
+      this.orderedNamedArguments = namedArguments.isEmpty
+          ? namedArguments
+          : <SourceString>[] {
+    assert(!name.isPrivate() || library != null);
+  }
+
+  Selector.getter(SourceString name, LibraryElement library)
+      : this(SelectorKind.GETTER, name, library, 0);
+
+  Selector.getterFrom(Selector selector)
+      : this(SelectorKind.GETTER, selector.name, selector.library, 0);
+
+  Selector.setter(SourceString name, LibraryElement library)
+      : this(SelectorKind.SETTER, name, library, 1);
+
+  Selector.unaryOperator(SourceString name)
+      : this(SelectorKind.OPERATOR,
+             Elements.constructOperatorName(name, true),
+             null, 0);
+
+  Selector.binaryOperator(SourceString name)
+      : this(SelectorKind.OPERATOR,
+             Elements.constructOperatorName(name, false),
+             null, 1);
+
+  Selector.index()
+      : this(SelectorKind.INDEX,
+             Elements.constructOperatorName(const SourceString("[]"), false),
+             null, 1);
+
+  Selector.indexSet()
+      : this(SelectorKind.INDEX,
+             Elements.constructOperatorName(const SourceString("[]="), false),
+             null, 2);
+
+  Selector.call(SourceString name,
+                LibraryElement library,
+                int arity,
+                [List<SourceString> named = const []])
+      : this(SelectorKind.CALL, name, library, arity, named);
+
+  Selector.callClosure(int arity, [List<SourceString> named = const []])
+      : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null,
+             arity, named);
+
+  Selector.callClosureFrom(Selector selector)
+      : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null,
+             selector.argumentCount, selector.namedArguments);
+
+  Selector.callConstructor(SourceString constructorName,
+                           LibraryElement library)
+      : this(SelectorKind.CALL,
+             constructorName,
+             library,
+             0,
+             const []);
+
+  Selector.callDefaultConstructor(LibraryElement library)
+      : this(SelectorKind.CALL, const SourceString(""), library, 0, const []);
+
+  // TODO(kasperl): This belongs somewhere else.
+  Selector.noSuchMethod()
+      : this(SelectorKind.CALL, Compiler.NO_SUCH_METHOD, null,
+             Compiler.NO_SUCH_METHOD_ARG_COUNT);
+
+  bool isGetter() => identical(kind, SelectorKind.GETTER);
+  bool isSetter() => identical(kind, SelectorKind.SETTER);
+  bool isCall() => identical(kind, SelectorKind.CALL);
+  bool isClosureCall() {
+    SourceString callName = Compiler.CALL_OPERATOR_NAME;
+    return isCall() && name == callName;
+  }
+
+  bool isIndex() => identical(kind, SelectorKind.INDEX) && argumentCount == 1;
+  bool isIndexSet() => identical(kind, SelectorKind.INDEX) && argumentCount == 2;
+
+  bool isOperator() => identical(kind, SelectorKind.OPERATOR);
+  bool isUnaryOperator() => isOperator() && argumentCount == 0;
+  bool isBinaryOperator() => isOperator() && argumentCount == 1;
+
+  /** Check whether this is a call to 'assert'. */
+  bool isAssert() => isCall() && identical(name.stringValue, "assert");
+
+  int get hashCode => argumentCount + 1000 * namedArguments.length;
+  int get namedArgumentCount => namedArguments.length;
+  int get positionalArgumentCount => argumentCount - namedArgumentCount;
+  DartType get receiverType => null;
+
+  Selector get asUntyped => this;
+
+  /**
+   * The member name for invocation mirrors created from this selector.
+   */
+  String get invocationMirrorMemberName =>
+      isSetter() ? '${name.slowToString()}=' : name.slowToString();
+
+  int get invocationMirrorKind {
+    const int METHOD = 0;
+    const int GETTER = 1;
+    const int SETTER = 2;
+    int kind = METHOD;
+    if (isGetter()) {
+      kind = GETTER;
+    } else if (isSetter()) {
+      kind = SETTER;
+    }
+    return kind;
+  }
+
+  bool appliesUnnamed(Element element, Compiler compiler) {
+    assert(sameNameHack(element, compiler));
+    return appliesUntyped(element, compiler);
+  }
+
+  bool appliesUntyped(Element element, Compiler compiler) {
+    assert(sameNameHack(element, compiler));
+    if (Elements.isUnresolved(element)) return false;
+    if (name.isPrivate() && library != element.getLibrary()) return false;
+    if (element.isForeign(compiler)) return true;
+    if (element.isSetter()) return isSetter();
+    if (element.isGetter()) return isGetter() || isCall();
+    if (element.isField()) return isGetter() || isSetter() || isCall();
+    if (isGetter()) return true;
+    if (isSetter()) return false;
+
+    FunctionElement function = element;
+    FunctionSignature parameters = function.computeSignature(compiler);
+    if (argumentCount > parameters.parameterCount) return false;
+    int requiredParameterCount = parameters.requiredParameterCount;
+    int optionalParameterCount = parameters.optionalParameterCount;
+    if (positionalArgumentCount < requiredParameterCount) return false;
+
+    if (!parameters.optionalParametersAreNamed) {
+      // We have already checked that the number of arguments are
+      // not greater than the number of parameters. Therefore the
+      // number of positional arguments are not greater than the
+      // number of parameters.
+      assert(positionalArgumentCount <= parameters.parameterCount);
+      return namedArguments.isEmpty;
+    } else {
+      if (positionalArgumentCount > requiredParameterCount) return false;
+      assert(positionalArgumentCount == requiredParameterCount);
+      if (namedArgumentCount > optionalParameterCount) return false;
+      Set<SourceString> nameSet = new Set<SourceString>();
+      parameters.optionalParameters.forEach((Element element) {
+        nameSet.add(element.name);
+      });
+      for (SourceString name in namedArguments) {
+        if (!nameSet.contains(name)) return false;
+        // TODO(5213): By removing from the set we are checking
+        // that we are not passing the name twice. We should have this
+        // check in the resolver also.
+        nameSet.remove(name);
+      }
+      return true;
+    }
+  }
+
+  bool sameNameHack(Element element, Compiler compiler) {
+    // TODO(ngeoffray): Remove workaround checks.
+    return element == compiler.assertMethod
+        || element.isConstructor()
+        || name == element.name;
+  }
+
+  bool applies(Element element, Compiler compiler) {
+    if (!sameNameHack(element, compiler)) return false;
+    return appliesUnnamed(element, compiler);
+  }
+
+  /**
+   * Fills [list] with the arguments in a defined order.
+   *
+   * [compileArgument] is a function that returns a compiled version
+   * of an argument located in [arguments].
+   *
+   * [compileConstant] is a function that returns a compiled constant
+   * of an optional argument that is not in [arguments.
+   *
+   * Returns [:true:] if the selector and the [element] match; [:false:]
+   * otherwise.
+   *
+   * Invariant: [element] must be the implementation element.
+   */
+  bool addArgumentsToList(Link<Node> arguments,
+                          List list,
+                          FunctionElement element,
+                          compileArgument(Node argument),
+                          compileConstant(Element element),
+                          Compiler compiler) {
+    assert(invariant(element, element.isImplementation));
+    if (!this.applies(element, compiler)) return false;
+
+    FunctionSignature parameters = element.computeSignature(compiler);
+    parameters.forEachRequiredParameter((element) {
+      list.add(compileArgument(arguments.head));
+      arguments = arguments.tail;
+    });
+
+    if (!parameters.optionalParametersAreNamed) {
+      parameters.forEachOptionalParameter((element) {
+        if (!arguments.isEmpty) {
+          list.add(compileArgument(arguments.head));
+          arguments = arguments.tail;
+        } else {
+          list.add(compileConstant(element));
+        }
+      });
+    } else {
+      // Visit named arguments and add them into a temporary list.
+      List compiledNamedArguments = [];
+      for (; !arguments.isEmpty; arguments = arguments.tail) {
+        NamedArgument namedArgument = arguments.head;
+        compiledNamedArguments.add(compileArgument(namedArgument.expression));
+      }
+      // Iterate over the optional parameters of the signature, and try to
+      // find them in [compiledNamedArguments]. If found, we use the
+      // value in the temporary list, otherwise the default value.
+      parameters.orderedOptionalParameters.forEach((element) {
+        int foundIndex = namedArguments.indexOf(element.name);
+        if (foundIndex != -1) {
+          list.add(compiledNamedArguments[foundIndex]);
+        } else {
+          list.add(compileConstant(element));
+        }
+      });
+    }
+    return true;
+  }
+
+  static bool sameNames(List<SourceString> first, List<SourceString> second) {
+    for (int i = 0; i < first.length; i++) {
+      if (first[i] != second[i]) return false;
+    }
+    return true;
+  }
+
+  bool operator ==(other) {
+    if (other is !Selector) return false;
+    return identical(receiverType, other.receiverType)
+        && equalsUntyped(other);
+  }
+
+  bool equalsUntyped(Selector other) {
+    return name == other.name
+           && kind == other.kind
+           && identical(library, other.library)
+           && argumentCount == other.argumentCount
+           && namedArguments.length == other.namedArguments.length
+           && sameNames(namedArguments, other.namedArguments);
+  }
+
+  List<SourceString> getOrderedNamedArguments() {
+    if (namedArguments.isEmpty) return namedArguments;
+    if (!orderedNamedArguments.isEmpty) return orderedNamedArguments;
+
+    orderedNamedArguments.addAll(namedArguments);
+    orderedNamedArguments.sort((SourceString first, SourceString second) {
+      return first.slowToString().compareTo(second.slowToString());
+    });
+    return orderedNamedArguments;
+  }
+
+  String namedArgumentsToString() {
+    if (namedArgumentCount > 0) {
+      StringBuffer result = new StringBuffer();
+      for (int i = 0; i < namedArgumentCount; i++) {
+        if (i != 0) result.add(', ');
+        result.add(namedArguments[i].slowToString());
+      }
+      return "[$result]";
+    }
+    return '';
+  }
+
+  String toString() {
+    String named = '';
+    String type = '';
+    if (namedArgumentCount > 0) named = ', named=${namedArgumentsToString()}';
+    if (receiverType != null) type = ', type=$receiverType';
+    return 'Selector($kind, ${name.slowToString()}, '
+           'arity=$argumentCount$named$type)';
+  }
+}
+
+class TypedSelector extends Selector {
+  /**
+   * The type of the receiver. Any subtype of that type can be the
+   * target of the invocation.
+   */
+  final DartType receiverType;
+
+  final Selector asUntyped;
+
+  TypedSelector(DartType this.receiverType, Selector selector)
+      : asUntyped = selector.asUntyped,
+        super(selector.kind,
+              selector.name,
+              selector.library,
+              selector.argumentCount,
+              selector.namedArguments) {
+    // Invariant: Typed selector can not be based on a malformed type.
+    assert(!identical(receiverType.kind, TypeKind.MALFORMED_TYPE));
+    assert(asUntyped.receiverType == null);
+  }
+
+  /**
+   * Check if [element] will be the one used at runtime when being
+   * invoked on an instance of [cls].
+   */
+  bool hasElementIn(ClassElement cls, Element element) {
+    // Use the selector for the lookup instead of [:element.name:]
+    // because the selector has the right privacy information.
+    Element resolved = cls.lookupSelector(this);
+    if (resolved == element) return true;
+    if (resolved == null) return false;
+    if (resolved.isAbstractField()) {
+      AbstractFieldElement field = resolved;
+      if (element == field.getter || element == field.setter) {
+        return true;
+      } else {
+        ClassElement otherCls = field.getEnclosingClass();
+        // We have not found a match, but another class higher in the
+        // hierarchy may define the getter or the setter.
+        return hasElementIn(otherCls.superclass, element);
+      }
+    }
+    return false;
+  }
+
+  bool appliesUnnamed(Element element, Compiler compiler) {
+    assert(sameNameHack(element, compiler));
+    // [TypedSelector] are only used when compiling.
+    assert(compiler.phase == Compiler.PHASE_COMPILING);
+    if (!element.isMember()) return false;
+
+    // A closure can be called through any typed selector:
+    // class A {
+    //   get foo => () => 42;
+    //   bar() => foo(); // The call to 'foo' is a typed selector.
+    // }
+    ClassElement other = element.getEnclosingClass();
+    if (identical(other.superclass, compiler.closureClass)) {
+      return appliesUntyped(element, compiler);
+    }
+
+    Element self = receiverType.element;
+    if (self.isTypedef()) {
+      // A typedef is a function type that doesn't have any
+      // user-defined members.
+      return false;
+    }
+
+    if (other.implementsInterface(self)
+        || other.isSubclassOf(self)
+        || compiler.world.hasAnySubclassThatImplements(other, receiverType)) {
+      return appliesUntyped(element, compiler);
+    }
+
+    // If [self] is a subclass of [other], it inherits the
+    // implementation of [element].
+    ClassElement cls = self;
+    if (cls.isSubclassOf(other)) {
+      // Resolve an invocation of [element.name] on [self]. If it
+      // is found, this selector is a candidate.
+      return hasElementIn(self, element) && appliesUntyped(element, compiler);
+    }
+
+    return false;
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart
new file mode 100644
index 0000000..5961d07
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2011, 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.
+
+library characters;
+
+const int $EOF = 0;
+const int $STX = 2;
+const int $BS  = 8;
+const int $TAB = 9;
+const int $LF = 10;
+const int $VTAB = 11;
+const int $FF = 12;
+const int $CR = 13;
+const int $SPACE = 32;
+const int $BANG = 33;
+const int $DQ = 34;
+const int $HASH = 35;
+const int $$ = 36;
+const int $PERCENT = 37;
+const int $AMPERSAND = 38;
+const int $SQ = 39;
+const int $OPEN_PAREN = 40;
+const int $CLOSE_PAREN = 41;
+const int $STAR = 42;
+const int $PLUS = 43;
+const int $COMMA = 44;
+const int $MINUS = 45;
+const int $PERIOD = 46;
+const int $SLASH = 47;
+const int $0 = 48;
+const int $1 = 49;
+const int $2 = 50;
+const int $3 = 51;
+const int $4 = 52;
+const int $5 = 53;
+const int $6 = 54;
+const int $7 = 55;
+const int $8 = 56;
+const int $9 = 57;
+const int $COLON = 58;
+const int $SEMICOLON = 59;
+const int $LT = 60;
+const int $EQ = 61;
+const int $GT = 62;
+const int $QUESTION = 63;
+const int $AT = 64;
+const int $A = 65;
+const int $B = 66;
+const int $C = 67;
+const int $D = 68;
+const int $E = 69;
+const int $F = 70;
+const int $G = 71;
+const int $H = 72;
+const int $I = 73;
+const int $J = 74;
+const int $K = 75;
+const int $L = 76;
+const int $M = 77;
+const int $N = 78;
+const int $O = 79;
+const int $P = 80;
+const int $Q = 81;
+const int $R = 82;
+const int $S = 83;
+const int $T = 84;
+const int $U = 85;
+const int $V = 86;
+const int $W = 87;
+const int $X = 88;
+const int $Y = 89;
+const int $Z = 90;
+const int $OPEN_SQUARE_BRACKET = 91;
+const int $BACKSLASH = 92;
+const int $CLOSE_SQUARE_BRACKET = 93;
+const int $CARET = 94;
+const int $_ = 95;
+const int $BACKPING = 96;
+const int $a = 97;
+const int $b = 98;
+const int $c = 99;
+const int $d = 100;
+const int $e = 101;
+const int $f = 102;
+const int $g = 103;
+const int $h = 104;
+const int $i = 105;
+const int $j = 106;
+const int $k = 107;
+const int $l = 108;
+const int $m = 109;
+const int $n = 110;
+const int $o = 111;
+const int $p = 112;
+const int $q = 113;
+const int $r = 114;
+const int $s = 115;
+const int $t = 116;
+const int $u = 117;
+const int $v = 118;
+const int $w = 119;
+const int $x = 120;
+const int $y = 121;
+const int $z = 122;
+const int $OPEN_CURLY_BRACKET = 123;
+const int $BAR = 124;
+const int $CLOSE_CURLY_BRACKET = 125;
+const int $TILDE = 126;
+const int $DEL = 127;
+const int $NBSP = 160;
+const int $LS = 0x2028;
+const int $PS = 0x2029;
+
+const int $FIRST_SURROGATE = 0xd800;
+const int $LAST_SURROGATE = 0xdfff;
+const int $LAST_CODE_POINT = 0x10ffff;
+
+bool isHexDigit(int characterCode) {
+  if (characterCode <= $9) return $0 <= characterCode;
+  characterCode |= $a ^ $A;
+  return ($a <= characterCode && characterCode <= $f);
+}
+
+int hexDigitValue(int hexDigit) {
+  assert(isHexDigit(hexDigit));
+  // hexDigit is one of '0'..'9', 'A'..'F' and 'a'..'f'.
+  if (hexDigit <= $9) return hexDigit - $0;
+  return (hexDigit | ($a ^ $A)) - ($a - 10);
+}
+
+bool isUnicodeScalarValue(int value) {
+  return value < $FIRST_SURROGATE ||
+      (value > $LAST_SURROGATE && value <= $LAST_CODE_POINT);
+}
+
+bool isUtf16LeadSurrogate(int value) {
+  return value >= 0xd800 && value <= 0xdbff;
+}
+
+bool isUtf16TrailSurrogate(int value) {
+  return value >= 0xdc00 && value <= 0xdfff;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart
new file mode 100644
index 0000000..9cc81cc
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2011, 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.
+
+part of org_dartlang_compiler_util;
+
+class Link<T> extends Iterable<T> {
+  T get head => null;
+  Link<T> get tail => null;
+
+  factory Link.fromList(List<T> list) {
+    switch (list.length) {
+      case 0:
+        return new Link<T>();
+      case 1:
+        return new LinkEntry<T>(list[0]);
+      case 2:
+        return new LinkEntry<T>(list[0], new LinkEntry<T>(list[1]));
+      case 3:
+        return new LinkEntry<T>(
+            list[0], new LinkEntry<T>(list[1], new LinkEntry<T>(list[2])));
+    }
+    Link link = new Link<T>();
+    for (int i = list.length ; i > 0; i--) {
+      link = link.prepend(list[i - 1]);
+    }
+    return link;
+  }
+
+  const Link();
+
+  Link<T> prepend(T element) {
+    return new LinkEntry<T>(element, this);
+  }
+
+  Iterator<T> get iterator => new LinkIterator<T>(this);
+
+  void printOn(StringBuffer buffer, [separatedBy]) {
+  }
+
+  List toList() => new List<T>.fixedLength(0);
+
+  bool get isEmpty => true;
+
+  Link<T> reverse() => this;
+
+  Link<T> reversePrependAll(Link<T> from) {
+    if (from.isEmpty) return this;
+    return this.prepend(from.head).reversePrependAll(from.tail);
+  }
+
+  Link<T> skip(int n) {
+    if (n == 0) return this;
+    throw new RangeError('Index $n out of range');
+  }
+
+  void forEach(void f(T element)) {}
+
+  bool operator ==(other) {
+    if (other is !Link<T>) return false;
+    return other.isEmpty;
+  }
+
+  String toString() => "[]";
+
+  get length {
+    throw new UnsupportedError('get:length');
+  }
+
+  int slowLength() => 0;
+}
+
+abstract class LinkBuilder<T> {
+  factory LinkBuilder() = LinkBuilderImplementation;
+
+  Link<T> toLink();
+  void addLast(T t);
+
+  final int length;
+  final bool isEmpty;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart
new file mode 100644
index 0000000..d00ec86
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2011, 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.
+
+part of util_implementation;
+
+class LinkIterator<T> implements Iterator<T> {
+  T _current;
+  Link<T> _link;
+
+  LinkIterator(Link<T> this._link);
+
+  T get current => _current;
+
+  bool moveNext() {
+    if (_link.isEmpty) {
+      _current = null;
+      return false;
+    }
+    _current = _link.head;
+    _link = _link.tail;
+    return true;
+  }
+}
+
+class LinkEntry<T> extends Link<T> {
+  final T head;
+  Link<T> tail;
+
+  LinkEntry(T this.head, [Link<T> tail])
+    : this.tail = ((tail == null) ? new Link<T>() : tail);
+
+  Link<T> prepend(T element) {
+    // TODO(ahe): Use new Link<T>, but this cost 8% performance on VM.
+    return new LinkEntry<T>(element, this);
+  }
+
+  void printOn(StringBuffer buffer, [separatedBy]) {
+    buffer.add(head);
+    if (separatedBy == null) separatedBy = '';
+    for (Link link = tail; !link.isEmpty; link = link.tail) {
+      buffer.add(separatedBy);
+      buffer.add(link.head);
+    }
+  }
+
+  String toString() {
+    StringBuffer buffer = new StringBuffer();
+    buffer.add('[ ');
+    printOn(buffer, ', ');
+    buffer.add(' ]');
+    return buffer.toString();
+  }
+
+  Link<T> reverse() {
+    Link<T> result = const Link();
+    for (Link<T> link = this; !link.isEmpty; link = link.tail) {
+      result = result.prepend(link.head);
+    }
+    return result;
+  }
+
+  Link<T> reversePrependAll(Link<T> from) {
+    Link<T> result;
+    for (result = this; !from.isEmpty; from = from.tail) {
+      result = result.prepend(from.head);
+    }
+    return result;
+  }
+
+  Link<T> skip(int n) {
+    Link<T> link = this;
+    for (int i = 0 ; i < n ; i++) {
+      if (link.isEmpty) {
+        throw new RangeError('Index $n out of range');
+      }
+      link = link.tail;
+    }
+    return link;
+  }
+
+  bool get isEmpty => false;
+
+  List<T> toList() {
+    List<T> list = new List<T>();
+    for (Link<T> link = this; !link.isEmpty; link = link.tail) {
+      list.addLast(link.head);
+    }
+    return list;
+  }
+
+  void forEach(void f(T element)) {
+    for (Link<T> link = this; !link.isEmpty; link = link.tail) {
+      f(link.head);
+    }
+  }
+
+  bool operator ==(other) {
+    if (other is !Link<T>) return false;
+    Link<T> myElements = this;
+    while (!myElements.isEmpty && !other.isEmpty) {
+      if (myElements.head != other.head) {
+        return false;
+      }
+      myElements = myElements.tail;
+      other = other.tail;
+    }
+    return myElements.isEmpty && other.isEmpty;
+  }
+
+  int slowLength() => 1 + tail.slowLength();
+}
+
+class LinkBuilderImplementation<T> implements LinkBuilder<T> {
+  LinkEntry<T> head = null;
+  LinkEntry<T> lastLink = null;
+  int length = 0;
+
+  LinkBuilderImplementation();
+
+  Link<T> toLink() {
+    if (head == null) return const Link();
+    lastLink.tail = const Link();
+    Link<T> link = head;
+    lastLink = null;
+    head = null;
+    return link;
+  }
+
+  void addLast(T t) {
+    length++;
+    LinkEntry<T> entry = new LinkEntry<T>(t, null);
+    if (head == null) {
+      head = entry;
+    } else {
+      lastLink.tail = entry;
+    }
+    lastLink = entry;
+  }
+
+  bool get isEmpty => length == 0;
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart
new file mode 100644
index 0000000..fed5f15
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart
@@ -0,0 +1,65 @@
+// 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.
+
+library uri_extras;
+
+import 'dart:math';
+import 'dart:uri';
+
+String relativize(Uri base, Uri uri, bool isWindows) {
+  if (!base.path.startsWith('/')) {
+    // Also throw an exception if [base] or base.path is null.
+    throw new ArgumentError('Expected absolute path: ${base.path}');
+  }
+  if (!uri.path.startsWith('/')) {
+    // Also throw an exception if [uri] or uri.path is null.
+    throw new ArgumentError('Expected absolute path: ${uri.path}');
+  }
+  bool equalsNCS(String a, String b) {
+    return a.toLowerCase() == b.toLowerCase();
+  }
+
+  String normalize(String path) {
+    if (isWindows) {
+      return path.toLowerCase();
+    } else {
+      return path;
+    }
+  }
+
+  if (equalsNCS(base.scheme, 'file') &&
+      equalsNCS(base.scheme, uri.scheme) &&
+      base.userInfo == uri.userInfo &&
+      equalsNCS(base.domain, uri.domain) &&
+      base.port == uri.port &&
+      uri.query == "" && uri.fragment == "") {
+    if (normalize(uri.path).startsWith(normalize(base.path))) {
+      return uri.path.substring(base.path.length);
+    }
+    List<String> uriParts = uri.path.split('/');
+    List<String> baseParts = base.path.split('/');
+    int common = 0;
+    int length = min(uriParts.length, baseParts.length);
+    while (common < length &&
+           normalize(uriParts[common]) == normalize(baseParts[common])) {
+      common++;
+    }
+    if (common == 1 || (isWindows && common == 2)) {
+      // The first part will always be an empty string because the
+      // paths are absolute. On Windows, we must also consider drive
+      // letters or hostnames.
+      return uri.path;
+    }
+    StringBuffer sb = new StringBuffer();
+    for (int i = common + 1; i < baseParts.length; i++) {
+      sb.add('../');
+    }
+    for (int i = common; i < uriParts.length - 1; i++) {
+      sb.add('${uriParts[i]}/');
+    }
+    sb.add('${uriParts.last}');
+    return sb.toString();
+  }
+  return uri.toString();
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart
new file mode 100644
index 0000000..8a07687
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2011, 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.
+
+library org_dartlang_compiler_util;
+
+import 'util_implementation.dart';
+import 'characters.dart';
+
+part 'link.dart';
+
+/**
+ * Tagging interface for classes from which source spans can be generated.
+ */
+// TODO(johnniwinther): Find a better name.
+// TODO(ahe): How about "Bolt"?
+abstract class Spannable {}
+
+class _SpannableSentinel implements Spannable {
+  final String name;
+
+  const _SpannableSentinel(this.name);
+
+  String toString() => name;
+}
+
+const Spannable CURRENT_ELEMENT_SPANNABLE =
+    const _SpannableSentinel("Current element");
+
+class SpannableAssertionFailure {
+  final Spannable node;
+  final String message;
+  SpannableAssertionFailure(this.node, this.message);
+
+  String toString() => 'Compiler crashed: $message.';
+}
+
+/// Writes the characters of [string] on [buffer].  The characters
+/// are escaped as suitable for JavaScript and JSON.  [buffer] is
+/// anything which supports [:add:] and [:addCharCode:], for example,
+/// [StringBuffer].  Note that JS supports \xnn and \unnnn whereas JSON only
+/// supports the \unnnn notation.  Therefore we use the \unnnn notation.
+void writeJsonEscapedCharsOn(String string, buffer) {
+  void addCodeUnitEscaped(var buffer, int code) {
+    assert(code < 0x10000);
+    buffer.add(r'\u');
+    if (code < 0x1000) {
+      buffer.add('0');
+      if (code < 0x100) {
+        buffer.add('0');
+        if (code < 0x10) {
+          buffer.add('0');
+        }
+      }
+    }
+    buffer.add(code.toRadixString(16));
+  }
+
+  void writeEscapedOn(String string, var buffer) {
+    for (int i = 0; i < string.length; i++) {
+      int code = string.charCodeAt(i);
+      if (code == $DQ) {
+        buffer.add(r'\"');
+      } else if (code == $TAB) {
+        buffer.add(r'\t');
+      } else if (code == $LF) {
+        buffer.add(r'\n');
+      } else if (code == $CR) {
+        buffer.add(r'\r');
+      } else if (code == $DEL) {
+        addCodeUnitEscaped(buffer, $DEL);
+      } else if (code == $LS) {
+        // This Unicode line terminator and $PS are invalid in JS string
+        // literals.
+        addCodeUnitEscaped(buffer, $LS);  // 0x2028.
+      } else if (code == $PS) {
+        addCodeUnitEscaped(buffer, $PS);  // 0x2029.
+      } else if (code == $BACKSLASH) {
+        buffer.add(r'\\');
+      } else {
+        if (code < 0x20) {
+          addCodeUnitEscaped(buffer, code);
+          // We emit DEL (ASCII 0x7f) as an escape because it would be confusing
+          // to have it unescaped in a string literal.  We also escape
+          // everything above 0x7f because that means we don't have to worry
+          // about whether the web server serves it up as Latin1 or UTF-8.
+        } else if (code < 0x7f) {
+          buffer.addCharCode(code);
+        } else {
+          // This will output surrogate pairs in the form \udxxx\udyyy, rather
+          // than the more logical \u{zzzzzz}.  This should work in JavaScript
+          // (especially old UCS-2 based implementations) and is the only
+          // format that is allowed in JSON.
+          addCodeUnitEscaped(buffer, code);
+        }
+      }
+    }
+  }
+
+  for (int i = 0; i < string.length; i++) {
+    int code = string.charCodeAt(i);
+    if (code < 0x20 || code == $DEL || code == $DQ || code == $LS ||
+        code == $PS || code == $BACKSLASH || code >= 0x80) {
+      writeEscapedOn(string, buffer);
+      return;
+    }
+  }
+  buffer.add(string);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart
new file mode 100644
index 0000000..bbb852a
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2011, 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.
+
+library util_implementation;
+
+import 'util.dart';
+
+part 'link_implementation.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart b/pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart
new file mode 100644
index 0000000..56f3698
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart
@@ -0,0 +1,568 @@
+// Copyright (c) 2011, 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.
+
+part of dart2js;
+
+class MessageKind {
+  final String template;
+  const MessageKind(this.template);
+
+  static const GENERIC = const MessageKind('#{text}');
+
+  static const NOT_ASSIGNABLE = const MessageKind(
+      '#{fromType} is not assignable to #{toType}');
+  static const VOID_EXPRESSION = const MessageKind(
+      'expression does not yield a value');
+  static const VOID_VARIABLE = const MessageKind(
+      'variable cannot be of type void');
+  static const RETURN_VALUE_IN_VOID = const MessageKind(
+      'cannot return value from void function');
+  static const RETURN_NOTHING = const MessageKind(
+      'value of type #{returnType} expected');
+  static const MISSING_ARGUMENT = const MessageKind(
+      'missing argument of type #{argumentType}');
+  static const ADDITIONAL_ARGUMENT = const MessageKind(
+      'additional argument');
+  static const NAMED_ARGUMENT_NOT_FOUND = const MessageKind(
+      "no named argument '#{argumentName}' found on method");
+  static const METHOD_NOT_FOUND = const MessageKind(
+      'no method named #{methodName} in class #{className}');
+  static const MEMBER_NOT_STATIC = const MessageKind(
+      '#{className}.#{memberName} is not static');
+  static const NO_INSTANCE_AVAILABLE = const MessageKind(
+      '#{name} is only available in instance methods');
+
+  static const UNREACHABLE_CODE = const MessageKind(
+      'unreachable code');
+  static const MISSING_RETURN = const MessageKind(
+      'missing return');
+  static const MAYBE_MISSING_RETURN = const MessageKind(
+      'not all paths lead to a return or throw statement');
+
+  static const CANNOT_RESOLVE = const MessageKind(
+      'cannot resolve #{name}');
+  static const CANNOT_RESOLVE_CONSTRUCTOR = const MessageKind(
+      'cannot resolve constructor #{constructorName}');
+  static const CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind(
+      'cannot resolve constructor #{constructorName} for implicit super call');
+  static const CANNOT_RESOLVE_TYPE = const MessageKind(
+      'cannot resolve type #{typeName}');
+  static const DUPLICATE_DEFINITION = const MessageKind(
+      'duplicate definition of #{name}');
+  static const DUPLICATE_IMPORT = const MessageKind(
+      'duplicate import of #{name}');
+  static const DUPLICATE_EXPORT = const MessageKind(
+      'duplicate export of #{name}');
+  static const NOT_A_TYPE = const MessageKind(
+      '#{node} is not a type');
+  static const NOT_A_PREFIX = const MessageKind(
+      '#{node} is not a prefix');
+  static const NO_SUPER_IN_OBJECT = const MessageKind(
+      "'Object' does not have a superclass");
+  static const CANNOT_FIND_CONSTRUCTOR = const MessageKind(
+      'cannot find constructor #{constructorName}');
+  static const CANNOT_FIND_CONSTRUCTOR2 = const MessageKind(
+      'cannot find constructor #{constructorName} in #{className}');
+  static const CYCLIC_CLASS_HIERARCHY = const MessageKind(
+      '#{className} creates a cycle in the class hierarchy');
+  static const INVALID_RECEIVER_IN_INITIALIZER = const MessageKind(
+      'field initializer expected');
+  static const NO_SUPER_IN_STATIC = const MessageKind(
+      "'super' is only available in instance methods");
+  static const DUPLICATE_INITIALIZER = const MessageKind(
+      'field #{fieldName} is initialized more than once');
+  static const ALREADY_INITIALIZED = const MessageKind(
+      '#{fieldName} was already initialized here');
+  static const INIT_STATIC_FIELD = const MessageKind(
+      'cannot initialize static field #{fieldName}');
+  static const NOT_A_FIELD = const MessageKind(
+      '#{fieldName} is not a field');
+  static const CONSTRUCTOR_CALL_EXPECTED = const MessageKind(
+      "only call to 'this' or 'super' constructor allowed");
+  static const INVALID_FOR_IN = const MessageKind(
+      'invalid for-in variable declaration.');
+  static const INVALID_INITIALIZER = const MessageKind(
+      'invalid initializer');
+  static const FUNCTION_WITH_INITIALIZER = const MessageKind(
+      'only constructors can have initializers');
+  static const REDIRECTING_CONSTRUCTOR_CYCLE = const MessageKind(
+      'cyclic constructor redirection');
+  static const REDIRECTING_CONSTRUCTOR_HAS_BODY = const MessageKind(
+      'redirecting constructor cannot have a body');
+  static const REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER = const MessageKind(
+      'redirecting constructor cannot have other initializers');
+  static const SUPER_INITIALIZER_IN_OBJECT = const MessageKind(
+      "'Object' cannot have a super initializer");
+  static const DUPLICATE_SUPER_INITIALIZER = const MessageKind(
+      'cannot have more than one super initializer');
+  static const INVALID_ARGUMENTS = const MessageKind(
+      "arguments do not match the expected parameters of #{methodName}");
+  static const NO_MATCHING_CONSTRUCTOR = const MessageKind(
+      "super call arguments and constructor parameters don't match");
+  static const NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind(
+      "implicit super call arguments and constructor parameters don't match");
+  static const FIELD_PARAMETER_NOT_ALLOWED = const MessageKind(
+      'a field parameter is only allowed in generative constructors');
+  static const INVALID_PARAMETER = const MessageKind(
+      "cannot resolve parameter");
+  static const NOT_INSTANCE_FIELD = const MessageKind(
+      '#{fieldName} is not an instance field');
+  static const NO_CATCH_NOR_FINALLY = const MessageKind(
+      "expected 'catch' or 'finally'");
+  static const EMPTY_CATCH_DECLARATION = const MessageKind(
+      'expected an identifier in catch declaration');
+  static const EXTRA_CATCH_DECLARATION = const MessageKind(
+      'extra parameter in catch declaration');
+  static const PARAMETER_WITH_TYPE_IN_CATCH = const MessageKind(
+      'cannot use type annotations in catch');
+  static const PARAMETER_WITH_MODIFIER_IN_CATCH = const MessageKind(
+      'cannot use modifiers in catch');
+  static const OPTIONAL_PARAMETER_IN_CATCH = const MessageKind(
+      'cannot use optional parameters in catch');
+  static const THROW_WITHOUT_EXPRESSION = const MessageKind(
+      'cannot use re-throw outside of catch block (expression expected after '
+      '"throw")');
+  static const UNBOUND_LABEL = const MessageKind(
+      'cannot resolve label #{labelName}');
+  static const NO_BREAK_TARGET = const MessageKind(
+      'break statement not inside switch or loop');
+  static const NO_CONTINUE_TARGET = const MessageKind(
+      'continue statement not inside loop');
+  static const EXISTING_LABEL = const MessageKind(
+      'original declaration of duplicate label #{labelName}');
+  static const DUPLICATE_LABEL = const MessageKind(
+      'duplicate declaration of label #{labelName}');
+  static const UNUSED_LABEL = const MessageKind(
+      'unused label #{labelName}');
+  static const INVALID_CONTINUE = const MessageKind(
+      'target of continue is not a loop or switch case');
+  static const INVALID_BREAK = const MessageKind(
+      'target of break is not a statement');
+
+  static const TYPE_VARIABLE_AS_CONSTRUCTOR = const MessageKind(
+      'cannot use type variable as constructor');
+  static const DUPLICATE_TYPE_VARIABLE_NAME = const MessageKind(
+      'type variable #{typeVariableName} already declared');
+  static const TYPE_VARIABLE_WITHIN_STATIC_MEMBER = const MessageKind(
+      'cannot refer to type variable #{typeVariableName} '
+      'within a static member');
+
+  static const INVALID_USE_OF_SUPER = const MessageKind(
+      'super not allowed here');
+  static const INVALID_CASE_DEFAULT = const MessageKind(
+      'default only allowed on last case of a switch');
+
+  static const SWITCH_CASE_TYPES_NOT_EQUAL = const MessageKind(
+      "case expressions don't all have the same type.");
+  static const SWITCH_CASE_VALUE_OVERRIDES_EQUALS = const MessageKind(
+      "case expression value overrides 'operator=='.");
+  static const SWITCH_INVALID = const MessageKind(
+      "switch cases contain invalid expressions.");
+
+  static const INVALID_ARGUMENT_AFTER_NAMED = const MessageKind(
+      'non-named argument after named argument');
+
+  static const NOT_A_COMPILE_TIME_CONSTANT = const MessageKind(
+      'not a compile-time constant');
+  static const CYCLIC_COMPILE_TIME_CONSTANTS = const MessageKind(
+      'cycle in the compile-time constant computation');
+  static const CONSTRUCTOR_IS_NOT_CONST = const MessageKind(
+      'constructor is not a const constructor');
+
+  static const KEY_NOT_A_STRING_LITERAL = const MessageKind(
+      'map-literal key not a string literal');
+
+  static const NO_SUCH_LIBRARY_MEMBER = const MessageKind(
+      '#{libraryName} has no member named #{memberName}');
+
+  static const CANNOT_INSTANTIATE_INTERFACE = const MessageKind(
+      "cannot instantiate interface '#{interfaceName}'");
+
+  static const CANNOT_INSTANTIATE_TYPEDEF = const MessageKind(
+      "cannot instantiate typedef '#{typedefName}'");
+
+  static const CANNOT_INSTANTIATE_TYPE_VARIABLE = const MessageKind(
+      "cannot instantiate type variable '#{typeVariableName}'");
+
+  static const NO_DEFAULT_CLASS = const MessageKind(
+      "no default class on enclosing interface '#{interfaceName}'");
+
+  static const CYCLIC_TYPE_VARIABLE = const MessageKind(
+      "cyclic reference to type variable #{typeVariableName}");
+
+  static const CLASS_NAME_EXPECTED = const MessageKind(
+      "class name expected");
+
+  static const INTERFACE_TYPE_EXPECTED = const MessageKind(
+      "interface type expected");
+
+  static const CANNOT_EXTEND = const MessageKind(
+      "#{type} cannot be extended");
+
+  static const CANNOT_IMPLEMENT = const MessageKind(
+      "#{type} cannot be implemented");
+
+  static const DUPLICATE_EXTENDS_IMPLEMENTS = const MessageKind(
+      "Error: #{type} can not be both extended and implemented.");
+
+  static const DUPLICATE_IMPLEMENTS = const MessageKind(
+      "Error: #{type} must not occur more than once "
+      "in the implements clause.");
+
+  static const ILLEGAL_SUPER_SEND = const MessageKind(
+      "#{name} cannot be called on super");
+
+  static const ADDITIONAL_TYPE_ARGUMENT = const MessageKind(
+      "additional type argument");
+
+  static const MISSING_TYPE_ARGUMENT = const MessageKind(
+      "missing type argument");
+
+  // TODO(johnniwinther): Use ADDITIONAL_TYPE_ARGUMENT or MISSING_TYPE_ARGUMENT
+  // instead.
+  static const TYPE_ARGUMENT_COUNT_MISMATCH = const MessageKind(
+      "incorrect number of type arguments on #{type}");
+
+  static const MISSING_ARGUMENTS_TO_ASSERT = const MessageKind(
+      "missing arguments to assert");
+
+  static const GETTER_MISMATCH = const MessageKind(
+      "Error: setter disagrees on: #{modifiers}.");
+
+  static const SETTER_MISMATCH = const MessageKind(
+      "Error: getter disagrees on: #{modifiers}.");
+
+  static const ILLEGAL_SETTER_FORMALS = const MessageKind(
+      "Error: a setter must have exactly one argument.");
+
+  static const NO_STATIC_OVERRIDE = const MessageKind(
+      "Error: static member cannot override instance member '#{memberName}' of "
+      "'#{className}'.");
+
+  static const NO_STATIC_OVERRIDE_CONT = const MessageKind(
+      "Info: this is the instance member that cannot be overridden "
+      "by a static member.");
+
+  static const CANNOT_OVERRIDE_FIELD_WITH_METHOD = const MessageKind(
+      "Error: method cannot override field '#{memberName}' of '#{className}'.");
+
+  static const CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT = const MessageKind(
+      "Info: this is the field that cannot be overridden by a method.");
+
+  static const CANNOT_OVERRIDE_METHOD_WITH_FIELD = const MessageKind(
+      "Error: field cannot override method '#{memberName}' of '#{className}'.");
+
+  static const CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT = const MessageKind(
+      "Info: this is the method that cannot be overridden by a field.");
+
+  static const BAD_ARITY_OVERRIDE = const MessageKind(
+      "Error: cannot override method '#{memberName}' in '#{className}'; "
+      "the parameters do not match.");
+
+  static const BAD_ARITY_OVERRIDE_CONT = const MessageKind(
+      "Info: this is the method whose parameters do not match.");
+
+  static const MISSING_FORMALS = const MessageKind(
+      "Error: Formal parameters are missing.");
+
+  static const EXTRA_FORMALS = const MessageKind(
+      "Error: Formal parameters are not allowed here.");
+
+  static const UNARY_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator #{operatorName} must have no parameters.");
+
+  static const MINUS_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator - must have 0 or 1 parameters.");
+
+  static const BINARY_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator #{operatorName} must have exactly 1 parameter.");
+
+  static const TERNARY_OPERATOR_BAD_ARITY = const MessageKind(
+      "Error: Operator #{operatorName} must have exactly 2 parameters.");
+
+  static const OPERATOR_OPTIONAL_PARAMETERS = const MessageKind(
+      "Error: Operator #{operatorName} cannot have optional parameters.");
+
+  static const OPERATOR_NAMED_PARAMETERS = const MessageKind(
+      "Error: Operator #{operatorName} cannot have named parameters.");
+
+  // TODO(ahe): This message is hard to localize.  This is acceptable,
+  // as it will be removed when we ship Dart version 1.0.
+  static const DEPRECATED_FEATURE_WARNING = const MessageKind(
+      "Warning: deprecated language feature, #{featureName}, "
+      "will be removed in a future Dart milestone.");
+
+  // TODO(ahe): This message is hard to localize.  This is acceptable,
+  // as it will be removed when we ship Dart version 1.0.
+  static const DEPRECATED_FEATURE_ERROR = const MessageKind(
+      "Error: #{featureName} are not legal "
+      "due to option --reject-deprecated-language-features.");
+
+  static const CONSTRUCTOR_WITH_RETURN_TYPE = const MessageKind(
+      "Error: cannot have return type for constructor.");
+
+  static const ILLEGAL_FINAL_METHOD_MODIFIER = const MessageKind(
+      "Error: cannot have final modifier on method.");
+
+  static const ILLEGAL_CONSTRUCTOR_MODIFIERS = const MessageKind(
+      "Error: illegal constructor modifiers: #{modifiers}.");
+
+  static const ILLEGAL_MIXIN_APPLICATION_MODIFIERS = const MessageKind(
+      "Error: illegal mixin application modifiers: #{modifiers}.");
+
+  static const ILLEGAL_MIXIN_SUPERCLASS = const MessageKind(
+      "Error: class used as mixin must have Object as superclass.");
+
+  static const ILLEGAL_MIXIN_CONSTRUCTOR = const MessageKind(
+      "Error: class used as mixin cannot have non-factory constructor.");
+
+  static const ILLEGAL_MIXIN_CYCLE = const MessageKind(
+      "Error: class used as mixin introduces mixin cycle: "
+      "#{mixinName1} <-> #{mixinName2}.");
+
+  static const ILLEGAL_MIXIN_WITH_SUPER = const MessageKind(
+      "Error: cannot use class #{className} as a mixin because it uses super.");
+
+  static const ILLEGAL_MIXIN_SUPER_USE = const MessageKind(
+      "Use of super in class used as mixin.");
+
+  static const PARAMETER_NAME_EXPECTED = const MessageKind(
+      "Error: parameter name expected.");
+
+  static const CANNOT_RESOLVE_GETTER = const MessageKind(
+      'cannot resolve getter.');
+
+  static const CANNOT_RESOLVE_SETTER = const MessageKind(
+      'cannot resolve setter.');
+
+  static const VOID_NOT_ALLOWED = const MessageKind(
+      'type void is only allowed in a return type.');
+
+  static const BEFORE_TOP_LEVEL = const MessageKind(
+      'Error: part header must come before top-level definitions.');
+
+  static const LIBRARY_NAME_MISMATCH = const MessageKind(
+      'Warning: expected part of library name "#{libraryName}".');
+
+  static const MISSING_PART_OF_TAG = const MessageKind(
+      'Note: This file has no part-of tag, but it is being used as a part.');
+
+  static const DUPLICATED_PART_OF = const MessageKind(
+      'Error: duplicated part-of directive.');
+
+  static const ILLEGAL_DIRECTIVE = const MessageKind(
+      'Error: directive not allowed here.');
+
+  static const DUPLICATED_LIBRARY_NAME = const MessageKind(
+      'Warning: duplicated library name "#{libraryName}".');
+
+  static const INVALID_SOURCE_FILE_LOCATION = const MessageKind('''
+Invalid offset (#{offset}) in source map.
+File: #{fileName}
+Length: #{length}''');
+
+  static const TOP_LEVEL_VARIABLE_DECLARED_STATIC = const MessageKind(
+      "Top-level variable cannot be declared static.");
+
+  static const WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT = const MessageKind(
+      "Wrong number of arguments to assert. Should be 1, but given "
+      "#{argumentCount}.");
+
+  static const ASSERT_IS_GIVEN_NAMED_ARGUMENTS = const MessageKind(
+      "assert takes no named arguments, but given #{argumentCount}.");
+
+  static const FACTORY_REDIRECTION_IN_NON_FACTORY = const MessageKind(
+      "Error: Factory redirection only allowed in factories.");
+
+  static const MISSING_FACTORY_KEYWORD = const MessageKind(
+      "Did you forget a factory keyword here?");
+
+  static const COMPILER_CRASHED = const MessageKind(
+      "Error: The compiler crashed when compiling this element.");
+
+  static const PLEASE_REPORT_THE_CRASH = const MessageKind('''
+The compiler is broken.
+
+When compiling the above element, the compiler crashed. It is not
+possible to tell if this is caused by a problem in your program or
+not. Regardless, the compiler should not crash.
+
+The Dart team would greatly appreciate if you would take a moment to
+report this problem at http://dartbug.com/new.
+
+Please include the following information:
+
+* the name and version of your operating system,
+
+* the Dart SDK build number (#{buildId}), and
+
+* the entire message you see here (including the full stack trace
+  below as well as the source location above).
+''');
+
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Patch errors start.
+  //////////////////////////////////////////////////////////////////////////////
+
+  static const PATCH_RETURN_TYPE_MISMATCH = const MessageKind(
+      "Patch return type '#{patchReturnType}' doesn't match "
+      "'#{originReturnType}' on origin method '#{methodName}'.");
+
+  static const PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH = const MessageKind(
+      "Required parameter count of patch method (#{patchParameterCount}) "
+      "doesn't match parameter count on origin method '#{methodName}' "
+      "(#{originParameterCount}).");
+
+  static const PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH = const MessageKind(
+      "Optional parameter count of patch method (#{patchParameterCount}) "
+      "doesn't match parameter count on origin method '#{methodName}' "
+      "(#{originParameterCount}).");
+
+  static const PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH = const MessageKind(
+      "Optional parameters of origin and patch method '#{methodName}' must "
+      "both be either named or positional.");
+
+  static const PATCH_PARAMETER_MISMATCH = const MessageKind(
+      "Patch method parameter '#{patchParameter}' doesn't match "
+      "'#{originParameter}' on origin method #{methodName}.");
+
+  static const PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION = const MessageKind(
+      "External method without an implementation.");
+
+  static const PATCH_POINT_TO_FUNCTION = const MessageKind(
+      "Info: This is the function patch '#{functionName}'.");
+
+  static const PATCH_POINT_TO_CLASS = const MessageKind(
+      "Info: This is the class patch '#{className}'.");
+
+  static const PATCH_POINT_TO_GETTER = const MessageKind(
+      "Info: This is the getter patch '#{getterName}'.");
+
+  static const PATCH_POINT_TO_SETTER = const MessageKind(
+      "Info: This is the setter patch '#{setterName}'.");
+
+  static const PATCH_POINT_TO_CONSTRUCTOR = const MessageKind(
+      "Info: This is the constructor patch '#{constructorName}'.");
+
+  static const PATCH_NON_EXISTING = const MessageKind(
+      "Error: Origin does not exist for patch '#{name}'.");
+
+  static const PATCH_NONPATCHABLE = const MessageKind(
+      "Error: Only classes and functions can be patched.");
+
+  static const PATCH_NON_EXTERNAL = const MessageKind(
+      "Error: Only external functions can be patched.");
+
+  static const PATCH_NON_CLASS = const MessageKind(
+      "Error: Patching non-class with class patch '#{className}'.");
+
+  static const PATCH_NON_GETTER = const MessageKind(
+      "Error: Cannot patch non-getter '#{name}' with getter patch.");
+
+  static const PATCH_NO_GETTER = const MessageKind(
+      "Error: No getter found for getter patch '#{getterName}'.");
+
+  static const PATCH_NON_SETTER = const MessageKind(
+      "Error: Cannot patch non-setter '#{name}' with setter patch.");
+
+  static const PATCH_NO_SETTER = const MessageKind(
+      "Error: No setter found for setter patch '#{setterName}'.");
+
+  static const PATCH_NON_CONSTRUCTOR = const MessageKind(
+      "Error: Cannot patch non-constructor with constructor patch "
+      "'#{constructorName}'.");
+
+  static const PATCH_NON_FUNCTION = const MessageKind(
+      "Error: Cannot patch non-function with function patch "
+      "'#{functionName}'.");
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Patch errors end.
+  //////////////////////////////////////////////////////////////////////////////
+
+  toString() => template;
+
+  Message message([Map arguments = const {}]) {
+    return new Message(this, arguments);
+  }
+
+  CompilationError error([Map arguments = const {}]) {
+    return new CompilationError(this, arguments);
+  }
+}
+
+class Message {
+  final kind;
+  final Map arguments;
+  String message;
+
+  Message(this.kind, this.arguments) {
+    assert(() { computeMessage(); return true; });
+  }
+
+  String computeMessage() {
+    if (message == null) {
+      message = kind.template;
+      arguments.forEach((key, value) {
+        String string = slowToString(value);
+        message = message.replaceAll('#{${key}}', string);
+      });
+      assert(invariant(
+          CURRENT_ELEMENT_SPANNABLE,
+          !message.contains(new RegExp(r"#\{.+\}")),
+          message: 'Missing arguments in error message: "$message"'));
+    }
+    return message;
+  }
+
+  String toString() {
+    return computeMessage();
+  }
+
+  bool operator==(other) {
+    if (other is !Message) return false;
+    return (kind == other.kind) && (toString() == other.toString());
+  }
+
+  String slowToString(object) {
+    if (object is SourceString) {
+      return object.slowToString();
+    } else {
+      return object.toString();
+    }
+  }
+}
+
+class Diagnostic {
+  final Message message;
+  Diagnostic(MessageKind kind, [Map arguments = const {}])
+      : message = new Message(kind, arguments);
+  String toString() => message.toString();
+}
+
+class TypeWarning extends Diagnostic {
+  TypeWarning(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
+
+class ResolutionError extends Diagnostic {
+  ResolutionError(MessageKind kind, [Map arguments = const {}])
+      : super(kind, arguments);
+}
+
+class ResolutionWarning extends Diagnostic {
+  ResolutionWarning(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
+
+class CompileTimeConstantError extends Diagnostic {
+  CompileTimeConstantError(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
+
+class CompilationError extends Diagnostic {
+  CompilationError(MessageKind kind, [Map arguments = const {}])
+    : super(kind, arguments);
+}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/world.dart b/pkgs/markdown/test/lib/src/compiler/implementation/world.dart
new file mode 100644
index 0000000..a40050e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/compiler/implementation/world.dart
@@ -0,0 +1,232 @@
+// 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.
+
+part of dart2js;
+
+class World {
+  final Compiler compiler;
+  final Map<ClassElement, Set<ClassElement>> subtypes;
+  final Map<ClassElement, Set<MixinApplicationElement>> mixinUses;
+  final Map<ClassElement, Set<ClassElement>> typesImplementedBySubclasses;
+  final Set<ClassElement> classesNeedingRti;
+  final Map<ClassElement, Set<ClassElement>> rtiDependencies;
+  final FunctionSet userDefinedGetters;
+  final FunctionSet userDefinedSetters;
+
+  World(Compiler compiler)
+      : subtypes = new Map<ClassElement, Set<ClassElement>>(),
+        mixinUses = new Map<ClassElement, Set<MixinApplicationElement>>(),
+        typesImplementedBySubclasses =
+            new Map<ClassElement, Set<ClassElement>>(),
+        userDefinedGetters = new FunctionSet(compiler),
+        userDefinedSetters = new FunctionSet(compiler),
+        classesNeedingRti = new Set<ClassElement>(),
+        rtiDependencies = new Map<ClassElement, Set<ClassElement>>(),
+        this.compiler = compiler;
+
+  void populate() {
+    void addSubtypes(ClassElement cls) {
+      if (cls.resolutionState != STATE_DONE) {
+        compiler.internalErrorOnElement(
+            cls, 'Class "${cls.name.slowToString()}" is not resolved.');
+      }
+
+      for (DartType type in cls.allSupertypes) {
+        Set<Element> subtypesOfCls =
+          subtypes.putIfAbsent(type.element, () => new Set<ClassElement>());
+        subtypesOfCls.add(cls);
+      }
+
+      // Walk through the superclasses, and record the types
+      // implemented by that type on the superclasses.
+      DartType type = cls.supertype;
+      while (type != null) {
+        Set<Element> typesImplementedBySubclassesOfCls =
+          typesImplementedBySubclasses.putIfAbsent(
+              type.element, () => new Set<ClassElement>());
+        for (DartType current in cls.allSupertypes) {
+          typesImplementedBySubclassesOfCls.add(current.element);
+        }
+        ClassElement classElement = type.element;
+        type = classElement.supertype;
+      }
+    }
+
+    compiler.resolverWorld.instantiatedClasses.forEach(addSubtypes);
+
+    // Find the classes that need runtime type information. Such
+    // classes are:
+    // (1) used in a is check with type variables,
+    // (2) dependencies of classes in (1),
+    // (3) subclasses of (2) and (3).
+
+    void potentiallyAddForRti(ClassElement cls) {
+      if (cls.typeVariables.isEmpty) return;
+      if (classesNeedingRti.contains(cls)) return;
+      classesNeedingRti.add(cls);
+
+      Set<ClassElement> classes = subtypes[cls];
+      if (classes != null) {
+        classes.forEach((ClassElement sub) {
+          potentiallyAddForRti(sub);
+        });
+      }
+
+      Set<ClassElement> dependencies = rtiDependencies[cls];
+      if (dependencies != null) {
+        dependencies.forEach((ClassElement other) {
+          potentiallyAddForRti(other);
+        });
+      }
+    }
+
+    compiler.resolverWorld.isChecks.forEach((DartType type) {
+      if (type is InterfaceType) {
+        InterfaceType itf = type;
+        if (!itf.isRaw) {
+          potentiallyAddForRti(itf.element);
+        }
+      }
+    });
+  }
+
+  bool needsRti(ClassElement cls) {
+    return classesNeedingRti.contains(cls) || compiler.enabledRuntimeType;
+  }
+
+  void registerMixinUse(MixinApplicationElement mixinApplication,
+                        ClassElement mixin) {
+    Set<MixinApplicationElement> users =
+        mixinUses.putIfAbsent(mixin, () =>
+                              new Set<MixinApplicationElement>());
+    users.add(mixinApplication);
+  }
+
+  void registerRtiDependency(Element element, Element dependency) {
+    // We're not dealing with typedef for now.
+    if (!element.isClass() || !dependency.isClass()) return;
+    Set<ClassElement> classes =
+        rtiDependencies.putIfAbsent(element, () => new Set<ClassElement>());
+    classes.add(dependency);
+  }
+
+  void recordUserDefinedGetter(Element element) {
+    assert(element.isGetter());
+    userDefinedGetters.add(element);
+  }
+
+  void recordUserDefinedSetter(Element element) {
+    assert(element.isSetter());
+    userDefinedSetters.add(element);
+  }
+
+  bool hasAnyUserDefinedGetter(Selector selector) {
+    return userDefinedGetters.hasAnyElementMatchingSelector(selector);
+  }
+
+  bool hasAnyUserDefinedSetter(Selector selector) {
+    return userDefinedSetters.hasAnyElementMatchingSelector(selector);
+  }
+
+  // Returns whether a subclass of [superclass] implements [type].
+  bool hasAnySubclassThatImplements(ClassElement superclass, DartType type) {
+    Set<ClassElement> subclasses= typesImplementedBySubclasses[superclass];
+    if (subclasses == null) return false;
+    return subclasses.contains(type.element);
+  }
+
+  bool hasNoOverridingMember(Element element) {
+    ClassElement cls = element.getEnclosingClass();
+    Set<ClassElement> subclasses = compiler.world.subtypes[cls];
+    // TODO(ngeoffray): Implement the full thing.
+    return subclasses == null || subclasses.isEmpty;
+  }
+
+  void registerUsedElement(Element element) {
+    if (element.isMember()) {
+      if (element.isGetter()) {
+        // We're collecting user-defined getters to let the codegen know which
+        // field accesses might have side effects.
+        recordUserDefinedGetter(element);
+      } else if (element.isSetter()) {
+        recordUserDefinedSetter(element);
+      }
+    }
+  }
+
+  /**
+   * Returns a [MemberSet] that contains the possible targets of the given
+   * [selector] on a receiver with the given [type]. This includes all sub
+   * types.
+   */
+  MemberSet _memberSetFor(DartType type, Selector selector) {
+    assert(compiler != null);
+    ClassElement cls = type.element;
+    SourceString name = selector.name;
+    LibraryElement library = selector.library;
+    MemberSet result = new MemberSet(name);
+    Element element = cls.implementation.lookupSelector(selector);
+    if (element != null) result.add(element);
+
+    bool isPrivate = name.isPrivate();
+    Set<ClassElement> subtypesOfCls = subtypes[cls];
+    if (subtypesOfCls != null) {
+      for (ClassElement sub in subtypesOfCls) {
+        // Private members from a different library are not visible.
+        if (isPrivate && sub.getLibrary() != library) continue;
+        element = sub.implementation.lookupLocalMember(name);
+        if (element != null) result.add(element);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns the field in [type] described by the given [selector].
+   * If no such field exists, or a subclass overrides the field
+   * returns [:null:].
+   */
+  VariableElement locateSingleField(DartType type, Selector selector) {
+    MemberSet memberSet = _memberSetFor(type, selector);
+    ClassElement cls = type.element;
+    Element result = cls.implementation.lookupSelector(selector);
+    if (result == null) return null;
+    if (!result.isField()) return null;
+
+    // Verify that no subclass overrides the field.
+    if (memberSet.elements.length != 1) return null;
+    assert(memberSet.elements.contains(result));
+    return result;
+  }
+
+  Set<ClassElement> findNoSuchMethodHolders(DartType type) {
+    Set<ClassElement> result = new Set<ClassElement>();
+    Selector noSuchMethodSelector = new Selector.noSuchMethod();
+    MemberSet memberSet = _memberSetFor(type, noSuchMethodSelector);
+    for (Element element in memberSet.elements) {
+      ClassElement holder = element.getEnclosingClass();
+      if (!identical(holder, compiler.objectClass) &&
+          noSuchMethodSelector.applies(element, compiler)) {
+        result.add(holder);
+      }
+    }
+    return result;
+  }
+}
+
+/**
+ * A [MemberSet] contains all the possible targets for a selector.
+ */
+class MemberSet {
+  final Set<Element> elements;
+  final SourceString name;
+
+  MemberSet(SourceString this.name) : elements = new Set<Element>();
+
+  void add(Element element) {
+    elements.add(element);
+  }
+
+  bool get isEmpty => elements.isEmpty;
+}
diff --git a/pkgs/markdown/test/lib/src/libraries.dart b/pkgs/markdown/test/lib/src/libraries.dart
new file mode 100644
index 0000000..5c35483
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/libraries.dart
@@ -0,0 +1,200 @@
+// 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.
+
+library libraries;
+
+/**
+ * A bit flag used by [LibraryInfo] indicating that a library is used by dart2js
+ */
+const int DART2JS_PLATFORM = 1;
+
+/**
+ * A bit flag used by [LibraryInfo] indicating that a library is used by the VM
+ */
+const int VM_PLATFORM = 2;
+
+/**
+ * Mapping of "dart:" library name (e.g. "core") to information about that library.
+ * This information is structured such that Dart Editor can parse this file
+ * and extract the necessary information without executing it
+ * while other tools can access via execution.
+ */
+const Map<String, LibraryInfo> LIBRARIES = const {
+
+  "async": const LibraryInfo(
+      "async/async.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/async_patch.dart"),
+
+  "chrome": const LibraryInfo(
+      "chrome/dartium/chrome_dartium.dart",
+      category: "Client",
+      dart2jsPath: "chrome/dart2js/chrome_dart2js.dart",
+      documented: false,
+      implementation: true), // Not really, just hiding it for now.
+
+  "collection": const LibraryInfo("collection/collection.dart"),
+
+  "core": const LibraryInfo(
+      "core/core.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/core_patch.dart"),
+
+  "crypto": const LibraryInfo(
+      "crypto/crypto.dart"),
+
+  "html": const LibraryInfo(
+      "html/dartium/html_dartium.dart",
+      category: "Client",
+      dart2jsPath: "html/dart2js/html_dart2js.dart"),
+
+  "html_common": const LibraryInfo(
+      "html/html_common/html_common.dart",
+      category: "Client",
+      dart2jsPath: "html/html_common/html_common_dart2js.dart",
+      documented: false,
+      implementation: true),
+
+  "indexed_db": const LibraryInfo(
+      "indexed_db/dartium/indexed_db_dartium.dart",
+      category: "Client",
+      dart2jsPath: "indexed_db/dart2js/indexed_db_dart2js.dart"),
+
+  "io": const LibraryInfo(
+      "io/io.dart",
+      category: "Server",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/io_patch.dart"),
+
+  "isolate": const LibraryInfo(
+      "isolate/isolate.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/isolate_patch.dart"),
+
+  "json": const LibraryInfo(
+      "json/json.dart"),
+
+  "math": const LibraryInfo(
+      "math/math.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/math_patch.dart"),
+
+  "mirrors": const LibraryInfo(
+      "mirrors/mirrors.dart",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/mirrors_patch.dart"),
+
+  "nativewrappers": const LibraryInfo(
+      "html/dartium/nativewrappers.dart",
+      category: "Client",
+      implementation: true,
+      documented: false,
+      platforms: VM_PLATFORM),
+
+  "scalarlist": const LibraryInfo(
+      "scalarlist/scalarlist.dart",
+      category: "Server",
+      dart2jsPatchPath: "_internal/compiler/implementation/lib/scalarlist_patch.dart"),
+
+  "svg": const LibraryInfo(
+        "svg/dartium/svg_dartium.dart",
+        category: "Client",
+        dart2jsPath: "svg/dart2js/svg_dart2js.dart"),
+
+  "uri": const LibraryInfo(
+      "uri/uri.dart"),
+
+  "utf": const LibraryInfo(
+      "utf/utf.dart"),
+
+  "web_audio": const LibraryInfo(
+        "web_audio/dartium/web_audio_dartium.dart",
+        category: "Client",
+        dart2jsPath: "web_audio/dart2js/web_audio_dart2js.dart"),
+
+  "_collection-dev": const LibraryInfo(
+      "_collection_dev/collection_dev.dart",
+      category: "Internal",
+      documented: false),
+
+  "_js_helper": const LibraryInfo(
+      "_internal/compiler/implementation/lib/js_helper.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+
+  "_interceptors": const LibraryInfo(
+      "_internal/compiler/implementation/lib/interceptors.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+
+  "_foreign_helper": const LibraryInfo(
+      "_internal/compiler/implementation/lib/foreign_helper.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+
+  "_isolate_helper": const LibraryInfo(
+      "_internal/compiler/implementation/lib/isolate_helper.dart",
+      category: "Internal",
+      documented: false,
+      platforms: DART2JS_PLATFORM),
+};
+
+/**
+ * Information about a "dart:" library.
+ */
+class LibraryInfo {
+
+  /**
+   * Path to the library's *.dart file relative to this file.
+   */
+  final String path;
+
+  /**
+   * The category in which the library should appear in the editor
+   * (e.g. "Common", "Client", "Server", ...).
+   */
+  final String category;
+
+  /**
+   * Path to the dart2js library's *.dart file relative to this file
+   * or null if dart2js uses the common library path defined above.
+   * Access using the [#getDart2JsPath()] method.
+   */
+  final String dart2jsPath;
+
+  /**
+   * Path to the dart2js library's patch file relative to this file
+   * or null if no dart2js patch file associated with this library.
+   * Access using the [#getDart2JsPatchPath()] method.
+   */
+  final String dart2jsPatchPath;
+
+  /**
+   * True if this library is documented and should be shown to the user.
+   */
+  final bool documented;
+
+  /**
+   * Bit flags indicating which platforms consume this library.
+   * See [DART2JS_LIBRARY] and [VM_LIBRARY].
+   */
+  final int platforms;
+
+  /**
+   * True if the library contains implementation details for another library.
+   * The implication is that these libraries are less commonly used
+   * and that tools like Dart Editor should not show these libraries
+   * in a list of all libraries unless the user specifically asks the tool to
+   * do so.
+   */
+  final bool implementation;
+
+  const LibraryInfo(this.path, {
+                    this.category: "Shared",
+                    this.dart2jsPath,
+                    this.dart2jsPatchPath,
+                    this.implementation: false,
+                    this.documented: true,
+                    this.platforms: DART2JS_PLATFORM | VM_PLATFORM});
+
+  bool get isDart2jsLibrary => (platforms & DART2JS_PLATFORM) != 0;
+  bool get isVmLibrary => (platforms & VM_PLATFORM) != 0;
+}
diff --git a/pkgs/markdown/test/lib/src/markdown/ast.dart b/pkgs/markdown/test/lib/src/markdown/ast.dart
new file mode 100644
index 0000000..c966cea
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/markdown/ast.dart
@@ -0,0 +1,65 @@
+// 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.
+
+part of markdown;
+
+/// Base class for any AST item. Roughly corresponds to Node in the DOM. Will
+/// be either an Element or Text.
+abstract class Node {
+  void accept(NodeVisitor visitor);
+}
+
+/// A named tag that can contain other nodes.
+class Element implements Node {
+  final String tag;
+  final List<Node> children;
+  final Map<String, String> attributes;
+
+  Element(this.tag, this.children)
+    : attributes = <String, String>{};
+
+  Element.empty(this.tag)
+    : children = null,
+      attributes = <String, String>{};
+
+  Element.withTag(this.tag)
+    : children = [],
+      attributes = <String, String>{};
+
+  Element.text(this.tag, String text)
+    : children = [new Text(text)],
+      attributes = <String, String>{};
+
+  bool get isEmpty => children == null;
+
+  void accept(NodeVisitor visitor) {
+    if (visitor.visitElementBefore(this)) {
+      for (final child in children) child.accept(visitor);
+      visitor.visitElementAfter(this);
+    }
+  }
+}
+
+/// A plain text element.
+class Text implements Node {
+  final String text;
+  Text(this.text);
+
+  void accept(NodeVisitor visitor) => visitor.visitText(this);
+}
+
+/// Visitor pattern for the AST. Renderers or other AST transformers should
+/// implement this.
+abstract class NodeVisitor {
+  /// Called when a Text node has been reached.
+  void visitText(Text text);
+
+  /// Called when an Element has been reached, before its children have been
+  /// visited. Return `false` to skip its children.
+  bool visitElementBefore(Element element);
+
+  /// Called when an Element has been reached, after its children have been
+  /// visited. Will not be called if [visitElementBefore] returns `false`.
+  void visitElementAfter(Element element);
+}
diff --git a/pkgs/markdown/test/lib/src/markdown/block_parser.dart b/pkgs/markdown/test/lib/src/markdown/block_parser.dart
new file mode 100644
index 0000000..67109a4
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/markdown/block_parser.dart
@@ -0,0 +1,464 @@
+// 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.
+
+part of markdown;
+
+/// The line contains only whitespace or is empty.
+final _RE_EMPTY = new RegExp(r'^([ \t]*)$');
+
+/// A series of `=` or `-` (on the next line) define setext-style headers.
+final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$');
+
+/// Leading (and trailing) `#` define atx-style headers.
+final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$');
+
+/// The line starts with `>` with one optional space after.
+final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
+
+/// A line indented four spaces. Used for code blocks and lists.
+final _RE_INDENT = new RegExp(r'^(?:    |\t)(.*)$');
+
+/// Three or more hyphens, asterisks or underscores by themselves. Note that
+/// a line like `----` is valid as both HR and SETEXT. In case of a tie,
+/// SETEXT should win.
+final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|'
+                                 r'(_+[ ]{0,2}){3,}|'
+                                 r'(\*+[ ]{0,2}){3,})$');
+
+/// Really hacky way to detect block-level embedded HTML. Just looks for
+/// "<somename".
+final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]');
+
+/// A line starting with one of these markers: `-`, `*`, `+`. May have up to
+/// three leading spaces before the marker and any number of spaces or tabs
+/// after.
+final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
+
+/// A line starting with a number like `123.`. May have up to three leading
+/// spaces before the marker and any number of spaces or tabs after.
+final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
+
+/// Maintains the internal state needed to parse a series of lines into blocks
+/// of markdown suitable for further inline parsing.
+class BlockParser {
+  final List<String> lines;
+
+  /// The markdown document this parser is parsing.
+  final Document document;
+
+  /// Index of the current line.
+  int pos;
+
+  BlockParser(this.lines, this.document)
+    : pos = 0;
+
+  /// Gets the current line.
+  String get current => lines[pos];
+
+  /// Gets the line after the current one or `null` if there is none.
+  String get next {
+    // Don't read past the end.
+    if (pos >= lines.length - 1) return null;
+    return lines[pos + 1];
+  }
+
+  void advance() {
+    pos++;
+  }
+
+  bool get isDone => pos >= lines.length;
+
+  /// Gets whether or not the current line matches the given pattern.
+  bool matches(RegExp regex) {
+    if (isDone) return false;
+    return regex.firstMatch(current) != null;
+  }
+
+  /// Gets whether or not the current line matches the given pattern.
+  bool matchesNext(RegExp regex) {
+    if (next == null) return false;
+    return regex.firstMatch(next) != null;
+  }
+}
+
+abstract class BlockSyntax {
+  /// Gets the collection of built-in block parsers. To turn a series of lines
+  /// into blocks, each of these will be tried in turn. Order matters here.
+  static List<BlockSyntax> get syntaxes {
+    // Lazy initialize.
+    if (_syntaxes == null) {
+      _syntaxes = [
+          new EmptyBlockSyntax(),
+          new BlockHtmlSyntax(),
+          new SetextHeaderSyntax(),
+          new HeaderSyntax(),
+          new CodeBlockSyntax(),
+          new BlockquoteSyntax(),
+          new HorizontalRuleSyntax(),
+          new UnorderedListSyntax(),
+          new OrderedListSyntax(),
+          new ParagraphSyntax()
+        ];
+    }
+
+    return _syntaxes;
+  }
+
+  static List<BlockSyntax> _syntaxes;
+
+  /// Gets the regex used to identify the beginning of this block, if any.
+  RegExp get pattern => null;
+
+  bool get canEndBlock => true;
+
+  bool canParse(BlockParser parser) {
+    return pattern.firstMatch(parser.current) != null;
+  }
+
+  Node parse(BlockParser parser);
+
+  List<String> parseChildLines(BlockParser parser) {
+    // Grab all of the lines that form the blockquote, stripping off the ">".
+    final childLines = <String>[];
+
+    while (!parser.isDone) {
+      final match = pattern.firstMatch(parser.current);
+      if (match == null) break;
+      childLines.add(match[1]);
+      parser.advance();
+    }
+
+    return childLines;
+  }
+
+  /// Gets whether or not [parser]'s current line should end the previous block.
+  static bool isAtBlockEnd(BlockParser parser) {
+    if (parser.isDone) return true;
+    return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock);
+  }
+}
+
+class EmptyBlockSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_EMPTY;
+
+  Node parse(BlockParser parser) {
+    parser.advance();
+
+    // Don't actually emit anything.
+    return null;
+  }
+}
+
+/// Parses setext-style headers.
+class SetextHeaderSyntax extends BlockSyntax {
+  bool canParse(BlockParser parser) {
+    // Note: matches *next* line, not the current one. We're looking for the
+    // underlining after this line.
+    return parser.matchesNext(_RE_SETEXT);
+  }
+
+  Node parse(BlockParser parser) {
+    final match = _RE_SETEXT.firstMatch(parser.next);
+
+    final tag = (match[1][0] == '=') ? 'h1' : 'h2';
+    final contents = parser.document.parseInline(parser.current);
+    parser.advance();
+    parser.advance();
+
+    return new Element(tag, contents);
+  }
+}
+
+/// Parses atx-style headers: `## Header ##`.
+class HeaderSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_HEADER;
+
+  Node parse(BlockParser parser) {
+    final match = pattern.firstMatch(parser.current);
+    parser.advance();
+    final level = match[1].length;
+    final contents = parser.document.parseInline(match[2].trim());
+    return new Element('h$level', contents);
+  }
+}
+
+/// Parses email-style blockquotes: `> quote`.
+class BlockquoteSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_BLOCKQUOTE;
+
+  Node parse(BlockParser parser) {
+    final childLines = parseChildLines(parser);
+
+    // Recursively parse the contents of the blockquote.
+    final children = parser.document.parseLines(childLines);
+
+    return new Element('blockquote', children);
+  }
+}
+
+/// Parses preformatted code blocks that are indented four spaces.
+class CodeBlockSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_INDENT;
+
+  List<String> parseChildLines(BlockParser parser) {
+    final childLines = <String>[];
+
+    while (!parser.isDone) {
+      var match = pattern.firstMatch(parser.current);
+      if (match != null) {
+        childLines.add(match[1]);
+        parser.advance();
+      } else {
+        // If there's a codeblock, then a newline, then a codeblock, keep the
+        // code blocks together.
+        var nextMatch = parser.next != null ?
+            pattern.firstMatch(parser.next) : null;
+        if (parser.current.trim() == '' && nextMatch != null) {
+          childLines.add('');
+          childLines.add(nextMatch[1]);
+          parser.advance();
+          parser.advance();
+        } else {
+          break;
+        }
+      }
+    }
+    return childLines;
+  }
+
+  Node parse(BlockParser parser) {
+    final childLines = parseChildLines(parser);
+
+    // The Markdown tests expect a trailing newline.
+    childLines.add('');
+
+    // Escape the code.
+    final escaped = classifySource(Strings.join(childLines, '\n'));
+
+    return new Element.text('pre', escaped);
+  }
+}
+
+/// Parses horizontal rules like `---`, `_ _ _`, `*  *  *`, etc.
+class HorizontalRuleSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_HR;
+
+  Node parse(BlockParser parser) {
+    final match = pattern.firstMatch(parser.current);
+    parser.advance();
+    return new Element.empty('hr');
+  }
+}
+
+/// Parses inline HTML at the block level. This differs from other markdown
+/// implementations in several ways:
+///
+/// 1.  This one is way way WAY simpler.
+/// 2.  All HTML tags at the block level will be treated as blocks. If you
+///     start a paragraph with `<em>`, it will not wrap it in a `<p>` for you.
+///     As soon as it sees something like HTML, it stops mucking with it until
+///     it hits the next block.
+/// 3.  Absolutely no HTML parsing or validation is done. We're a markdown
+///     parser not an HTML parser!
+class BlockHtmlSyntax extends BlockSyntax {
+  RegExp get pattern => _RE_HTML;
+
+  bool get canEndBlock => false;
+
+  Node parse(BlockParser parser) {
+    final childLines = [];
+
+    // Eat until we hit a blank line.
+    while (!parser.isDone && !parser.matches(_RE_EMPTY)) {
+      childLines.add(parser.current);
+      parser.advance();
+    }
+
+    return new Text(Strings.join(childLines, '\n'));
+  }
+}
+
+class ListItem {
+  bool forceBlock = false;
+  final List<String> lines;
+
+  ListItem(this.lines);
+}
+
+/// Base class for both ordered and unordered lists.
+abstract class ListSyntax extends BlockSyntax {
+  bool get canEndBlock => false;
+
+  String get listTag;
+
+  Node parse(BlockParser parser) {
+    final items = <ListItem>[];
+    var childLines = <String>[];
+
+    endItem() {
+      if (childLines.length > 0) {
+        items.add(new ListItem(childLines));
+        childLines = <String>[];
+      }
+    }
+
+    var match;
+    tryMatch(RegExp pattern) {
+      match = pattern.firstMatch(parser.current);
+      return match != null;
+    }
+
+    bool afterEmpty = false;
+    while (!parser.isDone) {
+      if (tryMatch(_RE_EMPTY)) {
+        // Add a blank line to the current list item.
+        childLines.add('');
+      } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) {
+        // End the current list item and start a new one.
+        endItem();
+        childLines.add(match[1]);
+      } else if (tryMatch(_RE_INDENT)) {
+        // Strip off indent and add to current item.
+        childLines.add(match[1]);
+      } else if (BlockSyntax.isAtBlockEnd(parser)) {
+        // Done with the list.
+        break;
+      } else {
+        // Anything else is paragraph text or other stuff that can be in a list
+        // item. However, if the previous item is a blank line, this means we're
+        // done with the list and are starting a new top-level paragraph.
+        if ((childLines.length > 0) && (childLines.last == '')) break;
+        childLines.add(parser.current);
+      }
+      parser.advance();
+    }
+
+    endItem();
+
+    // Markdown, because it hates us, specifies two kinds of list items. If you
+    // have a list like:
+    //
+    // * one
+    // * two
+    //
+    // Then it will insert the conents of the lines directly in the <li>, like:
+    // <ul>
+    //   <li>one</li>
+    //   <li>two</li>
+    // <ul>
+    //
+    // If, however, there are blank lines between the items, each is wrapped in
+    // paragraphs:
+    //
+    // * one
+    //
+    // * two
+    //
+    // <ul>
+    //   <li><p>one</p></li>
+    //   <li><p>two</p></li>
+    // <ul>
+    //
+    // In other words, sometimes we parse the contents of a list item like a
+    // block, and sometimes line an inline. The rules our parser implements are:
+    //
+    // - If it has more than one line, it's a block.
+    // - If the line matches any block parser (BLOCKQUOTE, HEADER, HR, INDENT,
+    //   UL, OL) it's a block. (This is for cases like "* > quote".)
+    // - If there was a blank line between this item and the previous one, it's
+    //   a block.
+    // - If there was a blank line between this item and the next one, it's a
+    //   block.
+    // - Otherwise, parse it as an inline.
+
+    // Remove any trailing empty lines and note which items are separated by
+    // empty lines. Do this before seeing which items are single-line so that
+    // trailing empty lines on the last item don't force it into being a block.
+    for (int i = 0; i < items.length; i++) {
+      for (int j = items[i].lines.length - 1; j > 0; j--) {
+        if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) {
+          // Found an empty line. Item and one after it are blocks.
+          if (i < items.length - 1) {
+            items[i].forceBlock = true;
+            items[i + 1].forceBlock = true;
+          }
+          items[i].lines.removeLast();
+        } else {
+          break;
+        }
+      }
+    }
+
+    // Convert the list items to Nodes.
+    final itemNodes = <Node>[];
+    for (final item in items) {
+      bool blockItem = item.forceBlock || (item.lines.length > 1);
+
+      // See if it matches some block parser.
+      final blocksInList = [
+        _RE_BLOCKQUOTE,
+        _RE_HEADER,
+        _RE_HR,
+        _RE_INDENT,
+        _RE_UL,
+        _RE_OL
+      ];
+
+      if (!blockItem) {
+        for (final pattern in blocksInList) {
+          if (pattern.firstMatch(item.lines[0]) != null) {
+            blockItem = true;
+            break;
+          }
+        }
+      }
+
+      // Parse the item as a block or inline.
+      if (blockItem) {
+        // Block list item.
+        final children = parser.document.parseLines(item.lines);
+        itemNodes.add(new Element('li', children));
+      } else {
+        // Raw list item.
+        final contents = parser.document.parseInline(item.lines[0]);
+        itemNodes.add(new Element('li', contents));
+      }
+    }
+
+    return new Element(listTag, itemNodes);
+  }
+}
+
+/// Parses unordered lists.
+class UnorderedListSyntax extends ListSyntax {
+  RegExp get pattern => _RE_UL;
+  String get listTag => 'ul';
+}
+
+/// Parses ordered lists.
+class OrderedListSyntax extends ListSyntax {
+  RegExp get pattern => _RE_OL;
+  String get listTag => 'ol';
+}
+
+/// Parses paragraphs of regular text.
+class ParagraphSyntax extends BlockSyntax {
+  bool get canEndBlock => false;
+
+  bool canParse(BlockParser parser) => true;
+
+  Node parse(BlockParser parser) {
+    final childLines = [];
+
+    // Eat until we hit something that ends a paragraph.
+    while (!BlockSyntax.isAtBlockEnd(parser)) {
+      childLines.add(parser.current);
+      parser.advance();
+    }
+
+    final contents = parser.document.parseInline(
+        Strings.join(childLines, '\n'));
+    return new Element('p', contents);
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/markdown/html_renderer.dart b/pkgs/markdown/test/lib/src/markdown/html_renderer.dart
new file mode 100644
index 0000000..1695e96
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/markdown/html_renderer.dart
@@ -0,0 +1,61 @@
+// 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.
+
+part of markdown;
+
+String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes);
+
+/// Translates a parsed AST to HTML.
+class HtmlRenderer implements NodeVisitor {
+  static final _BLOCK_TAGS = new RegExp(
+      'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
+
+  StringBuffer buffer;
+
+  HtmlRenderer();
+
+  String render(List<Node> nodes) {
+    buffer = new StringBuffer();
+
+    for (final node in nodes) node.accept(this);
+
+    return buffer.toString();
+  }
+
+  void visitText(Text text) {
+    buffer.add(text.text);
+  }
+
+  bool visitElementBefore(Element element) {
+    // Hackish. Separate block-level elements with newlines.
+    if (!buffer.isEmpty &&
+        _BLOCK_TAGS.firstMatch(element.tag) != null) {
+      buffer.add('\n');
+    }
+
+    buffer.add('<${element.tag}');
+
+    // Sort the keys so that we generate stable output.
+    // TODO(rnystrom): This assumes keys returns a fresh mutable
+    // collection.
+    final attributeNames = element.attributes.keys.toList();
+    attributeNames.sort((a, b) => a.compareTo(b));
+    for (final name in attributeNames) {
+      buffer.add(' $name="${element.attributes[name]}"');
+    }
+
+    if (element.isEmpty) {
+      // Empty element like <hr/>.
+      buffer.add(' />');
+      return false;
+    } else {
+      buffer.add('>');
+      return true;
+    }
+  }
+
+  void visitElementAfter(Element element) {
+    buffer.add('</${element.tag}>');
+  }
+}
diff --git a/pkgs/markdown/test/lib/src/markdown/inline_parser.dart b/pkgs/markdown/test/lib/src/markdown/inline_parser.dart
new file mode 100644
index 0000000..af42e3e
--- /dev/null
+++ b/pkgs/markdown/test/lib/src/markdown/inline_parser.dart
@@ -0,0 +1,410 @@
+// 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.
+
+part of markdown;
+
+/// Maintains the internal state needed to parse inline span elements in
+/// markdown.
+class InlineParser {
+  static List<InlineSyntax> get syntaxes {
+    // Lazy initialize.
+    if (_syntaxes == null) {
+      _syntaxes = <InlineSyntax>[
+        // This first regexp matches plain text to accelerate parsing.  It must
+        // be written so that it does not match any prefix of any following
+        // syntax.  Most markdown is plain text, so it is faster to match one
+        // regexp per 'word' rather than fail to match all the following regexps
+        // at each non-syntax character position.  It is much more important
+        // that the regexp is fast than complete (for example, adding grouping
+        // is likely to slow the regexp down enough to negate its benefit).
+        // Since it is purely for optimization, it can be removed for debugging.
+        new TextSyntax(r'\s*[A-Za-z0-9]+'),
+
+        // The real syntaxes.
+
+        new AutolinkSyntax(),
+        new LinkSyntax(),
+        // "*" surrounded by spaces is left alone.
+        new TextSyntax(r' \* '),
+        // "_" surrounded by spaces is left alone.
+        new TextSyntax(r' _ '),
+        // Leave already-encoded HTML entities alone. Ensures we don't turn
+        // "&amp;" into "&amp;amp;"
+        new TextSyntax(r'&[#a-zA-Z0-9]*;'),
+        // Encode "&".
+        new TextSyntax(r'&', sub: '&amp;'),
+        // Encode "<". (Why not encode ">" too? Gruber is toying with us.)
+        new TextSyntax(r'<', sub: '&lt;'),
+        // Parse "**strong**" tags.
+        new TagSyntax(r'\*\*', tag: 'strong'),
+        // Parse "__strong__" tags.
+        new TagSyntax(r'__', tag: 'strong'),
+        // Parse "*emphasis*" tags.
+        new TagSyntax(r'\*', tag: 'em'),
+        // Parse "_emphasis_" tags.
+        // TODO(rnystrom): Underscores in the middle of a word should not be
+        // parsed as emphasis like_in_this.
+        new TagSyntax(r'_', tag: 'em'),
+        // Parse inline code within double backticks: "``code``".
+        new CodeSyntax(r'``\s?((?:.|\n)*?)\s?``'),
+        // Parse inline code within backticks: "`code`".
+        new CodeSyntax(r'`([^`]*)`')
+      ];
+    }
+
+    return _syntaxes;
+  }
+
+  static List<InlineSyntax> _syntaxes;
+
+  /// The string of markdown being parsed.
+  final String source;
+
+  /// The markdown document this parser is parsing.
+  final Document document;
+
+  /// The current read position.
+  int pos = 0;
+
+  /// Starting position of the last unconsumed text.
+  int start = 0;
+
+  final List<TagState> _stack;
+
+  InlineParser(this.source, this.document)
+    : _stack = <TagState>[];
+
+  List<Node> parse() {
+    // Make a fake top tag to hold the results.
+    _stack.add(new TagState(0, 0, null));
+
+    while (!isDone) {
+      bool matched = false;
+
+      // See if any of the current tags on the stack match. We don't allow tags
+      // of the same kind to nest, so this takes priority over other possible // matches.
+      for (int i = _stack.length - 1; i > 0; i--) {
+        if (_stack[i].tryMatch(this)) {
+          matched = true;
+          break;
+        }
+      }
+      if (matched) continue;
+
+      // See if the current text matches any defined markdown syntax.
+      for (final syntax in syntaxes) {
+        if (syntax.tryMatch(this)) {
+          matched = true;
+          break;
+        }
+      }
+      if (matched) continue;
+
+      // If we got here, it's just text.
+      advanceBy(1);
+    }
+
+    // Unwind any unmatched tags and get the results.
+    return _stack[0].close(this, null);
+  }
+
+  writeText() {
+    writeTextRange(start, pos);
+    start = pos;
+  }
+
+  writeTextRange(int start, int end) {
+    if (end > start) {
+      final text = source.substring(start, end);
+      final nodes = _stack.last.children;
+
+      // If the previous node is text too, just append.
+      if ((nodes.length > 0) && (nodes.last is Text)) {
+        final newNode = new Text('${nodes.last.text}$text');
+        nodes[nodes.length - 1] = newNode;
+      } else {
+        nodes.add(new Text(text));
+      }
+    }
+  }
+
+  addNode(Node node) {
+    _stack.last.children.add(node);
+  }
+
+  // TODO(rnystrom): Only need this because RegExp doesn't let you start
+  // searching from a given offset.
+  String get currentSource => source.substring(pos, source.length);
+
+  bool get isDone => pos == source.length;
+
+  void advanceBy(int length) {
+    pos += length;
+  }
+
+  void consume(int length) {
+    pos += length;
+    start = pos;
+  }
+}
+
+/// Represents one kind of markdown tag that can be parsed.
+abstract class InlineSyntax {
+  final RegExp pattern;
+
+  InlineSyntax(String pattern)
+    : pattern = new RegExp(pattern, multiLine: true);
+
+  bool tryMatch(InlineParser parser) {
+    final startMatch = pattern.firstMatch(parser.currentSource);
+    if ((startMatch != null) && (startMatch.start == 0)) {
+      // Write any existing plain text up to this point.
+      parser.writeText();
+
+      if (onMatch(parser, startMatch)) {
+        parser.consume(startMatch[0].length);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  bool onMatch(InlineParser parser, Match match);
+}
+
+/// Matches stuff that should just be passed through as straight text.
+class TextSyntax extends InlineSyntax {
+  String substitute;
+  TextSyntax(String pattern, {String sub})
+    : super(pattern),
+      substitute = sub;
+
+  bool onMatch(InlineParser parser, Match match) {
+    if (substitute == null) {
+      // Just use the original matched text.
+      parser.advanceBy(match[0].length);
+      return false;
+    }
+
+    // Insert the substitution.
+    parser.addNode(new Text(substitute));
+    return true;
+  }
+}
+
+/// Matches autolinks like `<http://foo.com>`.
+class AutolinkSyntax extends InlineSyntax {
+  AutolinkSyntax()
+    : super(r'<((http|https|ftp)://[^>]*)>');
+  // TODO(rnystrom): Make case insensitive.
+
+  bool onMatch(InlineParser parser, Match match) {
+    final url = match[1];
+
+    final anchor = new Element.text('a', escapeHtml(url));
+    anchor.attributes['href'] = url;
+    parser.addNode(anchor);
+
+    return true;
+  }
+}
+
+/// Matches syntax that has a pair of tags and becomes an element, like `*` for
+/// `<em>`. Allows nested tags.
+class TagSyntax extends InlineSyntax {
+  final RegExp endPattern;
+  final String tag;
+
+  TagSyntax(String pattern, {String tag, String end})
+    : super(pattern),
+      endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
+      tag = tag;
+    // TODO(rnystrom): Doing this.field doesn't seem to work with named args.
+
+  bool onMatch(InlineParser parser, Match match) {
+    parser._stack.add(new TagState(parser.pos,
+      parser.pos + match[0].length, this));
+    return true;
+  }
+
+  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
+    parser.addNode(new Element(tag, state.children));
+    return true;
+  }
+}
+
+/// Matches inline links like `[blah] [id]` and `[blah] (url)`.
+class LinkSyntax extends TagSyntax {
+  /// The regex for the end of a link needs to handle both reference style and
+  /// inline styles as well as optional titles for inline links. To make that
+  /// a bit more palatable, this breaks it into pieces.
+  static get linkPattern {
+    final refLink    = r'\s?\[([^\]]*)\]';        // "[id]" reflink id.
+    final title      = r'(?:[ ]*"([^"]+)"|)';     // Optional title in quotes.
+    final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
+    return '\](?:($refLink|$inlineLink)|)';
+
+    // The groups matched by this are:
+    // 1: Will be non-empty if it's either a ref or inline link. Will be empty
+    //    if it's just a bare pair of square brackets with nothing after them.
+    // 2: Contains the id inside [] for a reference-style link.
+    // 3: Contains the URL for an inline link.
+    // 4: Contains the title, if present, for an inline link.
+  }
+
+  LinkSyntax()
+    : super(r'\[', end: linkPattern);
+
+  bool onMatchEnd(InlineParser parser, Match match, TagState state) {
+    var url;
+    var title;
+
+    // If we didn't match refLink or inlineLink, then it means there was
+    // nothing after the first square bracket, so it isn't a normal markdown
+    // link at all. Instead, we allow users of the library to specify a special
+    // resolver function ([setImplicitLinkResolver]) that may choose to handle
+    // this. Otherwise, it's just treated as plain text.
+    if ((match[1] == null) || (match[1] == '')) {
+      if (_implicitLinkResolver == null) return false;
+
+      // Only allow implicit links if the content is just text.
+      // TODO(rnystrom): Do we want to relax this?
+      if (state.children.length != 1) return false;
+      if (state.children[0] is! Text) return false;
+
+      Text link = state.children[0];
+
+      // See if we have a resolver that will generate a link for us.
+      final node = _implicitLinkResolver(link.text);
+      if (node == null) return false;
+
+      parser.addNode(node);
+      return true;
+    }
+
+    if ((match[3] != null) && (match[3] != '')) {
+      // Inline link like [foo](url).
+      url = match[3];
+      title = match[4];
+
+      // For whatever reason, markdown allows angle-bracketed URLs here.
+      if (url.startsWith('<') && url.endsWith('>')) {
+        url = url.substring(1, url.length - 1);
+      }
+    } else {
+      // Reference link like [foo] [bar].
+      var id = match[2];
+      if (id == '') {
+        // The id is empty ("[]") so infer it from the contents.
+        id = parser.source.substring(state.startPos + 1, parser.pos);
+      }
+
+      // References are case-insensitive.
+      id = id.toLowerCase();
+
+      // Look up the link.
+      final link = parser.document.refLinks[id];
+      // If it's an unknown link just emit plaintext.
+      if (link == null) return false;
+
+      url = link.url;
+      title = link.title;
+    }
+
+    final anchor = new Element('a', state.children);
+    anchor.attributes['href'] = escapeHtml(url);
+    if ((title != null) && (title != '')) {
+      anchor.attributes['title'] = escapeHtml(title);
+    }
+
+    parser.addNode(anchor);
+    return true;
+  }
+}
+
+/// Matches backtick-enclosed inline code blocks.
+class CodeSyntax extends InlineSyntax {
+  CodeSyntax(String pattern)
+    : super(pattern);
+
+  bool onMatch(InlineParser parser, Match match) {
+    parser.addNode(new Element.text('code', escapeHtml(match[1])));
+    return true;
+  }
+}
+
+/// Keeps track of a currently open tag while it is being parsed. The parser
+/// maintains a stack of these so it can handle nested tags.
+class TagState {
+  /// The point in the original source where this tag started.
+  int startPos;
+
+  /// The point in the original source where open tag ended.
+  int endPos;
+
+  /// The syntax that created this node.
+  final TagSyntax syntax;
+
+  /// The children of this node. Will be `null` for text nodes.
+  final List<Node> children;
+
+  TagState(this.startPos, this.endPos, this.syntax)
+    : children = <Node>[];
+
+  /// Attempts to close this tag by matching the current text against its end
+  /// pattern.
+  bool tryMatch(InlineParser parser) {
+    Match endMatch = syntax.endPattern.firstMatch(parser.currentSource);
+    if ((endMatch != null) && (endMatch.start == 0)) {
+      // Close the tag.
+      close(parser, endMatch);
+      return true;
+    }
+
+    return false;
+  }
+
+  /// Pops this tag off the stack, completes it, and adds it to the output.
+  /// Will discard any unmatched tags that happen to be above it on the stack.
+  /// If this is the last node in the stack, returns its children.
+  List<Node> close(InlineParser parser, Match endMatch) {
+    // If there are unclosed tags on top of this one when it's closed, that
+    // means they are mismatched. Mismatched tags are treated as plain text in
+    // markdown. So for each tag above this one, we write its start tag as text
+    // and then adds its children to this one's children.
+    int index = parser._stack.indexOf(this);
+
+    // Remove the unmatched children.
+    final unmatchedTags = parser._stack.getRange(index + 1,
+        parser._stack.length - index - 1);
+    parser._stack.removeRange(index + 1, parser._stack.length - index - 1);
+
+    // Flatten them out onto this tag.
+    for (final unmatched in unmatchedTags) {
+      // Write the start tag as text.
+      parser.writeTextRange(unmatched.startPos, unmatched.endPos);
+
+      // Bequeath its children unto this tag.
+      children.addAll(unmatched.children);
+    }
+
+    // Pop this off the stack.
+    parser.writeText();
+    parser._stack.removeLast();
+
+    // If the stack is empty now, this is the special "results" node.
+    if (parser._stack.length == 0) return children;
+
+    // We are still parsing, so add this to its parent's children.
+    if (syntax.onMatchEnd(parser, endMatch, this)) {
+      parser.consume(endMatch[0].length);
+    } else {
+      // Didn't close correctly so revert to text.
+      parser.start = startPos;
+      parser.advanceBy(endMatch[0].length);
+    }
+
+    return null;
+  }
+}
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
new file mode 100644
index 0000000..3875de0
--- /dev/null
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -0,0 +1,883 @@
+// Copyright (c) 2011, 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.
+
+/// Unit tests for markdown.
+library markdownTests;
+
+// TODO(rnystrom): Use "package:" URL (#4968).
+import '../lib/markdown.dart';
+
+// TODO(rnystrom): Better path to unittest.
+import 'package:unittest/unittest.dart';
+
+/// Most of these tests are based on observing how showdown behaves:
+/// http://softwaremaniacs.org/playground/showdown-highlight/
+void main() {
+  group('Paragraphs', () {
+    validate('consecutive lines form a single paragraph', '''
+        This is the first line.
+        This is the second line.
+        ''', '''
+        <p>This is the first line.
+        This is the second line.</p>
+        ''');
+
+    // TODO(rnystrom): The rules here for what happens to lines following a
+    // paragraph appear to be completely arbitrary in markdown. If it makes the
+    // code significantly cleaner, we should consider ourselves free to change
+    // these tests.
+
+    validate('are terminated by a header', '''
+        para
+        # header
+        ''', '''
+        <p>para</p>
+        <h1>header</h1>
+        ''');
+
+    validate('are terminated by a setext header', '''
+        para
+        header
+        ==
+        ''', '''
+        <p>para</p>
+        <h1>header</h1>
+        ''');
+
+    validate('are terminated by a hr', '''
+        para
+        ___
+        ''', '''
+        <p>para</p>
+        <hr />
+        ''');
+
+    validate('consume an unordered list', '''
+        para
+        * list
+        ''', '''
+        <p>para
+        * list</p>
+        ''');
+
+    validate('consume an ordered list', '''
+        para
+        1. list
+        ''', '''
+        <p>para
+        1. list</p>
+        ''');
+
+    // Windows line endings have a \r\n format
+    // instead of the unix \n format.
+    validate('take account of windows line endings', '''
+        line1\r\n\r\n        line2\r\n
+        ''', '''
+        <p>line1</p>
+        <p>line2</p>
+        ''');
+  });
+
+  group('Setext headers', () {
+    validate('h1', '''
+        text
+        ===
+        ''', '''
+        <h1>text</h1>
+        ''');
+
+    validate('h2', '''
+        text
+        ---
+        ''', '''
+        <h2>text</h2>
+        ''');
+
+    validate('h1 on first line becomes text', '''
+        ===
+        ''', '''
+        <p>===</p>
+        ''');
+
+    validate('h2 on first line becomes text', '''
+        -
+        ''', '''
+        <p>-</p>
+        ''');
+
+    validate('h1 turns preceding list into text', '''
+        - list
+        ===
+        ''', '''
+        <h1>- list</h1>
+        ''');
+
+    validate('h2 turns preceding list into text', '''
+        - list
+        ===
+        ''', '''
+        <h1>- list</h1>
+        ''');
+
+    validate('h1 turns preceding blockquote into text', '''
+        > quote
+        ===
+        ''', '''
+        <h1>> quote</h1>
+        ''');
+
+    validate('h2 turns preceding blockquote into text', '''
+        > quote
+        ===
+        ''', '''
+        <h1>> quote</h1>
+        ''');
+  });
+
+  group('Headers', () {
+    validate('h1', '''
+        # header
+        ''', '''
+        <h1>header</h1>
+        ''');
+
+    validate('h2', '''
+        ## header
+        ''', '''
+        <h2>header</h2>
+        ''');
+
+    validate('h3', '''
+        ### header
+        ''', '''
+        <h3>header</h3>
+        ''');
+
+    validate('h4', '''
+        #### header
+        ''', '''
+        <h4>header</h4>
+        ''');
+
+    validate('h5', '''
+        ##### header
+        ''', '''
+        <h5>header</h5>
+        ''');
+
+    validate('h6', '''
+        ###### header
+        ''', '''
+        <h6>header</h6>
+        ''');
+
+    validate('trailing "#" are removed', '''
+        # header ######
+        ''', '''
+        <h1>header</h1>
+        ''');
+
+  });
+
+  group('Unordered lists', () {
+    validate('asterisk, plus and hyphen', '''
+        * star
+        - dash
+        + plus
+        ''', '''
+        <ul>
+          <li>star</li>
+          <li>dash</li>
+          <li>plus</li>
+        </ul>
+        ''');
+
+    validate('allow numbered lines after first', '''
+        * a
+        1. b
+        ''', '''
+        <ul>
+          <li>a</li>
+          <li>b</li>
+        </ul>
+        ''');
+
+    validate('allow a tab after the marker', '''
+        *\ta
+        +\tb
+        -\tc
+        1.\td
+        ''', '''
+        <ul>
+          <li>a</li>
+          <li>b</li>
+          <li>c</li>
+          <li>d</li>
+        </ul>
+        ''');
+
+    validate('wrap items in paragraphs if blank lines separate', '''
+        * one
+
+        * two
+        ''', '''
+        <ul>
+          <li><p>one</p></li>
+          <li><p>two</p></li>
+        </ul>
+        ''');
+
+    validate('force paragraph on item before and after blank lines', '''
+        *   one
+        *   two
+
+        *   three
+        ''', '''
+        <ul>
+          <li>one</li>
+          <li>
+            <p>two</p>
+          </li>
+          <li>
+            <p>three</p>
+          </li>
+        </ul>
+        ''');
+
+    validate('do not force paragraph if item is already block', '''
+        * > quote
+
+        * # header
+        ''', '''
+        <ul>
+          <li><blockquote><p>quote</p></blockquote></li>
+          <li><h1>header</h1></li>
+        </ul>
+        ''');
+
+    validate('can contain multiple paragraphs', '''
+        *   one
+
+            two
+
+        *   three
+        ''', '''
+        <ul>
+          <li>
+            <p>one</p>
+            <p>two</p>
+          </li>
+          <li>
+            <p>three</p>
+          </li>
+        </ul>
+        ''');
+
+    validate('can span newlines', '''
+        *   one
+            two
+        *   three
+        ''', '''
+        <ul>
+          <li>
+            <p>one
+            two</p>
+          </li>
+          <li>
+            three
+          </li>
+        </ul>
+        ''');
+
+    // TODO(rnystrom): This is how most other markdown parsers handle
+    // this but that seems like a nasty special case. For now, let's not
+    // worry about it.
+    /*
+    validate('can nest using indentation', '''
+        *   parent
+            *   child
+        ''', '''
+        <ul>
+        <li>parent
+        <ul><li>child</li></ul></li>
+        </ul>
+        ''');
+    */
+  });
+
+  group('Ordered lists', () {
+    validate('start with numbers', '''
+        1. one
+        45.  two
+           12345. three
+        ''', '''
+        <ol>
+          <li>one</li>
+          <li>two</li>
+          <li>three</li>
+        </ol>
+        ''');
+
+    validate('allow unordered lines after first', '''
+        1. a
+        * b
+        ''', '''
+        <ol>
+          <li>a</li>
+          <li>b</li>
+        </ol>
+        ''');
+  });
+
+  group('Blockquotes', () {
+    validate('single line', '''
+        > blah
+        ''', '''
+        <blockquote>
+          <p>blah</p>
+        </blockquote>
+        ''');
+
+    validate('with two paragraphs', '''
+        > first
+        >
+        > second
+        ''', '''
+        <blockquote>
+          <p>first</p>
+          <p>second</p>
+        </blockquote>
+        ''');
+
+    validate('nested', '''
+        > one
+        >> two
+        > > > three
+        ''', '''
+        <blockquote>
+          <p>one</p>
+          <blockquote>
+            <p>two</p>
+            <blockquote>
+              <p>three</p>
+            </blockquote>
+          </blockquote>
+        </blockquote>
+        ''');
+  });
+
+  group('Code blocks', () {
+    validate('single line', '''
+            code
+        ''', '''
+        <pre><code>code</code></pre>
+        ''');
+
+    validate('include leading whitespace after indentation', '''
+            zero
+             one
+              two
+               three
+        ''', '''
+        <pre><code>zero
+         one
+          two
+           three</code></pre>
+        ''');
+
+    validate('code blocks separated by newlines form one block', '''
+            zero
+            one
+
+            two
+
+            three
+        ''', '''
+        <pre><code>zero
+         one
+
+         two
+
+         three</code></pre>
+        ''');
+
+    validate('code blocks separated by two newlines form multiple blocks', '''
+            zero
+            one
+
+
+            two
+
+
+            three
+        ''', '''
+        <pre><code>zero
+         one</code></pre>
+        <pre><code>two</code></pre>
+        <pre><code>three</code></pre>
+        ''');
+
+    validate('escape HTML characters', '''
+            <&>
+        ''', '''
+        <pre><code>&lt;&amp;&gt;</code></pre>
+        ''');
+  });
+
+  group('Horizontal rules', () {
+    validate('from dashes', '''
+        ---
+        ''', '''
+        <hr />
+        ''');
+
+    validate('from asterisks', '''
+        ***
+        ''', '''
+        <hr />
+        ''');
+
+    validate('from underscores', '''
+        ___
+        ''', '''
+        <hr />
+        ''');
+
+    validate('can include up to two spaces', '''
+        _ _  _
+        ''', '''
+        <hr />
+        ''');
+  });
+
+  group('Block-level HTML', () {
+    validate('single line', '''
+        <table></table>
+        ''', '''
+        <table></table>
+        ''');
+
+    validate('multi-line', '''
+        <table>
+            blah
+        </table>
+        ''', '''
+        <table>
+            blah
+        </table>
+        ''');
+
+    validate('blank line ends block', '''
+        <table>
+            blah
+        </table>
+
+        para
+        ''', '''
+        <table>
+            blah
+        </table>
+        <p>para</p>
+        ''');
+
+    validate('HTML can be bogus', '''
+        <bogus>
+        blah
+        </weird>
+
+        para
+        ''', '''
+        <bogus>
+        blah
+        </weird>
+        <p>para</p>
+        ''');
+  });
+
+  group('Strong', () {
+    validate('using asterisks', '''
+        before **strong** after
+        ''', '''
+        <p>before <strong>strong</strong> after</p>
+        ''');
+
+    validate('using underscores', '''
+        before __strong__ after
+        ''', '''
+        <p>before <strong>strong</strong> after</p>
+        ''');
+
+    validate('unmatched asterisks', '''
+        before ** after
+        ''', '''
+        <p>before ** after</p>
+        ''');
+
+    validate('unmatched underscores', '''
+        before __ after
+        ''', '''
+        <p>before __ after</p>
+        ''');
+
+    validate('multiple spans in one text', '''
+        a **one** b __two__ c
+        ''', '''
+        <p>a <strong>one</strong> b <strong>two</strong> c</p>
+        ''');
+
+    validate('multi-line', '''
+        before **first
+        second** after
+        ''', '''
+        <p>before <strong>first
+        second</strong> after</p>
+        ''');
+  });
+
+  group('Emphasis and strong', () {
+    validate('single asterisks', '''
+        before *em* after
+        ''', '''
+        <p>before <em>em</em> after</p>
+        ''');
+
+    validate('single underscores', '''
+        before _em_ after
+        ''', '''
+        <p>before <em>em</em> after</p>
+        ''');
+
+    validate('double asterisks', '''
+        before **strong** after
+        ''', '''
+        <p>before <strong>strong</strong> after</p>
+        ''');
+
+    validate('double underscores', '''
+        before __strong__ after
+        ''', '''
+        <p>before <strong>strong</strong> after</p>
+        ''');
+
+    validate('unmatched asterisk', '''
+        before *after
+        ''', '''
+        <p>before *after</p>
+        ''');
+
+    validate('unmatched underscore', '''
+        before _after
+        ''', '''
+        <p>before _after</p>
+        ''');
+
+    validate('multiple spans in one text', '''
+        a *one* b _two_ c
+        ''', '''
+        <p>a <em>one</em> b <em>two</em> c</p>
+        ''');
+
+    validate('multi-line', '''
+        before *first
+        second* after
+        ''', '''
+        <p>before <em>first
+        second</em> after</p>
+        ''');
+
+    validate('not processed when surrounded by spaces', '''
+        a * b * c _ d _ e
+        ''', '''
+        <p>a * b * c _ d _ e</p>
+        ''');
+
+    validate('strong then emphasis', '''
+        **strong***em*
+        ''', '''
+        <p><strong>strong</strong><em>em</em></p>
+        ''');
+
+    validate('emphasis then strong', '''
+        *em***strong**
+        ''', '''
+        <p><em>em</em><strong>strong</strong></p>
+        ''');
+
+    validate('emphasis inside strong', '''
+        **strong *em***
+        ''', '''
+        <p><strong>strong <em>em</em></strong></p>
+        ''');
+
+    validate('mismatched in nested', '''
+        *a _b* c_
+        ''', '''
+        <p><em>a _b</em> c_</p>
+        ''');
+
+    validate('cannot nest tags of same type', '''
+        *a _b *c* d_ e*
+        ''', '''
+        <p><em>a _b </em>c<em> d_ e</em></p>
+        ''');
+  });
+
+  group('Inline code', () {
+    validate('simple case', '''
+        before `source` after
+        ''', '''
+        <p>before <code>source</code> after</p>
+        ''');
+
+    validate('unmatched backtick', '''
+        before ` after
+        ''', '''
+        <p>before ` after</p>
+        ''');
+    validate('multiple spans in one text', '''
+        a `one` b `two` c
+        ''', '''
+        <p>a <code>one</code> b <code>two</code> c</p>
+        ''');
+
+    validate('multi-line', '''
+        before `first
+        second` after
+        ''', '''
+        <p>before <code>first
+        second</code> after</p>
+        ''');
+
+    validate('double backticks', '''
+        before ``can `contain` backticks`` after
+        ''', '''
+        <p>before <code>can `contain` backticks</code> after</p>
+        ''');
+
+    validate('double backticks with spaces', '''
+        before `` `tick` `` after
+        ''', '''
+        <p>before <code>`tick`</code> after</p>
+        ''');
+
+    validate('multiline double backticks with spaces', '''
+        before ``in `tick`
+        another`` after
+        ''', '''
+        <p>before <code>in `tick`
+        another</code> after</p>
+        ''');
+
+    validate('ignore markup inside code', '''
+        before `*b* _c_` after
+        ''', '''
+        <p>before <code>*b* _c_</code> after</p>
+        ''');
+
+    validate('escape HTML characters', '''
+        `<&>`
+        ''', '''
+        <p><code>&lt;&amp;&gt;</code></p>
+        ''');
+
+    validate('escape HTML tags', '''
+        '*' `<em>`
+        ''', '''
+        <p>'*' <code>&lt;em&gt;</code></p>
+        ''');
+  });
+
+  group('HTML encoding', () {
+    validate('less than and ampersand are escaped', '''
+        < &
+        ''', '''
+        <p>&lt; &amp;</p>
+        ''');
+    validate('greater than is not escaped', '''
+        not you >
+        ''', '''
+        <p>not you ></p>
+        ''');
+    validate('existing entities are untouched', '''
+        &amp;
+        ''', '''
+        <p>&amp;</p>
+        ''');
+  });
+
+  group('Autolinks', () {
+    validate('basic link', '''
+        before <http://foo.com/> after
+        ''', '''
+        <p>before <a href="http://foo.com/">http://foo.com/</a> after</p>
+        ''');
+    validate('handles ampersand in url', '''
+        <http://foo.com/?a=1&b=2>
+        ''', '''
+        <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&amp;b=2</a></p>
+        ''');
+  });
+
+  group('Reference links', () {
+    validate('double quotes for title', '''
+        links [are] [a] awesome
+
+        [a]: http://foo.com "woo"
+        ''', '''
+        <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+        ''');
+    validate('single quoted title', """
+        links [are] [a] awesome
+
+        [a]: http://foo.com 'woo'
+        """, '''
+        <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+        ''');
+    validate('parentheses for title', '''
+        links [are] [a] awesome
+
+        [a]: http://foo.com (woo)
+        ''', '''
+        <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+        ''');
+    validate('no title', '''
+        links [are] [a] awesome
+
+        [a]: http://foo.com
+        ''', '''
+        <p>links <a href="http://foo.com">are</a> awesome</p>
+        ''');
+    validate('unknown link becomes plaintext', '''
+        [not] [known]
+        ''', '''
+        <p>[not] [known]</p>
+        ''');
+    validate('can style link contents', '''
+        links [*are*] [a] awesome
+
+        [a]: http://foo.com
+        ''', '''
+        <p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
+        ''');
+    validate('inline styles after a bad link are processed', '''
+        [bad] `code`
+        ''', '''
+        <p>[bad] <code>code</code></p>
+        ''');
+    validate('empty reference uses text from link', '''
+        links [are][] awesome
+
+        [are]: http://foo.com
+        ''', '''
+        <p>links <a href="http://foo.com">are</a> awesome</p>
+        ''');
+    validate('references are case-insensitive', '''
+        links [ARE][] awesome
+
+        [are]: http://foo.com
+        ''', '''
+        <p>links <a href="http://foo.com">ARE</a> awesome</p>
+        ''');
+  });
+
+  group('Inline links', () {
+    validate('double quotes for title', '''
+        links [are](http://foo.com "woo") awesome
+        ''', '''
+        <p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
+        ''');
+    validate('no title', '''
+        links [are] (http://foo.com) awesome
+        ''', '''
+        <p>links <a href="http://foo.com">are</a> awesome</p>
+        ''');
+    validate('can style link contents', '''
+        links [*are*](http://foo.com) awesome
+        ''', '''
+        <p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
+        ''');
+  });
+}
+
+/**
+ * Removes eight spaces of leading indentation from a multiline string.
+ *
+ * Note that this is very sensitive to how the literals are styled. They should
+ * be:
+ *     '''
+ *     Text starts on own line. Lines up with subsequent lines.
+ *     Lines are indented exactly 8 characters from the left margin.'''
+ *
+ * This does nothing if text is only a single line.
+ */
+// TODO(nweiz): Make this auto-detect the indentation level from the first
+// non-whitespace line.
+String cleanUpLiteral(String text) {
+  var lines = text.split('\n');
+  if (lines.length <= 1) return text;
+
+  for (var j = 0; j < lines.length; j++) {
+    if (lines[j].length > 8) {
+      lines[j] = lines[j].substring(8, lines[j].length);
+    } else {
+      lines[j] = '';
+    }
+  }
+
+  return Strings.join(lines, '\n');
+}
+
+validate(String description, String markdown, String html,
+         {bool verbose: false}) {
+  test(description, () {
+    markdown = cleanUpLiteral(markdown);
+    html = cleanUpLiteral(html);
+
+    var result = markdownToHtml(markdown);
+    var passed = compareOutput(html, result);
+
+    if (!passed) {
+      // Remove trailing newline.
+      html = html.substring(0, html.length - 1);
+
+      print('FAIL: $description');
+      print('  expect: ${html.replaceAll("\n", "\n          ")}');
+      print('  actual: ${result.replaceAll("\n", "\n          ")}');
+      print('');
+    }
+
+    expect(passed, isTrue, verbose: verbose);
+  });
+}
+
+/// Does a loose comparison of the two strings of HTML. Ignores differences in
+/// newlines and indentation.
+compareOutput(String a, String b) {
+  int i = 0;
+  int j = 0;
+
+  skipIgnored(String s, int i) {
+    // Ignore newlines.
+    while ((i < s.length) && (s[i] == '\n')) {
+      i++;
+      // Ignore indentation.
+      while ((i < s.length) && (s[i] == ' ')) i++;
+    }
+
+    return i;
+  }
+
+  while (true) {
+    i = skipIgnored(a, i);
+    j = skipIgnored(b, j);
+
+    // If one string runs out of non-ignored strings, the other must too.
+    if (i == a.length) return j == b.length;
+    if (j == b.length) return i == a.length;
+
+    if (a[i] != b[j]) return false;
+    i++;
+    j++;
+  }
+}
diff --git a/pkgs/markdown/test/pubspec.yaml b/pkgs/markdown/test/pubspec.yaml
new file mode 100644
index 0000000..f45db3a
--- /dev/null
+++ b/pkgs/markdown/test/pubspec.yaml
@@ -0,0 +1,7 @@
+name: markdown
+author: "Dart Team <misc@dartlang.org>"
+# homepage: https://github.com/dart-lang/csslib
+description: A library for converting markdown to HTML.
+version: 0.3.4
+dependencies:
+  unittest: any
\ No newline at end of file