| 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 | |
| 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 | |
| 32 In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript | |
| 33 AST that would pretty-print as `b`. | |
| 34 | |
| 35 The [call] method constructs an Expression AST. | |
| 36 | |
| 37 No argument | |
| 38 | |
| 39 js('window.alert("hello")') --> window.alert("hello") | |
| 40 | |
| 41 The input text can contain placeholders `#` that are replaced with provided | |
| 42 arguments. A single argument can be passed directly: | |
| 43 | |
| 44 js('window.alert(#)', s) --> window.alert("hello") | |
| 45 | |
| 46 Multiple arguments are passed as a list: | |
| 47 | |
| 48 js('# + #', [s, s]) --> "hello" + "hello" | |
| 49 | |
| 50 The [statement] method constructs a Statement AST, but is otherwise like the | |
| 51 [call] method. This constructs a Return AST: | |
| 52 | |
| 53 var ret = js.statement('return #;', n); --> return 123; | |
| 54 | |
| 55 A placeholder in a Statement context must be followed by a semicolon ';'. You | |
| 56 can think of a statement placeholder as being `#;` to explain why the output | |
| 57 still has one semicolon: | |
| 58 | |
| 59 js.statement('if (happy) #;', ret) | |
| 60 --> | |
| 61 if (happy) | |
| 62 return 123; | |
| 63 | |
| 64 If the placeholder is not followed by a semicolon, it is part of an expression. | |
| 65 Here the paceholder is in the position of the function in a function call: | |
| 66 | |
| 67 var vFoo = new VariableUse('foo'); | |
| 68 js.statement('if (happy) #("Happy!")', vFoo) | |
| 69 --> | |
| 70 if (happy) | |
| 71 foo("Happy!"); | |
| 72 | |
| 73 Generally, a placeholder in an expression position requires an Expression AST as | |
| 74 an argument and a placeholder in a statement position requires a Statement AST. | |
| 75 An expression will be converted to a Statement if needed by creating an | |
| 76 ExpessionStatement. A String argument will be converted into a VariableUse and | |
| 77 requires that the string is a JavaScript identifier. | |
| 78 | |
| 79 js('# + 1', vFoo) --> foo + 1 | |
| 80 js('# + 1', 'foo') --> foo + 1 | |
| 81 js('# + 1', 'foo.bar') --> assertion failure | |
| 82 | |
| 83 Some placeholder positions are _splicing contexts_. A function argument list is | |
| 84 a splicing expression context. A placeholder in a splicing expression context | |
| 85 can take a single Expression (or String, converted to VariableUse) or an | |
| 86 Iterable of Expressions (and/or Strings). | |
| 87 | |
| 88 // non-splicing argument: | |
| 89 js('#(#)', ['say', s]) --> say("hello") | |
| 90 // splicing arguments: | |
| 91 js('#(#)', ['say', []]) --> say() | |
| 92 js('#(#)', ['say', [s]]) --> say("hello") | |
| 93 js('#(#)', ['say', [s, n]]) --> say("hello", 123) | |
| 94 | |
| 95 A splicing context can be used to append 'lists' and add extra elements: | |
| 96 | |
| 97 js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1) | |
| 98 js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1) | |
| 99 js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1) | |
| 100 js('foo(#, #, 1)', [ [], [] ]) --> foo(1) | |
| 101 | |
| 102 The generation of a compile-time optional argument expression can be chosen by | |
| 103 providing an empty or singleton list. | |
| 104 | |
| 105 In addition to Expressions and Statements, there are Parameters, which occur | |
| 106 only in the parameter list of a function expression or declaration. | |
| 107 Placeholders in parameter positions behave like placeholders in Expression | |
| 108 positions, except only Parameter AST nodes are permitted. String arguments for | |
| 109 parameter placeholders are converted to Parameter AST nodes. | |
| 110 | |
| 111 var pFoo = new Parameter('foo') | |
| 112 js('function(#) { return #; }', [pFoo, vFoo]) | |
| 113 --> | |
| 114 function(foo) { return foo; } | |
| 115 | |
| 116 Expressions and Parameters are not compatible with each other's context: | |
| 117 | |
| 118 js('function(#) { return #; }', [vFoo, vFoo]) --> error | |
| 119 js('function(#) { return #; }', [pFoo, pFoo]) --> error | |
| 120 | |
| 121 The parameter context is a splicing context. When combined with the | |
| 122 context-sensitive conversion of Strings, this simplifies the construction of | |
| 123 trampoline-like functions: | |
| 124 | |
| 125 var args = ['a', 'b']; | |
| 126 js('function(#) { return f(this, #); }', [args, args]) | |
| 127 --> | |
| 128 function(a, b) { return f(this, a, b); } | |
| 129 | |
| 130 A statement placeholder in a Block is also in a splicing context. In addition | |
| 131 to splicing Iterables, statement placeholders in a Block will also splice a | |
| 132 Block or an EmptyStatement. This flattens nested blocks and allows blocks to be | |
| 133 appended. | |
| 134 | |
| 135 var b1 = js.statement('{ 1; 2; }'); | |
| 136 var sEmpty = new Emptystatement(); | |
| 137 js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty]) | |
| 138 --> | |
| 139 { 1; 2; 1; 2; } | |
| 140 | |
| 141 A placeholder in the context of an if-statement condition also accepts a Dart | |
| 142 bool argument, which selects the then-part or else-part of the if-statement: | |
| 143 | |
| 144 js.statement('if (#) return;', vFoo) --> if (foo) return; | |
| 145 js.statement('if (#) return;', true) --> return; | |
| 146 js.statement('if (#) return;', false) --> ; // empty statement | |
| 147 var eTrue = new LiteralBool(true); | |
| 148 js.statement('if (#) return;', eTrue) --> if (true) return; | |
| 149 | |
| 150 Combined with block splicing, if-statement condition context placeholders allows | |
| 151 the creation of tenplates that select code depending on variables. | |
| 152 | |
| 153 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true) | |
| 154 --> { 1; 2; 5; } | |
| 155 | |
| 156 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false) | |
| 157 --> { 1; 3; 4; 5; } | |
| 158 | |
| 159 A placeholder following a period in a property access is in a property access | |
| 160 context. This is just like an expression context, except String arguments are | |
| 161 converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand | |
| 162 for `a["b"]`: | |
| 163 | |
| 164 js('a[#]', vFoo) --> a[foo] | |
| 165 js('a[#]', s) --> a.hello (i.e. a["hello"]). | |
| 166 js('a[#]', 'x') --> a[x] | |
| 167 | |
| 168 js('a.#', vFoo) --> a[foo] | |
| 169 js('a.#', s) --> a.hello (i.e. a["hello"]) | |
| 170 js('a.#', 'x') --> a.x (i.e. a["x"]) | |
| 171 | |
| 172 (Question - should `.#` be restricted to permit only String arguments? The | |
| 173 template should probably be writted with `[]` if non-strings are accepted.) | |
| 174 | |
| 175 | |
| 176 Object initialiers allow placeholders in the key property name position: | |
| 177 | |
| 178 js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2} | |
| 179 | |
| 180 | |
| 181 What is not implemented: | |
| 182 | |
| 183 - Array initializers and object initializers could support splicing. In the | |
| 184 array case, we would need some way to know if an ArrayInitializer argument | |
| 185 should be splice or is intended as a single value. | |
| 186 | |
| 187 - There are no placeholders in definition contexts: | |
| 188 | |
| 189 function #(){} | |
| 190 var # = 1; | |
| 191 | |
| 192 */ | |
| 193 const JsBuilder js = const JsBuilder(); | |
| 194 | |
| 195 | |
| 196 class JsBuilder { | 10 class JsBuilder { |
| 197 const JsBuilder(); | 11 const JsBuilder(); |
| 198 | 12 |
| 199 /** | 13 /** |
| 200 * Parses a bit of JavaScript, and returns an expression. | 14 * Parses a bit of JavaScript, and returns an expression. |
| 201 * | 15 * |
| 202 * See the MiniJsParser class. | 16 * See the MiniJsParser class. |
| 203 * | 17 * |
| 204 * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or | 18 * [expression] can be an [Expression] or a list of [Expression]s, which will |
| 205 * a list of [Node]s, which will be interpolated into the source at the '#' | 19 * be interpolated into the source at the '#' signs. |
| 206 * signs. | |
| 207 */ | 20 */ |
| 208 Expression call(String source, [var arguments]) { | 21 Expression call(String source, [var expression]) { |
| 209 Template template = _findExpressionTemplate(source); | 22 var result = new MiniJsParser(source).expression(); |
| 210 if (arguments == null) return template.instantiate([]); | 23 if (expression == null) return result; |
| 211 return template.instantiate(arguments is List ? arguments : [arguments]); | 24 |
| 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; |
| 212 } | 40 } |
| 213 | 41 |
| 214 /** | 42 Statement statement(String source) { |
| 215 * Parses a JavaScript Statement, otherwise just like [call]. | 43 var result = new MiniJsParser(source).statement(); |
| 216 */ | 44 // TODO(sra): Interpolation. |
| 217 Statement statement(String source, [var arguments]) { | 45 return result; |
| 218 Template template = _findStatementTemplate(source); | |
| 219 if (arguments == null) return template.instantiate([]); | |
| 220 return template.instantiate(arguments is List ? arguments : [arguments]); | |
| 221 } | 46 } |
| 222 | 47 |
| 223 /** | 48 // Parse JavaScript written in the JS foreign instruction. |
| 224 * Parses JavaScript written in the `JS` foreign instruction. | 49 Expression parseForeignJS(String source, [var expression]) { |
| 225 * | 50 // We can parse simple JS with the mini parser. At the moment we can't |
| 226 * The [source] must be a JavaScript expression or a JavaScript throw | 51 // handle JSON literals and function literals, both of which contain "{". |
| 227 * statement. | 52 if (source.contains("{") || source.startsWith("throw ")) { |
| 228 */ | 53 assert(expression == null); |
| 229 Template parseForeignJS(String source) { | 54 return new LiteralExpression(source); |
| 230 // TODO(sra): Parse with extra validation to forbid `#` interpolation in | |
| 231 // functions, as this leads to unanticipated capture of temporaries that are | |
| 232 // reused after capture. | |
| 233 if (source.startsWith("throw ")) { | |
| 234 return _findStatementTemplate(source); | |
| 235 } else { | |
| 236 return _findExpressionTemplate(source); | |
| 237 } | 55 } |
| 56 return call(source, expression); |
| 238 } | 57 } |
| 239 | 58 |
| 240 Template _findExpressionTemplate(String source) { | 59 /// Creates a litteral js string from [value]. |
| 241 Template template = templateManager.lookupExpressionTemplate(source); | |
| 242 if (template == null) { | |
| 243 MiniJsParser parser = new MiniJsParser(source); | |
| 244 Expression expression = parser.expression(); | |
| 245 template = templateManager.defineExpressionTemplate(source, expression); | |
| 246 } | |
| 247 return template; | |
| 248 } | |
| 249 | |
| 250 Template _findStatementTemplate(String source) { | |
| 251 Template template = templateManager.lookupStatementTemplate(source); | |
| 252 if (template == null) { | |
| 253 MiniJsParser parser = new MiniJsParser(source); | |
| 254 Statement statement = parser.statement(); | |
| 255 template = templateManager.defineStatementTemplate(source, statement); | |
| 256 } | |
| 257 return template; | |
| 258 } | |
| 259 | |
| 260 /** | |
| 261 * Creates an Expression template without caching the result. | |
| 262 */ | |
| 263 Template uncachedExpressionTemplate(String source) { | |
| 264 MiniJsParser parser = new MiniJsParser(source); | |
| 265 Expression expression = parser.expression(); | |
| 266 return new Template( | |
| 267 source, expression, isExpression: true, forceCopy: false); | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * Create an Expression template which has [ast] as the result. This is used | |
| 272 * to wrap a generated AST in a zero-argument Template so it can be passed to | |
| 273 * context that expects a template. | |
| 274 */ | |
| 275 Template expressionTemplateYielding(Node ast) { | |
| 276 return new Template.withExpressionResult(ast); | |
| 277 } | |
| 278 | |
| 279 Template statementTemplateYielding(Node ast) { | |
| 280 return new Template.withStatementResult(ast); | |
| 281 } | |
| 282 | |
| 283 /// Creates a literal js string from [value]. | |
| 284 LiteralString escapedString(String value) { | 60 LiteralString escapedString(String value) { |
| 285 // Do not escape unicode characters and ' because they are allowed in the | 61 // Do not escape unicode characters and ' because they are allowed in the |
| 286 // string literal anyway. | 62 // string literal anyway. |
| 287 String escaped = | 63 String escaped = |
| 288 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { | 64 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { |
| 289 switch (match.group(0)) { | 65 switch (match.group(0)) { |
| 290 case "\n" : return r"\n"; | 66 case "\n" : return r"\n"; |
| 291 case "\\" : return r"\\"; | 67 case "\\" : return r"\\"; |
| 292 case "\"" : return r'\"'; | 68 case "\"" : return r'\"'; |
| 293 case "\0" : return r"\0"; | 69 case "\0" : return r"\0"; |
| 294 case "\b" : return r"\b"; | 70 case "\b" : return r"\b"; |
| 295 case "\t" : return r"\t"; | 71 case "\t" : return r"\t"; |
| 296 case "\f" : return r"\f"; | 72 case "\f" : return r"\f"; |
| 297 case "\v" : return r"\v"; | 73 case "\v" : return r"\v"; |
| 298 } | 74 } |
| 299 }); | 75 }); |
| 300 LiteralString result = string(escaped); | 76 LiteralString result = string(escaped); |
| 301 // We don't escape ' under the assumption that the string is wrapped | 77 // We don't escape ' under the assumption that the string is wrapped |
| 302 // into ". Verify that assumption. | 78 // into ". Verify that assumption. |
| 303 assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0)); | 79 assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0)); |
| 304 return result; | 80 return result; |
| 305 } | 81 } |
| 306 | 82 |
| 307 /// Creates a literal js string from [value]. | 83 /// Creates a litteral js string from [value]. |
| 308 /// | 84 /// |
| 309 /// Note that this function only puts quotes around [value]. It does not do | 85 /// Note that this function only puts quotes around [value]. It does not do |
| 310 /// any escaping, so use only when you can guarantee that [value] does not | 86 /// any escaping, so use only when you can guarantee that [value] does not |
| 311 /// contain newlines or backslashes. For escaping the string use | 87 /// contain newlines or backslashes. For escaping the string use |
| 312 /// [escapedString]. | 88 /// [escapedString]. |
| 313 LiteralString string(String value) => new LiteralString('"$value"'); | 89 LiteralString string(String value) => new LiteralString('"$value"'); |
| 314 | 90 |
| 315 LiteralNumber number(num value) => new LiteralNumber('$value'); | 91 LiteralNumber number(num value) => new LiteralNumber('$value'); |
| 316 | 92 |
| 93 If if_(condition, thenPart, [elsePart]) { |
| 94 condition = toExpression(condition); |
| 95 return (elsePart == null) |
| 96 ? new If.noElse(condition, toStatement(thenPart)) |
| 97 : new If(condition, toStatement(thenPart), toStatement(elsePart)); |
| 98 } |
| 99 |
| 100 Return return_([value]) { |
| 101 return new Return(value == null ? null : toExpression(value)); |
| 102 } |
| 103 |
| 104 Block block(statement) { |
| 105 if (statement is Block) { |
| 106 return statement; |
| 107 } else if (statement is List) { |
| 108 List<Statement> statements = statement |
| 109 .map(toStatement) |
| 110 .where((s) => s is !EmptyStatement) |
| 111 .toList(); |
| 112 return new Block(statements); |
| 113 } else { |
| 114 return new Block(<Statement>[toStatement(statement)]); |
| 115 } |
| 116 } |
| 117 |
| 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 |
| 317 Comment comment(String text) => new Comment(text); | 202 Comment comment(String text) => new Comment(text); |
| 318 } | 203 } |
| 319 | 204 |
| 205 const JsBuilder js = const JsBuilder(); |
| 206 |
| 320 LiteralString string(String value) => js.string(value); | 207 LiteralString string(String value) => js.string(value); |
| 321 | 208 |
| 322 class MiniJsParserError { | 209 class MiniJsParserError { |
| 323 MiniJsParserError(this.parser, this.message) { } | 210 MiniJsParserError(this.parser, this.message) { } |
| 324 | 211 |
| 325 final MiniJsParser parser; | 212 final MiniJsParser parser; |
| 326 final String message; | 213 final String message; |
| 327 | 214 |
| 328 String toString() { | 215 String toString() { |
| 329 int pos = parser.lastPosition; | 216 int pos = parser.lastPosition; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 350 /// AST nodes. Handles: | 237 /// AST nodes. Handles: |
| 351 /// * identifiers. | 238 /// * identifiers. |
| 352 /// * dot access. | 239 /// * dot access. |
| 353 /// * method calls. | 240 /// * method calls. |
| 354 /// * [] access. | 241 /// * [] access. |
| 355 /// * array, string, regexp, boolean, null and numeric literals. | 242 /// * array, string, regexp, boolean, null and numeric literals. |
| 356 /// * most operators. | 243 /// * most operators. |
| 357 /// * brackets. | 244 /// * brackets. |
| 358 /// * var declarations. | 245 /// * var declarations. |
| 359 /// * operator precedence. | 246 /// * operator precedence. |
| 360 /// * anonymous funtions and named function expressions and declarations. | |
| 361 /// Notable things it can't do yet include: | 247 /// Notable things it can't do yet include: |
| 362 /// * some statements are still missing (do-while, while, switch). | 248 /// * non-empty object literals. |
| 249 /// * throw, return. |
| 250 /// * statements, including any flow control (if, while, for, etc.) |
| 363 /// | 251 /// |
| 364 /// It's a fairly standard recursive descent parser. | 252 /// It's a fairly standard recursive descent parser. |
| 365 /// | 253 /// |
| 366 /// Literal strings are passed through to the final JS source code unchanged, | 254 /// Literal strings are passed through to the final JS source code unchanged, |
| 367 /// including the choice of surrounding quotes, so if you parse | 255 /// including the choice of surrounding quotes, so if you parse |
| 368 /// r'var x = "foo\n\"bar\""' you will end up with | 256 /// r'var x = "foo\n\"bar\""' you will end up with |
| 369 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not | 257 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not |
| 370 /// allowed in string and regexp literals because the machinery for checking | 258 /// allowed in string and regexp literals because the machinery for checking |
| 371 /// their correctness is rather involved. | 259 /// their correctness is rather involved. |
| 372 class MiniJsParser { | 260 class MiniJsParser { |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 507 } while (currentCode != delimiter); | 395 } while (currentCode != delimiter); |
| 508 position++; | 396 position++; |
| 509 return src.substring(lastPosition, position); | 397 return src.substring(lastPosition, position); |
| 510 } | 398 } |
| 511 | 399 |
| 512 void getToken() { | 400 void getToken() { |
| 513 skippedNewline = false; | 401 skippedNewline = false; |
| 514 for (;;) { | 402 for (;;) { |
| 515 if (position >= src.length) break; | 403 if (position >= src.length) break; |
| 516 int code = src.codeUnitAt(position); | 404 int code = src.codeUnitAt(position); |
| 517 // Skip '//' and '/*' style comments. | 405 // Skip '//' style comment. |
| 518 if (code == charCodes.$SLASH && | 406 if (code == charCodes.$SLASH && |
| 519 position + 1 < src.length) { | 407 position + 1 < src.length && |
| 520 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) { | 408 src.codeUnitAt(position + 1) == charCodes.$SLASH) { |
| 521 int nextPosition = src.indexOf('\n', position); | 409 int nextPosition = src.indexOf('\n', position); |
| 522 if (nextPosition == -1) nextPosition = src.length; | 410 if (nextPosition == -1) nextPosition = src.length; |
| 523 position = nextPosition; | 411 position = nextPosition; |
| 524 continue; | 412 } else { |
| 525 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) { | 413 if (category(code) != WHITESPACE) break; |
| 526 int nextPosition = src.indexOf('*/', position + 2); | 414 if (code == charCodes.$LF) skippedNewline = true; |
| 527 if (nextPosition == -1) error('Unterminated comment'); | 415 ++position; |
| 528 position = nextPosition + 2; | |
| 529 continue; | |
| 530 } | |
| 531 } | 416 } |
| 532 if (category(code) != WHITESPACE) break; | |
| 533 if (code == charCodes.$LF) skippedNewline = true; | |
| 534 ++position; | |
| 535 } | 417 } |
| 536 | 418 |
| 537 if (position == src.length) { | 419 if (position == src.length) { |
| 538 lastCategory = NONE; | 420 lastCategory = NONE; |
| 539 lastToken = null; | 421 lastToken = null; |
| 540 lastPosition = position; | 422 lastPosition = position; |
| 541 return; | 423 return; |
| 542 } | 424 } |
| 543 int code = src.codeUnitAt(position); | 425 int code = src.codeUnitAt(position); |
| 544 lastPosition = position; | 426 lastPosition = position; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 620 | 502 |
| 621 void expectSemicolon() { | 503 void expectSemicolon() { |
| 622 if (acceptSemicolon()) return; | 504 if (acceptSemicolon()) return; |
| 623 error('Expected SEMICOLON'); | 505 error('Expected SEMICOLON'); |
| 624 } | 506 } |
| 625 | 507 |
| 626 bool acceptSemicolon() { | 508 bool acceptSemicolon() { |
| 627 // Accept semicolon or automatically inserted semicolon before close brace. | 509 // Accept semicolon or automatically inserted semicolon before close brace. |
| 628 // Miniparser forbids other kinds of semicolon insertion. | 510 // Miniparser forbids other kinds of semicolon insertion. |
| 629 if (RBRACE == lastCategory) return true; | 511 if (RBRACE == lastCategory) return true; |
| 630 if (NONE == lastCategory) return true; // end of input | |
| 631 if (skippedNewline) { | 512 if (skippedNewline) { |
| 632 error('No automatic semicolon insertion at preceding newline'); | 513 error('No automatic semicolon insertion at preceding newline'); |
| 633 } | 514 } |
| 634 return acceptCategory(SEMICOLON); | 515 return acceptCategory(SEMICOLON); |
| 635 } | 516 } |
| 636 | 517 |
| 637 bool acceptString(String string) { | 518 bool acceptString(String string) { |
| 638 if (lastToken == string) { | 519 if (lastToken == string) { |
| 639 getToken(); | 520 getToken(); |
| 640 return true; | 521 return true; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 680 } | 561 } |
| 681 return new ArrayInitializer(values.length, values); | 562 return new ArrayInitializer(values.length, values); |
| 682 } else if (last.startsWith("/")) { | 563 } else if (last.startsWith("/")) { |
| 683 String regexp = getDelimited(lastPosition); | 564 String regexp = getDelimited(lastPosition); |
| 684 getToken(); | 565 getToken(); |
| 685 String flags = lastToken; | 566 String flags = lastToken; |
| 686 if (!acceptCategory(ALPHA)) flags = ""; | 567 if (!acceptCategory(ALPHA)) flags = ""; |
| 687 Expression expression = new RegExpLiteral(regexp + flags); | 568 Expression expression = new RegExpLiteral(regexp + flags); |
| 688 return expression; | 569 return expression; |
| 689 } else if (acceptCategory(HASH)) { | 570 } else if (acceptCategory(HASH)) { |
| 690 InterpolatedExpression expression = | 571 InterpolatedExpression expression = new InterpolatedExpression(null); |
| 691 new InterpolatedExpression(interpolatedValues.length); | |
| 692 interpolatedValues.add(expression); | 572 interpolatedValues.add(expression); |
| 693 return expression; | 573 return expression; |
| 694 } else { | 574 } else { |
| 695 error("Expected primary expression"); | 575 error("Expected primary expression"); |
| 696 return null; | 576 return null; |
| 697 } | 577 } |
| 698 } | 578 } |
| 699 | 579 |
| 700 Expression parseFunctionExpression() { | 580 Expression parseFunctionExpression() { |
| 701 String last = lastToken; | 581 String last = lastToken; |
| 702 if (acceptCategory(ALPHA)) { | 582 if (acceptCategory(ALPHA)) { |
| 703 String functionName = last; | 583 String functionName = last; |
| 704 return new NamedFunction(new VariableDeclaration(functionName), | 584 return new NamedFunction(new VariableDeclaration(functionName), |
| 705 parseFun()); | 585 parseFun()); |
| 706 } | 586 } |
| 707 return parseFun(); | 587 return parseFun(); |
| 708 } | 588 } |
| 709 | 589 |
| 710 Expression parseFun() { | 590 Expression parseFun() { |
| 711 List<Parameter> params = <Parameter>[]; | 591 List<Parameter> params = <Parameter>[]; |
| 712 | |
| 713 expectCategory(LPAREN); | 592 expectCategory(LPAREN); |
| 714 if (!acceptCategory(RPAREN)) { | 593 String argumentName = lastToken; |
| 715 for (;;) { | 594 if (acceptCategory(ALPHA)) { |
| 716 if (acceptCategory(HASH)) { | 595 params.add(new Parameter(argumentName)); |
| 717 InterpolatedParameter parameter = | 596 while (acceptCategory(COMMA)) { |
| 718 new InterpolatedParameter(interpolatedValues.length); | 597 argumentName = lastToken; |
| 719 interpolatedValues.add(parameter); | 598 expectCategory(ALPHA); |
| 720 params.add(parameter); | 599 params.add(new Parameter(argumentName)); |
| 721 } else { | |
| 722 String argumentName = lastToken; | |
| 723 expectCategory(ALPHA); | |
| 724 params.add(new Parameter(argumentName)); | |
| 725 } | |
| 726 if (acceptCategory(COMMA)) continue; | |
| 727 expectCategory(RPAREN); | |
| 728 break; | |
| 729 } | 600 } |
| 730 } | 601 } |
| 731 | 602 expectCategory(RPAREN); |
| 732 expectCategory(LBRACE); | 603 expectCategory(LBRACE); |
| 733 Block block = parseBlock(); | 604 Block block = parseBlock(); |
| 734 return new Fun(params, block); | 605 return new Fun(params, block); |
| 735 } | 606 } |
| 736 | 607 |
| 737 Expression parseObjectInitializer() { | 608 Expression parseObjectInitializer() { |
| 738 List<Property> properties = <Property>[]; | 609 List<Property> properties = <Property>[]; |
| 739 for (;;) { | 610 for (;;) { |
| 740 if (acceptCategory(RBRACE)) break; | 611 if (acceptCategory(RBRACE)) break; |
| 741 // Limited subset: keys are identifiers, no 'get' or 'set' properties. | 612 // Limited subset: keys are identifiers, no 'get' or 'set' properties. |
| 742 Literal propertyName; | 613 Literal propertyName; |
| 743 String identifier = lastToken; | 614 String identifier = lastToken; |
| 744 if (acceptCategory(ALPHA)) { | 615 if (acceptCategory(ALPHA)) { |
| 745 propertyName = new LiteralString('"$identifier"'); | 616 propertyName = new LiteralString('"$identifier"'); |
| 746 } else if (acceptCategory(STRING)) { | 617 } else if (acceptCategory(STRING)) { |
| 747 propertyName = new LiteralString(identifier); | 618 propertyName = new LiteralString(identifier); |
| 748 } else if (acceptCategory(SYMBOL)) { // e.g. void | |
| 749 propertyName = new LiteralString('"$identifier"'); | |
| 750 } else if (acceptCategory(HASH)) { | |
| 751 InterpolatedLiteral interpolatedLiteral = | |
| 752 new InterpolatedLiteral(interpolatedValues.length); | |
| 753 interpolatedValues.add(interpolatedLiteral); | |
| 754 propertyName = interpolatedLiteral; | |
| 755 } else { | 619 } else { |
| 756 error('Expected property name'); | 620 error('Expected property name'); |
| 757 } | 621 } |
| 758 expectCategory(COLON); | 622 expectCategory(COLON); |
| 759 Expression value = parseAssignment(); | 623 Expression value = parseAssignment(); |
| 760 properties.add(new Property(propertyName, value)); | 624 properties.add(new Property(propertyName, value)); |
| 761 if (acceptCategory(RBRACE)) break; | 625 if (acceptCategory(RBRACE)) break; |
| 762 expectCategory(COMMA); | 626 expectCategory(COMMA); |
| 763 } | 627 } |
| 764 return new ObjectInitializer(properties); | 628 return new ObjectInitializer(properties); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 807 } else { | 671 } else { |
| 808 // JS allows new without (), but we don't. | 672 // JS allows new without (), but we don't. |
| 809 if (constructor) error("Parentheses are required for new"); | 673 if (constructor) error("Parentheses are required for new"); |
| 810 break; | 674 break; |
| 811 } | 675 } |
| 812 } | 676 } |
| 813 return receiver; | 677 return receiver; |
| 814 } | 678 } |
| 815 | 679 |
| 816 Expression getDotRhs(Expression receiver) { | 680 Expression getDotRhs(Expression receiver) { |
| 817 if (acceptCategory(HASH)) { | |
| 818 InterpolatedSelector property = | |
| 819 new InterpolatedSelector(interpolatedValues.length); | |
| 820 interpolatedValues.add(property); | |
| 821 return new PropertyAccess(receiver, property); | |
| 822 } | |
| 823 String identifier = lastToken; | 681 String identifier = lastToken; |
| 824 // In ES5 keywords like delete and continue are allowed as property | 682 // In ES5 keywords like delete and continue are allowed as property |
| 825 // names, and the IndexedDB API uses that, so we need to allow it here. | 683 // names, and the IndexedDB API uses that, so we need to allow it here. |
| 826 if (acceptCategory(SYMBOL)) { | 684 if (acceptCategory(SYMBOL)) { |
| 827 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { | 685 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { |
| 828 error("Expected alphanumeric identifier"); | 686 error("Expected alphanumeric identifier"); |
| 829 } | 687 } |
| 830 } else { | 688 } else { |
| 831 expectCategory(ALPHA); | 689 expectCategory(ALPHA); |
| 832 } | 690 } |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 964 } else { | 822 } else { |
| 965 return parseExpression(); | 823 return parseExpression(); |
| 966 } | 824 } |
| 967 } | 825 } |
| 968 | 826 |
| 969 Expression expression() { | 827 Expression expression() { |
| 970 Expression expression = parseVarDeclarationOrExpression(); | 828 Expression expression = parseVarDeclarationOrExpression(); |
| 971 if (lastCategory != NONE || position != src.length) { | 829 if (lastCategory != NONE || position != src.length) { |
| 972 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 830 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 973 } | 831 } |
| 832 if (!interpolatedValues.isEmpty) { |
| 833 return new JSExpression(expression, interpolatedValues); |
| 834 } |
| 974 return expression; | 835 return expression; |
| 975 } | 836 } |
| 976 | 837 |
| 977 Statement statement() { | 838 Statement statement() { |
| 978 Statement statement = parseStatement(); | 839 Statement statement = parseStatement(); |
| 979 if (lastCategory != NONE || position != src.length) { | 840 if (lastCategory != NONE || position != src.length) { |
| 980 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 841 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 981 } | 842 } |
| 982 // TODO(sra): interpolated capture here? | 843 // TODO(sra): interpolated capture here? |
| 983 return statement; | 844 return statement; |
| 984 } | 845 } |
| 985 | 846 |
| 986 Block parseBlock() { | 847 Block parseBlock() { |
| 987 List<Statement> statements = <Statement>[]; | 848 List<Statement> statements = <Statement>[]; |
| 988 | 849 |
| 989 while (!acceptCategory(RBRACE)) { | 850 while (!acceptCategory(RBRACE)) { |
| 990 Statement statement = parseStatement(); | 851 Statement statement = parseStatement(); |
| 991 statements.add(statement); | 852 statements.add(statement); |
| 992 } | 853 } |
| 993 return new Block(statements); | 854 return new Block(statements); |
| 994 } | 855 } |
| 995 | 856 |
| 996 Statement parseStatement() { | 857 Statement parseStatement() { |
| 997 if (acceptCategory(LBRACE)) return parseBlock(); | 858 if (acceptCategory(LBRACE)) return parseBlock(); |
| 998 | 859 |
| 999 if (acceptCategory(SEMICOLON)) return new EmptyStatement(); | |
| 1000 | |
| 1001 if (lastCategory == ALPHA) { | 860 if (lastCategory == ALPHA) { |
| 1002 if (acceptString('return')) return parseReturn(); | 861 if (acceptString('return')) return parseReturn(); |
| 1003 | 862 |
| 1004 if (acceptString('throw')) return parseThrow(); | 863 if (acceptString('throw')) return parseThrow(); |
| 1005 | 864 |
| 1006 if (acceptString('break')) { | 865 if (acceptString('break')) { |
| 1007 return parseBreakOrContinue((label) => new Break(label)); | 866 return parseBreakOrContinue((label) => new Break(label)); |
| 1008 } | 867 } |
| 1009 | 868 |
| 1010 if (acceptString('continue')) { | 869 if (acceptString('continue')) { |
| 1011 return parseBreakOrContinue((label) => new Continue(label)); | 870 return parseBreakOrContinue((label) => new Continue(label)); |
| 1012 } | 871 } |
| 1013 | 872 |
| 1014 if (acceptString('if')) return parseIfThenElse(); | 873 if (acceptString('if')) return parseIfThenElse(); |
| 1015 | 874 |
| 1016 if (acceptString('for')) return parseFor(); | 875 if (acceptString('for')) return parseFor(); |
| 1017 | 876 |
| 1018 if (acceptString('function')) return parseFunctionDeclaration(); | 877 if (acceptString('function')) return parseFunctionDeclaration(); |
| 1019 | 878 |
| 1020 if (acceptString('try')) return parseTry(); | |
| 1021 | |
| 1022 if (acceptString('var')) { | 879 if (acceptString('var')) { |
| 1023 Expression declarations = parseVariableDeclarationList(); | 880 Expression declarations = parseVariableDeclarationList(); |
| 1024 expectSemicolon(); | 881 expectSemicolon(); |
| 1025 return new ExpressionStatement(declarations); | 882 return new ExpressionStatement(declarations); |
| 1026 } | 883 } |
| 1027 | 884 |
| 1028 if (lastToken == 'case' || | 885 if (lastToken == 'case' || |
| 1029 lastToken == 'do' || | 886 lastToken == 'do' || |
| 1030 lastToken == 'while' || | 887 lastToken == 'while' || |
| 1031 lastToken == 'switch' || | 888 lastToken == 'switch' || |
| 889 lastToken == 'try' || |
| 1032 lastToken == 'with') { | 890 lastToken == 'with') { |
| 1033 error('Not implemented in mini parser'); | 891 error('Not implemented in mini parser'); |
| 1034 } | 892 } |
| 1035 } | 893 } |
| 1036 | 894 |
| 895 if (acceptCategory(HASH)) { |
| 896 InterpolatedStatement statement = new InterpolatedStatement(null); |
| 897 interpolatedValues.add(statement); |
| 898 return statement; |
| 899 } |
| 1037 | 900 |
| 1038 // TODO: label: statement | 901 // TODO: label: statement |
| 1039 | 902 |
| 1040 bool checkForInterpolatedStatement = lastCategory == HASH; | |
| 1041 | |
| 1042 Expression expression = parseExpression(); | 903 Expression expression = parseExpression(); |
| 1043 expectSemicolon(); | 904 expectSemicolon(); |
| 1044 | |
| 1045 if (checkForInterpolatedStatement) { | |
| 1046 // 'Promote' the interpolated expression `#;` to an interpolated | |
| 1047 // statement. | |
| 1048 if (expression is InterpolatedExpression) { | |
| 1049 assert(identical(interpolatedValues.last, expression)); | |
| 1050 InterpolatedStatement statement = | |
| 1051 new InterpolatedStatement(expression.name); | |
| 1052 interpolatedValues[interpolatedValues.length - 1] = statement; | |
| 1053 return statement; | |
| 1054 } | |
| 1055 } | |
| 1056 | |
| 1057 return new ExpressionStatement(expression); | 905 return new ExpressionStatement(expression); |
| 1058 } | 906 } |
| 1059 | 907 |
| 1060 Statement parseReturn() { | 908 Statement parseReturn() { |
| 1061 if (acceptSemicolon()) return new Return(); | 909 if (acceptSemicolon()) return new Return(); |
| 1062 Expression expression = parseExpression(); | 910 Expression expression = parseExpression(); |
| 1063 expectSemicolon(); | 911 expectSemicolon(); |
| 1064 return new Return(expression); | 912 return new Return(expression); |
| 1065 } | 913 } |
| 1066 | 914 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1122 return finishFor(null); | 970 return finishFor(null); |
| 1123 } | 971 } |
| 1124 | 972 |
| 1125 if (acceptString('var')) { | 973 if (acceptString('var')) { |
| 1126 String identifier = lastToken; | 974 String identifier = lastToken; |
| 1127 expectCategory(ALPHA); | 975 expectCategory(ALPHA); |
| 1128 if (acceptString('in')) { | 976 if (acceptString('in')) { |
| 1129 Expression objectExpression = parseExpression(); | 977 Expression objectExpression = parseExpression(); |
| 1130 expectCategory(RPAREN); | 978 expectCategory(RPAREN); |
| 1131 Statement body = parseStatement(); | 979 Statement body = parseStatement(); |
| 1132 return new ForIn( | 980 return new ForIn(js.defineVar(identifier), objectExpression, body); |
| 1133 new VariableDeclarationList([ | |
| 1134 new VariableInitialization( | |
| 1135 new VariableDeclaration(identifier), null)]), | |
| 1136 objectExpression, | |
| 1137 body); | |
| 1138 } | 981 } |
| 1139 Expression declarations = finishVariableDeclarationList(identifier); | 982 Expression declarations = finishVariableDeclarationList(identifier); |
| 1140 expectCategory(SEMICOLON); | 983 expectCategory(SEMICOLON); |
| 1141 return finishFor(declarations); | 984 return finishFor(declarations); |
| 1142 } | 985 } |
| 1143 | 986 |
| 1144 Expression init = parseExpression(); | 987 Expression init = parseExpression(); |
| 1145 expectCategory(SEMICOLON); | 988 expectCategory(SEMICOLON); |
| 1146 return finishFor(init); | 989 return finishFor(init); |
| 1147 } | 990 } |
| 1148 | 991 |
| 1149 Statement parseFunctionDeclaration() { | 992 Statement parseFunctionDeclaration() { |
| 1150 String name = lastToken; | 993 String name = lastToken; |
| 1151 expectCategory(ALPHA); | 994 expectCategory(ALPHA); |
| 1152 Expression fun = parseFun(); | 995 Expression fun = parseFun(); |
| 1153 return new FunctionDeclaration(new VariableDeclaration(name), fun); | 996 return new FunctionDeclaration(new VariableDeclaration(name), fun); |
| 1154 } | 997 } |
| 998 } |
| 1155 | 999 |
| 1156 Statement parseTry() { | 1000 /** |
| 1157 expectCategory(LBRACE); | 1001 * Clone a JSExpression node into an expression where all children |
| 1158 Block body = parseBlock(); | 1002 * have been cloned, and [InterpolatedExpression]s have been replaced |
| 1159 String token = lastToken; | 1003 * with real [Expression]. |
| 1160 Catch catchPart = null; | 1004 */ |
| 1161 if (acceptString('catch')) catchPart = parseCatch(); | 1005 class UninterpolateJSExpression extends BaseVisitor<Node> { |
| 1162 Block finallyPart = null; | 1006 final List<Expression> arguments; |
| 1163 if (acceptString('finally')) { | 1007 int argumentIndex = 0; |
| 1164 expectCategory(LBRACE); | 1008 |
| 1165 finallyPart = parseBlock(); | 1009 UninterpolateJSExpression(this.arguments); |
| 1166 } else { | 1010 |
| 1167 if (catchPart == null) error("expected 'finally'"); | 1011 void error(message) { |
| 1168 } | 1012 throw message; |
| 1169 return new Try(body, catchPart, finallyPart); | |
| 1170 } | 1013 } |
| 1171 | 1014 |
| 1172 Catch parseCatch() { | 1015 Node visitNode(Node node) { |
| 1173 expectCategory(LPAREN); | 1016 error('Cannot handle $node'); |
| 1174 String identifier = lastToken; | 1017 return null; |
| 1175 expectCategory(ALPHA); | 1018 } |
| 1176 expectCategory(RPAREN); | 1019 |
| 1177 expectCategory(LBRACE); | 1020 Node copyPosition(Node oldNode, Node newNode) { |
| 1178 Block body = parseBlock(); | 1021 newNode.sourcePosition = oldNode.sourcePosition; |
| 1179 return new Catch(new VariableDeclaration(identifier), body); | 1022 newNode.endSourcePosition = oldNode.endSourcePosition; |
| 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; |
| 1180 } | 1134 } |
| 1181 } | 1135 } |
| OLD | NEW |