Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(106)

Side by Side Diff: sdk/lib/_internal/compiler/implementation/js/builder.dart

Issue 237583014: JS templates (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merge Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | sdk/lib/_internal/compiler/implementation/js/js.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | sdk/lib/_internal/compiler/implementation/js/js.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698