Attach source range information to parsed YAML nodes.

Also release yaml 1.0.0.

R=efortuna@google.com, rnystrom@google.com, sigmund@google.com

Review URL: https://codereview.chromium.org//302313007

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/yaml@36937 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/yaml/CHANGELOG.md b/pkgs/yaml/CHANGELOG.md
index 3520d4c..4b243d8 100644
--- a/pkgs/yaml/CHANGELOG.md
+++ b/pkgs/yaml/CHANGELOG.md
@@ -1,3 +1,18 @@
+## 1.0.0
+
+* **Backwards incompatibility**: The data structures returned by `loadYaml` and
+  `loadYamlStream` are now immutable.
+
+* **Backwards incompatibility**: The interface of the `YamlMap` class has
+  changed substantially in numerous ways. External users may no longer construct
+  their own instances.
+
+* Maps and lists returned by `loadYaml` and `loadYamlStream` now contain
+  information about their source locations.
+
+* A new `loadYamlNode` function returns the source location of top-level scalars
+  as well.
+
 ## 0.10.0
 
 * Improve error messages when a file fails to parse.
diff --git a/pkgs/yaml/lib/src/composer.dart b/pkgs/yaml/lib/src/composer.dart
index 3178096..4c19da1 100644
--- a/pkgs/yaml/lib/src/composer.dart
+++ b/pkgs/yaml/lib/src/composer.dart
@@ -4,6 +4,8 @@
 
 library yaml.composer;
 
+import 'package:source_maps/source_maps.dart';
+
 import 'model.dart';
 import 'visitor.dart';
 import 'yaml_exception.dart';
@@ -45,13 +47,13 @@
   /// Currently this only supports the YAML core type schema.
   Node visitScalar(ScalarNode scalar) {
     if (scalar.tag.name == "!") {
-      return setAnchor(scalar, parseString(scalar.content));
+      return setAnchor(scalar, parseString(scalar));
     } else if (scalar.tag.name == "?") {
       for (var fn in [parseNull, parseBool, parseInt, parseFloat]) {
-        var result = fn(scalar.content);
+        var result = fn(scalar);
         if (result != null) return result;
       }
-      return setAnchor(scalar, parseString(scalar.content));
+      return setAnchor(scalar, parseString(scalar));
     }
 
     var result = _parseByTag(scalar);
@@ -62,11 +64,11 @@
 
   ScalarNode _parseByTag(ScalarNode scalar) {
     switch (scalar.tag.name) {
-      case "null": return parseNull(scalar.content);
-      case "bool": return parseBool(scalar.content);
-      case "int": return parseInt(scalar.content);
-      case "float": return parseFloat(scalar.content);
-      case "str": return parseString(scalar.content);
+      case "null": return parseNull(scalar);
+      case "bool": return parseBool(scalar);
+      case "int": return parseInt(scalar);
+      case "float": return parseFloat(scalar);
+      case "str": return parseString(scalar);
     }
     throw new YamlException('Undefined tag: ${scalar.tag}.');
   }
@@ -78,7 +80,8 @@
       throw new YamlException("Invalid tag for sequence: ${seq.tag}.");
     }
 
-    var result = setAnchor(seq, new SequenceNode(Tag.yaml('seq'), null));
+    var result = setAnchor(seq,
+        new SequenceNode(Tag.yaml('seq'), null, seq.span));
     result.content = super.visitSequence(seq);
     return result;
   }
@@ -90,7 +93,8 @@
       throw new YamlException("Invalid tag for mapping: ${map.tag}.");
     }
 
-    var result = setAnchor(map, new MappingNode(Tag.yaml('map'), null));
+    var result = setAnchor(map,
+        new MappingNode(Tag.yaml('map'), null, map.span));
     result.content = super.visitMapping(map);
     return result;
   }
@@ -105,36 +109,40 @@
   }
 
   /// Parses a null scalar.
