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 TemplateManager templateManager = new TemplateManager(); | |
| 17 | |
| 18 | |
| 19 /** | |
| 20 | |
| 21 [js] is a singleton instace of JsBuilder. JsBuilder is a set of conveniences | |
| 22 for constructing JavaScript ASTs. | |
| 23 | |
| 24 [string] and [number] are used to create leaf AST nodes: | |
| 25 | |
| 26 var s = js.string('hello'); // s = new LiteralString('"hello"') | |
| 27 var n = js.number(123); // n = new LiteralNumber(123) | |
| 28 var vFoo = new VariableUse('foo') --> foo | |
| 29 | |
| 30 In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript | |
| 31 AST that would pretty-print as `b`. | |
| 32 | |
| 33 The [call] method constructs an Expression AST. Since the builder is bound to | |
| 34 [js] `js` is used to construct JavaScript from a fragment of source. | |
| 35 | |
| 36 No argument | |
| 37 | |
| 38 js('window.alert("hello")') --> window.alert("hello") | |
| 39 | |
| 40 The input text can contain placeholders `#` that are replace with provided | |
| 41 arguments. A single argument can be passed directly: | |
| 42 | |
| 43 js('window.alert(#)', s) --> window.alert("hello") | |
| 44 | |
| 45 Multiple arguments are passed as a list: | |
| 46 | |
| 47 js('# + #', [s, s]) --> "hello" + "hello" | |
| 48 | |
| 49 The [statement] method constructs a Statement AST, but is otherwise like the | |
| 50 [call] method. This constructs a Return AST: | |
| 51 | |
| 52 var ret = js.statement('return #;', n); --> return 123; | |
| 53 | |
| 54 A placeholder in a Statement context must be followed by a semicolon ';'. You | |
| 55 can think of a statement placeholder as being `#;` to explain why the output | |
| 56 still has one semicolon: | |
| 57 | |
| 58 js.statement('if (happy) #;', ret) | |
| 59 --> | |
| 60 if (happy) | |
| 61 return 123; | |
| 62 | |
| 63 If the placeholder is not followed by a semicolon, it is part of an expression. | |
| 64 Here the paceholder is in the position of a function in a function call. | |
| 65 | |
| 66 js.statement('if (happy) #("Happy!")', vFoo) | |
| 67 --> | |
| 68 if (happy) | |
| 69 foo("Happy!"); | |
| 70 | |
| 71 Generally, a placeholder in an expression position requires an Expression AST as | |
| 72 an argument and a placeholder in a statement position requires a Statement AST. | |
| 73 An expression will be converted to a Statement of needed by creating an | |
| 74 ExpessionStatement. A String argument in a will be converted into a VariableUse | |
| 75 and requires that the string is a JavaScript identifier. | |
| 76 | |
| 77 js('# + 1', vFoo) --> foo + 1 | |
| 78 js('# + 1', 'foo') --> foo + 1 | |
| 79 js('# + 1', 'foo.bar') --> assertion failure | |
| 80 | |
| 81 Some placeholder positions are _splicing contexts_. A function argument list is | |
| 82 a splicing expression context. A placeholder in a splicing expression context | |
| 83 can take a single Expression (or String, converted to VariableUse) or an | |
| 84 Iterable of Expressions (and/or Strings). | |
| 85 | |
| 86 // non-splicing argument: | |
| 87 js('#(#)', ['say', s]) --> say("hello") | |
| 88 // splicing arguments: | |
| 89 js('#(#)', ['say', []]) --> say() | |
| 90 js('#(#)', ['say', [s]]) --> say("hello") | |
| 91 js('#(#)', ['say', [s, n]]) --> say("hello", 123) | |
| 92 | |
| 93 A splicing context can be used to append 'lists' and add extra elements: | |
| 94 | |
| 95 js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1) | |
| 96 js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1) | |
| 97 js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1) | |
| 98 js('foo(#, #, 1)', [ [], [] ]) --> foo(1) | |
| 99 | |
| 100 The generation of a compile-time optional argument expression can be chosen by | |
| 101 providing an empty or singleton list. | |
| 102 | |
| 103 In addition to Expressions and Statements, there are Parameters, which occur | |
| 104 only in the parameter list of a function expression or declaration. | |
| 105 Placeholders in parameter positions behave like placeholders in Expression | |
| 106 positions, except only Parameter AST nodes are permitted. String arguments for | |
| 107 parameter placeholders are converted to Parameter AST nodes. | |
| 108 | |
| 109 var pFoo = new Parameter('foo') | |
| 110 js('function(#) { return #; }', [pFoo, vFoo]) --> function(foo){return foo;} | |
| 111 | |
| 112 Expressions and Parameters are not compatible with each other's context: | |
| 113 | |
| 114 js('function(#) { return #; }', [vFoo, vFoo]) --> error | |
| 115 js('function(#) { return #; }', [pFoo, pFoo]) --> error | |
| 116 | |
| 117 The parameter context is a splicing context. When combined with the | |
| 118 context-sensitive conversion of Strings, this simplifies the construction of | |
| 119 trampoline-like functions: | |
| 120 | |
| 121 var args = ['a', 'b']; | |
| 122 js('function(#) { return f(this, #); }', [args, args]) | |
| 123 --> | |
| 124 function(a, b) { return f(this, a, b); } | |
| 125 | |
| 126 A statement placeholder in a Block is also in a splicing context. In addition | |
| 127 to splicing Iterables, statement placeholders in a Block will also splice a | |
| 128 Block or an EmptyStatement. This flattens nested blocks and allows blocks to be | |
| 129 appended. | |
| 130 | |
| 131 var b1 = js.statement('{ 1; 2; }'); | |
| 132 var sEmpty = new Emptystatement(); | |
| 133 js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty]) | |
| 134 --> | |
| 135 { 1; 2; 1; 2; } | |
| 136 | |
| 137 A placeholder in the context of an if-statement condition also accepts a Dart | |
| 138 bool argument, which select then-part or else-part of the if-statement: | |
| 139 | |
| 140 js.statement('if (#) return;', vFoo) --> if (foo) return; | |
| 141 js.statement('if (#) return;', true) --> return; | |
| 142 js.statement('if (#) return;', false) --> ; // empty statement | |
| 143 var eTrue = new LiteralBool(true); | |
| 144 js.statement('if (#) return;', eTrue) --> if (true) return; | |
| 145 | |
| 146 Combined with block splicing, if-statement condition context placeholders allows | |
| 147 the creation of tenplates that select code depending on variables. | |
| 148 | |
| 149 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true) | |
| 150 --> { 1; 2; 5; } | |
| 151 | |
| 152 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false) | |
| 153 --> { 1; 3; 4; 5; } | |
| 154 | |
| 155 A placeholder following a period in a property access is in a property access | |
| 156 context. This is just like an expression context, except String arguments are | |
| 157 converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand | |
| 158 for `a["b"]`: | |
| 159 | |
| 160 js('a[#]', vFoo) --> a[foo] | |
| 161 js('a[#]', s) --> a.hello (i.e. a["hello"]). | |
| 162 js('a[#]', 'x') --> a[x] | |
| 163 | |
| 164 js('a.#', vFoo) --> a[foo] | |
| 165 js('a.#', s) --> a.hello (i.e. a["hello"]) | |
| 166 js('a.#', 'x') --> a.x (i.e. a["x"]) | |
| 167 | |
| 168 (Question - should `.#` be restricted to permit only String arguments? The | |
| 169 template should probably be writted with `[]` if non-strings are accepted.) | |
| 170 | |
| 171 | |
| 172 Object initialiers allow placeholders in the key posiition: | |
| 173 | |
| 174 js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2} | |
| 175 | |
| 176 | |
| 177 TODO: Array initializers and object initializers could support splicing. In | |
| 178 the array case, we would need some way to know if an ArrayInitializer argument | |
| 179 should be splice or is intended as a single value. | |
| 180 | |
| 181 TODO: There are no placeholders in definition contexts: | |
| 182 | |
| 183 function #(){} | |
| 184 var #=1; | |
| 185 | |
| 186 */ | |
| 187 const JsBuilder js = const JsBuilder(); | |
| 188 | |
| 189 | |
| 10 class JsBuilder { | 190 class JsBuilder { |
| 11 const JsBuilder(); | 191 const JsBuilder(); |
| 12 | 192 |
| 13 /** | 193 /** |
| 14 * Parses a bit of JavaScript, and returns an expression. | 194 * Parses a bit of JavaScript, and returns an expression. |
| 15 * | 195 * |
| 16 * See the MiniJsParser class. | 196 * See the MiniJsParser class. |
| 17 * | 197 * |
| 18 * [expression] can be an [Expression] or a list of [Expression]s, which will | 198 * [arguments] can be an [Expression] or a list of [Expression]s, which will |
| 19 * be interpolated into the source at the '#' signs. | 199 * be interpolated into the source at the '#' signs. |
| 20 */ | 200 */ |
| 21 Expression call(String source, [var expression]) { | 201 Expression call(String source, [var arguments]) { |
|
kevmoo
2014/05/12 04:19:33
var is unneeded here
| |
| 22 var result = new MiniJsParser(source).expression(); | 202 Template template = templateManager.findExpressionTemplate(source); |
| 23 if (expression == null) return result; | 203 if (arguments == null) return template.instantiate([]); |
| 24 | 204 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 } | 205 } |
| 41 | 206 |
| 42 Statement statement(String source) { | 207 Statement statement(String source, [var arguments]) { |
|
kevmoo
2014/05/12 04:19:33
var is unneeded here
| |
| 43 var result = new MiniJsParser(source).statement(); | 208 Template template = templateManager.findStatementTemplate(source); |
| 44 // TODO(sra): Interpolation. | 209 if (arguments == null) return template.instantiate([]); |
| 45 return result; | 210 return template.instantiate(arguments is List ? arguments : [arguments]); |
| 46 } | 211 } |
| 47 | 212 |
| 48 // Parse JavaScript written in the JS foreign instruction. | 213 /** |
| 49 Expression parseForeignJS(String source, [var expression]) { | 214 * Parses JavaScript written in the JS foreign instruction. |
| 50 // We can parse simple JS with the mini parser. At the moment we can't | 215 * |
| 51 // handle JSON literals and function literals, both of which contain "{". | 216 * With the single exception of a throw-statement, the [source] must be a |
| 52 if (source.contains("{") || source.startsWith("throw ")) { | 217 * JavaScript expression. |
| 53 assert(expression == null); | 218 */ |
| 54 return new LiteralExpression(source); | 219 Template parseForeignJS(String source) { |
| 220 // TODO(sra): Parse with extra validation to forbid `#` interpolation in | |
| 221 // functions. | |
| 222 if (source.startsWith("throw ")) { | |
| 223 return templateManager.findStatementTemplate(source); | |
| 224 } else { | |
| 225 return templateManager.findExpressionTemplate(source); | |
| 55 } | 226 } |
| 56 return call(source, expression); | 227 } |
| 228 | |
| 229 Template expressionTemplate(String source) { | |
| 230 return new Template.expression(source); | |
| 231 } | |
| 232 | |
| 233 Template expressionTemplateFromAst(Node ast) { | |
| 234 return new Template.expressionAst(ast); | |
| 57 } | 235 } |
| 58 | 236 |
| 59 /// Creates a litteral js string from [value]. | 237 /// Creates a litteral js string from [value]. |
| 60 LiteralString escapedString(String value) { | 238 LiteralString escapedString(String value) { |
| 61 // Do not escape unicode characters and ' because they are allowed in the | 239 // Do not escape unicode characters and ' because they are allowed in the |
| 62 // string literal anyway. | 240 // string literal anyway. |
| 63 String escaped = | 241 String escaped = |
| 64 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { | 242 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { |
| 65 switch (match.group(0)) { | 243 switch (match.group(0)) { |
| 66 case "\n" : return r"\n"; | 244 case "\n" : return r"\n"; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 83 /// Creates a litteral js string from [value]. | 261 /// Creates a litteral js string from [value]. |
| 84 /// | 262 /// |
| 85 /// Note that this function only puts quotes around [value]. It does not do | 263 /// 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 | 264 /// any escaping, so use only when you can guarantee that [value] does not |
| 87 /// contain newlines or backslashes. For escaping the string use | 265 /// contain newlines or backslashes. For escaping the string use |
| 88 /// [escapedString]. | 266 /// [escapedString]. |
| 89 LiteralString string(String value) => new LiteralString('"$value"'); | 267 LiteralString string(String value) => new LiteralString('"$value"'); |
| 90 | 268 |
| 91 LiteralNumber number(num value) => new LiteralNumber('$value'); | 269 LiteralNumber number(num value) => new LiteralNumber('$value'); |
| 92 | 270 |
| 93 If if_(condition, thenPart, [elsePart]) { | 271 //If if_(condition, thenPart, [elsePart]) { |
| 94 condition = toExpression(condition); | 272 // condition = toExpression(condition); |
| 95 return (elsePart == null) | 273 // return (elsePart == null) |
| 96 ? new If.noElse(condition, toStatement(thenPart)) | 274 // ? new If.noElse(condition, toStatement(thenPart)) |
| 97 : new If(condition, toStatement(thenPart), toStatement(elsePart)); | 275 // : new If(condition, toStatement(thenPart), toStatement(elsePart)); |
| 98 } | 276 //} |
| 99 | 277 |
| 100 Return return_([value]) { | 278 //Return return_([value]) { |
| 101 return new Return(value == null ? null : toExpression(value)); | 279 // return new Return(value == null ? null : toExpression(value)); |
| 102 } | 280 //} |
| 103 | 281 |
| 104 Block block(statement) { | 282 //Block _block(statement) { |
| 105 if (statement is Block) { | 283 // if (statement is Block) { |
| 106 return statement; | 284 // return statement; |
| 107 } else if (statement is List) { | 285 // } else if (statement is List) { |
| 108 List<Statement> statements = statement | 286 // List<Statement> statements = statement |
| 109 .map(toStatement) | 287 // .map(toStatement) |
| 110 .where((s) => s is !EmptyStatement) | 288 // .where((s) => s is !EmptyStatement) |
| 111 .toList(); | 289 // .toList(); |
| 112 return new Block(statements); | 290 // return new Block(statements); |
| 113 } else { | 291 // } else { |
| 114 return new Block(<Statement>[toStatement(statement)]); | 292 // return new Block(<Statement>[toStatement(statement)]); |
| 115 } | 293 // } |
| 116 } | 294 //} |
| 117 | 295 |
| 118 Fun fun(parameters, body) { | 296 //Fun fun(parameters, body) { |
| 119 Parameter toParameter(parameter) { | 297 // Parameter toParameter(parameter) { |
| 120 if (parameter is String) { | 298 // if (parameter is String) { |
| 121 return new Parameter(parameter); | 299 // return new Parameter(parameter); |
| 122 } else if (parameter is Parameter) { | 300 // } else if (parameter is Parameter) { |
| 123 return parameter; | 301 // return parameter; |
| 124 } else { | 302 // } else { |
| 125 throw new ArgumentError('parameter should be a String or a Parameter'); | 303 // throw new ArgumentError('parameter should be a String or a Parameter') ; |
| 126 } | 304 // } |
| 127 } | 305 // } |
| 128 if (parameters is! List) { | 306 // if (parameters is! List) { |
| 129 parameters = [parameters]; | 307 // parameters = [parameters]; |
| 130 } | 308 // } |
| 131 return new Fun(parameters.map(toParameter).toList(), block(body)); | 309 // return new Fun(parameters.map(toParameter).toList(), _block(body)); |
| 132 } | 310 //} |
| 133 | 311 |
| 134 VariableDeclarationList defineVar(String name, [initializer]) { | 312 //VariableDeclarationList defineVar(String name, [initializer]) { |
| 135 if (initializer != null) { | 313 // if (initializer != null) { |
| 136 initializer = toExpression(initializer); | 314 // initializer = toExpression(initializer); |
| 137 } | 315 // } |
| 138 var declaration = new VariableDeclaration(name); | 316 // var declaration = new VariableDeclaration(name); |
| 139 var initialization = [new VariableInitialization(declaration, initializer)]; | 317 // var initialization = [new VariableInitialization(declaration, initializer) ]; |
| 140 return new VariableDeclarationList(initialization); | 318 // return new VariableDeclarationList(initialization); |
| 141 } | 319 //} |
| 142 | 320 |
| 143 Statement toStatement(statement) { | 321 //Statement toStatement(statement) { |
| 144 if (statement is List) { | 322 // if (statement is List) { |
| 145 return block(statement); | 323 // return _block(statement); |
| 146 } else if (statement is Node) { | 324 // } else if (statement is Node) { |
| 147 return statement.toStatement(); | 325 // return statement.toStatement(); |
| 148 } else { | 326 // } else { |
| 149 throw new ArgumentError('statement'); | 327 // throw new ArgumentError('statement'); |
| 150 } | 328 // } |
| 151 } | 329 //} |
| 152 | 330 |
| 153 Expression toExpression(expression) { | 331 //Expression toExpression(expression) { |
| 154 if (expression == null) { | 332 // if (expression == null) { |
| 155 return null; | 333 // return null; |
| 156 } else if (expression is Expression) { | 334 // } else if (expression is Expression) { |
| 157 return expression; | 335 // return expression; |
| 158 } else if (expression is String) { | 336 // } else if (expression is String) { |
| 159 return this(expression); | 337 // return this(expression); |
| 160 } else if (expression is num) { | 338 // } else if (expression is num) { |
| 161 return new LiteralNumber('$expression'); | 339 // return new LiteralNumber('$expression'); |
| 162 } else if (expression is bool) { | 340 // } else if (expression is bool) { |
| 163 return new LiteralBool(expression); | 341 // return new LiteralBool(expression); |
| 164 } else if (expression is Map) { | 342 // } else if (expression is Map) { |
| 165 if (!expression.isEmpty) { | 343 // if (!expression.isEmpty) { |
| 166 throw new ArgumentError('expression should be an empty Map'); | 344 // throw new ArgumentError('expression should be an empty Map'); |
| 167 } | 345 // } |
| 168 return new ObjectInitializer([]); | 346 // return new ObjectInitializer([]); |
| 169 } else if (expression is List) { | 347 // } else if (expression is List) { |
| 170 var values = new List<ArrayElement>.generate(expression.length, | 348 // var values = new List<ArrayElement>.generate(expression.length, |
| 171 (index) => new ArrayElement(index, toExpression(expression[index]))); | 349 // (index) => new ArrayElement(index, toExpression(expression[index]))) ; |
| 172 return new ArrayInitializer(values.length, values); | 350 // return new ArrayInitializer(values.length, values); |
| 173 } else { | 351 // } else { |
| 174 throw new ArgumentError('expression should be an Expression, ' | 352 // throw new ArgumentError('expression should be an Expression, ' |
| 175 'a String, a num, a bool, a Map, or a List;'); | 353 // 'a String, a num, a bool, a Map, or a List;'); |
| 176 } | 354 // } |
| 177 } | 355 //} |
| 178 | 356 |
| 179 ForIn forIn(String name, object, statement) { | 357 //ForIn forIn(String name, object, statement) { |
| 180 return new ForIn(defineVar(name), | 358 // return new ForIn(defineVar(name), |
| 181 toExpression(object), | 359 // toExpression(object), |
| 182 toStatement(statement)); | 360 // toStatement(statement)); |
| 183 } | 361 //} |
| 184 | 362 |
| 185 For for_(init, condition, update, statement) { | 363 //For for_(init, condition, update, statement) { |
| 186 return new For( | 364 // return new For( |
| 187 toExpression(init), toExpression(condition), toExpression(update), | 365 // toExpression(init), toExpression(condition), toExpression(update), |
| 188 toStatement(statement)); | 366 // toStatement(statement)); |
| 189 } | 367 //} |
| 190 | 368 |
| 191 While while_(condition, statement) { | 369 //While while_(condition, statement) { |
| 192 return new While( | 370 // return new While( |
| 193 toExpression(condition), toStatement(statement)); | 371 // toExpression(condition), toStatement(statement)); |
| 194 } | 372 //} |
| 195 | 373 |
| 196 Try try_(body, {catchPart, finallyPart}) { | 374 //Try try_(body, {catchPart, finallyPart}) { |
| 197 if (catchPart != null) catchPart = toStatement(catchPart); | 375 // if (catchPart != null) catchPart = toStatement(catchPart); |
| 198 if (finallyPart != null) finallyPart = toStatement(finallyPart); | 376 // if (finallyPart != null) finallyPart = toStatement(finallyPart); |
| 199 return new Try(toStatement(body), catchPart, finallyPart); | 377 // return new Try(toStatement(body), catchPart, finallyPart); |
| 200 } | 378 //} |
| 201 | 379 |
| 202 Comment comment(String text) => new Comment(text); | 380 Comment comment(String text) => new Comment(text); |
| 203 } | 381 } |
| 204 | 382 |
| 205 const JsBuilder js = const JsBuilder(); | |
| 206 | |
| 207 LiteralString string(String value) => js.string(value); | 383 LiteralString string(String value) => js.string(value); |
| 208 | 384 |
| 209 class MiniJsParserError { | 385 class MiniJsParserError { |
| 210 MiniJsParserError(this.parser, this.message) { } | 386 MiniJsParserError(this.parser, this.message) { } |
| 211 | 387 |
| 212 final MiniJsParser parser; | 388 final MiniJsParser parser; |
| 213 final String message; | 389 final String message; |
| 214 | 390 |
| 215 String toString() { | 391 String toString() { |
| 216 int pos = parser.lastPosition; | 392 int pos = parser.lastPosition; |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 238 /// * identifiers. | 414 /// * identifiers. |
| 239 /// * dot access. | 415 /// * dot access. |
| 240 /// * method calls. | 416 /// * method calls. |
| 241 /// * [] access. | 417 /// * [] access. |
| 242 /// * array, string, regexp, boolean, null and numeric literals. | 418 /// * array, string, regexp, boolean, null and numeric literals. |
| 243 /// * most operators. | 419 /// * most operators. |
| 244 /// * brackets. | 420 /// * brackets. |
| 245 /// * var declarations. | 421 /// * var declarations. |
| 246 /// * operator precedence. | 422 /// * operator precedence. |
| 247 /// Notable things it can't do yet include: | 423 /// Notable things it can't do yet include: |
| 248 /// * non-empty object literals. | 424 /// * some statements are still missing (do-while, while, switch. |
| 249 /// * throw, return. | |
| 250 /// * statements, including any flow control (if, while, for, etc.) | |
| 251 /// | 425 /// |
| 252 /// It's a fairly standard recursive descent parser. | 426 /// It's a fairly standard recursive descent parser. |
| 253 /// | 427 /// |
| 254 /// Literal strings are passed through to the final JS source code unchanged, | 428 /// Literal strings are passed through to the final JS source code unchanged, |
| 255 /// including the choice of surrounding quotes, so if you parse | 429 /// including the choice of surrounding quotes, so if you parse |
| 256 /// r'var x = "foo\n\"bar\""' you will end up with | 430 /// 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 | 431 /// 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 | 432 /// allowed in string and regexp literals because the machinery for checking |
| 259 /// their correctness is rather involved. | 433 /// their correctness is rather involved. |
| 260 class MiniJsParser { | 434 class MiniJsParser { |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 395 } while (currentCode != delimiter); | 569 } while (currentCode != delimiter); |
| 396 position++; | 570 position++; |
| 397 return src.substring(lastPosition, position); | 571 return src.substring(lastPosition, position); |
| 398 } | 572 } |
| 399 | 573 |
| 400 void getToken() { | 574 void getToken() { |
| 401 skippedNewline = false; | 575 skippedNewline = false; |
| 402 for (;;) { | 576 for (;;) { |
| 403 if (position >= src.length) break; | 577 if (position >= src.length) break; |
| 404 int code = src.codeUnitAt(position); | 578 int code = src.codeUnitAt(position); |
| 405 // Skip '//' style comment. | 579 // Skip '//' and '/*' style comments. |
| 406 if (code == charCodes.$SLASH && | 580 if (code == charCodes.$SLASH && |
| 407 position + 1 < src.length && | 581 position + 1 < src.length) { |
| 408 src.codeUnitAt(position + 1) == charCodes.$SLASH) { | 582 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) { |
| 409 int nextPosition = src.indexOf('\n', position); | 583 int nextPosition = src.indexOf('\n', position); |
| 410 if (nextPosition == -1) nextPosition = src.length; | 584 if (nextPosition == -1) nextPosition = src.length; |
| 411 position = nextPosition; | 585 position = nextPosition; |
| 412 } else { | 586 continue; |
| 413 if (category(code) != WHITESPACE) break; | 587 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) { |
| 414 if (code == charCodes.$LF) skippedNewline = true; | 588 int nextPosition = src.indexOf('*/', position + 2); |
| 415 ++position; | 589 if (nextPosition == -1) error('Unterminated comment'); |
| 590 position = nextPosition + 2; | |
| 591 continue; | |
| 592 } | |
| 416 } | 593 } |
| 594 if (category(code) != WHITESPACE) break; | |
| 595 if (code == charCodes.$LF) skippedNewline = true; | |
| 596 ++position; | |
| 417 } | 597 } |
| 418 | 598 |
| 419 if (position == src.length) { | 599 if (position == src.length) { |
| 420 lastCategory = NONE; | 600 lastCategory = NONE; |
| 421 lastToken = null; | 601 lastToken = null; |
| 422 lastPosition = position; | 602 lastPosition = position; |
| 423 return; | 603 return; |
| 424 } | 604 } |
| 425 int code = src.codeUnitAt(position); | 605 int code = src.codeUnitAt(position); |
| 426 lastPosition = position; | 606 lastPosition = position; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 502 | 682 |
| 503 void expectSemicolon() { | 683 void expectSemicolon() { |
| 504 if (acceptSemicolon()) return; | 684 if (acceptSemicolon()) return; |
| 505 error('Expected SEMICOLON'); | 685 error('Expected SEMICOLON'); |
| 506 } | 686 } |
| 507 | 687 |
| 508 bool acceptSemicolon() { | 688 bool acceptSemicolon() { |
| 509 // Accept semicolon or automatically inserted semicolon before close brace. | 689 // Accept semicolon or automatically inserted semicolon before close brace. |
| 510 // Miniparser forbids other kinds of semicolon insertion. | 690 // Miniparser forbids other kinds of semicolon insertion. |
| 511 if (RBRACE == lastCategory) return true; | 691 if (RBRACE == lastCategory) return true; |
| 692 if (NONE == lastCategory) return true; // end of input | |
| 512 if (skippedNewline) { | 693 if (skippedNewline) { |
| 513 error('No automatic semicolon insertion at preceding newline'); | 694 error('No automatic semicolon insertion at preceding newline'); |
| 514 } | 695 } |
| 515 return acceptCategory(SEMICOLON); | 696 return acceptCategory(SEMICOLON); |
| 516 } | 697 } |
| 517 | 698 |
| 518 bool acceptString(String string) { | 699 bool acceptString(String string) { |
| 519 if (lastToken == string) { | 700 if (lastToken == string) { |
| 520 getToken(); | 701 getToken(); |
| 521 return true; | 702 return true; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 561 } | 742 } |
| 562 return new ArrayInitializer(values.length, values); | 743 return new ArrayInitializer(values.length, values); |
| 563 } else if (last.startsWith("/")) { | 744 } else if (last.startsWith("/")) { |
| 564 String regexp = getDelimited(lastPosition); | 745 String regexp = getDelimited(lastPosition); |
| 565 getToken(); | 746 getToken(); |
| 566 String flags = lastToken; | 747 String flags = lastToken; |
| 567 if (!acceptCategory(ALPHA)) flags = ""; | 748 if (!acceptCategory(ALPHA)) flags = ""; |
| 568 Expression expression = new RegExpLiteral(regexp + flags); | 749 Expression expression = new RegExpLiteral(regexp + flags); |
| 569 return expression; | 750 return expression; |
| 570 } else if (acceptCategory(HASH)) { | 751 } else if (acceptCategory(HASH)) { |
| 571 InterpolatedExpression expression = new InterpolatedExpression(null); | 752 InterpolatedExpression expression = |
| 753 new InterpolatedExpression(interpolatedValues.length); | |
| 572 interpolatedValues.add(expression); | 754 interpolatedValues.add(expression); |
| 573 return expression; | 755 return expression; |
| 574 } else { | 756 } else { |
| 575 error("Expected primary expression"); | 757 error("Expected primary expression"); |
| 576 return null; | 758 return null; |
| 577 } | 759 } |
| 578 } | 760 } |
| 579 | 761 |
| 580 Expression parseFunctionExpression() { | 762 Expression parseFunctionExpression() { |
| 581 String last = lastToken; | 763 String last = lastToken; |
| 582 if (acceptCategory(ALPHA)) { | 764 if (acceptCategory(ALPHA)) { |
| 583 String functionName = last; | 765 String functionName = last; |
| 584 return new NamedFunction(new VariableDeclaration(functionName), | 766 return new NamedFunction(new VariableDeclaration(functionName), |
| 585 parseFun()); | 767 parseFun()); |
| 586 } | 768 } |
| 587 return parseFun(); | 769 return parseFun(); |
| 588 } | 770 } |
| 589 | 771 |
| 590 Expression parseFun() { | 772 Expression parseFun() { |
| 591 List<Parameter> params = <Parameter>[]; | 773 List<Parameter> params = <Parameter>[]; |
| 774 | |
| 592 expectCategory(LPAREN); | 775 expectCategory(LPAREN); |
| 593 String argumentName = lastToken; | 776 if (!acceptCategory(RPAREN)) { |
| 594 if (acceptCategory(ALPHA)) { | 777 for (;;) { |
| 595 params.add(new Parameter(argumentName)); | 778 if (acceptCategory(HASH)) { |
| 596 while (acceptCategory(COMMA)) { | 779 InterpolatedParameter parameter = |
| 597 argumentName = lastToken; | 780 new InterpolatedParameter(interpolatedValues.length); |
| 598 expectCategory(ALPHA); | 781 interpolatedValues.add(parameter); |
| 599 params.add(new Parameter(argumentName)); | 782 params.add(parameter); |
| 783 } else { | |
| 784 String argumentName = lastToken; | |
| 785 expectCategory(ALPHA); | |
| 786 params.add(new Parameter(argumentName)); | |
| 787 } | |
| 788 if (acceptCategory(COMMA)) continue; | |
| 789 expectCategory(RPAREN); | |
| 790 break; | |
| 600 } | 791 } |
| 601 } | 792 } |
| 602 expectCategory(RPAREN); | 793 |
| 603 expectCategory(LBRACE); | 794 expectCategory(LBRACE); |
| 604 Block block = parseBlock(); | 795 Block block = parseBlock(); |
| 605 return new Fun(params, block); | 796 return new Fun(params, block); |
| 606 } | 797 } |
| 607 | 798 |
| 608 Expression parseObjectInitializer() { | 799 Expression parseObjectInitializer() { |
| 609 List<Property> properties = <Property>[]; | 800 List<Property> properties = <Property>[]; |
| 610 for (;;) { | 801 for (;;) { |
| 611 if (acceptCategory(RBRACE)) break; | 802 if (acceptCategory(RBRACE)) break; |
| 612 // Limited subset: keys are identifiers, no 'get' or 'set' properties. | 803 // Limited subset: keys are identifiers, no 'get' or 'set' properties. |
| 613 Literal propertyName; | 804 Literal propertyName; |
| 614 String identifier = lastToken; | 805 String identifier = lastToken; |
| 615 if (acceptCategory(ALPHA)) { | 806 if (acceptCategory(ALPHA)) { |
| 616 propertyName = new LiteralString('"$identifier"'); | 807 propertyName = new LiteralString('"$identifier"'); |
| 617 } else if (acceptCategory(STRING)) { | 808 } else if (acceptCategory(STRING)) { |
| 618 propertyName = new LiteralString(identifier); | 809 propertyName = new LiteralString(identifier); |
| 810 } else if (acceptCategory(SYMBOL)) { // e.g. void | |
| 811 propertyName = new LiteralString('"$identifier"'); | |
| 812 } else if (acceptCategory(HASH)) { | |
| 813 InterpolatedLiteral interpolatedLiteral = | |
| 814 new InterpolatedLiteral(interpolatedValues.length); | |
| 815 interpolatedValues.add(interpolatedLiteral); | |
| 816 propertyName = interpolatedLiteral; | |
| 619 } else { | 817 } else { |
| 620 error('Expected property name'); | 818 error('Expected property name'); |
| 621 } | 819 } |
| 622 expectCategory(COLON); | 820 expectCategory(COLON); |
| 623 Expression value = parseAssignment(); | 821 Expression value = parseAssignment(); |
| 624 properties.add(new Property(propertyName, value)); | 822 properties.add(new Property(propertyName, value)); |
| 625 if (acceptCategory(RBRACE)) break; | 823 if (acceptCategory(RBRACE)) break; |
| 626 expectCategory(COMMA); | 824 expectCategory(COMMA); |
| 627 } | 825 } |
| 628 return new ObjectInitializer(properties); | 826 return new ObjectInitializer(properties); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 671 } else { | 869 } else { |
| 672 // JS allows new without (), but we don't. | 870 // JS allows new without (), but we don't. |
| 673 if (constructor) error("Parentheses are required for new"); | 871 if (constructor) error("Parentheses are required for new"); |
| 674 break; | 872 break; |
| 675 } | 873 } |
| 676 } | 874 } |
| 677 return receiver; | 875 return receiver; |
| 678 } | 876 } |
| 679 | 877 |
| 680 Expression getDotRhs(Expression receiver) { | 878 Expression getDotRhs(Expression receiver) { |
| 879 if (acceptCategory(HASH)) { | |
| 880 InterpolatedSelector property = | |
| 881 new InterpolatedSelector(interpolatedValues.length); | |
| 882 interpolatedValues.add(property); | |
| 883 return new PropertyAccess(receiver, property); | |
| 884 } | |
| 681 String identifier = lastToken; | 885 String identifier = lastToken; |
| 682 // In ES5 keywords like delete and continue are allowed as property | 886 // 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. | 887 // names, and the IndexedDB API uses that, so we need to allow it here. |
| 684 if (acceptCategory(SYMBOL)) { | 888 if (acceptCategory(SYMBOL)) { |
| 685 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { | 889 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { |
| 686 error("Expected alphanumeric identifier"); | 890 error("Expected alphanumeric identifier"); |
| 687 } | 891 } |
| 688 } else { | 892 } else { |
| 689 expectCategory(ALPHA); | 893 expectCategory(ALPHA); |
| 690 } | 894 } |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 822 } else { | 1026 } else { |
| 823 return parseExpression(); | 1027 return parseExpression(); |
| 824 } | 1028 } |
| 825 } | 1029 } |
| 826 | 1030 |
| 827 Expression expression() { | 1031 Expression expression() { |
| 828 Expression expression = parseVarDeclarationOrExpression(); | 1032 Expression expression = parseVarDeclarationOrExpression(); |
| 829 if (lastCategory != NONE || position != src.length) { | 1033 if (lastCategory != NONE || position != src.length) { |
| 830 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 1034 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 831 } | 1035 } |
| 832 if (!interpolatedValues.isEmpty) { | |
| 833 return new JSExpression(expression, interpolatedValues); | |
| 834 } | |
| 835 return expression; | 1036 return expression; |
| 836 } | 1037 } |
| 837 | 1038 |
| 838 Statement statement() { | 1039 Statement statement() { |
| 839 Statement statement = parseStatement(); | 1040 Statement statement = parseStatement(); |
| 840 if (lastCategory != NONE || position != src.length) { | 1041 if (lastCategory != NONE || position != src.length) { |
| 841 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 1042 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 842 } | 1043 } |
| 843 // TODO(sra): interpolated capture here? | 1044 // TODO(sra): interpolated capture here? |
| 844 return statement; | 1045 return statement; |
| 845 } | 1046 } |
| 846 | 1047 |
| 847 Block parseBlock() { | 1048 Block parseBlock() { |
| 848 List<Statement> statements = <Statement>[]; | 1049 List<Statement> statements = <Statement>[]; |
| 849 | 1050 |
| 850 while (!acceptCategory(RBRACE)) { | 1051 while (!acceptCategory(RBRACE)) { |
| 851 Statement statement = parseStatement(); | 1052 Statement statement = parseStatement(); |
| 852 statements.add(statement); | 1053 statements.add(statement); |
| 853 } | 1054 } |
| 854 return new Block(statements); | 1055 return new Block(statements); |
| 855 } | 1056 } |
| 856 | 1057 |
| 857 Statement parseStatement() { | 1058 Statement parseStatement() { |
| 858 if (acceptCategory(LBRACE)) return parseBlock(); | 1059 if (acceptCategory(LBRACE)) return parseBlock(); |
| 859 | 1060 |
| 1061 if (acceptCategory(SEMICOLON)) return new EmptyStatement(); | |
| 1062 | |
| 860 if (lastCategory == ALPHA) { | 1063 if (lastCategory == ALPHA) { |
| 861 if (acceptString('return')) return parseReturn(); | 1064 if (acceptString('return')) return parseReturn(); |
| 862 | 1065 |
| 863 if (acceptString('throw')) return parseThrow(); | 1066 if (acceptString('throw')) return parseThrow(); |
| 864 | 1067 |
| 865 if (acceptString('break')) { | 1068 if (acceptString('break')) { |
| 866 return parseBreakOrContinue((label) => new Break(label)); | 1069 return parseBreakOrContinue((label) => new Break(label)); |
| 867 } | 1070 } |
| 868 | 1071 |
| 869 if (acceptString('continue')) { | 1072 if (acceptString('continue')) { |
| 870 return parseBreakOrContinue((label) => new Continue(label)); | 1073 return parseBreakOrContinue((label) => new Continue(label)); |
| 871 } | 1074 } |
| 872 | 1075 |
| 873 if (acceptString('if')) return parseIfThenElse(); | 1076 if (acceptString('if')) return parseIfThenElse(); |
| 874 | 1077 |
| 875 if (acceptString('for')) return parseFor(); | 1078 if (acceptString('for')) return parseFor(); |
| 876 | 1079 |
| 877 if (acceptString('function')) return parseFunctionDeclaration(); | 1080 if (acceptString('function')) return parseFunctionDeclaration(); |
| 878 | 1081 |
| 1082 if (acceptString('try')) return parseTry(); | |
| 1083 | |
| 879 if (acceptString('var')) { | 1084 if (acceptString('var')) { |
| 880 Expression declarations = parseVariableDeclarationList(); | 1085 Expression declarations = parseVariableDeclarationList(); |
| 881 expectSemicolon(); | 1086 expectSemicolon(); |
| 882 return new ExpressionStatement(declarations); | 1087 return new ExpressionStatement(declarations); |
| 883 } | 1088 } |
| 884 | 1089 |
| 885 if (lastToken == 'case' || | 1090 if (lastToken == 'case' || |
| 886 lastToken == 'do' || | 1091 lastToken == 'do' || |
| 887 lastToken == 'while' || | 1092 lastToken == 'while' || |
| 888 lastToken == 'switch' || | 1093 lastToken == 'switch' || |
| 889 lastToken == 'try' || | |
| 890 lastToken == 'with') { | 1094 lastToken == 'with') { |
| 891 error('Not implemented in mini parser'); | 1095 error('Not implemented in mini parser'); |
| 892 } | 1096 } |
| 893 } | 1097 } |
| 894 | 1098 |
| 895 if (acceptCategory(HASH)) { | |
| 896 InterpolatedStatement statement = new InterpolatedStatement(null); | |
| 897 interpolatedValues.add(statement); | |
| 898 return statement; | |
| 899 } | |
| 900 | 1099 |
| 901 // TODO: label: statement | 1100 // TODO: label: statement |
| 902 | 1101 |
| 1102 bool checkForInterpolatedStatement = lastCategory == HASH; | |
| 1103 | |
| 903 Expression expression = parseExpression(); | 1104 Expression expression = parseExpression(); |
| 904 expectSemicolon(); | 1105 expectSemicolon(); |
| 1106 | |
| 1107 if (checkForInterpolatedStatement) { | |
| 1108 // 'Promote' the interpolated expression `#;` to an interpolated | |
| 1109 // statement. | |
| 1110 if (expression is InterpolatedExpression) { | |
| 1111 assert(identical(interpolatedValues.last, expression)); | |
| 1112 InterpolatedStatement statement = | |
| 1113 new InterpolatedStatement(expression.name); | |
| 1114 interpolatedValues[interpolatedValues.length - 1] = statement; | |
| 1115 return statement; | |
| 1116 } | |
| 1117 } | |
| 1118 | |
| 905 return new ExpressionStatement(expression); | 1119 return new ExpressionStatement(expression); |
| 906 } | 1120 } |
| 907 | 1121 |
| 908 Statement parseReturn() { | 1122 Statement parseReturn() { |
| 909 if (acceptSemicolon()) return new Return(); | 1123 if (acceptSemicolon()) return new Return(); |
| 910 Expression expression = parseExpression(); | 1124 Expression expression = parseExpression(); |
| 911 expectSemicolon(); | 1125 expectSemicolon(); |
| 912 return new Return(expression); | 1126 return new Return(expression); |
| 913 } | 1127 } |
| 914 | 1128 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 970 return finishFor(null); | 1184 return finishFor(null); |
| 971 } | 1185 } |
| 972 | 1186 |
| 973 if (acceptString('var')) { | 1187 if (acceptString('var')) { |
| 974 String identifier = lastToken; | 1188 String identifier = lastToken; |
| 975 expectCategory(ALPHA); | 1189 expectCategory(ALPHA); |
| 976 if (acceptString('in')) { | 1190 if (acceptString('in')) { |
| 977 Expression objectExpression = parseExpression(); | 1191 Expression objectExpression = parseExpression(); |
| 978 expectCategory(RPAREN); | 1192 expectCategory(RPAREN); |
| 979 Statement body = parseStatement(); | 1193 Statement body = parseStatement(); |
| 980 return new ForIn(js.defineVar(identifier), objectExpression, body); | 1194 return new ForIn( |
| 1195 new VariableDeclarationList([ | |
| 1196 new VariableInitialization( | |
| 1197 new VariableDeclaration(identifier), null)]), | |
| 1198 objectExpression, | |
| 1199 body); | |
| 981 } | 1200 } |
| 982 Expression declarations = finishVariableDeclarationList(identifier); | 1201 Expression declarations = finishVariableDeclarationList(identifier); |
| 983 expectCategory(SEMICOLON); | 1202 expectCategory(SEMICOLON); |
| 984 return finishFor(declarations); | 1203 return finishFor(declarations); |
| 985 } | 1204 } |
| 986 | 1205 |
| 987 Expression init = parseExpression(); | 1206 Expression init = parseExpression(); |
| 988 expectCategory(SEMICOLON); | 1207 expectCategory(SEMICOLON); |
| 989 return finishFor(init); | 1208 return finishFor(init); |
| 990 } | 1209 } |
| 991 | 1210 |
| 992 Statement parseFunctionDeclaration() { | 1211 Statement parseFunctionDeclaration() { |
| 993 String name = lastToken; | 1212 String name = lastToken; |
| 994 expectCategory(ALPHA); | 1213 expectCategory(ALPHA); |
| 995 Expression fun = parseFun(); | 1214 Expression fun = parseFun(); |
| 996 return new FunctionDeclaration(new VariableDeclaration(name), fun); | 1215 return new FunctionDeclaration(new VariableDeclaration(name), fun); |
| 997 } | 1216 } |
| 998 } | |
| 999 | 1217 |
| 1000 /** | 1218 Statement parseTry() { |
| 1001 * Clone a JSExpression node into an expression where all children | 1219 expectCategory(LBRACE); |
| 1002 * have been cloned, and [InterpolatedExpression]s have been replaced | 1220 Block body = parseBlock(); |
| 1003 * with real [Expression]. | 1221 String token = lastToken; |
| 1004 */ | 1222 Catch catchPart = null; |
| 1005 class UninterpolateJSExpression extends BaseVisitor<Node> { | 1223 if (acceptString('catch')) catchPart = parseCatch(); |
| 1006 final List<Expression> arguments; | 1224 Block finallyPart = null; |
| 1007 int argumentIndex = 0; | 1225 if (acceptString('finally')) { |
| 1008 | 1226 expectCategory(LBRACE); |
| 1009 UninterpolateJSExpression(this.arguments); | 1227 finallyPart = parseBlock(); |
| 1010 | 1228 } else { |
| 1011 void error(message) { | 1229 if (catchPart == null) error("expected 'finally'"); |
| 1012 throw message; | 1230 } |
| 1231 return new Try(body, catchPart, finallyPart); | |
| 1013 } | 1232 } |
| 1014 | 1233 |
| 1015 Node visitNode(Node node) { | 1234 Catch parseCatch() { |
| 1016 error('Cannot handle $node'); | 1235 expectCategory(LPAREN); |
| 1017 return null; | 1236 String identifier = lastToken; |
| 1018 } | 1237 expectCategory(ALPHA); |
| 1019 | 1238 expectCategory(RPAREN); |
| 1020 Node copyPosition(Node oldNode, Node newNode) { | 1239 expectCategory(LBRACE); |
| 1021 newNode.sourcePosition = oldNode.sourcePosition; | 1240 Block body = parseBlock(); |
| 1022 newNode.endSourcePosition = oldNode.endSourcePosition; | 1241 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 } | 1242 } |
| 1135 } | 1243 } |
| OLD | NEW |