| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'environment.dart'; | |
| 6 | |
| 7 /// A parsed Boolean expression AST. | |
| 8 abstract class Expression { | |
| 9 /// Parses Boolean expressions in a .status file for Dart. | |
| 10 /// | |
| 11 /// The grammar is: | |
| 12 /// | |
| 13 /// expression := or | |
| 14 /// or := and ( "||" and )* | |
| 15 /// and := primary ( "&&" primary )* | |
| 16 /// primary := "$" identifier ( "==" | "!=" ) identifier | | |
| 17 /// "!"? "$" identifier | | |
| 18 /// "(" expression ")" | |
| 19 /// identifier := regex "\w+" | |
| 20 /// | |
| 21 /// Expressions evaluate as expected, with values of variables found in an | |
| 22 /// environment passed to the evaluator. | |
| 23 static Expression parse(String expression) => | |
| 24 new _ExpressionParser(expression).parse(); | |
| 25 | |
| 26 /// Validates that this expression does not contain any invalid uses of | |
| 27 /// variables. | |
| 28 /// | |
| 29 /// Ensures that any variable names are known and that any literal values are | |
| 30 /// allowed for their corresponding variable. If an invalid variable or value | |
| 31 /// is found, adds appropriate error messages to [errors]. | |
| 32 void validate(List<String> errors); | |
| 33 | |
| 34 /// Evaluates the expression where all variables are defined by the given | |
| 35 /// [environment]. | |
| 36 bool evaluate(Environment environment); | |
| 37 } | |
| 38 | |
| 39 /// Keyword token strings. | |
| 40 class _Token { | |
| 41 static const leftParen = "("; | |
| 42 static const rightParen = ")"; | |
| 43 static const dollar = r"$"; | |
| 44 static const equals = "=="; | |
| 45 static const notEqual = "!="; | |
| 46 static const and = "&&"; | |
| 47 static const or = "||"; | |
| 48 static const not = "!"; | |
| 49 } | |
| 50 | |
| 51 /// A reference to a variable. | |
| 52 class _Variable { | |
| 53 final String name; | |
| 54 | |
| 55 _Variable(this.name); | |
| 56 | |
| 57 String lookup(Environment environment) { | |
| 58 var value = environment.lookUp(name); | |
| 59 if (value == null) { | |
| 60 throw new Exception("Could not find '$name' in environment " | |
| 61 "while evaluating status file expression."); | |
| 62 } | |
| 63 | |
| 64 // Explicitly stringify all values so that things like: | |
| 65 // | |
| 66 // $strong == true | |
| 67 // | |
| 68 // work correctly even though "true" is treated as a string here. | |
| 69 // TODO(rnystrom): Is there a cleaner/safer way to do this? | |
| 70 return value.toString(); | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 /// Tests whether a given variable is or is not equal some literal value, as in: | |
| 75 /// ``` | |
| 76 /// $variable == someValue | |
| 77 /// ``` | |
| 78 /// Negate the result if [negate] is true. | |
| 79 class _ComparisonExpression implements Expression { | |
| 80 final _Variable left; | |
| 81 final String right; | |
| 82 final bool negate; | |
| 83 | |
| 84 _ComparisonExpression(this.left, this.right, this.negate); | |
| 85 | |
| 86 void validate(List<String> errors) { | |
| 87 Environment.validate(left.name, right, errors); | |
| 88 } | |
| 89 | |
| 90 bool evaluate(Environment environment) { | |
| 91 return negate != (left.lookup(environment) == right); | |
| 92 } | |
| 93 | |
| 94 String toString() => "(\$${left.name} ${negate ? '!=' : '=='} $right)"; | |
| 95 } | |
| 96 | |
| 97 /// A reference to a variable defined in the environment. The expression | |
| 98 /// evaluates to true if the variable's stringified value is "true". | |
| 99 /// ``` | |
| 100 /// $variable | |
| 101 /// ``` | |
| 102 /// is equivalent to | |
| 103 /// ``` | |
| 104 /// $variable == true | |
| 105 /// ``` | |
| 106 /// Negates result if [negate] is true, so | |
| 107 /// ``` | |
| 108 /// !$variable | |
| 109 /// ``` | |
| 110 /// is equivalent to | |
| 111 /// ``` | |
| 112 /// $variable != true | |
| 113 /// ``` | |
| 114 class _VariableExpression implements Expression { | |
| 115 final _Variable variable; | |
| 116 final bool negate; | |
| 117 | |
| 118 _VariableExpression(this.variable, {this.negate = false}); | |
| 119 | |
| 120 void validate(List<String> errors) { | |
| 121 // It must be a Boolean, so it should allow either Boolean value. | |
| 122 Environment.validate(variable.name, "true", errors); | |
| 123 } | |
| 124 | |
| 125 bool evaluate(Environment environment) => | |
| 126 negate != (variable.lookup(environment) == "true"); | |
| 127 | |
| 128 String toString() => "(bool ${negate ? "! " : ""}\$${variable.name})"; | |
| 129 } | |
| 130 | |
| 131 /// A logical `||` or `&&` expression. | |
| 132 class _LogicExpression implements Expression { | |
| 133 /// The operator, `||` or `&&`. | |
| 134 final String op; | |
| 135 | |
| 136 final Expression left; | |
| 137 final Expression right; | |
| 138 | |
| 139 _LogicExpression(this.op, this.left, this.right); | |
| 140 | |
| 141 void validate(List<String> errors) { | |
| 142 left.validate(errors); | |
| 143 right.validate(errors); | |
| 144 } | |
| 145 | |
| 146 bool evaluate(Environment environment) => (op == _Token.and) | |
| 147 ? left.evaluate(environment) && right.evaluate(environment) | |
| 148 : left.evaluate(environment) || right.evaluate(environment); | |
| 149 | |
| 150 String toString() => "($left $op $right)"; | |
| 151 } | |
| 152 | |
| 153 /// Parser for Boolean expressions in a .status file for Dart. | |
| 154 class _ExpressionParser { | |
| 155 final _Scanner _scanner; | |
| 156 | |
| 157 _ExpressionParser(String expression) : _scanner = new _Scanner(expression); | |
| 158 | |
| 159 Expression parse() { | |
| 160 var expression = _parseOr(); | |
| 161 | |
| 162 // Should consume entire string. | |
| 163 if (_scanner.hasMore) { | |
| 164 throw new FormatException("Unexpected input after expression"); | |
| 165 } | |
| 166 | |
| 167 return expression; | |
| 168 } | |
| 169 | |
| 170 Expression _parseOr() { | |
| 171 var left = _parseAnd(); | |
| 172 while (_scanner.match(_Token.or)) { | |
| 173 var right = _parseAnd(); | |
| 174 left = new _LogicExpression(_Token.or, left, right); | |
| 175 } | |
| 176 | |
| 177 return left; | |
| 178 } | |
| 179 | |
| 180 Expression _parseAnd() { | |
| 181 var left = _parsePrimary(); | |
| 182 while (_scanner.match(_Token.and)) { | |
| 183 var right = _parsePrimary(); | |
| 184 left = new _LogicExpression(_Token.and, left, right); | |
| 185 } | |
| 186 | |
| 187 return left; | |
| 188 } | |
| 189 | |
| 190 Expression _parsePrimary() { | |
| 191 if (_scanner.match(_Token.leftParen)) { | |
| 192 var value = _parseOr(); | |
| 193 if (!_scanner.match(_Token.rightParen)) { | |
| 194 throw new FormatException("Missing right parenthesis in expression"); | |
| 195 } | |
| 196 | |
| 197 return value; | |
| 198 } | |
| 199 | |
| 200 var negate = false; | |
| 201 if (_scanner.match(_Token.not)) { | |
| 202 negate = true; | |
| 203 } | |
| 204 | |
| 205 // The only atomic booleans are of the form $variable == value or | |
| 206 // of the form $variable. | |
| 207 if (!_scanner.match(_Token.dollar)) { | |
| 208 throw new FormatException( | |
| 209 "Expected \$ in expression, got ${_scanner.current}"); | |
| 210 } | |
| 211 | |
| 212 if (!_scanner.isIdentifier) { | |
| 213 throw new FormatException( | |
| 214 "Expected identifier in expression, got ${_scanner.current}"); | |
| 215 } | |
| 216 | |
| 217 var left = new _Variable(_scanner.current); | |
| 218 _scanner.advance(); | |
| 219 | |
| 220 if (!negate && | |
| 221 (_scanner.current == _Token.equals || | |
| 222 _scanner.current == _Token.notEqual)) { | |
| 223 var isNotEquals = _scanner.advance() == _Token.notEqual; | |
| 224 | |
| 225 if (!_scanner.isIdentifier) { | |
| 226 throw new FormatException( | |
| 227 "Expected value in expression, got ${_scanner.current}"); | |
| 228 } | |
| 229 | |
| 230 var right = _scanner.advance(); | |
| 231 return new _ComparisonExpression(left, right, isNotEquals); | |
| 232 } else { | |
| 233 return new _VariableExpression(left, negate: negate); | |
| 234 } | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 /// An iterator that allows peeking at the current token. | |
| 239 class _Scanner { | |
| 240 /// Tokens are "(", ")", "$", "&&", "||", "!", ==", "!=", and (maximal) \w+. | |
| 241 static final _testPattern = new RegExp(r"^(?:[()$\w\s]|&&|\|\||==|!=?)+$"); | |
| 242 static final _tokenPattern = new RegExp(r"[()$]|&&|\|\||==|!=?|\w+"); | |
| 243 | |
| 244 /// Pattern that recognizes identifier tokens. | |
| 245 /// | |
| 246 /// Only checks the first character, since no non-identifier token can start | |
| 247 /// with a word character. | |
| 248 static final _identifierPattern = new RegExp(r"^\w"); | |
| 249 | |
| 250 /// The token strings being iterated. | |
| 251 final Iterator<String> tokenIterator; | |
| 252 | |
| 253 String current; | |
| 254 | |
| 255 _Scanner(String expression) : tokenIterator = tokenize(expression).iterator { | |
| 256 advance(); | |
| 257 } | |
| 258 | |
| 259 static List<String> tokenize(String expression) { | |
| 260 if (!_testPattern.hasMatch(expression)) { | |
| 261 throw new FormatException("Syntax error in '$expression'"); | |
| 262 } | |
| 263 | |
| 264 return _tokenPattern | |
| 265 .allMatches(expression) | |
| 266 .map((match) => match[0]) | |
| 267 .toList(); | |
| 268 } | |
| 269 | |
| 270 bool get hasMore => current != null; | |
| 271 | |
| 272 /// Returns `true` if the current token is an identifier. | |
| 273 // All non-identifier tokens are one or two characters, | |
| 274 // so a longer token must be an identifier. | |
| 275 bool get isIdentifier => | |
| 276 current.length > 2 || _identifierPattern.hasMatch(current); | |
| 277 | |
| 278 /// If the current token is [token], consumes it and returns `true`. | |
| 279 bool match(String token) { | |
| 280 if (!hasMore || current != token) return false; | |
| 281 | |
| 282 advance(); | |
| 283 return true; | |
| 284 } | |
| 285 | |
| 286 /// Consumes the current token and returns it. | |
| 287 String advance() { | |
| 288 var previous = current; | |
| 289 current = tokenIterator.moveNext() ? tokenIterator.current : null; | |
| 290 return previous; | |
| 291 } | |
| 292 } | |
| OLD | NEW |