Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(33)

Side by Side Diff: tools/testing/dart/status_expression.dart

Issue 2984203002: Move the status file parser into its own package. (Closed)
Patch Set: Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/testing/dart/expectation_set.dart ('k') | tools/testing/dart/status_file.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « tools/testing/dart/expectation_set.dart ('k') | tools/testing/dart/status_file.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698