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 |