| // 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:source_span/source_span.dart'; | |
| import 'visitor.dart'; | |
| /// The superclass of nodes in the boolean selector abstract syntax tree. | |
| abstract class Node { | |
| /// The span indicating where this node came from. | |
| /// | |
| /// This is a [FileSpan] because the nodes are parsed from a single continuous | |
| /// string, but the string itself isn't actually a file. It might come from a | |
| /// statically-parsed annotation or from a parameter. | |
| /// | |
| /// This may be `null` for nodes without source information. | |
| FileSpan? get span; | |
| /// All the variables in this node, in the order they appear. | |
| Iterable<String> get variables; | |
| /// Calls the appropriate [Visitor] method on `this` and returns the result. | |
| T accept<T>(Visitor<T> visitor); | |
| } | |
| /// A single variable. | |
| class VariableNode implements Node { | |
| @override | |
| final FileSpan? span; | |
| /// The variable name. | |
| final String name; | |
| @override | |
| Iterable<String> get variables => [name]; | |
| VariableNode(this.name, [this.span]); | |
| @override | |
| T accept<T>(Visitor<T> visitor) => visitor.visitVariable(this); | |
| @override | |
| String toString() => name; | |
| @override | |
| bool operator ==(Object other) => other is VariableNode && name == other.name; | |
| @override | |
| int get hashCode => name.hashCode; | |
| } | |
| /// A negation expression. | |
| class NotNode implements Node { | |
| @override | |
| final FileSpan? span; | |
| /// The expression being negated. | |
| final Node child; | |
| @override | |
| Iterable<String> get variables => child.variables; | |
| NotNode(this.child, [this.span]); | |
| @override | |
| T accept<T>(Visitor<T> visitor) => visitor.visitNot(this); | |
| @override | |
| String toString() => | |
| child is VariableNode || child is NotNode ? '!$child' : '!($child)'; | |
| @override | |
| bool operator ==(Object other) => other is NotNode && child == other.child; | |
| @override | |
| int get hashCode => ~child.hashCode; | |
| } | |
| /// An or expression. | |
| class OrNode implements Node { | |
| @override | |
| FileSpan? get span => _expandSafe(left.span, right.span); | |
| /// The left-hand branch of the expression. | |
| final Node left; | |
| /// The right-hand branch of the expression. | |
| final Node right; | |
| @override | |
| Iterable<String> get variables sync* { | |
| yield* left.variables; | |
| yield* right.variables; | |
| } | |
| OrNode(this.left, this.right); | |
| @override | |
| T accept<T>(Visitor<T> visitor) => visitor.visitOr(this); | |
| @override | |
| String toString() { | |
| var string1 = left is AndNode || left is ConditionalNode ? '($left)' : left; | |
| var string2 = | |
| right is AndNode || right is ConditionalNode ? '($right)' : right; | |
| return '$string1 || $string2'; | |
| } | |
| @override | |
| bool operator ==(Object other) => | |
| other is OrNode && left == other.left && right == other.right; | |
| @override | |
| int get hashCode => left.hashCode ^ right.hashCode; | |
| } | |
| /// An and expression. | |
| class AndNode implements Node { | |
| @override | |
| FileSpan? get span => _expandSafe(left.span, right.span); | |
| /// The left-hand branch of the expression. | |
| final Node left; | |
| /// The right-hand branch of the expression. | |
| final Node right; | |
| @override | |
| Iterable<String> get variables sync* { | |
| yield* left.variables; | |
| yield* right.variables; | |
| } | |
| AndNode(this.left, this.right); | |
| @override | |
| T accept<T>(Visitor<T> visitor) => visitor.visitAnd(this); | |
| @override | |
| String toString() { | |
| var string1 = left is OrNode || left is ConditionalNode ? '($left)' : left; | |
| var string2 = | |
| right is OrNode || right is ConditionalNode ? '($right)' : right; | |
| return '$string1 && $string2'; | |
| } | |
| @override | |
| bool operator ==(Object other) => | |
| other is AndNode && left == other.left && right == other.right; | |
| @override | |
| int get hashCode => left.hashCode ^ right.hashCode; | |
| } | |
| /// A ternary conditional expression. | |
| class ConditionalNode implements Node { | |
| @override | |
| FileSpan? get span => _expandSafe(condition.span, whenFalse.span); | |
| /// The condition expression to check. | |
| final Node condition; | |
| /// The branch to run if the condition is true. | |
| final Node whenTrue; | |
| /// The branch to run if the condition is false. | |
| final Node whenFalse; | |
| @override | |
| Iterable<String> get variables sync* { | |
| yield* condition.variables; | |
| yield* whenTrue.variables; | |
| yield* whenFalse.variables; | |
| } | |
| ConditionalNode(this.condition, this.whenTrue, this.whenFalse); | |
| @override | |
| T accept<T>(Visitor<T> visitor) => visitor.visitConditional(this); | |
| @override | |
| String toString() { | |
| var conditionString = | |
| condition is ConditionalNode ? '($condition)' : condition; | |
| var trueString = whenTrue is ConditionalNode ? '($whenTrue)' : whenTrue; | |
| return '$conditionString ? $trueString : $whenFalse'; | |
| } | |
| @override | |
| bool operator ==(Object other) => | |
| other is ConditionalNode && | |
| condition == other.condition && | |
| whenTrue == other.whenTrue && | |
| whenFalse == other.whenFalse; | |
| @override | |
| int get hashCode => | |
| condition.hashCode ^ whenTrue.hashCode ^ whenFalse.hashCode; | |
| } | |
| /// Like [FileSpan.expand], except if [start] and [end] are `null` or from | |
| /// different files it returns `null` rather than throwing an error. | |
| FileSpan? _expandSafe(FileSpan? start, FileSpan? end) { | |
| if (start == null || end == null) return null; | |
| if (start.file != end.file) return null; | |
| return start.expand(end); | |
| } |