Define equality for boolean selectors.

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//1715553002 .
diff --git a/lib/boolean_selector.dart b/lib/boolean_selector.dart
index 92eedbd..790740f 100644
--- a/lib/boolean_selector.dart
+++ b/lib/boolean_selector.dart
@@ -15,6 +15,9 @@
 /// operations. See [the README][] for full details.
 ///
 /// [the README]: https://github.com/dart-lang/boolean_selector/blob/master/README.md
+///
+/// Boolean selectors support structural equality. Two selectors that have the
+/// same parsed structure are considered equal.
 abstract class BooleanSelector {
   /// A selector that accepts all inputs.
   static const all = const All();
diff --git a/lib/src/ast.dart b/lib/src/ast.dart
index 26c09bf..13eb633 100644
--- a/lib/src/ast.dart
+++ b/lib/src/ast.dart
@@ -38,6 +38,10 @@
   accept(Visitor visitor) => visitor.visitVariable(this);
 
   String toString() => name;
+
+  bool operator==(other) => other is VariableNode && name == other.name;
+
+  int get hashCode => name.hashCode;
 }
 
 /// A negation expression.
@@ -56,6 +60,10 @@
   String toString() => child is VariableNode || child is NotNode
       ? "!$child"
       : "!($child)";
+
+  bool operator==(other) => other is NotNode && child == other.child;
+
+  int get hashCode => ~child.hashCode;
 }
 
 /// An or expression.
@@ -87,6 +95,11 @@
 
     return "$string1 || $string2";
   }
+
+  bool operator==(other) =>
+      other is OrNode && left == other.left && right == other.right;
+
+  int get hashCode => left.hashCode ^ right.hashCode;
 }
 
 /// An and expression.
@@ -118,6 +131,11 @@
 
     return "$string1 && $string2";
   }
+
+  bool operator==(other) =>
+      other is AndNode && left == other.left && right == other.right;
+
+  int get hashCode => left.hashCode ^ right.hashCode;
 }
 
 /// A ternary conditional expression.
@@ -149,6 +167,15 @@
     var trueString = whenTrue is ConditionalNode ? "($whenTrue)" : whenTrue;
     return "$conditionString ? $trueString : $whenFalse";
   }
+
+  bool operator==(other) =>
+      other is ConditionalNode &&
+      condition == other.condition &&
+      whenTrue == other.whenTrue &&
+      whenFalse == other.whenFalse;
+
+  int get hashCode =>
+      condition.hashCode ^ whenTrue.hashCode ^ whenFalse.hashCode;
 }
 
 /// Like [FileSpan.expand], except if [start] and [end] are `null` or from
diff --git a/lib/src/impl.dart b/lib/src/impl.dart
index 6a40530..c920e52 100644
--- a/lib/src/impl.dart
+++ b/lib/src/impl.dart
@@ -53,4 +53,9 @@
   }
 
   String toString() => _selector.toString();
+
+  bool operator==(other) =>
+      other is BooleanSelectorImpl && _selector == other._selector;
+
+  int get hashCode => _selector.hashCode;
 }
diff --git a/lib/src/intersection_selector.dart b/lib/src/intersection_selector.dart
index aa51bf9..8c15254 100644
--- a/lib/src/intersection_selector.dart
+++ b/lib/src/intersection_selector.dart
@@ -32,4 +32,11 @@
   }
 
   String toString() => "($_selector1) && ($_selector2)";
+
+  bool operator==(other) =>
+      other is IntersectionSelector &&
+      _selector1 == other._selector1 &&
+      _selector2 == other._selector2;
+
+  int get hashCode => _selector1.hashCode ^ _selector2.hashCode;
 }
diff --git a/lib/src/union_selector.dart b/lib/src/union_selector.dart
index 6b6cf87..f513f6c 100644
--- a/lib/src/union_selector.dart
+++ b/lib/src/union_selector.dart
@@ -30,4 +30,11 @@
   }
 
   String toString() => "($_selector1) && ($_selector2)";
+
+  bool operator==(other) =>
+      other is UnionSelector &&
+      _selector1 == other._selector1 &&
+      _selector2 == other._selector2;
+
+  int get hashCode => _selector1.hashCode ^ _selector2.hashCode;
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index 2527dd5..9c0090b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: boolean_selector
-version: 1.0.0-dev
+version: 1.0.0
 description: A flexible syntax for boolean expressions.
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/boolean_selector
diff --git a/test/equality_test.dart b/test/equality_test.dart
new file mode 100644
index 0000000..7ee527b
--- /dev/null
+++ b/test/equality_test.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'package:boolean_selector/boolean_selector.dart';
+
+void main() {
+  test("variable", () {
+    _expectEqualsSelf("foo");
+  });
+
+  test("not", () {
+    _expectEqualsSelf("!foo");
+  });
+
+  test("or", () {
+    _expectEqualsSelf("foo || bar");
+  });
+
+  test("and", () {
+    _expectEqualsSelf("foo && bar");
+  });
+
+  test("conditional", () {
+    _expectEqualsSelf("foo ? bar : baz");
+  });
+
+  test("all", () {
+    expect(BooleanSelector.all, equals(BooleanSelector.all));
+  });
+
+  test("none", () {
+    expect(BooleanSelector.none, equals(BooleanSelector.none));
+  });
+
+  test("redundant parens don't matter", () {
+    expect(new BooleanSelector.parse("foo && (bar && baz)"),
+        equals(new BooleanSelector.parse("foo && (bar && baz)")));
+  });
+
+  test("meaningful parens do matter", () {
+    expect(new BooleanSelector.parse("(foo && bar) || baz"),
+        equals(new BooleanSelector.parse("foo && bar || baz")));
+  });
+}
+
+void _expectEqualsSelf(String selector) {
+  expect(new BooleanSelector.parse(selector),
+      equals(new BooleanSelector.parse(selector)));
+}