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

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

Issue 2875203005: Refactor and clean up the status file parsing code. (Closed)
Patch Set: Created 3 years, 7 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
OLDNEW
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 library status_expression; 5 /// A parsed Boolean expression AST.
6 6 abstract class Expression {
kevmoo 2017/05/15 21:06:48 Possible? https://github.com/dart-lang/boolean_sel
Bob Nystrom 2017/05/15 22:42:51 For test.dart, we try not to rely on any external
7 /** 7 /// Parses Boolean expressions in a .status file for Dart.
8 * Parse and evaluate expressions in a .status file for Dart and V8. 8 ///
9 * There are set expressions and Boolean expressions in a .status file. 9 /// The grammar is:
10 * The grammar is: 10 ///
11 * BooleanExpression := $variableName == value | $variableName != value | 11 /// expression := or
12 * $variableName | (BooleanExpression) | 12 /// or := and ( "||" and )*
13 * BooleanExpression && BooleanExpression | 13 /// and := primary ( "&&" primary )*
14 * BooleanExpression || BooleanExpression 14 /// primary := "$" identifier ( ( "==" | "!=" ) identifier )? |
15 * 15 /// "(" expression ")"
16 * SetExpression := value | (SetExpression) | 16 /// identifier := regex "\w+"
17 * SetExpression || SetExpression | 17 ///
18 * SetExpression if BooleanExpression | 18 /// Expressions evaluate as expected, with values of variables found in an
19 * SetExpression , SetExpression 19 /// environment passed to the evaluator.
20 * 20 static Expression parse(String expression) =>
21 * Productions are listed in order of precedence, and the || and , operators 21 new _ExpressionParser(expression).parse();
22 * both evaluate to set union, but with different precedence. 22
23 * 23 /// Evaluates the expression where all variables are defined by the given
24 * Values and variableNames are non-empty strings of word characters, matching 24 /// [environment].
25 * the RegExp \w+. 25 bool evaluate(Map<String, dynamic> environment);
26 * 26 }
27 * Expressions evaluate as expected, with values of variables found in 27
28 * an environment passed to the evaluator. The SetExpression "value" 28 /// Keyword token strings.
29 * evaluates to a singleton set containing that value. "A if B" evaluates 29 class _Token {
30 * to A if B is true, and to the empty set if B is false. 30 static const leftParen = "(";
31 */ 31 static const rightParen = ")";
32 32 static const dollar = r"$";
33 class ExprEvaluationException { 33 static const equals = "==";
34 String error; 34 static const notEqual = "!=";
35 35 static const and = "&&";
36 ExprEvaluationException(this.error); 36 static const or = "||";
37 37 }
38 toString() => error; 38
39 } 39 /// A reference to a variable.
40 40 class _Variable {
41 class Token { 41 final String name;
42 static const String LEFT_PAREN = "("; 42
43 static const String RIGHT_PAREN = ")"; 43 _Variable(this.name);
44 static const String DOLLAR_SYMBOL = r"$"; 44
45 static const String UNION = ","; 45 dynamic lookup(Map<String, dynamic> environment) {
46 static const String EQUALS = "==";
47 static const String NOT_EQUALS = "!=";
48 static const String AND = "&&";
49 static const String OR = "||";
50 }
51
52 class Tokenizer {
53 String expression;
54 List<String> tokens;
55
56 Tokenizer(String this.expression) : tokens = new List<String>();
57
58 // Tokens are : "(", ")", "$", ",", "&&", "||", "==", "!=", and (maximal) \w+.
59 static final testRegexp =
60 new RegExp(r"^([()$\w\s,]|(\&\&)|(\|\|)|(\=\=)|(\!\=))+$");
61 static final regexp = new RegExp(r"[()$,]|(\&\&)|(\|\|)|(\=\=)|(\!\=)|\w+");
62
63 List<String> tokenize() {
64 if (!testRegexp.hasMatch(expression)) {
65 throw new FormatException("Syntax error in '$expression'");
66 }
67 for (Match match in regexp.allMatches(expression)) tokens.add(match[0]);
68 return tokens;
69 }
70 }
71
72 abstract class BooleanExpression {
73 bool evaluate(Map<String, String> environment);
74 }
75
76 abstract class SetExpression {
77 Set<String> evaluate(Map<String, String> environment);
78 }
79
80 class Comparison implements BooleanExpression {
81 TermVariable left;
82 TermConstant right;
83 bool negate;
84
85 Comparison(this.left, this.right, this.negate);
86
87 bool evaluate(environment) {
88 return negate !=
89 (left.termValue(environment) == right.termValue(environment));
90 }
91
92 String toString() =>
93 "(\$${left.name} ${negate ? '!=' : '=='} ${right.value})";
94 }
95
96 class TermVariable {
97 String name;
98
99 TermVariable(this.name);
100
101 String termValue(environment) {
102 var value = environment[name]; 46 var value = environment[name];
103 if (value == null) { 47 if (value == null) {
104 throw new ExprEvaluationException("Could not find '$name' in environment " 48 throw new Exception("Could not find '$name' in environment "
105 "while evaluating status file expression."); 49 "while evaluating status file expression.");
106 } 50 }
107 return value.toString(); 51
108 } 52 return value;
109 } 53 }
110 54 }
111 class TermConstant { 55
112 String value; 56 /// Tests whether a given variable is or is not equal some literal value, as in:
113 57 ///
114 TermConstant(String this.value); 58 /// $variable == someValue
115 59 class _ComparisonExpression implements Expression {
116 String termValue(environment) => value; 60 final _Variable left;
117 } 61 final String right;
118 62 final bool negate;
119 class BooleanVariable implements BooleanExpression { 63
120 TermVariable variable; 64 _ComparisonExpression(this.left, this.right, this.negate);
121 65
122 BooleanVariable(this.variable); 66 bool evaluate(Map<String, dynamic> environment) {
123 67 return negate != (left.lookup(environment) == right);
124 bool evaluate(environment) => variable.termValue(environment) == 'true'; 68 }
69
70 String toString() => "(\$${left.name} ${negate ? '!=' : '=='} $right)";
71 }
72
73 /// A reference to a variable defined in the environment. The variable's value
74 /// should be Boolean and the expression is true when the value is "true".
75 ///
76 /// $variable
77 class _VariableExpression implements Expression {
78 final _Variable variable;
79
80 _VariableExpression(this.variable);
81
82 bool evaluate(Map<String, dynamic> environment) =>
83 variable.lookup(environment) == 'true';
84
125 String toString() => "(bool \$${variable.name})"; 85 String toString() => "(bool \$${variable.name})";
126 } 86 }
127 87
128 class BooleanOperation implements BooleanExpression { 88 /// A logical `||` or `&&` expression.
129 String op; 89 class _LogicExpression implements Expression {
130 BooleanExpression left; 90 /// The operator, `||` or `&&`.
131 BooleanExpression right; 91 final String op;
132 92
133 BooleanOperation(this.op, this.left, this.right); 93 final Expression left;
134 94 final Expression right;
135 bool evaluate(environment) => (op == Token.AND) 95
96 _LogicExpression(this.op, this.left, this.right);
97
98 bool evaluate(Map<String, dynamic> environment) => (op == _Token.and)
136 ? left.evaluate(environment) && right.evaluate(environment) 99 ? left.evaluate(environment) && right.evaluate(environment)
137 : left.evaluate(environment) || right.evaluate(environment); 100 : left.evaluate(environment) || right.evaluate(environment);
101
138 String toString() => "($left $op $right)"; 102 String toString() => "($left $op $right)";
139 } 103 }
140 104
141 class SetUnion implements SetExpression { 105 /// Parser for Boolean expressions in a .status file for Dart.
142 SetExpression left; 106 class _ExpressionParser {
143 SetExpression right; 107 final _Scanner _scanner;
144 108
145 SetUnion(this.left, this.right); 109 _ExpressionParser(String expression) : _scanner = new _Scanner(expression);
146 110
147 // Overwrites left.evaluate(env). 111 Expression parse() {
148 // Set.addAll does not return this. 112 var expression = _parseOr();
149 Set<String> evaluate(environment) { 113
150 Set<String> result = left.evaluate(environment); 114 // Should consume entire string.
151 result.addAll(right.evaluate(environment)); 115 if (_scanner.hasMore) {
152 return result; 116 throw new FormatException("Unexpected input after expression");
153 } 117 }
154 118
155 String toString() => "($left || $right)"; 119 return expression;
156 } 120 }
157 121
158 class SetIf implements SetExpression { 122 Expression _parseOr() {
159 SetExpression left; 123 var left = _parseAnd();
160 BooleanExpression right; 124 while (_scanner.match(_Token.or)) {
161 125 var right = _parseAnd();
162 SetIf(this.left, this.right); 126 left = new _LogicExpression(_Token.or, left, right);
163 127 }
164 Set<String> evaluate(environment) => right.evaluate(environment) 128
165 ? left.evaluate(environment)
166 : new Set<String>();
167 String toString() => "($left if $right)";
168 }
169
170 class SetConstant implements SetExpression {
171 String value;
172
173 SetConstant(String v) : value = v.toLowerCase();
174
175 Set<String> evaluate(environment) => [value].toSet();
176 String toString() => value;
177 }
178
179 // An iterator that allows peeking at the current token.
180 class Scanner {
181 List<String> tokens;
182 Iterator tokenIterator;
183 String current;
184
185 Scanner(this.tokens) {
186 tokenIterator = tokens.iterator;
187 advance();
188 }
189
190 bool hasMore() => current != null;
191
192 void advance() {
193 current = tokenIterator.moveNext() ? tokenIterator.current : null;
194 }
195 }
196
197 class ExpressionParser {
198 Scanner scanner;
199
200 ExpressionParser(this.scanner);
201
202 SetExpression parseSetExpression() => parseSetUnion();
203
204 SetExpression parseSetUnion() {
205 SetExpression left = parseSetIf();
206 while (scanner.hasMore() && scanner.current == Token.UNION) {
207 scanner.advance();
208 SetExpression right = parseSetIf();
209 left = new SetUnion(left, right);
210 }
211 return left; 129 return left;
212 } 130 }
213 131
214 SetExpression parseSetIf() { 132 Expression _parseAnd() {
215 SetExpression left = parseSetOr(); 133 var left = _parsePrimary();
216 while (scanner.hasMore() && scanner.current == "if") { 134 while (_scanner.match(_Token.and)) {
217 scanner.advance(); 135 var right = _parsePrimary();
218 BooleanExpression right = parseBooleanExpression(); 136 left = new _LogicExpression(_Token.and, left, right);
219 left = new SetIf(left, right); 137 }
220 } 138
221 return left; 139 return left;
222 } 140 }
223 141
224 SetExpression parseSetOr() { 142 Expression _parsePrimary() {
225 SetExpression left = parseSetAtomic(); 143 if (_scanner.match(_Token.leftParen)) {
226 while (scanner.hasMore() && scanner.current == Token.OR) { 144 var value = _parseOr();
227 scanner.advance(); 145 if (!_scanner.match(_Token.rightParen)) {
228 SetExpression right = parseSetAtomic();
229 left = new SetUnion(left, right);
230 }
231 return left;
232 }
233
234 SetExpression parseSetAtomic() {
235 if (scanner.current == Token.LEFT_PAREN) {
236 scanner.advance();
237 SetExpression value = parseSetExpression();
238 if (scanner.current != Token.RIGHT_PAREN) {
239 throw new FormatException("Missing right parenthesis in expression"); 146 throw new FormatException("Missing right parenthesis in expression");
240 } 147 }
241 scanner.advance(); 148
242 return value;
243 }
244 if (!new RegExp(r"^\w+$").hasMatch(scanner.current)) {
245 throw new FormatException(
246 "Expected identifier in expression, got ${scanner.current}");
247 }
248 SetExpression value = new SetConstant(scanner.current);
249 scanner.advance();
250 return value;
251 }
252
253 BooleanExpression parseBooleanExpression() => parseBooleanOr();
254
255 BooleanExpression parseBooleanOr() {
256 BooleanExpression left = parseBooleanAnd();
257 while (scanner.hasMore() && scanner.current == Token.OR) {
258 scanner.advance();
259 BooleanExpression right = parseBooleanAnd();
260 left = new BooleanOperation(Token.OR, left, right);
261 }
262 return left;
263 }
264
265 BooleanExpression parseBooleanAnd() {
266 BooleanExpression left = parseBooleanAtomic();
267 while (scanner.hasMore() && scanner.current == Token.AND) {
268 scanner.advance();
269 BooleanExpression right = parseBooleanAtomic();
270 left = new BooleanOperation(Token.AND, left, right);
271 }
272 return left;
273 }
274
275 BooleanExpression parseBooleanAtomic() {
276 if (scanner.current == Token.LEFT_PAREN) {
277 scanner.advance();
278 BooleanExpression value = parseBooleanExpression();
279 if (scanner.current != Token.RIGHT_PAREN) {
280 throw new FormatException("Missing right parenthesis in expression");
281 }
282 scanner.advance();
283 return value; 149 return value;
284 } 150 }
285 151
286 // The only atomic booleans are of the form $variable == value or 152 // The only atomic booleans are of the form $variable == value or
287 // of the form $variable. 153 // of the form $variable.
288 if (scanner.current != Token.DOLLAR_SYMBOL) { 154 if (!_scanner.match(_Token.dollar)) {
289 throw new FormatException( 155 throw new FormatException(
290 "Expected \$ in expression, got ${scanner.current}"); 156 "Expected \$ in expression, got ${_scanner.current}");
291 } 157 }
292 scanner.advance(); 158
293 if (!new RegExp(r"^\w+$").hasMatch(scanner.current)) { 159 if (!_scanner.isIdentifier) {
294 throw new FormatException( 160 throw new FormatException(
295 "Expected identifier in expression, got ${scanner.current}"); 161 "Expected identifier in expression, got ${_scanner.current}");
296 } 162 }
297 TermVariable left = new TermVariable(scanner.current); 163
298 scanner.advance(); 164 var left = new _Variable(_scanner.current);
299 if (scanner.current == Token.EQUALS || 165 _scanner.advance();
300 scanner.current == Token.NOT_EQUALS) { 166
301 bool negate = scanner.current == Token.NOT_EQUALS; 167 if (_scanner.current == _Token.equals ||
302 scanner.advance(); 168 _scanner.current == _Token.notEqual) {
303 if (!new RegExp(r"^\w+$").hasMatch(scanner.current)) { 169 var negate = _scanner.advance() == _Token.notEqual;
170
171 if (!_scanner.isIdentifier) {
304 throw new FormatException( 172 throw new FormatException(
305 "Expected value in expression, got ${scanner.current}"); 173 "Expected value in expression, got ${_scanner.current}");
306 } 174 }
307 TermConstant right = new TermConstant(scanner.current); 175
308 scanner.advance(); 176 var right = _scanner.advance();
309 return new Comparison(left, right, negate); 177 return new _ComparisonExpression(left, right, negate);
310 } else { 178 } else {
311 return new BooleanVariable(left); 179 return new _VariableExpression(left);
312 } 180 }
313 } 181 }
314 } 182 }
183
184 /// An iterator that allows peeking at the current token.
185 class _Scanner {
186 /// Tokens are "(", ")", "$", ",", "&&", "||", "==", "!=", and (maximal) \w+.
187 static final _testPattern =
188 new RegExp(r"^([()$\w\s,]|(\&\&)|(\|\|)|(\=\=)|(\!\=))+$");
Bill Hesse 2017/05/15 16:15:08 I think you can remove ',' from the two regexps, i
Bob Nystrom 2017/05/15 22:42:51 Nice catch! Done.
189 static final _tokenPattern =
190 new RegExp(r"[()$,]|(\&\&)|(\|\|)|(\=\=)|(\!\=)|\w+");
191
192 static final _identifierPattern = new RegExp(r"^\w+$");
193
194 /// The token strings being iterated.
195 final Iterator<String> tokenIterator;
196
197 String current;
198
199 _Scanner(String expression) : tokenIterator = tokenize(expression).iterator {
200 advance();
201 }
202
203 static List<String> tokenize(String expression) {
204 if (!_testPattern.hasMatch(expression)) {
205 throw new FormatException("Syntax error in '$expression'");
206 }
207
208 return _tokenPattern
209 .allMatches(expression)
Bill Hesse 2017/05/15 16:15:09 I had no idea that this was non-overlapping. You
Bob Nystrom 2017/05/15 22:42:51 Guess so. I did too! (This was in the original cod
210 .map((match) => match[0])
211 .toList();
212 }
213
214 bool get hasMore => current != null;
215
216 /// Returns `true` if the current token is an identifier.
217 bool get isIdentifier => _identifierPattern.hasMatch(current);
218
219 /// If the current token is [token], consumes it and returns `true`.
220 bool match(String token) {
221 if (!hasMore || current != token) return false;
222
223 advance();
224 return true;
225 }
226
227 /// Consumes the current token and returns it.
228 String advance() {
229 var previous = current;
230 current = tokenIterator.moveNext() ? tokenIterator.current : null;
231 return previous;
232 }
233 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698