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

Side by Side Diff: lib/src/js/builder.dart

Issue 1879373004: Implement modular compilation (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 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
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 // Utilities for building JS ASTs at runtime. Contains a builder class
6 // and a parser that parses part of the language.
7
8 part of js_ast;
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 Identifier('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 Identifier 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 Identifier) or an
86 Iterable of Expressions (and/or Strings).
87
88 // non-splicing argument:
89 js('#(#)', ['say', s]) --> say("hello")
90 // splicing arguments:
91 js('#(#)', ['say', []]) --> say()
92 js('#(#)', ['say', [s]]) --> say("hello")
93 js('#(#)', ['say', [s, n]]) --> say("hello", 123)
94
95 A splicing context can be used to append 'lists' and add extra elements:
96
97 js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1)
98 js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1)
99 js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1)
100 js('foo(#, #, 1)', [ [], [] ]) --> foo(1)
101
102 The generation of a compile-time optional argument expression can be chosen by
103 providing an empty or singleton list.
104
105 In addition to Expressions and Statements, there are Parameters, which occur
106 only in the parameter list of a function expression or declaration.
107 Placeholders in parameter positions behave like placeholders in Expression
108 positions, except only Parameter AST nodes are permitted. String arguments for
109 parameter placeholders are converted to Parameter AST nodes.
110
111 var pFoo = new Parameter('foo')
112 js('function(#) { return #; }', [pFoo, vFoo])
113 -->
114 function(foo) { return foo; }
115
116 Expressions and Parameters are not compatible with each other's context:
117
118 js('function(#) { return #; }', [vFoo, vFoo]) --> error
119 js('function(#) { return #; }', [pFoo, pFoo]) --> error
120
121 The parameter context is a splicing context. When combined with the
122 context-sensitive conversion of Strings, this simplifies the construction of
123 trampoline-like functions:
124
125 var args = ['a', 'b'];
126 js('function(#) { return f(this, #); }', [args, args])
127 -->
128 function(a, b) { return f(this, a, b); }
129
130 A statement placeholder in a Block is also in a splicing context. In addition
131 to splicing Iterables, statement placeholders in a Block will also splice a
132 Block or an EmptyStatement. This flattens nested blocks and allows blocks to be
133 appended.
134
135 var b1 = js.statement('{ 1; 2; }');
136 var sEmpty = new Emptystatement();
137 js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty])
138 -->
139 { 1; 2; 1; 2; }
140
141 A placeholder in the context of an if-statement condition also accepts a Dart
142 bool argument, which selects the then-part or else-part of the if-statement:
143
144 js.statement('if (#) return;', vFoo) --> if (foo) return;
145 js.statement('if (#) return;', true) --> return;
146 js.statement('if (#) return;', false) --> ; // empty statement
147 var eTrue = new LiteralBool(true);
148 js.statement('if (#) return;', eTrue) --> if (true) return;
149
150 Combined with block splicing, if-statement condition context placeholders allows
151 the creation of tenplates that select code depending on variables.
152
153 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true)
154 --> { 1; 2; 5; }
155
156 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false)
157 --> { 1; 3; 4; 5; }
158
159 A placeholder following a period in a property access is in a property access
160 context. This is just like an expression context, except String arguments are
161 converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand
162 for `a["b"]`:
163
164 js('a[#]', vFoo) --> a[foo]
165 js('a[#]', s) --> a.hello (i.e. a["hello"]).
166 js('a[#]', 'x') --> a[x]
167
168 js('a.#', vFoo) --> a[foo]
169 js('a.#', s) --> a.hello (i.e. a["hello"])
170 js('a.#', 'x') --> a.x (i.e. a["x"])
171
172 (Question - should `.#` be restricted to permit only String arguments? The
173 template should probably be writted with `[]` if non-strings are accepted.)
174
175
176 Object initialiers allow placeholders in the key property name position:
177
178 js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2}
179
180
181 What is not implemented:
182
183 - Array initializers and object initializers could support splicing. In the
184 array case, we would need some way to know if an ArrayInitializer argument
185 should be splice or is intended as a single value.
186
187 - There are no placeholders in definition contexts:
188
189 function #(){}
190 var # = 1;
191
192 */
193 const JsBuilder js = const JsBuilder();
194
195
196 class JsBuilder {
197 const JsBuilder();
198
199 /**
200 * Parses a bit of JavaScript, and returns an expression.
201 *
202 * See the MiniJsParser class.
203 *
204 * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or
205 * a list of [Node]s, which will be interpolated into the source at the '#'
206 * signs.
207 */
208 Expression call(String source, [var arguments]) {
209 Template template = _findExpressionTemplate(source);
210 if (arguments == null) return template.instantiate([]);
211 // We allow a single argument to be given directly.
212 if (arguments is! List && arguments is! Map) arguments = [arguments];
213 return template.instantiate(arguments);
214 }
215
216 /**
217 * Parses a JavaScript Statement, otherwise just like [call].
218 */
219 Statement statement(String source, [var arguments]) {
220 Template template = _findStatementTemplate(source);
221 if (arguments == null) return template.instantiate([]);
222 // We allow a single argument to be given directly.
223 if (arguments is! List && arguments is! Map) arguments = [arguments];
224 return template.instantiate(arguments);
225 }
226
227 /**
228 * Parses JavaScript written in the `JS` foreign instruction.
229 *
230 * The [source] must be a JavaScript expression or a JavaScript throw
231 * statement.
232 */
233 Template parseForeignJS(String source) {
234 // TODO(sra): Parse with extra validation to forbid `#` interpolation in
235 // functions, as this leads to unanticipated capture of temporaries that are
236 // reused after capture.
237 if (source.startsWith("throw ")) {
238 return _findStatementTemplate(source);
239 } else {
240 return _findExpressionTemplate(source);
241 }
242 }
243
244 Template _findExpressionTemplate(String source) {
245 Template template = templateManager.lookupExpressionTemplate(source);
246 if (template == null) {
247 MiniJsParser parser = new MiniJsParser(source);
248 Expression expression = parser.expression();
249 template = templateManager.defineExpressionTemplate(source, expression);
250 }
251 return template;
252 }
253
254 Template _findStatementTemplate(String source) {
255 Template template = templateManager.lookupStatementTemplate(source);
256 if (template == null) {
257 MiniJsParser parser = new MiniJsParser(source);
258 Statement statement = parser.statement();
259 template = templateManager.defineStatementTemplate(source, statement);
260 }
261 return template;
262 }
263
264 /**
265 * Creates an Expression template without caching the result.
266 */
267 Template uncachedExpressionTemplate(String source) {
268 MiniJsParser parser = new MiniJsParser(source);
269 Expression expression = parser.expression();
270 return new Template(
271 source, expression, isExpression: true, forceCopy: false);
272 }
273
274 /**
275 * Creates a Statement template without caching the result.
276 */
277 Template uncachedStatementTemplate(String source) {
278 MiniJsParser parser = new MiniJsParser(source);
279 Statement statement = parser.statement();
280 return new Template(
281 source, statement, isExpression: false, forceCopy: false);
282 }
283
284 /**
285 * Create an Expression template which has [ast] as the result. This is used
286 * to wrap a generated AST in a zero-argument Template so it can be passed to
287 * context that expects a template.
288 */
289 Template expressionTemplateYielding(Node ast) {
290 return new Template.withExpressionResult(ast);
291 }
292
293 Template statementTemplateYielding(Node ast) {
294 return new Template.withStatementResult(ast);
295 }
296
297 /// Creates a literal js string from [value].
298 LiteralString escapedString(String value, [String quote = '"']) {
299 // Start by escaping the backslashes.
300 String escaped = value.replaceAll('\\', '\\\\');
301
302
303 // Replace $ in template strings:
304 // http://www.ecma-international.org/ecma-262/6.0/#sec-template-literal-lexi cal-components
305 var quoteReplace = quote == '`' ? r'`$' : quote;
306
307 // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-string-liter als
308 // > All code points may appear literally in a string literal except for the
309 // > closing quote code points, U+005C (REVERSE SOLIDUS),
310 // > U+000D (CARRIAGE RETURN), U+2028 (LINE SEPARATOR),
311 // > U+2029 (PARAGRAPH SEPARATOR), and U+000A (LINE FEED).
312 var re = new RegExp('[\n\r$quoteReplace\b\f\t\v\u2028\u2029]');
313 escaped = escaped.replaceAllMapped(re, (m) {
314 switch (m.group(0)) {
315 case "\n" : return r"\n";
316 case "\r" : return r"\r";
317 case "\u2028": return r"\u2028";
318 case "\u2029": return r"\u2029";
319 // Quotes and $ are only replaced if they conflict with the containing
320 // quote, see regex above.
321 case '"': return r'\"';
322 case "'": return r"\'";
323 case "`": return r"\`";
324 case r"$": return r"\$";
325 // TODO(jmesserly): these don't need to be escaped for correctness,
326 // but they are conventionally escaped.
327 case "\b": return r"\b";
328 case "\t": return r"\t";
329 case "\f": return r"\f";
330 case "\v": return r"\v";
331 }
332 });
333 LiteralString result = new LiteralString('$quote$escaped$quote');
334 // We don't escape quotes of a different style under the assumption that the
335 // string is wrapped into quotes. Verify that assumption.
336 assert(result.value.codeUnitAt(0) == quote.codeUnitAt(0));
337 return result;
338 }
339
340 /// Creates a literal js string from [value].
341 ///
342 /// Note that this function only puts quotes around [value]. It does not do
343 /// any escaping, so use only when you can guarantee that [value] does not
344 /// contain newlines or backslashes. For escaping the string use
345 /// [escapedString].
346 LiteralString string(String value, [String quote = '"']) =>
347 new LiteralString('$quote$value$quote');
348
349 LiteralNumber number(num value) => new LiteralNumber('$value');
350
351 LiteralBool boolean(bool value) => new LiteralBool(value);
352
353 ArrayInitializer numArray(Iterable<int> list) =>
354 new ArrayInitializer(list.map(number).toList());
355
356 ArrayInitializer stringArray(Iterable<String> list) =>
357 new ArrayInitializer(list.map(string).toList());
358
359 Comment comment(String text) => new Comment(text);
360 CommentExpression commentExpression(String text, Expression expression) =>
361 new CommentExpression(text, expression);
362
363 Call propertyCall(Expression receiver,
364 String fieldName,
365 List<Expression> arguments) {
366 return new Call(new PropertyAccess.field(receiver, fieldName), arguments);
367 }
368 }
369
370 LiteralString string(String value) => js.string(value);
371 LiteralNumber number(num value) => js.number(value);
372 ArrayInitializer numArray(Iterable<int> list) => js.numArray(list);
373 ArrayInitializer stringArray(Iterable<String> list) => js.stringArray(list);
374 Call propertyCall(Expression receiver,
375 String fieldName,
376 List<Expression> arguments) {
377 return js.propertyCall(receiver, fieldName, arguments);
378 }
379
380 class MiniJsParserError {
381 MiniJsParserError(this.parser, this.message) { }
382
383 final MiniJsParser parser;
384 final String message;
385
386 String toString() {
387 int pos = parser.lastPosition;
388
389 // Discard lines following the line containing lastPosition.
390 String src = parser.src;
391 int newlinePos = src.indexOf('\n', pos);
392 if (newlinePos >= pos) src = src.substring(0, newlinePos);
393
394 // Extract the prefix of the error line before lastPosition.
395 String line = src;
396 int lastLineStart = line.lastIndexOf('\n');
397 if (lastLineStart >= 0) line = line.substring(lastLineStart + 1);
398 String prefix = line.substring(0, pos - (src.length - line.length));
399
400 // Replace non-tabs with spaces, giving a print indent that matches the text
401 // for tabbing.
402 String spaces = prefix.replaceAll(new RegExp(r'[^\t]'), ' ');
403 return 'Error in MiniJsParser:\n${src}\n$spaces^\n$spaces$message\n';
404 }
405 }
406
407 /// Mini JavaScript parser for tiny snippets of code that we want to make into
408 /// AST nodes. Handles:
409 /// * identifiers.
410 /// * dot access.
411 /// * method calls.
412 /// * [] access.
413 /// * array, string, regexp, boolean, null and numeric literals.
414 /// * most operators.
415 /// * brackets.
416 /// * var declarations.
417 /// * operator precedence.
418 /// * anonymous funtions and named function expressions and declarations.
419 /// Notable things it can't do yet include:
420 /// * some statements are still missing (do-while, while, switch).
421 ///
422 /// It's a fairly standard recursive descent parser.
423 ///
424 /// Literal strings are passed through to the final JS source code unchanged,
425 /// including the choice of surrounding quotes, so if you parse
426 /// r'var x = "foo\n\"bar\""' you will end up with
427 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not
428 /// allowed in string and regexp literals because the machinery for checking
429 /// their correctness is rather involved.
430 class MiniJsParser {
431 MiniJsParser(this.src)
432 : lastCategory = NONE,
433 lastToken = null,
434 lastPosition = 0,
435 position = 0 {
436 getToken();
437 }
438
439 int lastCategory = NONE;
440 String lastToken = null;
441 int lastPosition = 0;
442 int position = 0;
443 bool skippedNewline = false; // skipped newline in last getToken?
444 final String src;
445
446 final List<InterpolatedNode> interpolatedValues = <InterpolatedNode>[];
447 bool get hasNamedHoles =>
448 interpolatedValues.isNotEmpty && interpolatedValues.first.isNamed;
449 bool get hasPositionalHoles =>
450 interpolatedValues.isNotEmpty && interpolatedValues.first.isPositional;
451
452 static const NONE = -1;
453 static const ALPHA = 0;
454 static const NUMERIC = 1;
455 static const STRING = 2;
456 static const SYMBOL = 3;
457 static const ASSIGNMENT = 4;
458 static const DOT = 5;
459 static const LPAREN = 6;
460 static const RPAREN = 7;
461 static const LBRACE = 8;
462 static const RBRACE = 9;
463 static const LSQUARE = 10;
464 static const RSQUARE = 11;
465 static const COMMA = 12;
466 static const QUERY = 13;
467 static const COLON = 14;
468 static const SEMICOLON = 15;
469 static const ARROW = 16;
470 static const ELLIPSIS = 17;
471 static const HASH = 18;
472 static const WHITESPACE = 19;
473 static const OTHER = 20;
474
475 // Make sure that ]] is two symbols.
476 // TODO(jmesserly): => and ... are not single char tokens, should we change
477 // their numbers? It shouldn't matter because this is only called on values
478 // from the [CATEGORIES] table.
479 bool singleCharCategory(int category) => category > DOT;
480
481 static String categoryToString(int cat) {
482 switch (cat) {
483 case NONE: return "NONE";
484 case ALPHA: return "ALPHA";
485 case NUMERIC: return "NUMERIC";
486 case SYMBOL: return "SYMBOL";
487 case ASSIGNMENT: return "ASSIGNMENT";
488 case DOT: return "DOT";
489 case LPAREN: return "LPAREN";
490 case RPAREN: return "RPAREN";
491 case LBRACE: return "LBRACE";
492 case RBRACE: return "RBRACE";
493 case LSQUARE: return "LSQUARE";
494 case RSQUARE: return "RSQUARE";
495 case STRING: return "STRING";
496 case COMMA: return "COMMA";
497 case QUERY: return "QUERY";
498 case COLON: return "COLON";
499 case SEMICOLON: return "SEMICOLON";
500 case ARROW: return "ARROW";
501 case ELLIPSIS: return "ELLIPSIS";
502 case HASH: return "HASH";
503 case WHITESPACE: return "WHITESPACE";
504 case OTHER: return "OTHER";
505 }
506 return "Unknown: $cat";
507 }
508
509 static const CATEGORIES = const <int>[
510 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
511 OTHER, WHITESPACE, WHITESPACE, OTHER, OTHER, WHITESPACE, // 8-13
512 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 14-21
513 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 22-29
514 OTHER, OTHER, WHITESPACE, // 30-32
515 SYMBOL, OTHER, HASH, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&´
516 LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./
517 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234
518 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789
519 COLON, SEMICOLON, SYMBOL, SYMBOL, SYMBOL, QUERY, OTHER, // :;<=>?@
520 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ABCDEFGH
521 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // IJKLMNOP
522 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // QRSTUVWX
523 ALPHA, ALPHA, LSQUARE, OTHER, RSQUARE, SYMBOL, ALPHA, OTHER, // YZ[\]^_'
524 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // abcdefgh
525 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ijklmnop
526 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // qrstuvwx
527 ALPHA, ALPHA, LBRACE, SYMBOL, RBRACE, SYMBOL]; // yz{|}~
528
529 // This must be a >= the highest precedence number handled by parseBinary.
530 static var HIGHEST_PARSE_BINARY_PRECEDENCE = 16;
531 static bool isAssignment(String symbol) => BINARY_PRECEDENCE[symbol] == 17;
532
533 // From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operator s/Operator_Precedence
534 static final BINARY_PRECEDENCE = {
535 '+=': 17, '-=': 17, '*=': 17, '/=': 17, '%=': 17, '^=': 17, '|=': 17,
536 '&=': 17, '<<=': 17, '>>=': 17, '>>>=': 17, '=': 17,
537 '||': 14,
538 '&&': 13,
539 '|': 12,
540 '^': 11,
541 '&': 10,
542 '!=': 9, '==': 9, '!==': 9, '===': 9,
543 '<': 8, '<=': 8, '>=': 8, '>': 8, 'in': 8, 'instanceof': 8,
544 '<<': 7, '>>': 7, '>>>': 7,
545 '+': 6, '-': 6,
546 '*': 5, '/': 5, '%': 5
547 };
548 static final UNARY_OPERATORS =
549 ['++', '--', '+', '-', '~', '!', 'typeof', 'void', 'delete', 'await']
550 .toSet();
551
552 static final ARROW_TOKEN = '=>';
553 static final ELLIPSIS_TOKEN = '...';
554
555 static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
556 ['typeof', 'void', 'delete', 'in', 'instanceof', 'await'].toSet();
557
558 static int category(int code) {
559 if (code >= CATEGORIES.length) return OTHER;
560 return CATEGORIES[code];
561 }
562
563 String getDelimited(int startPosition) {
564 position = startPosition;
565 int delimiter = src.codeUnitAt(startPosition);
566 int currentCode;
567 do {
568 position++;
569 if (position >= src.length) error("Unterminated literal");
570 currentCode = src.codeUnitAt(position);
571 if (currentCode == charCodes.$LF) error("Unterminated literal");
572 if (currentCode == charCodes.$BACKSLASH) {
573 if (++position >= src.length) error("Unterminated literal");
574 int escaped = src.codeUnitAt(position);
575 if (escaped == charCodes.$x || escaped == charCodes.$X ||
576 escaped == charCodes.$u || escaped == charCodes.$U ||
577 category(escaped) == NUMERIC) {
578 error('Numeric and hex escapes are not allowed in literals');
579 }
580 }
581 } while (currentCode != delimiter);
582 position++;
583 return src.substring(lastPosition, position);
584 }
585
586 void getToken() {
587 skippedNewline = false;
588 for (;;) {
589 if (position >= src.length) break;
590 int code = src.codeUnitAt(position);
591 // Skip '//' and '/*' style comments.
592 if (code == charCodes.$SLASH &&
593 position + 1 < src.length) {
594 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) {
595 int nextPosition = src.indexOf('\n', position);
596 if (nextPosition == -1) nextPosition = src.length;
597 position = nextPosition;
598 continue;
599 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) {
600 int nextPosition = src.indexOf('*/', position + 2);
601 if (nextPosition == -1) error('Unterminated comment');
602 position = nextPosition + 2;
603 continue;
604 }
605 }
606 if (category(code) != WHITESPACE) break;
607 if (code == charCodes.$LF) skippedNewline = true;
608 ++position;
609 }
610
611 if (position == src.length) {
612 lastCategory = NONE;
613 lastToken = null;
614 lastPosition = position;
615 return;
616 }
617 int code = src.codeUnitAt(position);
618 lastPosition = position;
619 if (code == charCodes.$SQ || code == charCodes.$DQ) {
620 // String literal.
621 lastCategory = STRING;
622 lastToken = getDelimited(position);
623 } else if (code == charCodes.$0 &&
624 position + 2 < src.length &&
625 src.codeUnitAt(position + 1) == charCodes.$x) {
626 // Hex literal.
627 for (position += 2; position < src.length; position++) {
628 int cat = category(src.codeUnitAt(position));
629 if (cat != NUMERIC && cat != ALPHA) break;
630 }
631 lastCategory = NUMERIC;
632 lastToken = src.substring(lastPosition, position);
633 int.parse(lastToken, onError: (_) {
634 error("Unparseable number");
635 });
636 } else if (code == charCodes.$SLASH) {
637 // Tokens that start with / are special due to regexp literals.
638 lastCategory = SYMBOL;
639 position++;
640 if (position < src.length && src.codeUnitAt(position) == charCodes.$EQ) {
641 position++;
642 }
643 lastToken = src.substring(lastPosition, position);
644 } else {
645 // All other tokens handled here.
646 int cat = category(src.codeUnitAt(position));
647 int newCat;
648 do {
649 position++;
650 if (position == src.length) break;
651 int code = src.codeUnitAt(position);
652 // Special code to disallow ! and / in non-first position in token, so
653 // that !! parses as two tokens and != parses as one, while =/ parses
654 // as a an equals token followed by a regexp literal start.
655 newCat = (code == charCodes.$BANG || code == charCodes.$SLASH)
656 ? NONE
657 : category(code);
658 } while (!singleCharCategory(cat) &&
659 (cat == newCat ||
660 (cat == ALPHA && newCat == NUMERIC) || // eg. level42.
661 (cat == NUMERIC && newCat == DOT))); // eg. 3.1415
662 lastCategory = cat;
663 lastToken = src.substring(lastPosition, position);
664 if (cat == NUMERIC) {
665 double.parse(lastToken, (_) {
666 error("Unparseable number");
667 });
668 } else if (cat == DOT && lastToken.length > 1) {
669 if (lastToken == ELLIPSIS_TOKEN) {
670 lastCategory = ELLIPSIS;
671 } else {
672 error("Unknown operator");
673 }
674 } else if (cat == SYMBOL) {
675 if (lastToken == ARROW_TOKEN) {
676 lastCategory = ARROW;
677 } else {
678 int binaryPrecendence = BINARY_PRECEDENCE[lastToken];
679 if (binaryPrecendence == null && !UNARY_OPERATORS.contains(lastToken)) {
680 error("Unknown operator");
681 }
682 if (isAssignment(lastToken)) lastCategory = ASSIGNMENT;
683 }
684 } else if (cat == ALPHA) {
685 if (OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(lastToken)) {
686 lastCategory = SYMBOL;
687 }
688 }
689 }
690 }
691
692 void expectCategory(int cat) {
693 if (cat != lastCategory) error("Expected ${categoryToString(cat)}");
694 getToken();
695 }
696
697 bool acceptCategory(int cat) {
698 if (cat == lastCategory) {
699 getToken();
700 return true;
701 }
702 return false;
703 }
704
705 void expectSemicolon() {
706 if (acceptSemicolon()) return;
707 error('Expected SEMICOLON');
708 }
709
710 bool acceptSemicolon() {
711 // Accept semicolon or automatically inserted semicolon before close brace.
712 // Miniparser forbids other kinds of semicolon insertion.
713 if (RBRACE == lastCategory) return true;
714 if (NONE == lastCategory) return true; // end of input
715 if (skippedNewline) {
716 error('No automatic semicolon insertion at preceding newline');
717 }
718 return acceptCategory(SEMICOLON);
719 }
720
721 bool acceptString(String string) {
722 if (lastToken == string) {
723 getToken();
724 return true;
725 }
726 return false;
727 }
728
729 void error(message) {
730 throw new MiniJsParserError(this, message);
731 }
732
733 /// Returns either the name for the hole, or its integer position.
734 parseHash() {
735 String holeName = lastToken;
736 if (acceptCategory(ALPHA)) {
737 // Named hole. Example: 'function #funName() { ... }'
738 if (hasPositionalHoles) {
739 error('Holes must all be positional or named. $holeName');
740 }
741 return holeName;
742 } else {
743 if (hasNamedHoles) {
744 error('Holes must all be positional or named. $holeName');
745 }
746 int position = interpolatedValues.length;
747 return position;
748 }
749 }
750
751 Expression parsePrimary() {
752 String last = lastToken;
753 if (acceptCategory(ALPHA)) {
754 if (last == "true") {
755 return new LiteralBool(true);
756 } else if (last == "false") {
757 return new LiteralBool(false);
758 } else if (last == "null") {
759 return new LiteralNull();
760 } else if (last == "function") {
761 return parseFunctionExpression();
762 } else if (last == "this") {
763 return new This();
764 } else if (last == "super") {
765 return new Super();
766 } else if (last == "class") {
767 return parseClass();
768 } else {
769 return new Identifier(last);
770 }
771 } else if (acceptCategory(LPAREN)) {
772 return parseExpressionOrArrowFunction();
773 } else if (acceptCategory(STRING)) {
774 return new LiteralString(last);
775 } else if (acceptCategory(NUMERIC)) {
776 return new LiteralNumber(last);
777 } else if (acceptCategory(LBRACE)) {
778 return parseObjectInitializer();
779 } else if (acceptCategory(LSQUARE)) {
780 var values = <Expression>[];
781
782 while (true) {
783 if (acceptCategory(COMMA)) {
784 values.add(new ArrayHole());
785 continue;
786 }
787 if (acceptCategory(RSQUARE)) break;
788 values.add(parseAssignment());
789 if (acceptCategory(RSQUARE)) break;
790 expectCategory(COMMA);
791 }
792 return new ArrayInitializer(values);
793 } else if (last != null && last.startsWith("/")) {
794 String regexp = getDelimited(lastPosition);
795 getToken();
796 String flags = lastToken;
797 if (!acceptCategory(ALPHA)) flags = "";
798 Expression expression = new RegExpLiteral(regexp + flags);
799 return expression;
800 } else if (acceptCategory(HASH)) {
801 return parseInterpolatedExpression();
802 } else {
803 error("Expected primary expression");
804 return null;
805 }
806 }
807
808 InterpolatedExpression parseInterpolatedExpression() {
809 var expression = new InterpolatedExpression(parseHash());
810 interpolatedValues.add(expression);
811 return expression;
812 }
813
814 InterpolatedIdentifier parseInterpolatedIdentifier() {
815 var id = new InterpolatedIdentifier(parseHash());
816 interpolatedValues.add(id);
817 return id;
818 }
819
820 Identifier parseIdentifier() {
821 if (acceptCategory(HASH)) {
822 return parseInterpolatedIdentifier();
823 } else {
824 var id = new Identifier(lastToken);
825 expectCategory(ALPHA);
826 return id;
827 }
828 }
829
830 /**
831 * CoverParenthesizedExpressionAndArrowParameterList[Yield] :
832 * ( Expression )
833 * ( )
834 * ( ... BindingIdentifier )
835 * ( Expression , ... BindingIdentifier )
836 */
837 Expression parseExpressionOrArrowFunction() {
838 if (acceptCategory(RPAREN)) {
839 expectCategory(ARROW);
840 return parseArrowFunctionBody(<Parameter>[]);
841 }
842 if (acceptCategory(ELLIPSIS)) {
843 var params = <Parameter>[new RestParameter(parseParameter())];
844 expectCategory(RPAREN);
845 expectCategory(ARROW);
846 return parseArrowFunctionBody(params);
847 }
848 Expression expression = parseAssignment();
849 while (acceptCategory(COMMA)) {
850 if (acceptCategory(ELLIPSIS)) {
851 var params = <Parameter>[];
852 _expressionToParameterList(expression, params);
853 params.add(new RestParameter(parseParameter()));
854 expectCategory(RPAREN);
855 expectCategory(ARROW);
856 return parseArrowFunctionBody(params);
857 }
858 Expression right = parseAssignment();
859 expression = new Binary(',', expression, right);
860 }
861 expectCategory(RPAREN);
862 if (acceptCategory(ARROW)) {
863 var params = <Parameter>[];
864 _expressionToParameterList(expression, params);
865 return parseArrowFunctionBody(params);
866 }
867 return expression;
868 }
869
870 /**
871 * Converts a parenthesized expression into a list of parameters, issuing an
872 * error if the conversion fails.
873 */
874 void _expressionToParameterList(Expression node, List<Parameter> params) {
875 if (node is Identifier) {
876 params.add(node);
877 } else if (node is Binary && node.op == ',') {
878 // TODO(jmesserly): this will allow illegal parens, such as
879 // `((a, b), (c, d))`. Fixing it on the left side needs an explicit
880 // ParenthesizedExpression node, so we can distinguish
881 // `((a, b), c)` from `(a, b, c)`.
882 _expressionToParameterList(node.left, params);
883 _expressionToParameterList(node.right, params);
884 } else if (node is InterpolatedExpression) {
885 params.add(new InterpolatedParameter(node.nameOrPosition));
886 } else {
887 error("Expected arrow function parameter list");
888 }
889 }
890
891 Expression parseArrowFunctionBody(List<Parameter> params) {
892 Node body;
893 if (acceptCategory(LBRACE)) {
894 body = parseBlock();
895 } else {
896 body = parseAssignment();
897 }
898 return new ArrowFun(params, body);
899 }
900
901 Expression parseFunctionExpression() {
902 String last = lastToken;
903 if (acceptCategory(ALPHA)) {
904 String functionName = last;
905 return new NamedFunction(new Identifier(functionName),
906 parseFun());
907 }
908 return parseFun();
909 }
910
911 Expression parseFun() {
912 List<Parameter> params = <Parameter>[];
913
914 expectCategory(LPAREN);
915 if (!acceptCategory(RPAREN)) {
916 for (;;) {
917 if (acceptCategory(ELLIPSIS)) {
918 params.add(new RestParameter(parseParameter()));
919 expectCategory(RPAREN);
920 break;
921 }
922
923 params.add(parseParameter());
924 if (!acceptCategory(COMMA)) {
925 expectCategory(RPAREN);
926 break;
927 }
928 }
929 }
930 AsyncModifier asyncModifier;
931 if (acceptString('async')) {
932 if (acceptString('*')) {
933 asyncModifier = const AsyncModifier.asyncStar();
934 } else {
935 asyncModifier = const AsyncModifier.async();
936 }
937 } else if (acceptString('sync')) {
938 if (!acceptString('*')) error("Only sync* is valid - sync is implied");
939 asyncModifier = const AsyncModifier.syncStar();
940 } else {
941 asyncModifier = const AsyncModifier.sync();
942 }
943 expectCategory(LBRACE);
944 Block block = parseBlock();
945 return new Fun(params, block, asyncModifier: asyncModifier);
946 }
947
948 /** Parse parameter name or interpolated parameter. */
949 Identifier parseParameter() {
950 if (acceptCategory(HASH)) {
951 var nameOrPosition = parseHash();
952 var parameter = new InterpolatedParameter(nameOrPosition);
953 interpolatedValues.add(parameter);
954 return parameter;
955 } else {
956 // TODO(jmesserly): validate this is not a keyword
957 String argumentName = lastToken;
958 expectCategory(ALPHA);
959 return new Identifier(argumentName);
960 }
961 }
962
963 Expression parseObjectInitializer() {
964 List<Property> properties = <Property>[];
965 for (;;) {
966 if (acceptCategory(RBRACE)) break;
967 // Limited subset of ES6 object initializers.
968 //
969 // PropertyDefinition :
970 // PropertyName : AssignmentExpression
971 // MethodDefinition
972 properties.add(parseMethodOrProperty());
973
974 if (acceptCategory(RBRACE)) break;
975 expectCategory(COMMA);
976 }
977 return new ObjectInitializer(properties);
978 }
979
980 Expression parseMember() {
981 Expression receiver = parsePrimary();
982 while (true) {
983 if (acceptCategory(DOT)) {
984 receiver = getDotRhs(receiver);
985 } else if (acceptCategory(LSQUARE)) {
986 Expression inBraces = parseExpression();
987 expectCategory(RSQUARE);
988 receiver = new PropertyAccess(receiver, inBraces);
989 } else {
990 break;
991 }
992 }
993 return receiver;
994 }
995
996 Expression parseCall() {
997 bool constructor = acceptString("new");
998 Expression receiver = parseMember();
999 while (true) {
1000 if (acceptCategory(LPAREN)) {
1001 final arguments = <Expression>[];
1002 if (!acceptCategory(RPAREN)) {
1003 while (true) {
1004 if (acceptCategory(ELLIPSIS)) {
1005 arguments.add(new Spread(parseAssignment()));
1006 expectCategory(RPAREN);
1007 break;
1008 }
1009 arguments.add(parseAssignment());
1010 if (acceptCategory(RPAREN)) break;
1011 expectCategory(COMMA);
1012 }
1013 }
1014 receiver = constructor ?
1015 new New(receiver, arguments) :
1016 new Call(receiver, arguments);
1017 constructor = false;
1018 } else if (!constructor && acceptCategory(LSQUARE)) {
1019 Expression inBraces = parseExpression();
1020 expectCategory(RSQUARE);
1021 receiver = new PropertyAccess(receiver, inBraces);
1022 } else if (!constructor && acceptCategory(DOT)) {
1023 receiver = getDotRhs(receiver);
1024 } else {
1025 // JS allows new without (), but we don't.
1026 if (constructor) error("Parentheses are required for new");
1027 break;
1028 }
1029 }
1030 return receiver;
1031 }
1032
1033 Expression getDotRhs(Expression receiver) {
1034 if (acceptCategory(HASH)) {
1035 var nameOrPosition = parseHash();
1036 InterpolatedSelector property = new InterpolatedSelector(nameOrPosition);
1037 interpolatedValues.add(property);
1038 return new PropertyAccess(receiver, property);
1039 }
1040 String identifier = lastToken;
1041 // In ES5 keywords like delete and continue are allowed as property
1042 // names, and the IndexedDB API uses that, so we need to allow it here.
1043 if (acceptCategory(SYMBOL)) {
1044 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) {
1045 error("Expected alphanumeric identifier");
1046 }
1047 } else {
1048 expectCategory(ALPHA);
1049 }
1050 return new PropertyAccess.field(receiver, identifier);
1051 }
1052
1053 Expression parsePostfix() {
1054 Expression expression = parseCall();
1055 String operator = lastToken;
1056 // JavaScript grammar is:
1057 // LeftHandSideExpression [no LineTerminator here] ++
1058 if (lastCategory == SYMBOL &&
1059 !skippedNewline &&
1060 (acceptString("++") || acceptString("--"))) {
1061 return new Postfix(operator, expression);
1062 }
1063 // If we don't accept '++' or '--' due to skippedNewline a newline, no other
1064 // part of the parser will accept the token and we will get an error at the
1065 // whole expression level.
1066 return expression;
1067 }
1068
1069 Expression parseUnaryHigh() {
1070 String operator = lastToken;
1071 if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
1072 (acceptString("++") || acceptString("--") || acceptString('await'))) {
1073 if (operator == "await") return new Await(parsePostfix());
1074 return new Prefix(operator, parsePostfix());
1075 }
1076 return parsePostfix();
1077 }
1078
1079 Expression parseUnaryLow() {
1080 String operator = lastToken;
1081 if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
1082 operator != "++" && operator != "--") {
1083 expectCategory(SYMBOL);
1084 if (operator == "await") return new Await(parsePostfix());
1085 return new Prefix(operator, parseUnaryLow());
1086 }
1087 return parseUnaryHigh();
1088 }
1089
1090 Expression parseBinary(int maxPrecedence) {
1091 Expression lhs = parseUnaryLow();
1092 int minPrecedence;
1093 String lastSymbol;
1094 Expression rhs; // This is null first time around.
1095 while (true) {
1096 String symbol = lastToken;
1097 if (lastCategory != SYMBOL ||
1098 !BINARY_PRECEDENCE.containsKey(symbol) ||
1099 BINARY_PRECEDENCE[symbol] > maxPrecedence) {
1100 break;
1101 }
1102 expectCategory(SYMBOL);
1103 if (rhs == null || BINARY_PRECEDENCE[symbol] >= minPrecedence) {
1104 if (rhs != null) lhs = new Binary(lastSymbol, lhs, rhs);
1105 minPrecedence = BINARY_PRECEDENCE[symbol];
1106 rhs = parseUnaryLow();
1107 lastSymbol = symbol;
1108 } else {
1109 Expression higher = parseBinary(BINARY_PRECEDENCE[symbol]);
1110 rhs = new Binary(symbol, rhs, higher);
1111 }
1112 }
1113 if (rhs == null) return lhs;
1114 return new Binary(lastSymbol, lhs, rhs);
1115 }
1116
1117 Expression parseConditional() {
1118 Expression lhs = parseBinary(HIGHEST_PARSE_BINARY_PRECEDENCE);
1119 if (!acceptCategory(QUERY)) return lhs;
1120 Expression ifTrue = parseAssignment();
1121 expectCategory(COLON);
1122 Expression ifFalse = parseAssignment();
1123 return new Conditional(lhs, ifTrue, ifFalse);
1124 }
1125
1126 Expression parseLeftHandSide() => parseConditional();
1127
1128 Expression parseAssignment() {
1129 Expression lhs = parseLeftHandSide();
1130 String assignmentOperator = lastToken;
1131 if (acceptCategory(ASSIGNMENT)) {
1132 Expression rhs = parseAssignment();
1133 if (assignmentOperator == "=") {
1134 return new Assignment(lhs, rhs);
1135 } else {
1136 // Handle +=, -=, etc.
1137 String operator =
1138 assignmentOperator.substring(0, assignmentOperator.length - 1);
1139 return new Assignment.compound(lhs, operator, rhs);
1140 }
1141 }
1142 return lhs;
1143 }
1144
1145 Expression parseExpression() {
1146 Expression expression = parseAssignment();
1147 while (acceptCategory(COMMA)) {
1148 Expression right = parseAssignment();
1149 expression = new Binary(',', expression, right);
1150 }
1151 return expression;
1152 }
1153
1154 /** Parse a variable declaration list, with `var` or `let` [keyword] */
1155 VariableDeclarationList parseVariableDeclarationList(
1156 String keyword, [String firstIdentifier]) {
1157 var initialization = [];
1158
1159 do {
1160 var declarator;
1161 if (firstIdentifier != null) {
1162 declarator = new Identifier(firstIdentifier);
1163 firstIdentifier = null;
1164 } else {
1165 declarator = parseVariableBinding();
1166 }
1167
1168 var initializer = acceptString("=") ? parseAssignment() : null;
1169 initialization.add(new VariableInitialization(declarator, initializer));
1170 } while (acceptCategory(COMMA));
1171
1172 return new VariableDeclarationList(keyword, initialization);
1173 }
1174
1175 VariableBinding parseVariableBinding() {
1176 switch (lastCategory) {
1177 case ALPHA:
1178 case HASH:
1179 return parseIdentifier();
1180 case LBRACE:
1181 case LSQUARE:
1182 return parseBindingPattern();
1183 default:
1184 error('Unexpected token $lastToken: ${categoryToString(lastCategory)}');
1185 return null;
1186 }
1187 }
1188
1189 /// Note: this doesn't deal with general-case destructuring yet, it just
1190 /// supports it in variable initialization.
1191 /// See ES6 spec:
1192 /// http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding- patterns
1193 /// http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-assignme nt
1194 /// TODO(ochafik): Support destructuring in LeftHandSideExpression.
1195 BindingPattern parseBindingPattern() {
1196 if (acceptCategory(LBRACE)) {
1197 return parseObjectBindingPattern();
1198 } else {
1199 expectCategory(LSQUARE);
1200 return parseArrayBindingPattern();
1201 }
1202 }
1203
1204 ArrayBindingPattern parseArrayBindingPattern() {
1205 var variables = <DestructuredVariable>[];
1206 do {
1207 var name;
1208 var structure;
1209 var defaultValue;
1210
1211 var declarator = parseVariableBinding();
1212 if (declarator is Identifier) name = declarator;
1213 else if (declarator is BindingPattern) structure = declarator;
1214 else error("Unexpected LHS: $declarator");
1215
1216 if (acceptString("=")) {
1217 defaultValue = parseExpression();
1218 }
1219 variables.add(new DestructuredVariable(
1220 name: name, structure: structure, defaultValue: defaultValue));
1221 } while (acceptCategory(COMMA));
1222
1223 expectCategory(RSQUARE);
1224 return new ArrayBindingPattern(variables);
1225 }
1226
1227 ObjectBindingPattern parseObjectBindingPattern() {
1228 var variables = <DestructuredVariable>[];
1229 do {
1230 var name = parseIdentifier();
1231 var structure;
1232 var defaultValue;
1233
1234 if (acceptCategory(COLON)) {
1235 structure = parseBindingPattern();
1236 } else if (acceptString("=")) {
1237 defaultValue = parseExpression();
1238 }
1239 variables.add(new DestructuredVariable(
1240 name: name, structure: structure, defaultValue: defaultValue));
1241 } while (acceptCategory(COMMA));
1242
1243 expectCategory(RBRACE);
1244 return new ObjectBindingPattern(variables);
1245 }
1246
1247 Expression parseVarDeclarationOrExpression() {
1248 var keyword = acceptVarLetOrConst();
1249 if (keyword != null) {
1250 return parseVariableDeclarationList(keyword);
1251 } else {
1252 return parseExpression();
1253 }
1254 }
1255
1256 /** Accepts a `var` or `let` keyword. If neither is found, returns null. */
1257 String acceptVarLetOrConst() {
1258 if (acceptString('var')) return 'var';
1259 if (acceptString('let')) return 'let';
1260 if (acceptString('const')) return 'const';
1261 return null;
1262 }
1263
1264 Expression expression() {
1265 Expression expression = parseVarDeclarationOrExpression();
1266 if (lastCategory != NONE || position != src.length) {
1267 error("Unparsed junk: ${categoryToString(lastCategory)}");
1268 }
1269 return expression;
1270 }
1271
1272 Statement statement() {
1273 Statement statement = parseStatement();
1274 if (lastCategory != NONE || position != src.length) {
1275 error("Unparsed junk: ${categoryToString(lastCategory)}");
1276 }
1277 // TODO(sra): interpolated capture here?
1278 return statement;
1279 }
1280
1281 Block parseBlock() {
1282 List<Statement> statements = <Statement>[];
1283
1284 while (!acceptCategory(RBRACE)) {
1285 Statement statement = parseStatement();
1286 statements.add(statement);
1287 }
1288 return new Block(statements);
1289 }
1290
1291 Statement parseStatement() {
1292 if (acceptCategory(LBRACE)) return parseBlock();
1293
1294 if (acceptCategory(SEMICOLON)) return new EmptyStatement();
1295
1296 if (lastCategory == ALPHA) {
1297 if (acceptString('return')) return parseReturn();
1298
1299 if (acceptString('throw')) return parseThrow();
1300
1301 if (acceptString('break')) {
1302 return parseBreakOrContinue((label) => new Break(label));
1303 }
1304
1305 if (acceptString('continue')) {
1306 return parseBreakOrContinue((label) => new Continue(label));
1307 }
1308
1309 if (acceptString('if')) return parseIfThenElse();
1310
1311 if (acceptString('for')) return parseFor();
1312
1313 if (acceptString('function')) return parseFunctionDeclaration();
1314
1315 if (acceptString('class')) return new ClassDeclaration(parseClass());
1316
1317 if (acceptString('try')) return parseTry();
1318
1319 var keyword = acceptVarLetOrConst();
1320 if (keyword != null) {
1321 Expression declarations = parseVariableDeclarationList(keyword);
1322 expectSemicolon();
1323 return new ExpressionStatement(declarations);
1324 }
1325
1326 if (acceptString('while')) return parseWhile();
1327
1328 if (acceptString('do')) return parseDo();
1329
1330 if (acceptString('switch')) return parseSwitch();
1331
1332 if (lastToken == 'case') error("Case outside switch.");
1333
1334 if (lastToken == 'default') error("Default outside switch.");
1335
1336 if (lastToken == 'yield') return parseYield();
1337
1338 if (lastToken == 'with') {
1339 error('Not implemented in mini parser');
1340 }
1341
1342 }
1343
1344 bool checkForInterpolatedStatement = lastCategory == HASH;
1345
1346 Expression expression = parseExpression();
1347
1348 if (expression is Identifier && acceptCategory(COLON)) {
1349 return new LabeledStatement(expression.name, parseStatement());
1350 }
1351
1352 expectSemicolon();
1353
1354 if (checkForInterpolatedStatement) {
1355 // 'Promote' the interpolated expression `#;` to an interpolated
1356 // statement.
1357 if (expression is InterpolatedExpression) {
1358 assert(identical(interpolatedValues.last, expression));
1359 InterpolatedStatement statement =
1360 new InterpolatedStatement(expression.nameOrPosition);
1361 interpolatedValues[interpolatedValues.length - 1] = statement;
1362 return statement;
1363 }
1364 }
1365
1366 return new ExpressionStatement(expression);
1367 }
1368
1369 Statement parseReturn() {
1370 if (acceptSemicolon()) return new Return();
1371 Expression expression = parseExpression();
1372 expectSemicolon();
1373 return new Return(expression);
1374 }
1375
1376 Statement parseYield() {
1377 bool hasStar = acceptString('*');
1378 Expression expression = parseExpression();
1379 expectSemicolon();
1380 return new DartYield(expression, hasStar);
1381 }
1382
1383 Statement parseThrow() {
1384 if (skippedNewline) error('throw expression must be on same line');
1385 Expression expression = parseExpression();
1386 expectSemicolon();
1387 return new Throw(expression);
1388 }
1389
1390 Statement parseBreakOrContinue(constructor) {
1391 var identifier = lastToken;
1392 if (!skippedNewline && acceptCategory(ALPHA)) {
1393 expectSemicolon();
1394 return constructor(identifier);
1395 }
1396 expectSemicolon();
1397 return constructor(null);
1398 }
1399
1400 Statement parseIfThenElse() {
1401 expectCategory(LPAREN);
1402 Expression condition = parseExpression();
1403 expectCategory(RPAREN);
1404 Statement thenStatement = parseStatement();
1405 if (acceptString('else')) {
1406 // Resolves dangling else by binding 'else' to closest 'if'.
1407 Statement elseStatement = parseStatement();
1408 return new If(condition, thenStatement, elseStatement);
1409 } else {
1410 return new If.noElse(condition, thenStatement);
1411 }
1412 }
1413
1414 Statement parseFor() {
1415 // For-init-condition-increment style loops are fully supported.
1416 //
1417 // Only one for-in variant is currently implemented:
1418 //
1419 // for (var variable in Expression) Statement
1420 //
1421 // One variant of ES6 for-of is also implemented:
1422 //
1423 // for (let variable of Expression) Statement
1424 //
1425 Statement finishFor(Expression init) {
1426 Expression condition = null;
1427 if (!acceptCategory(SEMICOLON)) {
1428 condition = parseExpression();
1429 expectCategory(SEMICOLON);
1430 }
1431 Expression update = null;
1432 if (!acceptCategory(RPAREN)) {
1433 update = parseExpression();
1434 expectCategory(RPAREN);
1435 }
1436 Statement body = parseStatement();
1437 return new For(init, condition, update, body);
1438 }
1439
1440 expectCategory(LPAREN);
1441 if (acceptCategory(SEMICOLON)) {
1442 return finishFor(null);
1443 }
1444
1445 var keyword = acceptVarLetOrConst();
1446 if (keyword != null) {
1447 String identifier = lastToken;
1448 expectCategory(ALPHA);
1449
1450 if (acceptString('in')) {
1451 Expression objectExpression = parseExpression();
1452 expectCategory(RPAREN);
1453 Statement body = parseStatement();
1454 return new ForIn(
1455 _createVariableDeclarationList(keyword, identifier),
1456 objectExpression,
1457 body);
1458 } else if (acceptString('of')) {
1459 Expression iterableExpression = parseAssignment();
1460 expectCategory(RPAREN);
1461 Statement body = parseStatement();
1462 return new ForOf(
1463 _createVariableDeclarationList(keyword, identifier),
1464 iterableExpression,
1465 body);
1466 }
1467 var declarations = parseVariableDeclarationList(keyword, identifier);
1468 expectCategory(SEMICOLON);
1469 return finishFor(declarations);
1470 }
1471
1472 Expression init = parseExpression();
1473 expectCategory(SEMICOLON);
1474 return finishFor(init);
1475 }
1476
1477 static VariableDeclarationList _createVariableDeclarationList(
1478 String keyword, String identifier) {
1479 return new VariableDeclarationList(keyword, [
1480 new VariableInitialization(
1481 new Identifier(identifier), null)]);
1482 }
1483
1484 Statement parseFunctionDeclaration() {
1485 String name = lastToken;
1486 expectCategory(ALPHA);
1487 Expression fun = parseFun();
1488 return new FunctionDeclaration(new Identifier(name), fun);
1489 }
1490
1491 Statement parseTry() {
1492 expectCategory(LBRACE);
1493 Block body = parseBlock();
1494 Catch catchPart = null;
1495 if (acceptString('catch')) catchPart = parseCatch();
1496 Block finallyPart = null;
1497 if (acceptString('finally')) {
1498 expectCategory(LBRACE);
1499 finallyPart = parseBlock();
1500 } else {
1501 if (catchPart == null) error("expected 'finally'");
1502 }
1503 return new Try(body, catchPart, finallyPart);
1504 }
1505
1506 SwitchClause parseSwitchClause() {
1507 Expression expression = null;
1508 if (acceptString('case')) {
1509 expression = parseExpression();
1510 expectCategory(COLON);
1511 } else {
1512 if (!acceptString('default')) {
1513 error('expected case or default');
1514 }
1515 expectCategory(COLON);
1516 }
1517 List statements = new List<Statement>();
1518 while (lastCategory != RBRACE &&
1519 lastToken != 'case' &&
1520 lastToken != 'default') {
1521 statements.add(parseStatement());
1522 }
1523 return expression == null
1524 ? new Default(new Block(statements))
1525 : new Case(expression, new Block(statements));
1526 }
1527
1528 Statement parseWhile() {
1529 expectCategory(LPAREN);
1530 Expression condition = parseExpression();
1531 expectCategory(RPAREN);
1532 Statement body = parseStatement();
1533 return new While(condition, body);
1534 }
1535
1536 Statement parseDo() {
1537 Statement body = parseStatement();
1538 if (lastToken != "while") error("Missing while after do body.");
1539 getToken();
1540 expectCategory(LPAREN);
1541 Expression condition = parseExpression();
1542 expectCategory(RPAREN);
1543 expectSemicolon();
1544 return new Do(body, condition);
1545 }
1546
1547 Statement parseSwitch() {
1548 expectCategory(LPAREN);
1549 Expression key = parseExpression();
1550 expectCategory(RPAREN);
1551 expectCategory(LBRACE);
1552 List<SwitchClause> clauses = new List<SwitchClause>();
1553 while(lastCategory != RBRACE) {
1554 clauses.add(parseSwitchClause());
1555 }
1556 expectCategory(RBRACE);
1557 return new Switch(key, clauses);
1558 }
1559
1560 Catch parseCatch() {
1561 expectCategory(LPAREN);
1562 String identifier = lastToken;
1563 expectCategory(ALPHA);
1564 expectCategory(RPAREN);
1565 expectCategory(LBRACE);
1566 Block body = parseBlock();
1567 return new Catch(new Identifier(identifier), body);
1568 }
1569
1570 ClassExpression parseClass() {
1571 Identifier name = parseIdentifier();
1572 Expression heritage = null;
1573 if (acceptString('extends')) {
1574 heritage = parseConditional();
1575 }
1576 expectCategory(LBRACE);
1577 var methods = new List<Method>();
1578 while (lastCategory != RBRACE) {
1579 methods.add(parseMethodOrProperty(onlyMethods: true));
1580 }
1581 expectCategory(RBRACE);
1582 return new ClassExpression(name, heritage, methods);
1583 }
1584
1585 /**
1586 * Parses a [Method] or a [Property].
1587 *
1588 * Most of the complexity is from supporting interpolation. Several forms
1589 * are supported:
1590 *
1591 * - getter/setter names: `get #() { ... }`
1592 * - method names: `#() { ... }`
1593 * - property names: `#: ...`
1594 * - entire methods: `#`
1595 */
1596 Property parseMethodOrProperty({bool onlyMethods: false}) {
1597 bool isStatic = acceptString('static');
1598
1599 bool isGetter = lastToken == 'get';
1600 bool isSetter = lastToken == 'set';
1601 Expression name = null;
1602 if (isGetter || isSetter) {
1603 var token = lastToken;
1604 getToken();
1605 if (lastCategory == COLON) {
1606 // That wasn't a accessor but the 'get' or 'set' property: retropedal.
1607 isGetter = isSetter = false;
1608 name = new LiteralString('"$token"');
1609 }
1610 }
1611 if (acceptCategory(HASH)) {
1612 if (lastCategory != LPAREN && (onlyMethods || lastCategory != COLON)) {
1613 // Interpolated method
1614 var member = new InterpolatedMethod(parseHash());
1615 interpolatedValues.add(member);
1616 return member;
1617 }
1618 name = parseInterpolatedExpression();
1619 } else {
1620 name ??= parsePropertyName();
1621 }
1622
1623 if (!onlyMethods && acceptCategory(COLON)) {
1624 Expression value = parseAssignment();
1625 return new Property(name, value);
1626 } else {
1627 var fun = parseFun();
1628 return new Method(name, fun,
1629 isGetter: isGetter, isSetter: isSetter, isStatic: isStatic);
1630 }
1631 }
1632
1633 Expression parsePropertyName() {
1634 String identifier = lastToken;
1635 if (acceptCategory(STRING)) {
1636 return new LiteralString(identifier);
1637 } else if (acceptCategory(ALPHA) || acceptCategory(SYMBOL)) {
1638 // ALPHA or a SYMBOL, e.g. void
1639 return new LiteralString('"$identifier"');
1640 } else if (acceptCategory(LSQUARE)) {
1641 var expr = parseAssignment();
1642 expectCategory(RSQUARE);
1643 return expr;
1644 } else if (acceptCategory(HASH)) {
1645 return parseInterpolatedExpression();
1646 } else {
1647 error('Expected property name');
1648 return null;
1649 }
1650 }
1651 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698