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

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

Powered by Google App Engine
This is Rietveld 408576698