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 |