Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 // Utilities for building JS ASTs at runtime. Contains a builder class | 5 // Utilities for building JS ASTs at runtime. Contains a builder class |
| 6 // and a parser that parses part of the language. | 6 // and a parser that parses part of the language. |
| 7 | 7 |
| 8 part of js; | 8 part of js; |
| 9 | 9 |
| 10 | |
| 11 /** | |
| 12 * Global template manager. We should aim to have a fixed number of | |
| 13 * templates. This implies that we do not use js('xxx') to parse text that is | |
| 14 * constructed from values that depend on names in the Dart program. | |
| 15 * | |
| 16 * TODO(sra): Find the remaining places where js('xxx') used to parse an | |
|
floitsch
2014/04/22 16:11:18
I'm currently pushing to remove all statics from t
sra1
2014/04/23 02:33:50
I'm not sure how to find it without making the tem
| |
| 17 * unbounded number of expression, or institute a cache policy. | |
| 18 */ | |
| 19 TemplateManager templateManager = new TemplateManager(); | |
| 20 | |
| 21 | |
| 22 /** | |
| 23 | |
| 24 [js] is a singleton instace of JsBuilder. JsBuilder is a set of conveniences | |
| 25 for constructing JavaScript ASTs. | |
| 26 | |
| 27 [string] and [number] are used to create leaf AST nodes: | |
| 28 | |
| 29 var s = js.string('hello'); // s = new LiteralString('"hello"') | |
| 30 var n = js.number(123); // n = new LiteralNumber(123) | |
| 31 var vFoo = new VariableUse('foo') --> foo | |
|
floitsch
2014/04/22 16:11:18
what does `new VariableUse` have to do with `js`?
sra1
2014/04/23 02:33:50
Done.
| |
| 32 | |
| 33 In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript | |
| 34 AST that would pretty-print as `b`. | |
| 35 | |
| 36 The [call] method constructs an Expression AST. Since the builder is bound to | |
|
floitsch
2014/04/22 16:11:18
Not sure I understand the second sentence. It's ju
sra1
2014/04/23 02:33:50
Done.
| |
| 37 [js] `js` is used to construct JavaScript from a fragment of source. | |
| 38 | |
| 39 No argument | |
| 40 | |
| 41 js('window.alert("hello")') --> window.alert("hello") | |
| 42 | |
| 43 The input text can contain placeholders `#` that are replace with provided | |
|
floitsch
2014/04/22 16:11:18
replaced
sra1
2014/04/23 02:33:50
Done.
| |
| 44 arguments. A single argument can be passed directly: | |
| 45 | |
| 46 js('window.alert(#)', s) --> window.alert("hello") | |
| 47 | |
| 48 Multiple arguments are passed as a list: | |
| 49 | |
| 50 js('# + #', [s, s]) --> "hello" + "hello" | |
| 51 | |
| 52 The [statement] method constructs a Statement AST, but is otherwise like the | |
| 53 [call] method. This constructs a Return AST: | |
| 54 | |
| 55 var ret = js.statement('return #;', n); --> return 123; | |
| 56 | |
| 57 A placeholder in a Statement context must be followed by a semicolon ';'. You | |
| 58 can think of a statement placeholder as being `#;` to explain why the output | |
| 59 still has one semicolon: | |
| 60 | |
| 61 js.statement('if (happy) #;', ret) | |
| 62 --> | |
| 63 if (happy) | |
| 64 return 123; | |
| 65 | |
| 66 If the placeholder is not followed by a semicolon, it is part of an expression. | |
| 67 Here the paceholder is in the position of a function in a function call. | |
| 68 | |
| 69 js.statement('if (happy) #("Happy!")', vFoo) | |
| 70 --> | |
| 71 if (happy) | |
| 72 foo("Happy!"); | |
| 73 | |
| 74 Generally, a placeholder in an expression position requires an Expression AST as | |
| 75 an argument and a placeholder in a statement position requires a Statement AST. | |
| 76 An expression will be converted to a Statement of needed by creating an | |
| 77 ExpessionStatement. A String argument in a will be converted into a VariableUse | |
| 78 and requires that the string is a JavaScript identifier. | |
| 79 | |
| 80 js('# + 1', vFoo) --> foo + 1 | |
| 81 js('# + 1', 'foo') --> foo + 1 | |
| 82 js('# + 1', 'foo.bar') --> assertion failure | |
| 83 | |
| 84 Some placeholder positions are _splicing contexts_. A function argument list is | |
| 85 a splicing expression context. A placeholder in a splicing expression context | |
| 86 can take a single Expression (or String, converted to VariableUse) or an | |
| 87 Iterable of Expressions (and/or Strings). | |
| 88 | |
| 89 // non-splicing argument: | |
| 90 js('#(#)', ['say', s]) --> say("hello") | |
| 91 // splicing arguments: | |
| 92 js('#(#)', ['say', []]) --> say() | |
| 93 js('#(#)', ['say', [s]]) --> say("hello") | |
| 94 js('#(#)', ['say', [s, n]]) --> say("hello", 123) | |
| 95 | |
| 96 A splicing context can be used to append 'lists' and add extra elements: | |
| 97 | |
| 98 js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1) | |
| 99 js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1) | |
| 100 js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1) | |
| 101 js('foo(#, #, 1)', [ [], [] ]) --> foo(1) | |
| 102 | |
| 103 The generation of a compile-time optional argument expression can be chosen by | |
| 104 providing an empty or singleton list. | |
| 105 | |
| 106 In addition to Expressions and Statements, there are Parameters, which occur | |
| 107 only in the parameter list of a function expression or declaration. | |
| 108 Placeholders in parameter positions behave like placeholders in Expression | |
| 109 positions, except only Parameter AST nodes are permitted. String arguments for | |
| 110 parameter placeholders are converted to Parameter AST nodes. | |
| 111 | |
| 112 var pFoo = new Parameter('foo') | |
| 113 js('function(#) { return #; }', [pFoo, vFoo]) --> function(foo){return foo;} | |
| 114 | |
| 115 Expressions and Parameters are not compatible with each other's context: | |
| 116 | |
| 117 js('function(#) { return #; }', [vFoo, vFoo]) --> error | |
| 118 js('function(#) { return #; }', [pFoo, pFoo]) --> error | |
| 119 | |
| 120 The parameter context is a splicing context. When combined with the | |
| 121 context-sensitive conversion of Strings, this simplifies the construction of | |
| 122 trampoline-like functions: | |
| 123 | |
| 124 var args = ['a', 'b']; | |
| 125 js('function(#) { return f(this, #); }', [args, args]) | |
| 126 --> | |
| 127 function(a, b) { return f(this, a, b); } | |
| 128 | |
| 129 A statement placeholder in a Block is also in a splicing context. In addition | |
| 130 to splicing Iterables, statement placeholders in a Block will also splice a | |
| 131 Block or an EmptyStatement. This flattens nested blocks and allows blocks to be | |
| 132 appended. | |
| 133 | |
| 134 var b1 = js.statement('{ 1; 2; }'); | |
| 135 var sEmpty = new Emptystatement(); | |
| 136 js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty]) | |
| 137 --> | |
| 138 { 1; 2; 1; 2; } | |
| 139 | |
| 140 A placeholder in the context of an if-statement condition also accepts a Dart | |
| 141 bool argument, which select then-part or else-part of the if-statement: | |
| 142 | |
| 143 js.statement('if (#) return;', vFoo) --> if (foo) return; | |
| 144 js.statement('if (#) return;', true) --> return; | |
| 145 js.statement('if (#) return;', false) --> ; // empty statement | |
| 146 var eTrue = new LiteralBool(true); | |
| 147 js.statement('if (#) return;', eTrue) --> if (true) return; | |
| 148 | |
| 149 Combined with block splicing, if-statement condition context placeholders allows | |
| 150 the creation of tenplates that select code depending on variables. | |
| 151 | |
| 152 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true) | |
| 153 --> { 1; 2; 5; } | |
| 154 | |
| 155 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false) | |
| 156 --> { 1; 3; 4; 5; } | |
| 157 | |
| 158 A placeholder following a period in a property access is in a property access | |
| 159 context. This is just like an expression context, except String arguments are | |
| 160 converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand | |
| 161 for `a["b"]`: | |
| 162 | |
| 163 js('a[#]', vFoo) --> a[foo] | |
| 164 js('a[#]', s) --> a.hello (i.e. a["hello"]). | |
| 165 js('a[#]', 'x') --> a[x] | |
| 166 | |
| 167 js('a.#', vFoo) --> a[foo] | |
| 168 js('a.#', s) --> a.hello (i.e. a["hello"]) | |
| 169 js('a.#', 'x') --> a.x (i.e. a["x"]) | |
| 170 | |
| 171 (Question - should `.#` be restricted to permit only String arguments? The | |
| 172 template should probably be writted with `[]` if non-strings are accepted.) | |
|
floitsch
2014/04/22 16:11:18
I would say yes. Let's not allow non-string argume
sra1
2014/04/23 02:33:50
Presumably you saw that a.# was used extensively.
| |
| 173 | |
| 174 | |
| 175 Object initialiers allow placeholders in the key posiition: | |
|
floitsch
2014/04/22 16:11:18
initializers position
sra1
2014/04/23 02:33:50
Done.
| |
| 176 | |
| 177 js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2} | |
| 178 | |
| 179 | |
| 180 TODO: Array initializers and object initializers could support splicing. In | |
|
floitsch
2014/04/22 16:11:18
TODO(sra) or TODO(issue-number)
sra1
2014/04/23 02:33:50
I rephased this since I'm not sure it is a TODO.
| |
| 181 the array case, we would need some way to know if an ArrayInitializer argument | |
| 182 should be splice or is intended as a single value. | |
| 183 | |
| 184 TODO: There are no placeholders in definition contexts: | |
|
floitsch
2014/04/22 16:11:18
ditto.
sra1
2014/04/23 02:33:50
I rephased this since I'm not sure it is a TODO.
| |
| 185 | |
| 186 function #(){} | |
| 187 var #=1; | |
| 188 | |
| 189 */ | |
| 190 const JsBuilder js = const JsBuilder(); | |
|
floitsch
2014/04/22 16:11:18
In order to get rid of the static state we could r
floitsch
2014/04/22 16:11:18
On the other hand I can see how this makes repeate
sra1
2014/04/23 02:33:50
That is part of the plan :-)
sra1
2014/04/23 02:33:50
Or we could make the state independent of any comp
| |
| 191 | |
| 192 | |
| 10 class JsBuilder { | 193 class JsBuilder { |
| 11 const JsBuilder(); | 194 const JsBuilder(); |
| 12 | 195 |
| 13 /** | 196 /** |
| 14 * Parses a bit of JavaScript, and returns an expression. | 197 * Parses a bit of JavaScript, and returns an expression. |
| 15 * | 198 * |
| 16 * See the MiniJsParser class. | 199 * See the MiniJsParser class. |
| 17 * | 200 * |
| 18 * [expression] can be an [Expression] or a list of [Expression]s, which will | 201 * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or |
| 19 * be interpolated into the source at the '#' signs. | 202 * a list of [Node]s, which will be interpolated into the source at the '#' |
| 203 * signs. | |
| 20 */ | 204 */ |
| 21 Expression call(String source, [var expression]) { | 205 Expression call(String source, [var arguments]) { |
| 22 var result = new MiniJsParser(source).expression(); | 206 Template template = _findExpressionTemplate(source); |
| 23 if (expression == null) return result; | 207 if (arguments == null) return template.instantiate([]); |
| 24 | 208 return template.instantiate(arguments is List ? arguments : [arguments]); |
| 25 List<Node> nodes; | |
| 26 if (expression is List) { | |
| 27 nodes = expression; | |
| 28 } else { | |
| 29 nodes = <Node>[expression]; | |
| 30 } | |
| 31 if (nodes.length != result.interpolatedNodes.length) { | |
| 32 throw 'Unmatched number of interpolated expressions given ${nodes.length}' | |
| 33 ' expected ${result.interpolatedNodes.length}'; | |
| 34 } | |
| 35 for (int i = 0; i < nodes.length; i++) { | |
| 36 result.interpolatedNodes[i].assign(nodes[i]); | |
| 37 } | |
| 38 | |
| 39 return result.value; | |
| 40 } | 209 } |
| 41 | 210 |
| 42 Statement statement(String source) { | 211 /** |
| 43 var result = new MiniJsParser(source).statement(); | 212 * Parses a JavaScript Statement, otherwise just like [call]. |
| 44 // TODO(sra): Interpolation. | 213 */ |
| 45 return result; | 214 Statement statement(String source, [var arguments]) { |
| 215 Template template = _findStatementTemplate(source); | |
| 216 if (arguments == null) return template.instantiate([]); | |
| 217 return template.instantiate(arguments is List ? arguments : [arguments]); | |
| 46 } | 218 } |
| 47 | 219 |
| 48 // Parse JavaScript written in the JS foreign instruction. | 220 /** |
| 49 Expression parseForeignJS(String source, [var expression]) { | 221 * Parses JavaScript written in the `JS` foreign instruction. |
| 50 // We can parse simple JS with the mini parser. At the moment we can't | 222 * |
| 51 // handle JSON literals and function literals, both of which contain "{". | 223 * The [source] must be a JavaScript expression or a JavaScript throw |
| 52 if (source.contains("{") || source.startsWith("throw ")) { | 224 * statement. |
| 53 assert(expression == null); | 225 */ |
| 54 return new LiteralExpression(source); | 226 Template parseForeignJS(String source) { |
| 227 // TODO(sra): Parse with extra validation to forbid `#` interpolation in | |
| 228 // functions, as this leads to unanticipated capture of temporaries that can | |
|
floitsch
2014/04/22 16:11:18
-can-
sra1
2014/04/23 02:33:50
Done.
| |
| 229 // are reused after capture. | |
| 230 if (source.startsWith("throw ")) { | |
| 231 return _findStatementTemplate(source); | |
| 232 } else { | |
| 233 return _findExpressionTemplate(source); | |
| 55 } | 234 } |
| 56 return call(source, expression); | 235 } |
| 236 | |
| 237 Template _findExpressionTemplate(String source) { | |
| 238 Template template = templateManager.lookupExpressionTemplate(source); | |
| 239 if (template == null) { | |
| 240 MiniJsParser parser = new MiniJsParser(source); | |
| 241 Expression expression = parser.expression(); | |
| 242 template = templateManager.defineExpressionTemplate(source, expression); | |
| 243 } | |
| 244 return template; | |
| 245 } | |
| 246 | |
| 247 Template _findStatementTemplate(String source) { | |
| 248 Template template = templateManager.lookupStatementTemplate(source); | |
| 249 if (template == null) { | |
| 250 MiniJsParser parser = new MiniJsParser(source); | |
| 251 Statement expression = parser.statement(); | |
|
floitsch
2014/04/22 16:11:18
s/expression/statement
sra1
2014/04/23 02:33:50
Done.
| |
| 252 template = templateManager.defineStatementTemplate(source, expression); | |
| 253 } | |
| 254 return template; | |
| 255 } | |
| 256 | |
| 257 /** | |
| 258 * Creates an Expression template without caching the result. | |
| 259 */ | |
| 260 Template uncachedExpressionTemplate(String source) { | |
| 261 MiniJsParser parser = new MiniJsParser(source); | |
| 262 Expression expression = parser.expression(); | |
| 263 return new Template( | |
| 264 source, expression, isExpression: true, forceCopy: false); | |
| 265 } | |
| 266 | |
| 267 /** | |
| 268 * Create an Expression template which has [ast] as the result. This is used | |
| 269 * to wrap a generated AST in a zero-argument Template so it can be passed to | |
| 270 * context that expects a template. | |
| 271 */ | |
| 272 Template expressionTemplateYielding(Node ast) { | |
| 273 return new Template.withExpressionResult(ast); | |
| 57 } | 274 } |
| 58 | 275 |
| 59 /// Creates a litteral js string from [value]. | 276 /// Creates a litteral js string from [value]. |
| 60 LiteralString escapedString(String value) { | 277 LiteralString escapedString(String value) { |
| 61 // Do not escape unicode characters and ' because they are allowed in the | 278 // Do not escape unicode characters and ' because they are allowed in the |
| 62 // string literal anyway. | 279 // string literal anyway. |
| 63 String escaped = | 280 String escaped = |
| 64 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { | 281 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { |
| 65 switch (match.group(0)) { | 282 switch (match.group(0)) { |
| 66 case "\n" : return r"\n"; | 283 case "\n" : return r"\n"; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 83 /// Creates a litteral js string from [value]. | 300 /// Creates a litteral js string from [value]. |
| 84 /// | 301 /// |
| 85 /// Note that this function only puts quotes around [value]. It does not do | 302 /// Note that this function only puts quotes around [value]. It does not do |
| 86 /// any escaping, so use only when you can guarantee that [value] does not | 303 /// any escaping, so use only when you can guarantee that [value] does not |
| 87 /// contain newlines or backslashes. For escaping the string use | 304 /// contain newlines or backslashes. For escaping the string use |
| 88 /// [escapedString]. | 305 /// [escapedString]. |
| 89 LiteralString string(String value) => new LiteralString('"$value"'); | 306 LiteralString string(String value) => new LiteralString('"$value"'); |
| 90 | 307 |
| 91 LiteralNumber number(num value) => new LiteralNumber('$value'); | 308 LiteralNumber number(num value) => new LiteralNumber('$value'); |
| 92 | 309 |
| 93 If if_(condition, thenPart, [elsePart]) { | 310 //Expression toExpression(expression) { |
|
floitsch
2014/04/22 16:11:18
dead code?
sra1
2014/04/23 02:33:50
Yes. It was the only one that was slightly painfu
| |
| 94 condition = toExpression(condition); | 311 // if (expression == null) { |
| 95 return (elsePart == null) | 312 // return null; |
| 96 ? new If.noElse(condition, toStatement(thenPart)) | 313 // } else if (expression is Expression) { |
| 97 : new If(condition, toStatement(thenPart), toStatement(elsePart)); | 314 // return expression; |
| 98 } | 315 // } else if (expression is String) { |
| 99 | 316 // return this(expression); |
| 100 Return return_([value]) { | 317 // } else if (expression is num) { |
| 101 return new Return(value == null ? null : toExpression(value)); | 318 // return new LiteralNumber('$expression'); |
| 102 } | 319 // } else if (expression is bool) { |
| 103 | 320 // return new LiteralBool(expression); |
| 104 Block block(statement) { | 321 // } else if (expression is Map) { |
| 105 if (statement is Block) { | 322 // if (!expression.isEmpty) { |
| 106 return statement; | 323 // throw new ArgumentError('expression should be an empty Map'); |
| 107 } else if (statement is List) { | 324 // } |
| 108 List<Statement> statements = statement | 325 // return new ObjectInitializer([]); |
| 109 .map(toStatement) | 326 // } else if (expression is List) { |
| 110 .where((s) => s is !EmptyStatement) | 327 // var values = new List<ArrayElement>.generate(expression.length, |
| 111 .toList(); | 328 // (index) => new ArrayElement(index, toExpression(expression[index]))) ; |
| 112 return new Block(statements); | 329 // return new ArrayInitializer(values.length, values); |
| 113 } else { | 330 // } else { |
| 114 return new Block(<Statement>[toStatement(statement)]); | 331 // throw new ArgumentError('expression should be an Expression, ' |
| 115 } | 332 // 'a String, a num, a bool, a Map, or a List;'); |
| 116 } | 333 // } |
| 117 | 334 //} |
| 118 Fun fun(parameters, body) { | |
| 119 Parameter toParameter(parameter) { | |
| 120 if (parameter is String) { | |
| 121 return new Parameter(parameter); | |
| 122 } else if (parameter is Parameter) { | |
| 123 return parameter; | |
| 124 } else { | |
| 125 throw new ArgumentError('parameter should be a String or a Parameter'); | |
| 126 } | |
| 127 } | |
| 128 if (parameters is! List) { | |
| 129 parameters = [parameters]; | |
| 130 } | |
| 131 return new Fun(parameters.map(toParameter).toList(), block(body)); | |
| 132 } | |
| 133 | |
| 134 VariableDeclarationList defineVar(String name, [initializer]) { | |
| 135 if (initializer != null) { | |
| 136 initializer = toExpression(initializer); | |
| 137 } | |
| 138 var declaration = new VariableDeclaration(name); | |
| 139 var initialization = [new VariableInitialization(declaration, initializer)]; | |
| 140 return new VariableDeclarationList(initialization); | |
| 141 } | |
| 142 | |
| 143 Statement toStatement(statement) { | |
| 144 if (statement is List) { | |
| 145 return block(statement); | |
| 146 } else if (statement is Node) { | |
| 147 return statement.toStatement(); | |
| 148 } else { | |
| 149 throw new ArgumentError('statement'); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 Expression toExpression(expression) { | |
| 154 if (expression == null) { | |
| 155 return null; | |
| 156 } else if (expression is Expression) { | |
| 157 return expression; | |
| 158 } else if (expression is String) { | |
| 159 return this(expression); | |
| 160 } else if (expression is num) { | |
| 161 return new LiteralNumber('$expression'); | |
| 162 } else if (expression is bool) { | |
| 163 return new LiteralBool(expression); | |
| 164 } else if (expression is Map) { | |
| 165 if (!expression.isEmpty) { | |
| 166 throw new ArgumentError('expression should be an empty Map'); | |
| 167 } | |
| 168 return new ObjectInitializer([]); | |
| 169 } else if (expression is List) { | |
| 170 var values = new List<ArrayElement>.generate(expression.length, | |
| 171 (index) => new ArrayElement(index, toExpression(expression[index]))); | |
| 172 return new ArrayInitializer(values.length, values); | |
| 173 } else { | |
| 174 throw new ArgumentError('expression should be an Expression, ' | |
| 175 'a String, a num, a bool, a Map, or a List;'); | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 ForIn forIn(String name, object, statement) { | |
| 180 return new ForIn(defineVar(name), | |
| 181 toExpression(object), | |
| 182 toStatement(statement)); | |
| 183 } | |
| 184 | |
| 185 For for_(init, condition, update, statement) { | |
| 186 return new For( | |
| 187 toExpression(init), toExpression(condition), toExpression(update), | |
| 188 toStatement(statement)); | |
| 189 } | |
| 190 | |
| 191 While while_(condition, statement) { | |
| 192 return new While( | |
| 193 toExpression(condition), toStatement(statement)); | |
| 194 } | |
| 195 | |
| 196 Try try_(body, {catchPart, finallyPart}) { | |
| 197 if (catchPart != null) catchPart = toStatement(catchPart); | |
| 198 if (finallyPart != null) finallyPart = toStatement(finallyPart); | |
| 199 return new Try(toStatement(body), catchPart, finallyPart); | |
| 200 } | |
| 201 | 335 |
| 202 Comment comment(String text) => new Comment(text); | 336 Comment comment(String text) => new Comment(text); |
| 203 } | 337 } |
| 204 | 338 |
| 205 const JsBuilder js = const JsBuilder(); | |
| 206 | |
| 207 LiteralString string(String value) => js.string(value); | 339 LiteralString string(String value) => js.string(value); |
| 208 | 340 |
| 209 class MiniJsParserError { | 341 class MiniJsParserError { |
| 210 MiniJsParserError(this.parser, this.message) { } | 342 MiniJsParserError(this.parser, this.message) { } |
| 211 | 343 |
| 212 final MiniJsParser parser; | 344 final MiniJsParser parser; |
| 213 final String message; | 345 final String message; |
| 214 | 346 |
| 215 String toString() { | 347 String toString() { |
| 216 int pos = parser.lastPosition; | 348 int pos = parser.lastPosition; |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 238 /// * identifiers. | 370 /// * identifiers. |
| 239 /// * dot access. | 371 /// * dot access. |
| 240 /// * method calls. | 372 /// * method calls. |
| 241 /// * [] access. | 373 /// * [] access. |
| 242 /// * array, string, regexp, boolean, null and numeric literals. | 374 /// * array, string, regexp, boolean, null and numeric literals. |
| 243 /// * most operators. | 375 /// * most operators. |
| 244 /// * brackets. | 376 /// * brackets. |
| 245 /// * var declarations. | 377 /// * var declarations. |
| 246 /// * operator precedence. | 378 /// * operator precedence. |
| 247 /// Notable things it can't do yet include: | 379 /// Notable things it can't do yet include: |
| 248 /// * non-empty object literals. | 380 /// * some statements are still missing (do-while, while, switch. |
|
floitsch
2014/04/22 16:11:18
missing closing parenthesis.
sra1
2014/04/23 02:33:50
Done.
| |
| 249 /// * throw, return. | |
| 250 /// * statements, including any flow control (if, while, for, etc.) | |
| 251 /// | 381 /// |
| 252 /// It's a fairly standard recursive descent parser. | 382 /// It's a fairly standard recursive descent parser. |
| 253 /// | 383 /// |
| 254 /// Literal strings are passed through to the final JS source code unchanged, | 384 /// Literal strings are passed through to the final JS source code unchanged, |
| 255 /// including the choice of surrounding quotes, so if you parse | 385 /// including the choice of surrounding quotes, so if you parse |
| 256 /// r'var x = "foo\n\"bar\""' you will end up with | 386 /// r'var x = "foo\n\"bar\""' you will end up with |
| 257 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not | 387 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not |
| 258 /// allowed in string and regexp literals because the machinery for checking | 388 /// allowed in string and regexp literals because the machinery for checking |
| 259 /// their correctness is rather involved. | 389 /// their correctness is rather involved. |
| 260 class MiniJsParser { | 390 class MiniJsParser { |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 395 } while (currentCode != delimiter); | 525 } while (currentCode != delimiter); |
| 396 position++; | 526 position++; |
| 397 return src.substring(lastPosition, position); | 527 return src.substring(lastPosition, position); |
| 398 } | 528 } |
| 399 | 529 |
| 400 void getToken() { | 530 void getToken() { |
| 401 skippedNewline = false; | 531 skippedNewline = false; |
| 402 for (;;) { | 532 for (;;) { |
| 403 if (position >= src.length) break; | 533 if (position >= src.length) break; |
| 404 int code = src.codeUnitAt(position); | 534 int code = src.codeUnitAt(position); |
| 405 // Skip '//' style comment. | 535 // Skip '//' and '/*' style comments. |
| 406 if (code == charCodes.$SLASH && | 536 if (code == charCodes.$SLASH && |
| 407 position + 1 < src.length && | 537 position + 1 < src.length) { |
| 408 src.codeUnitAt(position + 1) == charCodes.$SLASH) { | 538 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) { |
| 409 int nextPosition = src.indexOf('\n', position); | 539 int nextPosition = src.indexOf('\n', position); |
| 410 if (nextPosition == -1) nextPosition = src.length; | 540 if (nextPosition == -1) nextPosition = src.length; |
| 411 position = nextPosition; | 541 position = nextPosition; |
| 412 } else { | 542 continue; |
| 413 if (category(code) != WHITESPACE) break; | 543 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) { |
| 414 if (code == charCodes.$LF) skippedNewline = true; | 544 int nextPosition = src.indexOf('*/', position + 2); |
| 415 ++position; | 545 if (nextPosition == -1) error('Unterminated comment'); |
| 546 position = nextPosition + 2; | |
| 547 continue; | |
| 548 } | |
| 416 } | 549 } |
| 550 if (category(code) != WHITESPACE) break; | |
| 551 if (code == charCodes.$LF) skippedNewline = true; | |
| 552 ++position; | |
| 417 } | 553 } |
| 418 | 554 |
| 419 if (position == src.length) { | 555 if (position == src.length) { |
| 420 lastCategory = NONE; | 556 lastCategory = NONE; |
| 421 lastToken = null; | 557 lastToken = null; |
| 422 lastPosition = position; | 558 lastPosition = position; |
| 423 return; | 559 return; |
| 424 } | 560 } |
| 425 int code = src.codeUnitAt(position); | 561 int code = src.codeUnitAt(position); |
| 426 lastPosition = position; | 562 lastPosition = position; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 502 | 638 |
| 503 void expectSemicolon() { | 639 void expectSemicolon() { |
| 504 if (acceptSemicolon()) return; | 640 if (acceptSemicolon()) return; |
| 505 error('Expected SEMICOLON'); | 641 error('Expected SEMICOLON'); |
| 506 } | 642 } |
| 507 | 643 |
| 508 bool acceptSemicolon() { | 644 bool acceptSemicolon() { |
| 509 // Accept semicolon or automatically inserted semicolon before close brace. | 645 // Accept semicolon or automatically inserted semicolon before close brace. |
| 510 // Miniparser forbids other kinds of semicolon insertion. | 646 // Miniparser forbids other kinds of semicolon insertion. |
| 511 if (RBRACE == lastCategory) return true; | 647 if (RBRACE == lastCategory) return true; |
| 648 if (NONE == lastCategory) return true; // end of input | |
| 512 if (skippedNewline) { | 649 if (skippedNewline) { |
| 513 error('No automatic semicolon insertion at preceding newline'); | 650 error('No automatic semicolon insertion at preceding newline'); |
| 514 } | 651 } |
| 515 return acceptCategory(SEMICOLON); | 652 return acceptCategory(SEMICOLON); |
| 516 } | 653 } |
| 517 | 654 |
| 518 bool acceptString(String string) { | 655 bool acceptString(String string) { |
| 519 if (lastToken == string) { | 656 if (lastToken == string) { |
| 520 getToken(); | 657 getToken(); |
| 521 return true; | 658 return true; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 561 } | 698 } |
| 562 return new ArrayInitializer(values.length, values); | 699 return new ArrayInitializer(values.length, values); |
| 563 } else if (last.startsWith("/")) { | 700 } else if (last.startsWith("/")) { |
| 564 String regexp = getDelimited(lastPosition); | 701 String regexp = getDelimited(lastPosition); |
| 565 getToken(); | 702 getToken(); |
| 566 String flags = lastToken; | 703 String flags = lastToken; |
| 567 if (!acceptCategory(ALPHA)) flags = ""; | 704 if (!acceptCategory(ALPHA)) flags = ""; |
| 568 Expression expression = new RegExpLiteral(regexp + flags); | 705 Expression expression = new RegExpLiteral(regexp + flags); |
| 569 return expression; | 706 return expression; |
| 570 } else if (acceptCategory(HASH)) { | 707 } else if (acceptCategory(HASH)) { |
| 571 InterpolatedExpression expression = new InterpolatedExpression(null); | 708 InterpolatedExpression expression = |
| 709 new InterpolatedExpression(interpolatedValues.length); | |
| 572 interpolatedValues.add(expression); | 710 interpolatedValues.add(expression); |
| 573 return expression; | 711 return expression; |
| 574 } else { | 712 } else { |
| 575 error("Expected primary expression"); | 713 error("Expected primary expression"); |
| 576 return null; | 714 return null; |
| 577 } | 715 } |
| 578 } | 716 } |
| 579 | 717 |
| 580 Expression parseFunctionExpression() { | 718 Expression parseFunctionExpression() { |
| 581 String last = lastToken; | 719 String last = lastToken; |
| 582 if (acceptCategory(ALPHA)) { | 720 if (acceptCategory(ALPHA)) { |
| 583 String functionName = last; | 721 String functionName = last; |
| 584 return new NamedFunction(new VariableDeclaration(functionName), | 722 return new NamedFunction(new VariableDeclaration(functionName), |
| 585 parseFun()); | 723 parseFun()); |
| 586 } | 724 } |
| 587 return parseFun(); | 725 return parseFun(); |
| 588 } | 726 } |
| 589 | 727 |
| 590 Expression parseFun() { | 728 Expression parseFun() { |
| 591 List<Parameter> params = <Parameter>[]; | 729 List<Parameter> params = <Parameter>[]; |
| 730 | |
| 592 expectCategory(LPAREN); | 731 expectCategory(LPAREN); |
| 593 String argumentName = lastToken; | 732 if (!acceptCategory(RPAREN)) { |
| 594 if (acceptCategory(ALPHA)) { | 733 for (;;) { |
| 595 params.add(new Parameter(argumentName)); | 734 if (acceptCategory(HASH)) { |
| 596 while (acceptCategory(COMMA)) { | 735 InterpolatedParameter parameter = |
| 597 argumentName = lastToken; | 736 new InterpolatedParameter(interpolatedValues.length); |
| 598 expectCategory(ALPHA); | 737 interpolatedValues.add(parameter); |
| 599 params.add(new Parameter(argumentName)); | 738 params.add(parameter); |
| 739 } else { | |
| 740 String argumentName = lastToken; | |
| 741 expectCategory(ALPHA); | |
| 742 params.add(new Parameter(argumentName)); | |
| 743 } | |
| 744 if (acceptCategory(COMMA)) continue; | |
| 745 expectCategory(RPAREN); | |
| 746 break; | |
| 600 } | 747 } |
| 601 } | 748 } |
| 602 expectCategory(RPAREN); | 749 |
| 603 expectCategory(LBRACE); | 750 expectCategory(LBRACE); |
| 604 Block block = parseBlock(); | 751 Block block = parseBlock(); |
| 605 return new Fun(params, block); | 752 return new Fun(params, block); |
| 606 } | 753 } |
| 607 | 754 |
| 608 Expression parseObjectInitializer() { | 755 Expression parseObjectInitializer() { |
| 609 List<Property> properties = <Property>[]; | 756 List<Property> properties = <Property>[]; |
| 610 for (;;) { | 757 for (;;) { |
| 611 if (acceptCategory(RBRACE)) break; | 758 if (acceptCategory(RBRACE)) break; |
| 612 // Limited subset: keys are identifiers, no 'get' or 'set' properties. | 759 // Limited subset: keys are identifiers, no 'get' or 'set' properties. |
| 613 Literal propertyName; | 760 Literal propertyName; |
| 614 String identifier = lastToken; | 761 String identifier = lastToken; |
| 615 if (acceptCategory(ALPHA)) { | 762 if (acceptCategory(ALPHA)) { |
| 616 propertyName = new LiteralString('"$identifier"'); | 763 propertyName = new LiteralString('"$identifier"'); |
| 617 } else if (acceptCategory(STRING)) { | 764 } else if (acceptCategory(STRING)) { |
| 618 propertyName = new LiteralString(identifier); | 765 propertyName = new LiteralString(identifier); |
| 766 } else if (acceptCategory(SYMBOL)) { // e.g. void | |
| 767 propertyName = new LiteralString('"$identifier"'); | |
| 768 } else if (acceptCategory(HASH)) { | |
| 769 InterpolatedLiteral interpolatedLiteral = | |
| 770 new InterpolatedLiteral(interpolatedValues.length); | |
| 771 interpolatedValues.add(interpolatedLiteral); | |
| 772 propertyName = interpolatedLiteral; | |
| 619 } else { | 773 } else { |
| 620 error('Expected property name'); | 774 error('Expected property name'); |
| 621 } | 775 } |
| 622 expectCategory(COLON); | 776 expectCategory(COLON); |
| 623 Expression value = parseAssignment(); | 777 Expression value = parseAssignment(); |
| 624 properties.add(new Property(propertyName, value)); | 778 properties.add(new Property(propertyName, value)); |
| 625 if (acceptCategory(RBRACE)) break; | 779 if (acceptCategory(RBRACE)) break; |
| 626 expectCategory(COMMA); | 780 expectCategory(COMMA); |
| 627 } | 781 } |
| 628 return new ObjectInitializer(properties); | 782 return new ObjectInitializer(properties); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 671 } else { | 825 } else { |
| 672 // JS allows new without (), but we don't. | 826 // JS allows new without (), but we don't. |
| 673 if (constructor) error("Parentheses are required for new"); | 827 if (constructor) error("Parentheses are required for new"); |
| 674 break; | 828 break; |
| 675 } | 829 } |
| 676 } | 830 } |
| 677 return receiver; | 831 return receiver; |
| 678 } | 832 } |
| 679 | 833 |
| 680 Expression getDotRhs(Expression receiver) { | 834 Expression getDotRhs(Expression receiver) { |
| 835 if (acceptCategory(HASH)) { | |
| 836 InterpolatedSelector property = | |
| 837 new InterpolatedSelector(interpolatedValues.length); | |
| 838 interpolatedValues.add(property); | |
| 839 return new PropertyAccess(receiver, property); | |
| 840 } | |
| 681 String identifier = lastToken; | 841 String identifier = lastToken; |
| 682 // In ES5 keywords like delete and continue are allowed as property | 842 // In ES5 keywords like delete and continue are allowed as property |
| 683 // names, and the IndexedDB API uses that, so we need to allow it here. | 843 // names, and the IndexedDB API uses that, so we need to allow it here. |
| 684 if (acceptCategory(SYMBOL)) { | 844 if (acceptCategory(SYMBOL)) { |
| 685 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { | 845 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { |
| 686 error("Expected alphanumeric identifier"); | 846 error("Expected alphanumeric identifier"); |
| 687 } | 847 } |
| 688 } else { | 848 } else { |
| 689 expectCategory(ALPHA); | 849 expectCategory(ALPHA); |
| 690 } | 850 } |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 822 } else { | 982 } else { |
| 823 return parseExpression(); | 983 return parseExpression(); |
| 824 } | 984 } |
| 825 } | 985 } |
| 826 | 986 |
| 827 Expression expression() { | 987 Expression expression() { |
| 828 Expression expression = parseVarDeclarationOrExpression(); | 988 Expression expression = parseVarDeclarationOrExpression(); |
| 829 if (lastCategory != NONE || position != src.length) { | 989 if (lastCategory != NONE || position != src.length) { |
| 830 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 990 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 831 } | 991 } |
| 832 if (!interpolatedValues.isEmpty) { | |
| 833 return new JSExpression(expression, interpolatedValues); | |
| 834 } | |
| 835 return expression; | 992 return expression; |
| 836 } | 993 } |
| 837 | 994 |
| 838 Statement statement() { | 995 Statement statement() { |
| 839 Statement statement = parseStatement(); | 996 Statement statement = parseStatement(); |
| 840 if (lastCategory != NONE || position != src.length) { | 997 if (lastCategory != NONE || position != src.length) { |
| 841 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 998 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 842 } | 999 } |
| 843 // TODO(sra): interpolated capture here? | 1000 // TODO(sra): interpolated capture here? |
| 844 return statement; | 1001 return statement; |
| 845 } | 1002 } |
| 846 | 1003 |
| 847 Block parseBlock() { | 1004 Block parseBlock() { |
| 848 List<Statement> statements = <Statement>[]; | 1005 List<Statement> statements = <Statement>[]; |
| 849 | 1006 |
| 850 while (!acceptCategory(RBRACE)) { | 1007 while (!acceptCategory(RBRACE)) { |
| 851 Statement statement = parseStatement(); | 1008 Statement statement = parseStatement(); |
| 852 statements.add(statement); | 1009 statements.add(statement); |
| 853 } | 1010 } |
| 854 return new Block(statements); | 1011 return new Block(statements); |
| 855 } | 1012 } |
| 856 | 1013 |
| 857 Statement parseStatement() { | 1014 Statement parseStatement() { |
| 858 if (acceptCategory(LBRACE)) return parseBlock(); | 1015 if (acceptCategory(LBRACE)) return parseBlock(); |
| 859 | 1016 |
| 1017 if (acceptCategory(SEMICOLON)) return new EmptyStatement(); | |
| 1018 | |
| 860 if (lastCategory == ALPHA) { | 1019 if (lastCategory == ALPHA) { |
| 861 if (acceptString('return')) return parseReturn(); | 1020 if (acceptString('return')) return parseReturn(); |
| 862 | 1021 |
| 863 if (acceptString('throw')) return parseThrow(); | 1022 if (acceptString('throw')) return parseThrow(); |
| 864 | 1023 |
| 865 if (acceptString('break')) { | 1024 if (acceptString('break')) { |
| 866 return parseBreakOrContinue((label) => new Break(label)); | 1025 return parseBreakOrContinue((label) => new Break(label)); |
| 867 } | 1026 } |
| 868 | 1027 |
| 869 if (acceptString('continue')) { | 1028 if (acceptString('continue')) { |
| 870 return parseBreakOrContinue((label) => new Continue(label)); | 1029 return parseBreakOrContinue((label) => new Continue(label)); |
| 871 } | 1030 } |
| 872 | 1031 |
| 873 if (acceptString('if')) return parseIfThenElse(); | 1032 if (acceptString('if')) return parseIfThenElse(); |
| 874 | 1033 |
| 875 if (acceptString('for')) return parseFor(); | 1034 if (acceptString('for')) return parseFor(); |
| 876 | 1035 |
| 877 if (acceptString('function')) return parseFunctionDeclaration(); | 1036 if (acceptString('function')) return parseFunctionDeclaration(); |
| 878 | 1037 |
| 1038 if (acceptString('try')) return parseTry(); | |
| 1039 | |
| 879 if (acceptString('var')) { | 1040 if (acceptString('var')) { |
| 880 Expression declarations = parseVariableDeclarationList(); | 1041 Expression declarations = parseVariableDeclarationList(); |
| 881 expectSemicolon(); | 1042 expectSemicolon(); |
| 882 return new ExpressionStatement(declarations); | 1043 return new ExpressionStatement(declarations); |
| 883 } | 1044 } |
| 884 | 1045 |
| 885 if (lastToken == 'case' || | 1046 if (lastToken == 'case' || |
| 886 lastToken == 'do' || | 1047 lastToken == 'do' || |
| 887 lastToken == 'while' || | 1048 lastToken == 'while' || |
| 888 lastToken == 'switch' || | 1049 lastToken == 'switch' || |
| 889 lastToken == 'try' || | |
| 890 lastToken == 'with') { | 1050 lastToken == 'with') { |
| 891 error('Not implemented in mini parser'); | 1051 error('Not implemented in mini parser'); |
| 892 } | 1052 } |
| 893 } | 1053 } |
| 894 | 1054 |
| 895 if (acceptCategory(HASH)) { | |
| 896 InterpolatedStatement statement = new InterpolatedStatement(null); | |
| 897 interpolatedValues.add(statement); | |
| 898 return statement; | |
| 899 } | |
| 900 | 1055 |
| 901 // TODO: label: statement | 1056 // TODO: label: statement |
| 902 | 1057 |
| 1058 bool checkForInterpolatedStatement = lastCategory == HASH; | |
| 1059 | |
| 903 Expression expression = parseExpression(); | 1060 Expression expression = parseExpression(); |
| 904 expectSemicolon(); | 1061 expectSemicolon(); |
| 1062 | |
| 1063 if (checkForInterpolatedStatement) { | |
| 1064 // 'Promote' the interpolated expression `#;` to an interpolated | |
| 1065 // statement. | |
| 1066 if (expression is InterpolatedExpression) { | |
| 1067 assert(identical(interpolatedValues.last, expression)); | |
| 1068 InterpolatedStatement statement = | |
| 1069 new InterpolatedStatement(expression.name); | |
| 1070 interpolatedValues[interpolatedValues.length - 1] = statement; | |
| 1071 return statement; | |
| 1072 } | |
| 1073 } | |
| 1074 | |
| 905 return new ExpressionStatement(expression); | 1075 return new ExpressionStatement(expression); |
| 906 } | 1076 } |
| 907 | 1077 |
| 908 Statement parseReturn() { | 1078 Statement parseReturn() { |
| 909 if (acceptSemicolon()) return new Return(); | 1079 if (acceptSemicolon()) return new Return(); |
| 910 Expression expression = parseExpression(); | 1080 Expression expression = parseExpression(); |
| 911 expectSemicolon(); | 1081 expectSemicolon(); |
| 912 return new Return(expression); | 1082 return new Return(expression); |
| 913 } | 1083 } |
| 914 | 1084 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 970 return finishFor(null); | 1140 return finishFor(null); |
| 971 } | 1141 } |
| 972 | 1142 |
| 973 if (acceptString('var')) { | 1143 if (acceptString('var')) { |
| 974 String identifier = lastToken; | 1144 String identifier = lastToken; |
| 975 expectCategory(ALPHA); | 1145 expectCategory(ALPHA); |
| 976 if (acceptString('in')) { | 1146 if (acceptString('in')) { |
| 977 Expression objectExpression = parseExpression(); | 1147 Expression objectExpression = parseExpression(); |
| 978 expectCategory(RPAREN); | 1148 expectCategory(RPAREN); |
| 979 Statement body = parseStatement(); | 1149 Statement body = parseStatement(); |
| 980 return new ForIn(js.defineVar(identifier), objectExpression, body); | 1150 return new ForIn( |
| 1151 new VariableDeclarationList([ | |
| 1152 new VariableInitialization( | |
| 1153 new VariableDeclaration(identifier), null)]), | |
| 1154 objectExpression, | |
| 1155 body); | |
| 981 } | 1156 } |
| 982 Expression declarations = finishVariableDeclarationList(identifier); | 1157 Expression declarations = finishVariableDeclarationList(identifier); |
| 983 expectCategory(SEMICOLON); | 1158 expectCategory(SEMICOLON); |
| 984 return finishFor(declarations); | 1159 return finishFor(declarations); |
| 985 } | 1160 } |
| 986 | 1161 |
| 987 Expression init = parseExpression(); | 1162 Expression init = parseExpression(); |
| 988 expectCategory(SEMICOLON); | 1163 expectCategory(SEMICOLON); |
| 989 return finishFor(init); | 1164 return finishFor(init); |
| 990 } | 1165 } |
| 991 | 1166 |
| 992 Statement parseFunctionDeclaration() { | 1167 Statement parseFunctionDeclaration() { |
| 993 String name = lastToken; | 1168 String name = lastToken; |
| 994 expectCategory(ALPHA); | 1169 expectCategory(ALPHA); |
| 995 Expression fun = parseFun(); | 1170 Expression fun = parseFun(); |
| 996 return new FunctionDeclaration(new VariableDeclaration(name), fun); | 1171 return new FunctionDeclaration(new VariableDeclaration(name), fun); |
| 997 } | 1172 } |
| 998 } | |
| 999 | 1173 |
| 1000 /** | 1174 Statement parseTry() { |
| 1001 * Clone a JSExpression node into an expression where all children | 1175 expectCategory(LBRACE); |
| 1002 * have been cloned, and [InterpolatedExpression]s have been replaced | 1176 Block body = parseBlock(); |
| 1003 * with real [Expression]. | 1177 String token = lastToken; |
| 1004 */ | 1178 Catch catchPart = null; |
| 1005 class UninterpolateJSExpression extends BaseVisitor<Node> { | 1179 if (acceptString('catch')) catchPart = parseCatch(); |
| 1006 final List<Expression> arguments; | 1180 Block finallyPart = null; |
| 1007 int argumentIndex = 0; | 1181 if (acceptString('finally')) { |
| 1008 | 1182 expectCategory(LBRACE); |
| 1009 UninterpolateJSExpression(this.arguments); | 1183 finallyPart = parseBlock(); |
| 1010 | 1184 } else { |
| 1011 void error(message) { | 1185 if (catchPart == null) error("expected 'finally'"); |
| 1012 throw message; | 1186 } |
| 1187 return new Try(body, catchPart, finallyPart); | |
| 1013 } | 1188 } |
| 1014 | 1189 |
| 1015 Node visitNode(Node node) { | 1190 Catch parseCatch() { |
| 1016 error('Cannot handle $node'); | 1191 expectCategory(LPAREN); |
| 1017 return null; | 1192 String identifier = lastToken; |
| 1018 } | 1193 expectCategory(ALPHA); |
| 1019 | 1194 expectCategory(RPAREN); |
| 1020 Node copyPosition(Node oldNode, Node newNode) { | 1195 expectCategory(LBRACE); |
| 1021 newNode.sourcePosition = oldNode.sourcePosition; | 1196 Block body = parseBlock(); |
| 1022 newNode.endSourcePosition = oldNode.endSourcePosition; | 1197 return new Catch(new VariableDeclaration(identifier), body); |
| 1023 return newNode; | |
| 1024 } | |
| 1025 | |
| 1026 Node visit(Node node) { | |
| 1027 return node == null ? null : node.accept(this); | |
| 1028 } | |
| 1029 | |
| 1030 List<Node> visitList(List<Node> list) { | |
| 1031 return list.map((e) => visit(e)).toList(); | |
| 1032 } | |
| 1033 | |
| 1034 Node visitLiteralString(LiteralString node) { | |
| 1035 return node; | |
| 1036 } | |
| 1037 | |
| 1038 Node visitVariableUse(VariableUse node) { | |
| 1039 return node; | |
| 1040 } | |
| 1041 | |
| 1042 Node visitAccess(PropertyAccess node) { | |
| 1043 return copyPosition(node, | |
| 1044 new PropertyAccess(visit(node.receiver), visit(node.selector))); | |
| 1045 } | |
| 1046 | |
| 1047 Node visitCall(Call node) { | |
| 1048 return copyPosition(node, | |
| 1049 new Call(visit(node.target), visitList(node.arguments))); | |
| 1050 } | |
| 1051 | |
| 1052 Node visitInterpolatedExpression(InterpolatedExpression expression) { | |
| 1053 return arguments[argumentIndex++]; | |
| 1054 } | |
| 1055 | |
| 1056 Node visitInterpolatedStatement(InterpolatedStatement statement) { | |
| 1057 return arguments[argumentIndex++]; | |
| 1058 } | |
| 1059 | |
| 1060 Node visitJSExpression(JSExpression expression) { | |
| 1061 assert(argumentIndex == 0); | |
| 1062 Node result = visit(expression.value); | |
| 1063 if (argumentIndex != arguments.length) { | |
| 1064 error("Invalid number of arguments"); | |
| 1065 } | |
| 1066 assert(result is! JSExpression); | |
| 1067 return result; | |
| 1068 } | |
| 1069 | |
| 1070 Node visitLiteralExpression(LiteralExpression node) { | |
| 1071 assert(argumentIndex == 0); | |
| 1072 return copyPosition(node, | |
| 1073 new LiteralExpression.withData(node.template, arguments)); | |
| 1074 } | |
| 1075 | |
| 1076 Node visitAssignment(Assignment node) { | |
| 1077 return copyPosition(node, | |
| 1078 new Assignment._internal(visit(node.leftHandSide), | |
| 1079 visit(node.compoundTarget), | |
| 1080 visit(node.value))); | |
| 1081 } | |
| 1082 | |
| 1083 Node visitRegExpLiteral(RegExpLiteral node) { | |
| 1084 return node; | |
| 1085 } | |
| 1086 | |
| 1087 Node visitLiteralNumber(LiteralNumber node) { | |
| 1088 return node; | |
| 1089 } | |
| 1090 | |
| 1091 Node visitBinary(Binary node) { | |
| 1092 return copyPosition(node, | |
| 1093 new Binary(node.op, visit(node.left), visit(node.right))); | |
| 1094 } | |
| 1095 | |
| 1096 Node visitPrefix(Prefix node) { | |
| 1097 return copyPosition(node, | |
| 1098 new Prefix(node.op, visit(node.argument))); | |
| 1099 } | |
| 1100 | |
| 1101 Node visitPostfix(Postfix node) { | |
| 1102 return copyPosition(node, | |
| 1103 new Postfix(node.op, visit(node.argument))); | |
| 1104 } | |
| 1105 | |
| 1106 Node visitNew(New node) { | |
| 1107 return copyPosition(node, | |
| 1108 new New(visit(node.target), visitList(node.arguments))); | |
| 1109 } | |
| 1110 | |
| 1111 Node visitArrayInitializer(ArrayInitializer node) { | |
| 1112 return copyPosition(node, | |
| 1113 new ArrayInitializer(node.length, visitList(node.elements))); | |
| 1114 } | |
| 1115 | |
| 1116 Node visitArrayElement(ArrayElement node) { | |
| 1117 return copyPosition(node, | |
| 1118 new ArrayElement(node.index, visit(node.value))); | |
| 1119 } | |
| 1120 | |
| 1121 Node visitConditional(Conditional node) { | |
| 1122 return copyPosition(node, | |
| 1123 new Conditional(visit(node.condition), | |
| 1124 visit(node.then), | |
| 1125 visit(node.otherwise))); | |
| 1126 } | |
| 1127 | |
| 1128 Node visitLiteralNull(LiteralNull node) { | |
| 1129 return node; | |
| 1130 } | |
| 1131 | |
| 1132 Node visitLiteralBool(LiteralBool node) { | |
| 1133 return node; | |
| 1134 } | 1198 } |
| 1135 } | 1199 } |
| OLD | NEW |