-  ScalarNode parseNull(String content) {
-    if (!new RegExp(r"^(null|Null|NULL|~|)$").hasMatch(content)) return null;
-    return new ScalarNode(Tag.yaml("null"), value: null);
+  ScalarNode parseNull(ScalarNode scalar) {
+    if (new RegExp(r"^(null|Null|NULL|~|)$").hasMatch(scalar.content)) {
+      return new ScalarNode(Tag.yaml("null"), scalar.span, value: null);
+    } else {
+      return null;
+    }
   }
 
   /// Parses a boolean scalar.
-  ScalarNode parseBool(String content) {
+  ScalarNode parseBool(ScalarNode scalar) {
     var match = new RegExp(r"^(?:(true|True|TRUE)|(false|False|FALSE))$").
-      firstMatch(content);
+        firstMatch(scalar.content);
     if (match == null) return null;
-    return new ScalarNode(Tag.yaml("bool"), value: match.group(1) != null);
+    return new ScalarNode(Tag.yaml("bool"), scalar.span,
+        value: match.group(1) != null);
   }
 
   /// Parses an integer scalar.
-  ScalarNode parseInt(String content) {
-    var match = new RegExp(r"^[-+]?[0-9]+$").firstMatch(content);
+  ScalarNode parseInt(ScalarNode scalar) {
+    var match = new RegExp(r"^[-+]?[0-9]+$").firstMatch(scalar.content);
     if (match != null) {
-      return new ScalarNode(Tag.yaml("int"),
+      return new ScalarNode(Tag.yaml("int"), scalar.span,
           value: int.parse(match.group(0)));
     }
 
-    match = new RegExp(r"^0o([0-7]+)$").firstMatch(content);
+    match = new RegExp(r"^0o([0-7]+)$").firstMatch(scalar.content);
     if (match != null) {
       int n = int.parse(match.group(1), radix: 8);
-      return new ScalarNode(Tag.yaml("int"), value: n);
+      return new ScalarNode(Tag.yaml("int"), scalar.span, value: n);
     }
 
-    match = new RegExp(r"^0x[0-9a-fA-F]+$").firstMatch(content);
+    match = new RegExp(r"^0x[0-9a-fA-F]+$").firstMatch(scalar.content);
     if (match != null) {
-      return new ScalarNode(Tag.yaml("int"),
+      return new ScalarNode(Tag.yaml("int"), scalar.span,
           value: int.parse(match.group(0)));
     }
 
@@ -142,33 +150,33 @@
   }
 
   /// Parses a floating-point scalar.
-  ScalarNode parseFloat(String content) {
+  ScalarNode parseFloat(ScalarNode scalar) {
     var match = new RegExp(
-        r"^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$").
-      firstMatch(content);
+          r"^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$").
+        firstMatch(scalar.content);
     if (match != null) {
       // YAML allows floats of the form "0.", but Dart does not. Fix up those
       // floats by removing the trailing dot.
       var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), "");
-      return new ScalarNode(Tag.yaml("float"),
+      return new ScalarNode(Tag.yaml("float"), scalar.span,
           value: double.parse(matchStr));
     }
 
-    match = new RegExp(r"^([+-]?)\.(inf|Inf|INF)$").firstMatch(content);
+    match = new RegExp(r"^([+-]?)\.(inf|Inf|INF)$").firstMatch(scalar.content);
     if (match != null) {
       var value = match.group(1) == "-" ? -double.INFINITY : double.INFINITY;
-      return new ScalarNode(Tag.yaml("float"), value: value);
+      return new ScalarNode(Tag.yaml("float"), scalar.span, value: value);
     }
 
-    match = new RegExp(r"^\.(nan|NaN|NAN)$").firstMatch(content);
+    match = new RegExp(r"^\.(nan|NaN|NAN)$").firstMatch(scalar.content);
     if (match != null) {
-      return new ScalarNode(Tag.yaml("float"), value: double.NAN);
+      return new ScalarNode(Tag.yaml("float"), scalar.span, value: double.NAN);
     }
 
     return null;
   }
 
   /// Parses a string scalar.
-  ScalarNode parseString(String content) =>
-    new ScalarNode(Tag.yaml("str"), value: content);
+  ScalarNode parseString(ScalarNode scalar) =>
+    new ScalarNode(Tag.yaml("str"), scalar.span, value: scalar.content);
 }
diff --git a/pkgs/yaml/lib/src/constructor.dart b/pkgs/yaml/lib/src/constructor.dart
index 116809d..a8224a4 100644
--- a/pkgs/yaml/lib/src/constructor.dart
+++ b/pkgs/yaml/lib/src/constructor.dart
@@ -4,9 +4,10 @@
 
 library yaml.constructor;
 
+import 'equality.dart';
 import 'model.dart';
 import 'visitor.dart';
-import 'yaml_map.dart';
+import 'yaml_node.dart';
 
 /// Takes a parsed and composed YAML document (what the spec calls the
 /// "representation graph") and creates native Dart objects that represent that
@@ -16,43 +17,54 @@
   final Node _root;
 
   /// Map from anchor names to the most recent Dart node with that anchor.
-  final _anchors = <String, dynamic>{};
+  final _anchors = <String, YamlNode>{};
 
   Constructor(this._root);
 
   /// Runs the Constructor to produce a Dart object.
-  construct() => _root.visit(this);
+  YamlNode construct() => _root.visit(this);
 
   /// Returns the value of a scalar.
-  visitScalar(ScalarNode scalar) => scalar.value;
+  YamlScalar visitScalar(ScalarNode scalar) =>
+      new YamlScalar(scalar.value, scalar.span);
 
   /// Converts a sequence into a List of Dart objects.
-  visitSequence(SequenceNode seq) {
+  YamlList visitSequence(SequenceNode seq) {
     var anchor = getAnchor(seq);
     if (anchor != null) return anchor;
-    var dartSeq = setAnchor(seq, []);
-    dartSeq.addAll(super.visitSequence(seq));
+    var nodes = [];
+    var dartSeq = setAnchor(seq, new YamlList(nodes, seq.span));
+    nodes.addAll(super.visitSequence(seq));
     return dartSeq;
   }
 
-  /// Converts a mapping into a Map of Dart objects.
-  visitMapping(MappingNode map) {
+  /// Converts a mapping into a [Map] of Dart objects.
+  YamlMap visitMapping(MappingNode map) {
     var anchor = getAnchor(map);
     if (anchor != null) return anchor;
-    var dartMap = setAnchor(map, new YamlMap());
-    super.visitMapping(map).forEach((k, v) { dartMap[k] = v; });
+    var nodes = deepEqualsMap();
+    var dartMap = setAnchor(map, new YamlMap(nodes, map.span));
+    super.visitMapping(map).forEach((k, v) => nodes[k] = v);
     return dartMap;
   }
 
-  /// Returns the Dart object that already represents [anchored], if such a
-  /// thing exists.
-  getAnchor(Node anchored) {
+  /// Returns a new Dart object wrapping the object that already represents
+  /// [anchored], if such a thing exists.
+  YamlNode getAnchor(Node anchored) {
     if (anchored.anchor == null) return null;
-    if (_anchors.containsKey(anchored.anchor)) return _anchors[anchored.anchor];
+    var value = _anchors[anchored.anchor];
+    if (vaule == null) return null;
+
+    // Re-wrap [value]'s contents so that it's associated with the span of the
+    // anchor rather than its original definition.
+    if (value is YamlMap) return new YamlMap(value.nodes, anchored.span);
+    if (value is YamlList) return new YamlList(value.nodes, anchored.span);
+    assert(value is YamlScalar);
+    return new YamlScalar(value.value, anchored.span);
   }
 
   /// Records that [value] is the Dart object representing [anchored].
-  setAnchor(Node anchored, value) {
+  YamlNode setAnchor(Node anchored, YamlNode value) {
     if (anchored.anchor == null) return value;
     _anchors[anchored.anchor] = value;
     return value;
diff --git a/pkgs/yaml/lib/src/deep_equals.dart b/pkgs/yaml/lib/src/deep_equals.dart
deleted file mode 100644
index 68fb236..0000000
--- a/pkgs/yaml/lib/src/deep_equals.dart
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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 yaml.deep_equals;
-
-/// Returns whether two objects are structurally equivalent.
-///
-/// This considers `NaN` values to be equivalent. It also handles
-/// self-referential structures.
-bool deepEquals(obj1, obj2) => new _DeepEquals().equals(obj1, obj2);
-
-/// A class that provides access to the list of parent objects used for loop
-/// detection.
-class _DeepEquals {
-  final _parents1 = [];
-  final _parents2 = [];
-
-  /// Returns whether [obj1] and [obj2] are structurally equivalent.
-  bool equals(obj1, obj2) {
-    // _parents1 and _parents2 are guaranteed to be the same size.
-    for (var i = 0; i < _parents1.length; i++) {
-      var loop1 = identical(obj1, _parents1[i]);
-      var loop2 = identical(obj2, _parents2[i]);
-      // If both structures loop in the same place, they're equal at that point
-      // in the structure. If one loops and the other doesn't, they're not
-      // equal.
-      if (loop1 && loop2) return true;
-      if (loop1 || loop2) return false;
-    }
-
-    _parents1.add(obj1);
-    _parents2.add(obj2);
-    try {
-      if (obj1 is List && obj2 is List) {
-        return _listEquals(obj1, obj2);
-      } else if (obj1 is Map && obj2 is Map) {
-        return _mapEquals(obj1, obj2);
-      } else if (obj1 is num && obj2 is num) {
-        return _numEquals(obj1, obj2);
-      } else {
-        return obj1 == obj2;
-      }
-    } finally {
-      _parents1.removeLast();
-      _parents2.removeLast();
-    }
-  }
-
-  /// Returns whether [list1] and [list2] are structurally equal. 
-  bool _listEquals(List list1, List list2) {
-    if (list1.length != list2.length) return false;
-
-    for (var i = 0; i < list1.length; i++) {
-      if (!equals(list1[i], list2[i])) return false;
-    }
-
-    return true;
-  }
-
-  /// Returns whether [map1] and [map2] are structurally equal. 
-  bool _mapEquals(Map map1, Map map2) {
-    if (map1.length != map2.length) return false;
-
-    for (var key in map1.keys) {
-      if (!map2.containsKey(key)) return false;
-      if (!equals(map1[key], map2[key])) return false;
-    }
-
-    return true;
-  }
-
-  /// Returns whether two numbers are equivalent.
-  ///
-  /// This differs from `n1 == n2` in that it considers `NaN` to be equal to
-  /// itself.
-  bool _numEquals(num n1, num n2) {
-    if (n1.isNaN && n2.isNaN) return true;
-    return n1 == n2;
-  }
-}
diff --git a/pkgs/yaml/lib/src/equality.dart b/pkgs/yaml/lib/src/equality.dart
new file mode 100644
index 0000000..5408135
--- /dev/null
+++ b/pkgs/yaml/lib/src/equality.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for 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 yaml.equality;
+
+import 'dart:collection';
+
+import 'package:collection/collection.dart';
+
+import 'yaml_node.dart';
+
+/// Returns a [Map] that compares its keys based on [deepEquals].
+Map deepEqualsMap() => new HashMap(equals: deepEquals, hashCode: deepHashCode);
+
+/// Returns whether two objects are structurally equivalent.
+///
+/// This considers `NaN` values to be equivalent, handles self-referential
+/// structures, and considers [YamlScalar]s to be equal to their values.
+bool deepEquals(obj1, obj2) => new _DeepEquals().equals(obj1, obj2);
+
+/// A class that provides access to the list of parent objects used for loop
+/// detection.
+class _DeepEquals {
+  final _parents1 = [];
+  final _parents2 = [];
+
+  /// Returns whether [obj1] and [obj2] are structurally equivalent.
+  bool equals(obj1, obj2) {
+    if (obj1 is YamlScalar) obj1 = obj1.value;
+    if (obj2 is YamlScalar) obj2 = obj2.value;
+
+    // _parents1 and _parents2 are guaranteed to be the same size.
+    for (var i = 0; i < _parents1.length; i++) {
+      var loop1 = identical(obj1, _parents1[i]);
+      var loop2 = identical(obj2, _parents2[i]);
+      // If both structures loop in the same place, they're equal at that point
+      // in the structure. If one loops and the other doesn't, they're not
+      // equal.
+      if (loop1 && loop2) return true;
+      if (loop1 || loop2) return false;
+    }
+
+    _parents1.add(obj1);
+    _parents2.add(obj2);
+    try {
+      if (obj1 is List && obj2 is List) {
+        return _listEquals(obj1, obj2);
+      } else if (obj1 is Map && obj2 is Map) {
+        return _mapEquals(obj1, obj2);
+      } else if (obj1 is num && obj2 is num) {
+        return _numEquals(obj1, obj2);
+      } else {
+        return obj1 == obj2;
+      }
+    } finally {
+      _parents1.removeLast();
+      _parents2.removeLast();
+    }
+  }
+
+  /// Returns whether [list1] and [list2] are structurally equal. 
+  bool _listEquals(List list1, List list2) {
+    if (list1.length != list2.length) return false;
+
+    for (var i = 0; i < list1.length; i++) {
+      if (!equals(list1[i], list2[i])) return false;
+    }
+
+    return true;
+  }
+
+  /// Returns whether [map1] and [map2] are structurally equal. 
+  bool _mapEquals(Map map1, Map map2) {
+    if (map1.length != map2.length) return false;
+
+    for (var key in map1.keys) {
+      if (!map2.containsKey(key)) return false;
+      if (!equals(map1[key], map2[key])) return false;
+    }
+
+    return true;
+  }
+
+  /// Returns whether two numbers are equivalent.
+  ///
+  /// This differs from `n1 == n2` in that it considers `NaN` to be equal to
+  /// itself.
+  bool _numEquals(num n1, num n2) {
+    if (n1.isNaN && n2.isNaN) return true;
+    return n1 == n2;
+  }
+}
+
+/// Returns a hash code for [obj] such that structurally equivalent objects
+/// will have the same hash code.
+///
+/// This supports deep equality for maps and lists, including those with
+/// self-referential structures, and returns the same hash code for
+/// [YamlScalar]s and their values.
+int deepHashCode(obj) {
+  var parents = [];
+
+  _deepHashCode(value) {
+    if (parents.any((parent) => identical(parent, value))) return -1;
+
+    parents.add(value);
+    try {
+      if (value is Map) {
+        var equality = const UnorderedIterableEquality();
+        return equality.hash(value.keys.map(_deepHashCode)) ^
+            equality.hash(value.values.map(_deepHashCode));
+      } else if (value is Iterable) {
+        return const IterableEquality().hash(value.map(deepHashCode));
+      } else if (value is YamlScalar) {
+        return value.value.hashCode;
+      } else {
+        return value.hashCode;
+      }
+    } finally {
+      parents.removeLast();
+    }
+  }
+
+  return _deepHashCode(obj);
+}
diff --git a/pkgs/yaml/lib/src/model.dart b/pkgs/yaml/lib/src/model.dart
index 564cac6..f0e839c 100644
--- a/pkgs/yaml/lib/src/model.dart
+++ b/pkgs/yaml/lib/src/model.dart
@@ -7,8 +7,10 @@
 /// representation graph.
 library yaml.model;
 
+import 'package:source_maps/source_maps.dart';
+
+import 'equality.dart';
 import 'parser.dart';
-import 'utils.dart';
 import 'visitor.dart';
 import 'yaml_exception.dart';
 
@@ -80,14 +82,17 @@
   /// Any YAML node can have an anchor associated with it.
   String anchor;
 
-  Node(this.tag, [this.anchor]);
+  /// The source span for this node.
+  Span span;
+
+  Node(this.tag, this.span, [this.anchor]);
 
   bool operator ==(other) {
     if (other is! Node) return false;
     return tag == other.tag;
   }
 
-  int get hashCode => hashCodeFor([tag, anchor]);
+  int get hashCode => tag.hashCode ^ anchor.hashCode;
 
   visit(Visitor v);
 }
@@ -97,8 +102,8 @@
   /// The nodes in the sequence.
   List<Node> content;
 
-  SequenceNode(String tagName, this.content)
-    : super(new Tag.sequence(tagName));
+  SequenceNode(String tagName, this.content, Span span)
+    : super(new Tag.sequence(tagName), span);
 
   /// Two sequences are equal if their tags and contents are equal.
   bool operator ==(other) {
@@ -113,14 +118,15 @@
 
   String toString() => '$tag [${content.map((e) => '$e').join(', ')}]';
 
-  int get hashCode => super.hashCode ^ hashCodeFor(content);
+  int get hashCode => super.hashCode ^ deepHashCode(content);
 
   visit(Visitor v) => v.visitSequence(this);
 }
 
 /// An alias node is a reference to an anchor.
 class AliasNode extends Node {
-  AliasNode(String anchor) : super(new Tag.scalar(Tag.yaml("str")), anchor);
+  AliasNode(String anchor, Span span)
+      : super(new Tag.scalar(Tag.yaml("str")), span, anchor);
 
   visit(Visitor v) => v.visitAlias(this);
 }
@@ -139,9 +145,9 @@
   /// be specified for a newly-parsed scalar that hasn't yet been composed.
   /// Value should be specified for a composed scalar, although `null` is a
   /// valid value.
-  ScalarNode(String tagName, {String content, this.value})
+  ScalarNode(String tagName, Span span, {String content, this.value})
    : _content = content,
-     super(new Tag.scalar(tagName));
+     super(new Tag.scalar(tagName), span);
 
   /// Two scalars are equal if their string representations are equal.
   bool operator ==(other) {
@@ -225,8 +231,8 @@
   /// The node map.
   Map<Node, Node> content;
 
-  MappingNode(String tagName, this.content)
-    : super(new Tag.mapping(tagName));
+  MappingNode(String tagName, this.content, Span span)
+    : super(new Tag.mapping(tagName), span);
 
   /// Two mappings are equal if their tags and contents are equal.
   bool operator ==(other) {
@@ -247,7 +253,7 @@
     return '$tag {$strContent}';
   }
 
-  int get hashCode => super.hashCode ^ hashCodeFor(content);
+  int get hashCode => super.hashCode ^ deepHashCode(content);
 
   visit(Visitor v) => v.visitMapping(this);
 }
diff --git a/pkgs/yaml/lib/src/parser.dart b/pkgs/yaml/lib/src/parser.dart
index d203c32..98658e6 100644
--- a/pkgs/yaml/lib/src/parser.dart
+++ b/pkgs/yaml/lib/src/parser.dart
@@ -6,11 +6,13 @@
 
 import 'dart:collection';
 
+import 'package:source_maps/source_maps.dart';
 import 'package:string_scanner/string_scanner.dart';
 
+import 'equality.dart';
 import 'model.dart';
+import 'utils.dart';
 import 'yaml_exception.dart';
-import 'yaml_map.dart';
 
 /// Translates a string of characters into a YAML serialization tree.
 ///
@@ -317,7 +319,7 @@
   }
 
   /// Adds a tag and an anchor to [node], if they're defined.
-  Node addProps(Node node, _Pair<Tag, String> props) {
+  Node addProps(Node node, Pair<Tag, String> props) {
     if (props == null || node == null) return node;
     if (truth(props.first)) node.tag = props.first;
     if (truth(props.last)) node.anchor = props.last;
@@ -325,10 +327,10 @@
   }
 
   /// Creates a MappingNode from [pairs].
-  MappingNode map(List<_Pair<Node, Node>> pairs) {
+  MappingNode map(List<Pair<Node, Node>> pairs, Span span) {
     var content = new Map<Node, Node>();
     pairs.forEach((pair) => content[pair.first] = pair.last);
-    return new MappingNode("?", content);
+    return new MappingNode("?", content, span);
   }
 
   /// Runs [fn] in a context named [name]. Used for error reporting.
@@ -759,7 +761,7 @@
   bool l_directive() => false; // TODO(nweiz): implement
 
   // 96
-  _Pair<Tag, String> c_ns_properties(int indent, int ctx) {
+  Pair<Tag, String> c_ns_properties(int indent, int ctx) {
     var tag, anchor;
     tag = c_ns_tagProperty();
     if (truth(tag)) {
@@ -767,7 +769,7 @@
         if (!truth(s_separate(indent, ctx))) return null;
         return c_ns_anchorProperty();
       });
-      return new _Pair<Tag, String>(tag, anchor);
+      return new Pair<Tag, String>(tag, anchor);
     }
 
     anchor = c_ns_anchorProperty();
@@ -776,7 +778,7 @@
         if (!truth(s_separate(indent, ctx))) return null;
         return c_ns_tagProperty();
       });
-      return new _Pair<Tag, String>(tag, anchor);
+      return new Pair<Tag, String>(tag, anchor);
     }
 
     return null;
@@ -797,13 +799,14 @@
 
   // 104
   Node c_ns_aliasNode() {
+    var start = _scanner.state;
     if (!truth(c_indicator(C_ALIAS))) return null;
     var name = expect(ns_anchorName(), 'anchor name');
-    return new AliasNode(name);
+    return new AliasNode(name, _scanner.spanFrom(start));
   }
 
   // 105
-  ScalarNode e_scalar() => new ScalarNode("?", content: "");
+  ScalarNode e_scalar() => new ScalarNode("?", _scanner.emptySpan, content: "");
 
   // 106
   ScalarNode e_node() => e_scalar();
@@ -820,10 +823,11 @@
   // 109
   Node c_doubleQuoted(int indent, int ctx) => context('string', () {
     return transaction(() {
+      var start = _scanner.state;
       if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null;
       var contents = nb_doubleText(indent, ctx);
       if (!truth(c_indicator(C_DOUBLE_QUOTE))) return null;
-      return new ScalarNode("!", content: contents);
+      return new ScalarNode("!", _scanner.spanFrom(start), content: contents);
     });
   });
 
@@ -908,10 +912,11 @@
   // 120
   Node c_singleQuoted(int indent, int ctx) => context('string', () {
     return transaction(() {
+      var start = _scanner.state;
       if (!truth(c_indicator(C_SINGLE_QUOTE))) return null;
       var contents = nb_singleText(indent, ctx);
       if (!truth(c_indicator(C_SINGLE_QUOTE))) return null;
-      return new ScalarNode("!", content: contents);
+      return new ScalarNode("!", _scanner.spanFrom(start), content: contents);
     });
   });
 
@@ -1075,11 +1080,13 @@
 
   // 137
   SequenceNode c_flowSequence(int indent, int ctx) => transaction(() {
+    var start = _scanner.state;
     if (!truth(c_indicator(C_SEQUENCE_START))) return null;
     zeroOrOne(() => s_separate(indent, ctx));
     var content = zeroOrOne(() => ns_s_flowSeqEntries(indent, inFlow(ctx)));
     if (!truth(c_indicator(C_SEQUENCE_END))) return null;
-    return new SequenceNode("?", new List<Node>.from(content));
+    return new SequenceNode("?", new List<Node>.from(content),
+        _scanner.spanFrom(start));
   });
 
   // 138
@@ -1108,17 +1115,18 @@
 
   // 140
   Node c_flowMapping(int indent, int ctx) {
+    var start = _scanner.state;
     if (!truth(c_indicator(C_MAPPING_START))) return null;
     zeroOrOne(() => s_separate(indent, ctx));
     var content = zeroOrOne(() => ns_s_flowMapEntries(indent, inFlow(ctx)));
     if (!truth(c_indicator(C_MAPPING_END))) return null;
-    return new MappingNode("?", content);
+    return new MappingNode("?", content, _scanner.spanFrom(start));
   }
 
   // 141
-  YamlMap ns_s_flowMapEntries(int indent, int ctx) {
+  Map ns_s_flowMapEntries(int indent, int ctx) {
     var first = ns_flowMapEntry(indent, ctx);
-    if (!truth(first)) return new YamlMap();
+    if (!truth(first)) return deepEqualsMap();
     zeroOrOne(() => s_separate(indent, ctx));
 
     var rest;
@@ -1127,7 +1135,7 @@
       rest = ns_s_flowMapEntries(indent, ctx);
     }
 
-    if (rest == null) rest = new YamlMap();
+    if (rest == null) rest = deepEqualsMap();
 
     // TODO(nweiz): Duplicate keys should be an error. This includes keys with
     // different representations but the same value (e.g. 10 vs 0xa). To make
@@ -1139,7 +1147,7 @@
   }
 
   // 142
-  _Pair<Node, Node> ns_flowMapEntry(int indent, int ctx) => or([
+  Pair<Node, Node> ns_flowMapEntry(int indent, int ctx) => or([
     () => transaction(() {
       if (!truth(c_indicator(C_MAPPING_KEY))) return false;
       if (!truth(s_separate(indent, ctx))) return false;
@@ -1149,20 +1157,20 @@
   ]);
 
   // 143
-  _Pair<Node, Node> ns_flowMapExplicitEntry(int indent, int ctx) => or([
+  Pair<Node, Node> ns_flowMapExplicitEntry(int indent, int ctx) => or([
     () => ns_flowMapImplicitEntry(indent, ctx),
-    () => new _Pair<Node, Node>(e_node(), e_node())
+    () => new Pair<Node, Node>(e_node(), e_node())
   ]);
 
   // 144
-  _Pair<Node, Node> ns_flowMapImplicitEntry(int indent, int ctx) => or([
+  Pair<Node, Node> ns_flowMapImplicitEntry(int indent, int ctx) => or([
     () => ns_flowMapYamlKeyEntry(indent, ctx),
     () => c_ns_flowMapEmptyKeyEntry(indent, ctx),
     () => c_ns_flowMapJsonKeyEntry(indent, ctx)
   ]);
 
   // 145
-  _Pair<Node, Node> ns_flowMapYamlKeyEntry(int indent, int ctx) {
+  Pair<Node, Node> ns_flowMapYamlKeyEntry(int indent, int ctx) {
     var key = ns_flowYamlNode(indent, ctx);
     if (!truth(key)) return null;
     var value = or([
@@ -1172,14 +1180,14 @@
       }),
       e_node
     ]);
-    return new _Pair<Node, Node>(key, value);
+    return new Pair<Node, Node>(key, value);
   }
 
   // 146
-  _Pair<Node, Node> c_ns_flowMapEmptyKeyEntry(int indent, int ctx) {
+  Pair<Node, Node> c_ns_flowMapEmptyKeyEntry(int indent, int ctx) {
     var value = c_ns_flowMapSeparateValue(indent, ctx);
     if (!truth(value)) return null;
-    return new _Pair<Node, Node>(e_node(), value);
+    return new Pair<Node, Node>(e_node(), value);
   }
 
   // 147
@@ -1197,7 +1205,7 @@
   });
 
   // 148
-  _Pair<Node, Node> c_ns_flowMapJsonKeyEntry(int indent, int ctx) {
+  Pair<Node, Node> c_ns_flowMapJsonKeyEntry(int indent, int ctx) {
     var key = c_flowJsonNode(indent, ctx);
     if (!truth(key)) return null;
     var value = or([
@@ -1207,7 +1215,7 @@
       }),
       e_node
     ]);
-    return new _Pair<Node, Node>(key, value);
+    return new Pair<Node, Node>(key, value);
   }
 
   // 149
@@ -1224,6 +1232,7 @@
 
   // 150
   Node ns_flowPair(int indent, int ctx) {
+    var start = _scanner.state;
     var pair = or([
       () => transaction(() {
         if (!truth(c_indicator(C_MAPPING_KEY))) return null;
@@ -1234,34 +1243,34 @@
     ]);
     if (!truth(pair)) return null;
 
-    return map([pair]);
+    return map([pair], _scanner.spanFrom(start));
   }
 
   // 151
-  _Pair<Node, Node> ns_flowPairEntry(int indent, int ctx) => or([
+  Pair<Node, Node> ns_flowPairEntry(int indent, int ctx) => or([
     () => ns_flowPairYamlKeyEntry(indent, ctx),
     () => c_ns_flowMapEmptyKeyEntry(indent, ctx),
     () => c_ns_flowPairJsonKeyEntry(indent, ctx)
   ]);
 
   // 152
-  _Pair<Node, Node> ns_flowPairYamlKeyEntry(int indent, int ctx) =>
+  Pair<Node, Node> ns_flowPairYamlKeyEntry(int indent, int ctx) =>
     transaction(() {
       var key = ns_s_implicitYamlKey(FLOW_KEY);
       if (!truth(key)) return null;
       var value = c_ns_flowMapSeparateValue(indent, ctx);
       if (!truth(value)) return null;
-      return new _Pair<Node, Node>(key, value);
+      return new Pair<Node, Node>(key, value);
     });
 
   // 153
-  _Pair<Node, Node> c_ns_flowPairJsonKeyEntry(int indent, int ctx) =>
+  Pair<Node, Node> c_ns_flowPairJsonKeyEntry(int indent, int ctx) =>
     transaction(() {
       var key = c_s_implicitJsonKey(FLOW_KEY);
       if (!truth(key)) return null;
       var value = c_ns_flowMapAdjacentValue(indent, ctx);
       if (!truth(value)) return null;
-      return new _Pair<Node, Node>(key, value);
+      return new Pair<Node, Node>(key, value);
     });
 
   // 154
@@ -1288,9 +1297,10 @@
 
   // 156
   Node ns_flowYamlContent(int indent, int ctx) {
+    var start = _scanner.state;
     var str = ns_plain(indent, ctx);
     if (!truth(str)) return null;
-    return new ScalarNode("?", content: str);
+    return new ScalarNode("?", _scanner.spanFrom(start), content: str);
   }
 
   // 157
@@ -1437,6 +1447,7 @@
 
   // 170
   Node c_l_literal(int indent) => transaction(() {
+    var start = _scanner.state;
     if (!truth(c_indicator(C_LITERAL))) return null;
     var header = c_b_blockHeader();
     if (!truth(header)) return null;
@@ -1445,7 +1456,7 @@
     var content = l_literalContent(indent + additionalIndent, header.chomping);
     if (!truth(content)) return null;
 
-    return new ScalarNode("!", content: content);
+    return new ScalarNode("!", _scanner.spanFrom(start), content: content);
   });
 
   // 171
@@ -1474,6 +1485,7 @@
 
   // 174
   Node c_l_folded(int indent) => transaction(() {
+    var start = _scanner.state;
     if (!truth(c_indicator(C_FOLDED))) return null;
     var header = c_b_blockHeader();
     if (!truth(header)) return null;
@@ -1482,7 +1494,7 @@
     var content = l_foldedContent(indent + additionalIndent, header.chomping);
     if (!truth(content)) return null;
 
-    return new ScalarNode("!", content: content);
+    return new ScalarNode("!", _scanner.spanFrom(start), content: content);
   });
 
   // 175
@@ -1562,13 +1574,14 @@
     var additionalIndent = countIndentation() - indent;
     if (additionalIndent <= 0) return null;
 
+    var start = _scanner.state;
     var content = oneOrMore(() => transaction(() {
       if (!truth(s_indent(indent + additionalIndent))) return null;
       return c_l_blockSeqEntry(indent + additionalIndent);
     }));
     if (!truth(content)) return null;
 
-    return new SequenceNode("?", content);
+    return new SequenceNode("?", content, _scanner.spanFrom(start));
   });
 
   // 184
@@ -1595,6 +1608,7 @@
 
   // 186
   Node ns_l_compactSequence(int indent) => context('sequence', () {
+    var start = _scanner.state;
     var first = c_l_blockSeqEntry(indent);
     if (!truth(first)) return null;
 
@@ -1604,7 +1618,7 @@
       }));
     content.insert(0, first);
 
-    return new SequenceNode("?", content);
+    return new SequenceNode("?", content, _scanner.spanFrom(start));
   });
 
   // 187
@@ -1612,23 +1626,24 @@
     var additionalIndent = countIndentation() - indent;
     if (additionalIndent <= 0) return null;
 
+    var start = _scanner.state;
     var pairs = oneOrMore(() => transaction(() {
       if (!truth(s_indent(indent + additionalIndent))) return null;
       return ns_l_blockMapEntry(indent + additionalIndent);
     }));
     if (!truth(pairs)) return null;
 
-    return map(pairs);
+    return map(pairs, _scanner.spanFrom(start));
   });
 
   // 188
-  _Pair<Node, Node> ns_l_blockMapEntry(int indent) => or([
+  Pair<Node, Node> ns_l_blockMapEntry(int indent) => or([
     () => c_l_blockMapExplicitEntry(indent),
     () => ns_l_blockMapImplicitEntry(indent)
   ]);
 
   // 189
-  _Pair<Node, Node> c_l_blockMapExplicitEntry(int indent) {
+  Pair<Node, Node> c_l_blockMapExplicitEntry(int indent) {
     var key = c_l_blockMapExplicitKey(indent);
     if (!truth(key)) return null;
 
@@ -1637,7 +1652,7 @@
       e_node
     ]);
 
-    return new _Pair<Node, Node>(key, value);
+    return new Pair<Node, Node>(key, value);
   }
 
   // 190
@@ -1654,10 +1669,10 @@
   });
 
   // 192
-  _Pair<Node, Node> ns_l_blockMapImplicitEntry(int indent) => transaction(() {
+  Pair<Node, Node> ns_l_blockMapImplicitEntry(int indent) => transaction(() {
     var key = or([ns_s_blockMapImplicitKey, e_node]);
     var value = c_l_blockMapImplicitValue(indent);
-    return truth(value) ? new _Pair<Node, Node>(key, value) : null;
+    return truth(value) ? new Pair<Node, Node>(key, value) : null;
   });
 
   // 193
@@ -1678,6 +1693,7 @@
 
   // 195
   Node ns_l_compactMapping(int indent) => context('mapping', () {
+    var start = _scanner.state;
     var first = ns_l_blockMapEntry(indent);
     if (!truth(first)) return null;
 
@@ -1687,7 +1703,7 @@
       }));
     pairs.insert(0, first);
 
-    return map(pairs);
+    return map(pairs, _scanner.spanFrom(start));
   });
 
   // 196
@@ -1807,7 +1823,8 @@
     or([l_directiveDocument, l_explicitDocument, l_bareDocument]);
 
   // 211
-  List<Node> l_yamlStream() {
+  Pair<List<Node>, Span> l_yamlStream() {
+    var start = _scanner.state;
     var docs = [];
     zeroOrMore(l_documentPrefix);
     var first = zeroOrOne(l_anyDocument);
@@ -1828,20 +1845,10 @@
     });
 
     if (!_scanner.isDone) parseFailed();
-    return docs;
+    return new Pair(docs, _scanner.spanFrom(start));
   }
 }
 
-/// A pair of values.
-class _Pair<E, F> {
-  E first;
-  F last;
-
-  _Pair(this.first, this.last);
-
-  String toString() => '($first, $last)';
-}
-
 /// The information in the header for a block scalar.
 class _BlockHeader {
   final int additionalIndent;
@@ -1872,7 +1879,7 @@
 /// expensive.
 class _RangeMap<E> {
   /// The ranges and their associated elements.
-  final List<_Pair<_Range, E>> _contents = <_Pair<_Range, E>>[];
+  final List<Pair<_Range, E>> _contents = <Pair<_Range, E>>[];
 
   _RangeMap();
 
@@ -1890,5 +1897,5 @@
 
   /// Associates [value] with [range].
   operator[]=(_Range range, E value) =>
-    _contents.add(new _Pair<_Range, E>(range, value));
+    _contents.add(new Pair<_Range, E>(range, value));
 }
diff --git a/pkgs/yaml/lib/src/utils.dart b/pkgs/yaml/lib/src/utils.dart
index 463af70..84c1113 100644
--- a/pkgs/yaml/lib/src/utils.dart
+++ b/pkgs/yaml/lib/src/utils.dart
@@ -4,33 +4,12 @@
 
 library yaml.utils;
 
-import 'package:collection/collection.dart';
+/// A pair of values.
+class Pair<E, F> {
+  final E first;
+  final F last;
 
-/// Returns a hash code for [obj] such that structurally equivalent objects
-/// will have the same hash code.
-///
-/// This supports deep equality for maps and lists, including those with
-/// self-referential structures.
-int hashCodeFor(obj) {
-  var parents = [];
+  Pair(this.first, this.last);
 
-  _hashCodeFor(value) {
-    if (parents.any((parent) => identical(parent, value))) return -1;
-
-    parents.add(value);
-    try {
-      if (value is Map) {
-        var equality = const UnorderedIterableEquality();
-        return equality.hash(value.keys.map(_hashCodeFor)) ^
-            equality.hash(value.values.map(_hashCodeFor));
-      } else if (value is Iterable) {
-        return const IterableEquality().hash(value.map(hashCodeFor));
-      }
-      return value.hashCode;
-    } finally {
-      parents.removeLast();
-    }
-  }
-
-  return _hashCodeFor(obj);
+  String toString() => '($first, $last)';
 }
diff --git a/pkgs/yaml/lib/src/visitor.dart b/pkgs/yaml/lib/src/visitor.dart
index 7095268..3f4c455 100644
--- a/pkgs/yaml/lib/src/visitor.dart
+++ b/pkgs/yaml/lib/src/visitor.dart
@@ -4,8 +4,8 @@
 
 library yaml.visitor;
 
+import 'equality.dart';
 import 'model.dart';
-import 'yaml_map.dart';
 
 /// The visitor pattern for YAML documents.
 class Visitor {
@@ -21,7 +21,7 @@
 
   /// Visits each key and value in [map] and returns a map of the results.
   visitMapping(MappingNode map) {
-    var out = new YamlMap();
+    var out = deepEqualsMap();
     for (var key in map.content.keys) {
       out[key.visit(this)] = map.content[key].visit(this);
     }
diff --git a/pkgs/yaml/lib/src/yaml_map.dart b/pkgs/yaml/lib/src/yaml_map.dart
deleted file mode 100644
index ff3da36..0000000
--- a/pkgs/yaml/lib/src/yaml_map.dart
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 yaml.map;
-
-import 'dart:collection';
-
-import 'package:collection/collection.dart';
-
-import 'deep_equals.dart';
-import 'utils.dart';
-
-/// This class behaves almost identically to the normal Dart [Map]
-/// implementation, with the following differences:
-///
-///  *  It allows NaN, list, and map keys.
-///  *  It defines `==` structurally. That is, `yamlMap1 == yamlMap2` if they
-///     have the same contents.
-///  *  It has a compatible [hashCode] method.
-///
-/// This class is deprecated. In future releases, this package will use
-/// a [HashMap] with a custom equality operation rather than a custom class.
-@Deprecated('1.0.0')
-class YamlMap extends DelegatingMap {
-  YamlMap()
-      : super(new HashMap(equals: deepEquals, hashCode: hashCodeFor));
-
-  YamlMap.from(Map map)
-      : super(new HashMap(equals: deepEquals, hashCode: hashCodeFor)) {
-    addAll(map);
-  }
-
-  int get hashCode => hashCodeFor(this);
-
-  bool operator ==(other) {
-    if (other is! YamlMap) return false;
-    return deepEquals(this, other);
-  }
-}
diff --git a/pkgs/yaml/lib/src/yaml_node.dart b/pkgs/yaml/lib/src/yaml_node.dart
new file mode 100644
index 0000000..d0e0578
--- /dev/null
+++ b/pkgs/yaml/lib/src/yaml_node.dart
@@ -0,0 +1,92 @@
+// 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 yaml.map;
+
+import 'dart:collection' as collection;
+
+import 'package:collection/collection.dart';
+import 'package:source_maps/source_maps.dart';
+
+/// An interface for parsed nodes from a YAML source tree.
+///
+/// [YamlMap]s and [YamlList]s implement this interface in addition to the
+/// normal [Map] and [List] interfaces, so any maps and lists will be
+/// [YamlNode]s regardless of how they're accessed.
+///
+/// Scalars values like strings and numbers, on the other hand, don't have this
+/// interface by default. Instead, they can be accessed as [YamlScalar]s via
+/// [YamlMap.nodes] or [YamlList.nodes].
+abstract class YamlNode {
+  /// The source span for this node.
+  ///
+  /// [Span.getLocationMessage] can be used to produce a human-friendly message
+  /// about this node.
+  Span get span;
+
+  /// The inner value of this node.
+  ///
+  /// For [YamlScalar]s, this will return the wrapped value. For [YamlMap] and
+  /// [YamlList], it will return [this], since they already implement [Map] and
+  /// [List], respectively.
+  get value;
+}
+
+/// A read-only [Map] parsed from YAML.
+class YamlMap extends YamlNode with collection.MapMixin, UnmodifiableMapMixin  {
+  final Span span;
+
+  final Map<dynamic, YamlNode> nodes;
+
+  Map get value => this;
+
+  Iterable get keys => nodes.keys.map((node) => node.value);
+
+  /// Users of the library should not construct [YamlMap]s manually.
+  YamlMap(Map<dynamic, YamlNode> nodes, this.span)
+      : nodes = new UnmodifiableMapView<dynamic, YamlNode>(nodes);
+
+  operator [](key) {
+    var node = nodes[key];
+    return node == null ? null : node.value;
+  }
+}
+
+// TODO(nweiz): Use UnmodifiableListMixin when issue 18970 is fixed.
+/// A read-only [List] parsed from YAML.
+class YamlList extends YamlNode with collection.ListMixin {
+  final Span span;
+
+  final List<YamlNode> nodes;
+
+  List get value => this;
+
+  int get length => nodes.length;
+
+  set length(int index) {
+    throw new UnsupportedError("Cannot modify an unmodifiable List");
+  }
+
+  /// Users of the library should not construct [YamlList]s manually.
+  YamlList(List<YamlNode> nodes, this.span)
+      : nodes = new UnmodifiableListView<YamlNode>(nodes);
+
+  operator [](int index) => nodes[index].value;
+
+  operator []=(int index, value) {
+    throw new UnsupportedError("Cannot modify an unmodifiable List");
+  }
+}
+
+/// A wrapped scalar value parsed from YAML.
+class YamlScalar extends YamlNode {
+  final Span span;
+
+  final value;
+
+  /// Users of the library should not construct [YamlScalar]s manually.
+  YamlScalar(this.value, this.span);
+
+  String toString() => value.toString();
+}
diff --git a/pkgs/yaml/lib/yaml.dart b/pkgs/yaml/lib/yaml.dart
index 209db8c..f59719d 100644
--- a/pkgs/yaml/lib/yaml.dart
+++ b/pkgs/yaml/lib/yaml.dart
@@ -8,9 +8,10 @@
 import 'src/constructor.dart';
 import 'src/parser.dart';
 import 'src/yaml_exception.dart';
+import 'src/yaml_node.dart';
 
 export 'src/yaml_exception.dart';
-export 'src/yaml_map.dart';
+export 'src/yaml_node.dart';
 
 /// Loads a single document from a YAML string.
 ///
@@ -28,12 +29,20 @@
 ///
 /// If [sourceName] is passed, it's used as the name of the file or URL from
 /// which the YAML originated for error reporting.
-loadYaml(String yaml, {String sourceName}) {
+loadYaml(String yaml, {String sourceName}) =>
+    loadYamlNode(yaml, sourceName: sourceName).value;
+
+/// Loads a single document from a YAML string as a [YamlNode].
+///
+/// This is just like [loadYaml], except that where [loadYaml] would return a
+/// normal Dart value this returns a [YamlNode] instead. This allows the caller
+/// to be confident that the return value will always be a [YamlNode].
+YamlNode loadYamlNode(String yaml, {String sourceName}) {
   var stream = loadYamlStream(yaml, sourceName: sourceName);
   if (stream.length != 1) {
     throw new YamlException("Expected 1 document, were ${stream.length}.");
   }
-  return stream[0];
+  return stream.nodes[0];
 }
 
 /// Loads a stream of documents from a YAML string.
@@ -49,15 +58,16 @@
 ///
 /// If [sourceName] is passed, it's used as the name of the file or URL from
 /// which the YAML originated for error reporting.
-List loadYamlStream(String yaml, {String sourceName}) {
-  var stream;
+YamlList loadYamlStream(String yaml, {String sourceName}) {
+  var pair;
   try {
-    stream = new Parser(yaml, sourceName).l_yamlStream();
+    pair = new Parser(yaml, sourceName).l_yamlStream();
   } on FormatException catch (error) {
     throw new YamlException(error.toString());
   }
 
-  return stream
+  var nodes = pair.first
       .map((doc) => new Constructor(new Composer(doc).compose()).construct())
       .toList();
+  return new YamlList(nodes, pair.last);
 }
diff --git a/pkgs/yaml/pubspec.yaml b/pkgs/yaml/pubspec.yaml
index b57224c..2672161 100644
--- a/pkgs/yaml/pubspec.yaml
+++ b/pkgs/yaml/pubspec.yaml
@@ -1,5 +1,5 @@
 name: yaml
-version: 0.10.0
+version: 1.0.0
 author: "Dart Team <misc@dartlang.org>"
 homepage: http://www.dartlang.org
 description: A parser for YAML.
diff --git a/pkgs/yaml/test/utils.dart b/pkgs/yaml/test/utils.dart
index 0bac9f9..9c96ec1 100644
--- a/pkgs/yaml/test/utils.dart
+++ b/pkgs/yaml/test/utils.dart
@@ -5,7 +5,7 @@
 library yaml.test.utils;
 
 import 'package:unittest/unittest.dart';
-import 'package:yaml/src/deep_equals.dart' as de;
+import 'package:yaml/src/equality.dart' as equality;
 import 'package:yaml/yaml.dart';
 
 /// A matcher that validates that a closure or Future throws a [YamlException].
@@ -14,25 +14,28 @@
 /// Returns a matcher that asserts that the value equals [expected].
 ///
 /// This handles recursive loops and considers `NaN` to equal itself.
-Matcher deepEquals(expected) =>
-    predicate((actual) => de.deepEquals(actual, expected), "equals $expected");
+Matcher deepEquals(expected) => predicate((actual) =>
+    equality.deepEquals(actual, expected), "equals $expected");
 
 /// Constructs a new yaml.YamlMap, optionally from a normal Map.
-Map yamlMap([Map from]) =>
-    from == null ? new YamlMap() : new YamlMap.from(from);
+Map deepEqualsMap([Map from]) {
+  var map = equality.deepEqualsMap();
+  if (from != null) map.addAll(from);
+  return map;
+}
 
 /// Asserts that a string containing a single YAML document produces a given
 /// value when loaded.
 void expectYamlLoads(expected, String source) {
   var actual = loadYaml(cleanUpLiteral(source));
-  expect(expected, deepEquals(actual));
+  expect(actual, deepEquals(expected));
 }
 
 /// Asserts that a string containing a stream of YAML documents produces a given
 /// list of values when loaded.
 void expectYamlStreamLoads(List expected, String source) {
   var actual = loadYamlStream(cleanUpLiteral(source));
-  expect(expected, deepEquals(actual));
+  expect(actual, deepEquals(expected));
 }
 
 /// Asserts that a string containing a single YAML document throws a
diff --git a/pkgs/yaml/test/yaml_test.dart b/pkgs/yaml/test/yaml_test.dart
index fcfc12e..4fbb108 100644
--- a/pkgs/yaml/test/yaml_test.dart
+++ b/pkgs/yaml/test/yaml_test.dart
@@ -14,37 +14,6 @@
   var infinity = double.parse("Infinity");
   var nan = double.parse("NaN");
 
-  group('YamlMap', () {
-    group('accepts as a key', () {
-      _expectKeyWorks(keyFn()) {
-        var map = yamlMap();
-        map[keyFn()] = 5;
-        expect(map.containsKey(keyFn()), isTrue);
-        expect(map[keyFn()], 5);
-      }
-
-      test('null', () => _expectKeyWorks(() => null));
-      test('true', () => _expectKeyWorks(() => true));
-      test('false', () => _expectKeyWorks(() => false));
-      test('a list', () => _expectKeyWorks(() => [1, 2, 3]));
-      test('a map', () => _expectKeyWorks(() => {'foo': 'bar'}));
-      test('a YAML map', () => _expectKeyWorks(() => yamlMap({'foo': 'bar'})));
-    });
-
-    test('works as a hash key', () {
-      var normalMap = new Map();
-      normalMap[yamlMap({'foo': 'bar'})] = 'baz';
-      expect(normalMap.containsKey(yamlMap({'foo': 'bar'})), isTrue);
-      expect(normalMap[yamlMap({'foo': 'bar'})], 'baz');
-    });
-
-    test('treats YamlMap keys the same as normal maps', () {
-      var map = yamlMap();
-      map[{'a': 'b'}] = 5;
-      expect(map[yamlMap({'a': 'b'})], 5);
-    });
-  });
-
   group('has a friendly error message for', () {
     var tabError = predicate((e) =>
         e.toString().contains('tab characters are not allowed as indentation'));
@@ -221,7 +190,7 @@
   //   });
 
     test('[Example 2.11]', () {
-      var doc = yamlMap();
+      var doc = deepEqualsMap();
       doc[["Detroit Tigers", "Chicago cubs"]] = ["2001-07-23"];
       doc[["New York Yankees", "Atlanta Braves"]] =
         ["2001-07-02", "2001-08-12", "2001-08-14"];
@@ -382,7 +351,7 @@
     });
 
     test('[Example 2.21]', () {
-      var doc = yamlMap({
+      var doc = deepEqualsMap({
         "booleans": [true, false],
         "string": "012345"
       });
@@ -900,7 +869,7 @@
     });
 
     test('[Example 6.12]', () {
-      var doc = yamlMap();
+      var doc = deepEqualsMap();
       doc[{'first': 'Sammy', 'last': 'Sosa'}] = {
         'hr': 65,
         'avg': 0.278
@@ -1092,7 +1061,7 @@
     // });
 
     test('[Example 7.3]', () {
-      var doc = yamlMap({"foo": null});
+      var doc = deepEqualsMap({"foo": null});
       doc[null] = "bar";
       expectYamlLoads(doc,
         """
@@ -1239,7 +1208,7 @@
     });
 
     test('[Example 7.16]', () {
-      var doc = yamlMap({
+      var doc = deepEqualsMap({
         "explicit": "entry",
         "implicit": "entry"
       });
@@ -1254,7 +1223,7 @@
     });
 
     test('[Example 7.17]', () {
-      var doc = yamlMap({
+      var doc = deepEqualsMap({
         "unquoted": "separate",
         "http://foo.com": null,
         "omitted value": null
@@ -1302,10 +1271,10 @@
     });
 
     test('[Example 7.21]', () {
-      var el1 = yamlMap();
+      var el1 = deepEqualsMap();
       el1[null] = "empty key entry";
 
-      var el2 = yamlMap();
+      var el2 = deepEqualsMap();
       el2[{"JSON": "like"}] = "adjacent";
 
       expectYamlLoads([[{"YAML": "separate"}], [el1], [el2]],
@@ -1577,7 +1546,7 @@
     });
 
     test('[Example 8.18]', () {
-      var doc = yamlMap({
+      var doc = deepEqualsMap({
         'plain key': 'in-line value',
         "quoted key": ["entry"]
       });
@@ -1591,7 +1560,7 @@
     });
 
     test('[Example 8.19]', () {
-      var el = yamlMap();
+      var el = deepEqualsMap();
       el[{'earth': 'blue'}] = {'moon': 'white'};
       expectYamlLoads([{'sun': 'yellow'}, el],
         """
@@ -1763,7 +1732,7 @@
 
   group('10.2: JSON Schema', () {
     // test('[Example 10.4]', () {
-    //   var doc = yamlMap({"key with null value": null});
+    //   var doc = deepEqualsMap({"key with null value": null});
     //   doc[null] = "value for null key";
     //   expectYamlStreamLoads(doc,
     //     """