| 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 |
| 10 class JsBuilder { | 196 class JsBuilder { |
| 11 const JsBuilder(); | 197 const JsBuilder(); |
| 12 | 198 |
| 13 /** | 199 /** |
| 14 * Parses a bit of JavaScript, and returns an expression. | 200 * Parses a bit of JavaScript, and returns an expression. |
| 15 * | 201 * |
| 16 * See the MiniJsParser class. | 202 * See the MiniJsParser class. |
| 17 * | 203 * |
| 18 * [expression] can be an [Expression] or a list of [Expression]s, which will | 204 * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or |
| 19 * be interpolated into the source at the '#' signs. | 205 * a list of [Node]s, which will be interpolated into the source at the '#' |
| 206 * signs. |
| 20 */ | 207 */ |
| 21 Expression call(String source, [var expression]) { | 208 Expression call(String source, [var arguments]) { |
| 22 var result = new MiniJsParser(source).expression(); | 209 Template template = _findExpressionTemplate(source); |
| 23 if (expression == null) return result; | 210 if (arguments == null) return template.instantiate([]); |
| 24 | 211 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 } | 212 } |
| 41 | 213 |
| 42 Statement statement(String source) { | 214 /** |
| 43 var result = new MiniJsParser(source).statement(); | 215 * Parses a JavaScript Statement, otherwise just like [call]. |
| 44 // TODO(sra): Interpolation. | 216 */ |
| 45 return result; | 217 Statement statement(String source, [var arguments]) { |
| 218 Template template = _findStatementTemplate(source); |
| 219 if (arguments == null) return template.instantiate([]); |
| 220 return template.instantiate(arguments is List ? arguments : [arguments]); |
| 46 } | 221 } |
| 47 | 222 |
| 48 // Parse JavaScript written in the JS foreign instruction. | 223 /** |
| 49 Expression parseForeignJS(String source, [var expression]) { | 224 * Parses JavaScript written in the `JS` foreign instruction. |
| 50 // We can parse simple JS with the mini parser. At the moment we can't | 225 * |
| 51 // handle JSON literals and function literals, both of which contain "{". | 226 * The [source] must be a JavaScript expression or a JavaScript throw |
| 52 if (source.contains("{") || source.startsWith("throw ")) { | 227 * statement. |
| 53 assert(expression == null); | 228 */ |
| 54 return new LiteralExpression(source); | 229 Template parseForeignJS(String 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); |
| 55 } | 237 } |
| 56 return call(source, expression); | |
| 57 } | 238 } |
| 58 | 239 |
| 59 /// Creates a litteral js string from [value]. | 240 Template _findExpressionTemplate(String source) { |
| 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]. |
| 60 LiteralString escapedString(String value) { | 284 LiteralString escapedString(String value) { |
| 61 // Do not escape unicode characters and ' because they are allowed in the | 285 // Do not escape unicode characters and ' because they are allowed in the |
| 62 // string literal anyway. | 286 // string literal anyway. |
| 63 String escaped = | 287 String escaped = |
| 64 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { | 288 value.replaceAllMapped(new RegExp('\n|"|\\|\0|\b|\t|\v'), (match) { |
| 65 switch (match.group(0)) { | 289 switch (match.group(0)) { |
| 66 case "\n" : return r"\n"; | 290 case "\n" : return r"\n"; |
| 67 case "\\" : return r"\\"; | 291 case "\\" : return r"\\"; |
| 68 case "\"" : return r'\"'; | 292 case "\"" : return r'\"'; |
| 69 case "\0" : return r"\0"; | 293 case "\0" : return r"\0"; |
| 70 case "\b" : return r"\b"; | 294 case "\b" : return r"\b"; |
| 71 case "\t" : return r"\t"; | 295 case "\t" : return r"\t"; |
| 72 case "\f" : return r"\f"; | 296 case "\f" : return r"\f"; |
| 73 case "\v" : return r"\v"; | 297 case "\v" : return r"\v"; |
| 74 } | 298 } |
| 75 }); | 299 }); |
| 76 LiteralString result = string(escaped); | 300 LiteralString result = string(escaped); |
| 77 // We don't escape ' under the assumption that the string is wrapped | 301 // We don't escape ' under the assumption that the string is wrapped |
| 78 // into ". Verify that assumption. | 302 // into ". Verify that assumption. |
| 79 assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0)); | 303 assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0)); |
| 80 return result; | 304 return result; |
| 81 } | 305 } |
| 82 | 306 |
| 83 /// Creates a litteral js string from [value]. | 307 /// Creates a literal js string from [value]. |
| 84 /// | 308 /// |
| 85 /// Note that this function only puts quotes around [value]. It does not do | 309 /// 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 | 310 /// any escaping, so use only when you can guarantee that [value] does not |
| 87 /// contain newlines or backslashes. For escaping the string use | 311 /// contain newlines or backslashes. For escaping the string use |
| 88 /// [escapedString]. | 312 /// [escapedString]. |
| 89 LiteralString string(String value) => new LiteralString('"$value"'); | 313 LiteralString string(String value) => new LiteralString('"$value"'); |
| 90 | 314 |
| 91 LiteralNumber number(num value) => new LiteralNumber('$value'); | 315 LiteralNumber number(num value) => new LiteralNumber('$value'); |
| 92 | 316 |
| 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 | |
| 202 Comment comment(String text) => new Comment(text); | 317 Comment comment(String text) => new Comment(text); |
| 203 } | 318 } |
| 204 | 319 |
| 205 const JsBuilder js = const JsBuilder(); | |
| 206 | |
| 207 LiteralString string(String value) => js.string(value); | 320 LiteralString string(String value) => js.string(value); |
| 208 | 321 |
| 209 class MiniJsParserError { | 322 class MiniJsParserError { |
| 210 MiniJsParserError(this.parser, this.message) { } | 323 MiniJsParserError(this.parser, this.message) { } |
| 211 | 324 |
| 212 final MiniJsParser parser; | 325 final MiniJsParser parser; |
| 213 final String message; | 326 final String message; |
| 214 | 327 |
| 215 String toString() { | 328 String toString() { |
| 216 int pos = parser.lastPosition; | 329 int pos = parser.lastPosition; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 237 /// AST nodes. Handles: | 350 /// AST nodes. Handles: |
| 238 /// * identifiers. | 351 /// * identifiers. |
| 239 /// * dot access. | 352 /// * dot access. |
| 240 /// * method calls. | 353 /// * method calls. |
| 241 /// * [] access. | 354 /// * [] access. |
| 242 /// * array, string, regexp, boolean, null and numeric literals. | 355 /// * array, string, regexp, boolean, null and numeric literals. |
| 243 /// * most operators. | 356 /// * most operators. |
| 244 /// * brackets. | 357 /// * brackets. |
| 245 /// * var declarations. | 358 /// * var declarations. |
| 246 /// * operator precedence. | 359 /// * operator precedence. |
| 360 /// * anonymous funtions and named function expressions and declarations. |
| 247 /// Notable things it can't do yet include: | 361 /// Notable things it can't do yet include: |
| 248 /// * non-empty object literals. | 362 /// * some statements are still missing (do-while, while, switch). |
| 249 /// * throw, return. | |
| 250 /// * statements, including any flow control (if, while, for, etc.) | |
| 251 /// | 363 /// |
| 252 /// It's a fairly standard recursive descent parser. | 364 /// It's a fairly standard recursive descent parser. |
| 253 /// | 365 /// |
| 254 /// Literal strings are passed through to the final JS source code unchanged, | 366 /// Literal strings are passed through to the final JS source code unchanged, |
| 255 /// including the choice of surrounding quotes, so if you parse | 367 /// including the choice of surrounding quotes, so if you parse |
| 256 /// r'var x = "foo\n\"bar\""' you will end up with | 368 /// 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 | 369 /// 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 | 370 /// allowed in string and regexp literals because the machinery for checking |
| 259 /// their correctness is rather involved. | 371 /// their correctness is rather involved. |
| 260 class MiniJsParser { | 372 class MiniJsParser { |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 395 } while (currentCode != delimiter); | 507 } while (currentCode != delimiter); |
| 396 position++; | 508 position++; |
| 397 return src.substring(lastPosition, position); | 509 return src.substring(lastPosition, position); |
| 398 } | 510 } |
| 399 | 511 |
| 400 void getToken() { | 512 void getToken() { |
| 401 skippedNewline = false; | 513 skippedNewline = false; |
| 402 for (;;) { | 514 for (;;) { |
| 403 if (position >= src.length) break; | 515 if (position >= src.length) break; |
| 404 int code = src.codeUnitAt(position); | 516 int code = src.codeUnitAt(position); |
| 405 // Skip '//' style comment. | 517 // Skip '//' and '/*' style comments. |
| 406 if (code == charCodes.$SLASH && | 518 if (code == charCodes.$SLASH && |
| 407 position + 1 < src.length && | 519 position + 1 < src.length) { |
| 408 src.codeUnitAt(position + 1) == charCodes.$SLASH) { | 520 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) { |
| 409 int nextPosition = src.indexOf('\n', position); | 521 int nextPosition = src.indexOf('\n', position); |
| 410 if (nextPosition == -1) nextPosition = src.length; | 522 if (nextPosition == -1) nextPosition = src.length; |
| 411 position = nextPosition; | 523 position = nextPosition; |
| 412 } else { | 524 continue; |
| 413 if (category(code) != WHITESPACE) break; | 525 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) { |
| 414 if (code == charCodes.$LF) skippedNewline = true; | 526 int nextPosition = src.indexOf('*/', position + 2); |
| 415 ++position; | 527 if (nextPosition == -1) error('Unterminated comment'); |
| 528 position = nextPosition + 2; |
| 529 continue; |
| 530 } |
| 416 } | 531 } |
| 532 if (category(code) != WHITESPACE) break; |
| 533 if (code == charCodes.$LF) skippedNewline = true; |
| 534 ++position; |
| 417 } | 535 } |
| 418 | 536 |
| 419 if (position == src.length) { | 537 if (position == src.length) { |
| 420 lastCategory = NONE; | 538 lastCategory = NONE; |
| 421 lastToken = null; | 539 lastToken = null; |
| 422 lastPosition = position; | 540 lastPosition = position; |
| 423 return; | 541 return; |
| 424 } | 542 } |
| 425 int code = src.codeUnitAt(position); | 543 int code = src.codeUnitAt(position); |
| 426 lastPosition = position; | 544 lastPosition = position; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 502 | 620 |
| 503 void expectSemicolon() { | 621 void expectSemicolon() { |
| 504 if (acceptSemicolon()) return; | 622 if (acceptSemicolon()) return; |
| 505 error('Expected SEMICOLON'); | 623 error('Expected SEMICOLON'); |
| 506 } | 624 } |
| 507 | 625 |
| 508 bool acceptSemicolon() { | 626 bool acceptSemicolon() { |
| 509 // Accept semicolon or automatically inserted semicolon before close brace. | 627 // Accept semicolon or automatically inserted semicolon before close brace. |
| 510 // Miniparser forbids other kinds of semicolon insertion. | 628 // Miniparser forbids other kinds of semicolon insertion. |
| 511 if (RBRACE == lastCategory) return true; | 629 if (RBRACE == lastCategory) return true; |
| 630 if (NONE == lastCategory) return true; // end of input |
| 512 if (skippedNewline) { | 631 if (skippedNewline) { |
| 513 error('No automatic semicolon insertion at preceding newline'); | 632 error('No automatic semicolon insertion at preceding newline'); |
| 514 } | 633 } |
| 515 return acceptCategory(SEMICOLON); | 634 return acceptCategory(SEMICOLON); |
| 516 } | 635 } |
| 517 | 636 |
| 518 bool acceptString(String string) { | 637 bool acceptString(String string) { |
| 519 if (lastToken == string) { | 638 if (lastToken == string) { |
| 520 getToken(); | 639 getToken(); |
| 521 return true; | 640 return true; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 561 } | 680 } |
| 562 return new ArrayInitializer(values.length, values); | 681 return new ArrayInitializer(values.length, values); |
| 563 } else if (last.startsWith("/")) { | 682 } else if (last.startsWith("/")) { |
| 564 String regexp = getDelimited(lastPosition); | 683 String regexp = getDelimited(lastPosition); |
| 565 getToken(); | 684 getToken(); |
| 566 String flags = lastToken; | 685 String flags = lastToken; |
| 567 if (!acceptCategory(ALPHA)) flags = ""; | 686 if (!acceptCategory(ALPHA)) flags = ""; |
| 568 Expression expression = new RegExpLiteral(regexp + flags); | 687 Expression expression = new RegExpLiteral(regexp + flags); |
| 569 return expression; | 688 return expression; |
| 570 } else if (acceptCategory(HASH)) { | 689 } else if (acceptCategory(HASH)) { |
| 571 InterpolatedExpression expression = new InterpolatedExpression(null); | 690 InterpolatedExpression expression = |
| 691 new InterpolatedExpression(interpolatedValues.length); |
| 572 interpolatedValues.add(expression); | 692 interpolatedValues.add(expression); |
| 573 return expression; | 693 return expression; |
| 574 } else { | 694 } else { |
| 575 error("Expected primary expression"); | 695 error("Expected primary expression"); |
| 576 return null; | 696 return null; |
| 577 } | 697 } |
| 578 } | 698 } |
| 579 | 699 |
| 580 Expression parseFunctionExpression() { | 700 Expression parseFunctionExpression() { |
| 581 String last = lastToken; | 701 String last = lastToken; |
| 582 if (acceptCategory(ALPHA)) { | 702 if (acceptCategory(ALPHA)) { |
| 583 String functionName = last; | 703 String functionName = last; |
| 584 return new NamedFunction(new VariableDeclaration(functionName), | 704 return new NamedFunction(new VariableDeclaration(functionName), |
| 585 parseFun()); | 705 parseFun()); |
| 586 } | 706 } |
| 587 return parseFun(); | 707 return parseFun(); |
| 588 } | 708 } |
| 589 | 709 |
| 590 Expression parseFun() { | 710 Expression parseFun() { |
| 591 List<Parameter> params = <Parameter>[]; | 711 List<Parameter> params = <Parameter>[]; |
| 712 |
| 592 expectCategory(LPAREN); | 713 expectCategory(LPAREN); |
| 593 String argumentName = lastToken; | 714 if (!acceptCategory(RPAREN)) { |
| 594 if (acceptCategory(ALPHA)) { | 715 for (;;) { |
| 595 params.add(new Parameter(argumentName)); | 716 if (acceptCategory(HASH)) { |
| 596 while (acceptCategory(COMMA)) { | 717 InterpolatedParameter parameter = |
| 597 argumentName = lastToken; | 718 new InterpolatedParameter(interpolatedValues.length); |
| 598 expectCategory(ALPHA); | 719 interpolatedValues.add(parameter); |
| 599 params.add(new Parameter(argumentName)); | 720 params.add(parameter); |
| 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; |
| 600 } | 729 } |
| 601 } | 730 } |
| 602 expectCategory(RPAREN); | 731 |
| 603 expectCategory(LBRACE); | 732 expectCategory(LBRACE); |
| 604 Block block = parseBlock(); | 733 Block block = parseBlock(); |
| 605 return new Fun(params, block); | 734 return new Fun(params, block); |
| 606 } | 735 } |
| 607 | 736 |
| 608 Expression parseObjectInitializer() { | 737 Expression parseObjectInitializer() { |
| 609 List<Property> properties = <Property>[]; | 738 List<Property> properties = <Property>[]; |
| 610 for (;;) { | 739 for (;;) { |
| 611 if (acceptCategory(RBRACE)) break; | 740 if (acceptCategory(RBRACE)) break; |
| 612 // Limited subset: keys are identifiers, no 'get' or 'set' properties. | 741 // Limited subset: keys are identifiers, no 'get' or 'set' properties. |
| 613 Literal propertyName; | 742 Literal propertyName; |
| 614 String identifier = lastToken; | 743 String identifier = lastToken; |
| 615 if (acceptCategory(ALPHA)) { | 744 if (acceptCategory(ALPHA)) { |
| 616 propertyName = new LiteralString('"$identifier"'); | 745 propertyName = new LiteralString('"$identifier"'); |
| 617 } else if (acceptCategory(STRING)) { | 746 } else if (acceptCategory(STRING)) { |
| 618 propertyName = new LiteralString(identifier); | 747 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; |
| 619 } else { | 755 } else { |
| 620 error('Expected property name'); | 756 error('Expected property name'); |
| 621 } | 757 } |
| 622 expectCategory(COLON); | 758 expectCategory(COLON); |
| 623 Expression value = parseAssignment(); | 759 Expression value = parseAssignment(); |
| 624 properties.add(new Property(propertyName, value)); | 760 properties.add(new Property(propertyName, value)); |
| 625 if (acceptCategory(RBRACE)) break; | 761 if (acceptCategory(RBRACE)) break; |
| 626 expectCategory(COMMA); | 762 expectCategory(COMMA); |
| 627 } | 763 } |
| 628 return new ObjectInitializer(properties); | 764 return new ObjectInitializer(properties); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 671 } else { | 807 } else { |
| 672 // JS allows new without (), but we don't. | 808 // JS allows new without (), but we don't. |
| 673 if (constructor) error("Parentheses are required for new"); | 809 if (constructor) error("Parentheses are required for new"); |
| 674 break; | 810 break; |
| 675 } | 811 } |
| 676 } | 812 } |
| 677 return receiver; | 813 return receiver; |
| 678 } | 814 } |
| 679 | 815 |
| 680 Expression getDotRhs(Expression receiver) { | 816 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 } |
| 681 String identifier = lastToken; | 823 String identifier = lastToken; |
| 682 // In ES5 keywords like delete and continue are allowed as property | 824 // 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. | 825 // names, and the IndexedDB API uses that, so we need to allow it here. |
| 684 if (acceptCategory(SYMBOL)) { | 826 if (acceptCategory(SYMBOL)) { |
| 685 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { | 827 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) { |
| 686 error("Expected alphanumeric identifier"); | 828 error("Expected alphanumeric identifier"); |
| 687 } | 829 } |
| 688 } else { | 830 } else { |
| 689 expectCategory(ALPHA); | 831 expectCategory(ALPHA); |
| 690 } | 832 } |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 822 } else { | 964 } else { |
| 823 return parseExpression(); | 965 return parseExpression(); |
| 824 } | 966 } |
| 825 } | 967 } |
| 826 | 968 |
| 827 Expression expression() { | 969 Expression expression() { |
| 828 Expression expression = parseVarDeclarationOrExpression(); | 970 Expression expression = parseVarDeclarationOrExpression(); |
| 829 if (lastCategory != NONE || position != src.length) { | 971 if (lastCategory != NONE || position != src.length) { |
| 830 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 972 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 831 } | 973 } |
| 832 if (!interpolatedValues.isEmpty) { | |
| 833 return new JSExpression(expression, interpolatedValues); | |
| 834 } | |
| 835 return expression; | 974 return expression; |
| 836 } | 975 } |
| 837 | 976 |
| 838 Statement statement() { | 977 Statement statement() { |
| 839 Statement statement = parseStatement(); | 978 Statement statement = parseStatement(); |
| 840 if (lastCategory != NONE || position != src.length) { | 979 if (lastCategory != NONE || position != src.length) { |
| 841 error("Unparsed junk: ${categoryToString(lastCategory)}"); | 980 error("Unparsed junk: ${categoryToString(lastCategory)}"); |
| 842 } | 981 } |
| 843 // TODO(sra): interpolated capture here? | 982 // TODO(sra): interpolated capture here? |
| 844 return statement; | 983 return statement; |
| 845 } | 984 } |
| 846 | 985 |
| 847 Block parseBlock() { | 986 Block parseBlock() { |
| 848 List<Statement> statements = <Statement>[]; | 987 List<Statement> statements = <Statement>[]; |
| 849 | 988 |
| 850 while (!acceptCategory(RBRACE)) { | 989 while (!acceptCategory(RBRACE)) { |
| 851 Statement statement = parseStatement(); | 990 Statement statement = parseStatement(); |
| 852 statements.add(statement); | 991 statements.add(statement); |
| 853 } | 992 } |
| 854 return new Block(statements); | 993 return new Block(statements); |
| 855 } | 994 } |
| 856 | 995 |
| 857 Statement parseStatement() { | 996 Statement parseStatement() { |
| 858 if (acceptCategory(LBRACE)) return parseBlock(); | 997 if (acceptCategory(LBRACE)) return parseBlock(); |
| 859 | 998 |
| 999 if (acceptCategory(SEMICOLON)) return new EmptyStatement(); |
| 1000 |
| 860 if (lastCategory == ALPHA) { | 1001 if (lastCategory == ALPHA) { |
| 861 if (acceptString('return')) return parseReturn(); | 1002 if (acceptString('return')) return parseReturn(); |
| 862 | 1003 |
| 863 if (acceptString('throw')) return parseThrow(); | 1004 if (acceptString('throw')) return parseThrow(); |
| 864 | 1005 |
| 865 if (acceptString('break')) { | 1006 if (acceptString('break')) { |
| 866 return parseBreakOrContinue((label) => new Break(label)); | 1007 return parseBreakOrContinue((label) => new Break(label)); |
| 867 } | 1008 } |
| 868 | 1009 |
| 869 if (acceptString('continue')) { | 1010 if (acceptString('continue')) { |
| 870 return parseBreakOrContinue((label) => new Continue(label)); | 1011 return parseBreakOrContinue((label) => new Continue(label)); |
| 871 } | 1012 } |
| 872 | 1013 |
| 873 if (acceptString('if')) return parseIfThenElse(); | 1014 if (acceptString('if')) return parseIfThenElse(); |
| 874 | 1015 |
| 875 if (acceptString('for')) return parseFor(); | 1016 if (acceptString('for')) return parseFor(); |
| 876 | 1017 |
| 877 if (acceptString('function')) return parseFunctionDeclaration(); | 1018 if (acceptString('function')) return parseFunctionDeclaration(); |
| 878 | 1019 |
| 1020 if (acceptString('try')) return parseTry(); |
| 1021 |
| 879 if (acceptString('var')) { | 1022 if (acceptString('var')) { |
| 880 Expression declarations = parseVariableDeclarationList(); | 1023 Expression declarations = parseVariableDeclarationList(); |
| 881 expectSemicolon(); | 1024 expectSemicolon(); |
| 882 return new ExpressionStatement(declarations); | 1025 return new ExpressionStatement(declarations); |
| 883 } | 1026 } |
| 884 | 1027 |
| 885 if (lastToken == 'case' || | 1028 if (lastToken == 'case' || |
| 886 lastToken == 'do' || | 1029 lastToken == 'do' || |
| 887 lastToken == 'while' || | 1030 lastToken == 'while' || |
| 888 lastToken == 'switch' || | 1031 lastToken == 'switch' || |
| 889 lastToken == 'try' || | |
| 890 lastToken == 'with') { | 1032 lastToken == 'with') { |
| 891 error('Not implemented in mini parser'); | 1033 error('Not implemented in mini parser'); |
| 892 } | 1034 } |
| 893 } | 1035 } |
| 894 | 1036 |
| 895 if (acceptCategory(HASH)) { | |
| 896 InterpolatedStatement statement = new InterpolatedStatement(null); | |
| 897 interpolatedValues.add(statement); | |
| 898 return statement; | |
| 899 } | |
| 900 | 1037 |
| 901 // TODO: label: statement | 1038 // TODO: label: statement |
| 902 | 1039 |
| 1040 bool checkForInterpolatedStatement = lastCategory == HASH; |
| 1041 |
| 903 Expression expression = parseExpression(); | 1042 Expression expression = parseExpression(); |
| 904 expectSemicolon(); | 1043 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 |
| 905 return new ExpressionStatement(expression); | 1057 return new ExpressionStatement(expression); |
| 906 } | 1058 } |
| 907 | 1059 |
| 908 Statement parseReturn() { | 1060 Statement parseReturn() { |
| 909 if (acceptSemicolon()) return new Return(); | 1061 if (acceptSemicolon()) return new Return(); |
| 910 Expression expression = parseExpression(); | 1062 Expression expression = parseExpression(); |
| 911 expectSemicolon(); | 1063 expectSemicolon(); |
| 912 return new Return(expression); | 1064 return new Return(expression); |
| 913 } | 1065 } |
| 914 | 1066 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 970 return finishFor(null); | 1122 return finishFor(null); |
| 971 } | 1123 } |
| 972 | 1124 |
| 973 if (acceptString('var')) { | 1125 if (acceptString('var')) { |
| 974 String identifier = lastToken; | 1126 String identifier = lastToken; |
| 975 expectCategory(ALPHA); | 1127 expectCategory(ALPHA); |
| 976 if (acceptString('in')) { | 1128 if (acceptString('in')) { |
| 977 Expression objectExpression = parseExpression(); | 1129 Expression objectExpression = parseExpression(); |
| 978 expectCategory(RPAREN); | 1130 expectCategory(RPAREN); |
| 979 Statement body = parseStatement(); | 1131 Statement body = parseStatement(); |
| 980 return new ForIn(js.defineVar(identifier), objectExpression, body); | 1132 return new ForIn( |
| 1133 new VariableDeclarationList([ |
| 1134 new VariableInitialization( |
| 1135 new VariableDeclaration(identifier), null)]), |
| 1136 objectExpression, |
| 1137 body); |
| 981 } | 1138 } |
| 982 Expression declarations = finishVariableDeclarationList(identifier); | 1139 Expression declarations = finishVariableDeclarationList(identifier); |
| 983 expectCategory(SEMICOLON); | 1140 expectCategory(SEMICOLON); |
| 984 return finishFor(declarations); | 1141 return finishFor(declarations); |
| 985 } | 1142 } |
| 986 | 1143 |
| 987 Expression init = parseExpression(); | 1144 Expression init = parseExpression(); |
| 988 expectCategory(SEMICOLON); | 1145 expectCategory(SEMICOLON); |
| 989 return finishFor(init); | 1146 return finishFor(init); |
| 990 } | 1147 } |
| 991 | 1148 |
| 992 Statement parseFunctionDeclaration() { | 1149 Statement parseFunctionDeclaration() { |
| 993 String name = lastToken; | 1150 String name = lastToken; |
| 994 expectCategory(ALPHA); | 1151 expectCategory(ALPHA); |
| 995 Expression fun = parseFun(); | 1152 Expression fun = parseFun(); |
| 996 return new FunctionDeclaration(new VariableDeclaration(name), fun); | 1153 return new FunctionDeclaration(new VariableDeclaration(name), fun); |
| 997 } | 1154 } |
| 998 } | |
| 999 | 1155 |
| 1000 /** | 1156 Statement parseTry() { |
| 1001 * Clone a JSExpression node into an expression where all children | 1157 expectCategory(LBRACE); |
| 1002 * have been cloned, and [InterpolatedExpression]s have been replaced | 1158 Block body = parseBlock(); |
| 1003 * with real [Expression]. | 1159 String token = lastToken; |
| 1004 */ | 1160 Catch catchPart = null; |
| 1005 class UninterpolateJSExpression extends BaseVisitor<Node> { | 1161 if (acceptString('catch')) catchPart = parseCatch(); |
| 1006 final List<Expression> arguments; | 1162 Block finallyPart = null; |
| 1007 int argumentIndex = 0; | 1163 if (acceptString('finally')) { |
| 1008 | 1164 expectCategory(LBRACE); |
| 1009 UninterpolateJSExpression(this.arguments); | 1165 finallyPart = parseBlock(); |
| 1010 | 1166 } else { |
| 1011 void error(message) { | 1167 if (catchPart == null) error("expected 'finally'"); |
| 1012 throw message; | 1168 } |
| 1169 return new Try(body, catchPart, finallyPart); |
| 1013 } | 1170 } |
| 1014 | 1171 |
| 1015 Node visitNode(Node node) { | 1172 Catch parseCatch() { |
| 1016 error('Cannot handle $node'); | 1173 expectCategory(LPAREN); |
| 1017 return null; | 1174 String identifier = lastToken; |
| 1018 } | 1175 expectCategory(ALPHA); |
| 1019 | 1176 expectCategory(RPAREN); |
| 1020 Node copyPosition(Node oldNode, Node newNode) { | 1177 expectCategory(LBRACE); |
| 1021 newNode.sourcePosition = oldNode.sourcePosition; | 1178 Block body = parseBlock(); |
| 1022 newNode.endSourcePosition = oldNode.endSourcePosition; | 1179 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 } | 1180 } |
| 1135 } | 1181 } |
| OLD | NEW |