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

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

Powered by Google App Engine
This is Rietveld 408576698