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