| 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 #library("status_expression"); |
| 6 |
| 7 /** |
| 8 * Parse and evaluate expressions in a .status file for Dart and V8. |
| 9 * There are set expressions and Boolean expressions in a .status file. |
| 10 * The grammar is: |
| 11 * BooleanExpression := $variableName == value | $variableName | |
| 12 * (BooleanExpression) | |
| 13 * BooleanExpression && BooleanExpression | |
| 14 * BooleanExpression || BooleanExpression |
| 15 * |
| 16 * SetExpression := value | (SetExpression) | |
| 17 * SetExpression || SetExpression | |
| 18 * SetExpression if BooleanExpression | |
| 19 * SetExpression , SetExpression |
| 20 * |
| 21 * Productions are listed in order of precedence, and the || and , operators |
| 22 * both evaluate to set union, but with different precedence. |
| 23 * |
| 24 * Values and variableNames are non-empty strings of word characters, matching |
| 25 * the RegExp \w+. |
| 26 * |
| 27 * Expressions evaluate as expected, with values of variables found in |
| 28 * an environment passed to the evaluator. The SetExpression "value" |
| 29 * evaluates to a singleton set containing that value. "A if B" evaluates |
| 30 * to A if B is true, and to the empty set if B is false. |
| 31 */ |
| 32 |
| 33 |
| 34 class Token |
| 35 { |
| 36 static final String LEFT_PAREN = "("; |
| 37 static final String RIGHT_PAREN = ")"; |
| 38 static final String DOLLAR_SYMBOL = @"$"; |
| 39 static final String UNION = ","; |
| 40 static final String EQUALS = "=="; |
| 41 static final String AND = "&&"; |
| 42 static final String OR = "||"; |
| 43 } |
| 44 |
| 45 |
| 46 class Tokenizer { |
| 47 String expression; |
| 48 List<String> tokens; |
| 49 |
| 50 Tokenizer(String this.expression) |
| 51 : tokens = new List<String>(); |
| 52 |
| 53 // Tokens are : "(", ")", "$", ",", "&&", "||", "==", and (maximal) \w+. |
| 54 static final testRegexp = |
| 55 const RegExp(@"^([()$\w\s,]|(\&\&)|(\|\|)|(\=\=))+$"); |
| 56 static final regexp = const RegExp(@"[()$,]|(\&\&)|(\|\|)|(\=\=)|\w+"); |
| 57 |
| 58 List<String> tokenize() { |
| 59 if (!testRegexp.hasMatch(expression)) { |
| 60 throw new ExpectException("Syntax error in '$expression'"); |
| 61 } |
| 62 for (Match match in regexp.allMatches(expression)) tokens.add(match[0]); |
| 63 return tokens; |
| 64 } |
| 65 } |
| 66 |
| 67 |
| 68 interface BooleanExpression { |
| 69 bool evaluate(Map<String, String> environment); |
| 70 } |
| 71 |
| 72 |
| 73 interface SetExpression { |
| 74 Set<String> evaluate(Map<String, String> environment); |
| 75 } |
| 76 |
| 77 |
| 78 class Comparison implements BooleanExpression { |
| 79 TermVariable left; |
| 80 TermConstant right; |
| 81 |
| 82 Comparison(this.left, this.right); |
| 83 |
| 84 bool evaluate(environment) => |
| 85 left.termValue(environment) == right.termValue(environment); |
| 86 String toString() => "(\$${left.name} == ${right.value})"; |
| 87 } |
| 88 |
| 89 |
| 90 class TermVariable { |
| 91 String name; |
| 92 |
| 93 TermVariable(this.name); |
| 94 |
| 95 String termValue(environment) => environment[name]; |
| 96 } |
| 97 |
| 98 |
| 99 class TermConstant { |
| 100 String value; |
| 101 |
| 102 TermConstant(String this.value); |
| 103 |
| 104 String termValue(environment) => value; |
| 105 } |
| 106 |
| 107 |
| 108 class BooleanVariable implements BooleanExpression { |
| 109 TermVariable variable; |
| 110 |
| 111 BooleanVariable(this.variable); |
| 112 |
| 113 bool evaluate(environment) => variable.termValue(environment) == "true"; |
| 114 String toString() => "(bool \$${variable.name})"; |
| 115 } |
| 116 |
| 117 |
| 118 class BooleanOperation implements BooleanExpression { |
| 119 String op; |
| 120 BooleanExpression left; |
| 121 BooleanExpression right; |
| 122 |
| 123 BooleanOperation(this.op, this.left, this.right); |
| 124 |
| 125 bool evaluate(environment) => (op == Token.AND) ? |
| 126 left.evaluate(environment) && right.evaluate(environment) : |
| 127 left.evaluate(environment) || right.evaluate(environment); |
| 128 String toString() => "($left $op $right)"; |
| 129 } |
| 130 |
| 131 |
| 132 class SetUnion implements SetExpression { |
| 133 SetExpression left; |
| 134 SetExpression right; |
| 135 |
| 136 SetUnion(this.left, this.right); |
| 137 |
| 138 // Overwrites left.evaluate(env). |
| 139 // Set.addAll does not return this. |
| 140 Set<String> evaluate(environment) { |
| 141 Set<String> result = left.evaluate(environment); |
| 142 result.addAll(right.evaluate(environment)); |
| 143 return result; |
| 144 } |
| 145 |
| 146 String toString() => "($left || $right)"; |
| 147 } |
| 148 |
| 149 |
| 150 class SetIf implements SetExpression { |
| 151 SetExpression left; |
| 152 BooleanExpression right; |
| 153 |
| 154 SetIf(this.left, this.right); |
| 155 |
| 156 Set<String> evaluate(environment) => right.evaluate(environment) ? |
| 157 left.evaluate(environment) : new Set<String>(); |
| 158 String toString() => "($left if $right)"; |
| 159 } |
| 160 |
| 161 |
| 162 class SetConstant implements SetExpression { |
| 163 String value; |
| 164 |
| 165 SetConstant(this.value); |
| 166 |
| 167 Set<String> evaluate(environment) => new Set<String>.from([value]); |
| 168 String toString() => value; |
| 169 } |
| 170 |
| 171 |
| 172 // An iterator that allows peeking at the current token. |
| 173 class Scanner { |
| 174 List<String> tokens; |
| 175 Iterator tokenIterator; |
| 176 String current; |
| 177 |
| 178 Scanner(this.tokens) { |
| 179 tokenIterator = tokens.iterator(); |
| 180 advance(); |
| 181 } |
| 182 |
| 183 bool hasMore() => current != null; |
| 184 |
| 185 void advance() { |
| 186 current = tokenIterator.hasNext() ? tokenIterator.next() : null; |
| 187 } |
| 188 } |
| 189 |
| 190 |
| 191 class ExpressionParser { |
| 192 Scanner scanner; |
| 193 |
| 194 ExpressionParser(this.scanner); |
| 195 |
| 196 SetExpression parseSetExpression() => parseSetUnion(); |
| 197 |
| 198 SetExpression parseSetUnion() { |
| 199 SetExpression left = parseSetIf(); |
| 200 while (scanner.hasMore() && scanner.current == Token.UNION){ |
| 201 scanner.advance(); |
| 202 SetExpression right = parseSetIf(); |
| 203 left = new SetUnion(left, right); |
| 204 } |
| 205 return left; |
| 206 } |
| 207 |
| 208 SetExpression parseSetIf() { |
| 209 SetExpression left = parseSetOr(); |
| 210 while (scanner.hasMore() && scanner.current == "if") { |
| 211 scanner.advance(); |
| 212 BooleanExpression right = parseBooleanExpression(); |
| 213 left = new SetIf(left, right); |
| 214 } |
| 215 return left; |
| 216 } |
| 217 |
| 218 SetExpression parseSetOr() { |
| 219 SetExpression left = parseSetAtomic(); |
| 220 while (scanner.hasMore() && scanner.current == Token.OR){ |
| 221 scanner.advance(); |
| 222 SetExpression right = parseSetAtomic(); |
| 223 left = new SetUnion(left, right); |
| 224 } |
| 225 return left; |
| 226 } |
| 227 |
| 228 |
| 229 SetExpression parseSetAtomic() { |
| 230 if (scanner.current == Token.LEFT_PAREN) { |
| 231 scanner.advance(); |
| 232 SetExpression value = parseSetExpression(); |
| 233 Expect.equals(scanner.current, Token.RIGHT_PAREN, |
| 234 "Missing right parenthesis in expression"); |
| 235 scanner.advance(); |
| 236 return value; |
| 237 } |
| 238 Expect.isTrue(const RegExp(@"^\w+$").hasMatch(scanner.current), |
| 239 "Expected identifier in expression, got ${scanner.current}"); |
| 240 SetExpression value = new SetConstant(scanner.current); |
| 241 scanner.advance(); |
| 242 return value; |
| 243 } |
| 244 |
| 245 BooleanExpression parseBooleanExpression() => parseBooleanOr(); |
| 246 |
| 247 BooleanExpression parseBooleanOr() { |
| 248 BooleanExpression left = parseBooleanAnd(); |
| 249 while (scanner.hasMore() && scanner.current == Token.OR) { |
| 250 scanner.advance(); |
| 251 BooleanExpression right = parseBooleanAnd(); |
| 252 left = new BooleanOperation(Token.OR, left, right); |
| 253 } |
| 254 return left; |
| 255 } |
| 256 |
| 257 BooleanExpression parseBooleanAnd() { |
| 258 BooleanExpression left = parseBooleanAtomic(); |
| 259 while (scanner.hasMore() && scanner.current == Token.AND) { |
| 260 scanner.advance(); |
| 261 BooleanExpression right = parseBooleanAtomic(); |
| 262 left = new BooleanOperation(Token.AND, left, right); |
| 263 } |
| 264 return left; |
| 265 } |
| 266 |
| 267 BooleanExpression parseBooleanAtomic() { |
| 268 if (scanner.current == Token.LEFT_PAREN) { |
| 269 scanner.advance(); |
| 270 SetExpression value = parseBooleanExpression(); |
| 271 Expect.equals(scanner.current, Token.RIGHT_PAREN, |
| 272 "Missing right parenthesis in expression"); |
| 273 scanner.advance(); |
| 274 return value; |
| 275 } |
| 276 |
| 277 // The only atomic booleans are of the form $variable == value or the |
| 278 // form $variable. |
| 279 Expect.equals(scanner.current, Token.DOLLAR_SYMBOL, |
| 280 "Expected \$ in expression, got ${scanner.current}"); |
| 281 scanner.advance(); |
| 282 Expect.isTrue(const RegExp(@"^\w+$").hasMatch(scanner.current), |
| 283 "Expected identifier in expression, got ${scanner.current}"); |
| 284 TermVariable left = new TermVariable(scanner.current); |
| 285 scanner.advance(); |
| 286 if (scanner.current == Token.EQUALS) { |
| 287 scanner.advance(); |
| 288 Expect.isTrue(const RegExp(@"^\w+$").hasMatch(scanner.current), |
| 289 "Expected identifier in expression, got ${scanner.current}"); |
| 290 TermConstant right = new TermConstant(scanner.current); |
| 291 scanner.advance(); |
| 292 return new Comparison(left, right); |
| 293 } else { |
| 294 return new BooleanVariable(left); |
| 295 } |
| 296 } |
| 297 } |
| 298 |
| OLD | NEW |