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

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

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month 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
(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;
9
10
11 /**
12 * Global template manager. We should aim to have a fixed number of
13 * templates. This implies that we do not use js('xxx') to parse text that is
14 * constructed from values that depend on names in the Dart program.
15 *
16 * TODO(sra): Find the remaining places where js('xxx') used to parse an
17 * unbounded number of expression, or institute a cache policy.
18 */
19 TemplateManager templateManager = new TemplateManager();
20
21
22 /**
23
24 [js] is a singleton instace of JsBuilder. JsBuilder is a set of conveniences
25 for constructing JavaScript ASTs.
26
27 [string] and [number] are used to create leaf AST nodes:
28
29 var s = js.string('hello'); // s = new LiteralString('"hello"')
30 var n = js.number(123); // n = new LiteralNumber(123)
31
32 In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript
33 AST that would pretty-print as `b`.
34
35 The [call] method constructs an Expression AST.
36
37 No argument
38
39 js('window.alert("hello")') --> window.alert("hello")
40
41 The input text can contain placeholders `#` that are replaced with provided
42 arguments. A single argument can be passed directly:
43
44 js('window.alert(#)', s) --> window.alert("hello")
45
46 Multiple arguments are passed as a list:
47
48 js('# + #', [s, s]) --> "hello" + "hello"
49
50 The [statement] method constructs a Statement AST, but is otherwise like the
51 [call] method. This constructs a Return AST:
52
53 var ret = js.statement('return #;', n); --> return 123;
54
55 A placeholder in a Statement context must be followed by a semicolon ';'. You
56 can think of a statement placeholder as being `#;` to explain why the output
57 still has one semicolon:
58
59 js.statement('if (happy) #;', ret)
60 -->
61 if (happy)
62 return 123;
63
64 If the placeholder is not followed by a semicolon, it is part of an expression.
65 Here the paceholder is in the position of the function in a function call:
66
67 var vFoo = new VariableUse('foo');
68 js.statement('if (happy) #("Happy!")', vFoo)
69 -->
70 if (happy)
71 foo("Happy!");
72
73 Generally, a placeholder in an expression position requires an Expression AST as
74 an argument and a placeholder in a statement position requires a Statement AST.
75 An expression will be converted to a Statement if needed by creating an
76 ExpessionStatement. A String argument will be converted into a VariableUse and
77 requires that the string is a JavaScript identifier.
78
79 js('# + 1', vFoo) --> foo + 1
80 js('# + 1', 'foo') --> foo + 1
81 js('# + 1', 'foo.bar') --> assertion failure
82
83 Some placeholder positions are _splicing contexts_. A function argument list is
84 a splicing expression context. A placeholder in a splicing expression context
85 can take a single Expression (or String, converted to VariableUse) or an
86 Iterable of Expressions (and/or Strings).
87
88 // non-splicing argument:
89 js('#(#)', ['say', s]) --> say("hello")
90 // splicing arguments:
91 js('#(#)', ['say', []]) --> say()
92 js('#(#)', ['say', [s]]) --> say("hello")
93 js('#(#)', ['say', [s, n]]) --> say("hello", 123)
94
95 A splicing context can be used to append 'lists' and add extra elements:
96
97 js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1)
98 js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1)
99 js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1)
100 js('foo(#, #, 1)', [ [], [] ]) --> foo(1)
101
102 The generation of a compile-time optional argument expression can be chosen by
103 providing an empty or singleton list.
104
105 In addition to Expressions and Statements, there are Parameters, which occur
106 only in the parameter list of a function expression or declaration.
107 Placeholders in parameter positions behave like placeholders in Expression
108 positions, except only Parameter AST nodes are permitted. String arguments for
109 parameter placeholders are converted to Parameter AST nodes.
110
111 var pFoo = new Parameter('foo')
112 js('function(#) { return #; }', [pFoo, vFoo])
113 -->
114 function(foo) { return foo; }
115
116 Expressions and Parameters are not compatible with each other's context:
117
118 js('function(#) { return #; }', [vFoo, vFoo]) --> error
119 js('function(#) { return #; }', [pFoo, pFoo]) --> error
120
121 The parameter context is a splicing context. When combined with the
122 context-sensitive conversion of Strings, this simplifies the construction of
123 trampoline-like functions:
124
125 var args = ['a', 'b'];
126 js('function(#) { return f(this, #); }', [args, args])
127 -->
128 function(a, b) { return f(this, a, b); }
129
130 A statement placeholder in a Block is also in a splicing context. In addition
131 to splicing Iterables, statement placeholders in a Block will also splice a
132 Block or an EmptyStatement. This flattens nested blocks and allows blocks to be
133 appended.
134
135 var b1 = js.statement('{ 1; 2; }');
136 var sEmpty = new Emptystatement();
137 js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty])
138 -->
139 { 1; 2; 1; 2; }
140
141 A placeholder in the context of an if-statement condition also accepts a Dart
142 bool argument, which selects the then-part or else-part of the if-statement:
143
144 js.statement('if (#) return;', vFoo) --> if (foo) return;
145 js.statement('if (#) return;', true) --> return;
146 js.statement('if (#) return;', false) --> ; // empty statement
147 var eTrue = new LiteralBool(true);
148 js.statement('if (#) return;', eTrue) --> if (true) return;
149
150 Combined with block splicing, if-statement condition context placeholders allows
151 the creation of tenplates that select code depending on variables.
152
153 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true)
154 --> { 1; 2; 5; }
155
156 js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false)
157 --> { 1; 3; 4; 5; }
158
159 A placeholder following a period in a property access is in a property access
160 context. This is just like an expression context, except String arguments are
161 converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand
162 for `a["b"]`:
163
164 js('a[#]', vFoo) --> a[foo]
165 js('a[#]', s) --> a.hello (i.e. a["hello"]).
166 js('a[#]', 'x') --> a[x]
167
168 js('a.#', vFoo) --> a[foo]
169 js('a.#', s) --> a.hello (i.e. a["hello"])
170 js('a.#', 'x') --> a.x (i.e. a["x"])
171
172 (Question - should `.#` be restricted to permit only String arguments? The
173 template should probably be writted with `[]` if non-strings are accepted.)
174
175
176 Object initialiers allow placeholders in the key property name position:
177
178 js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2}
179
180
181 What is not implemented:
182
183 - Array initializers and object initializers could support splicing. In the
184 array case, we would need some way to know if an ArrayInitializer argument
185 should be splice or is intended as a single value.
186
187 - There are no placeholders in definition contexts:
188
189 function #(){}
190 var # = 1;
191
192 */
193 const JsBuilder js = const JsBuilder();
194
195
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 return template.instantiate(arguments is List ? arguments : [arguments]);
212 }
213
214 /**
215 * Parses a JavaScript Statement, otherwise just like [call].
216 */
217 Statement statement(String source, [var arguments]) {
218 Template template = _findStatementTemplate(source);
219 if (arguments == null) return template.instantiate([]);
220 return template.instantiate(arguments is List ? arguments : [arguments]);
221 }
222
223 /**
224 * Parses JavaScript written in the `JS` foreign instruction.
225 *
226 * The [source] must be a JavaScript expression or a JavaScript throw
227 * statement.
228 */
229 Template parseForeignJS(String source) {
230 // TODO(sra): Parse with extra validation to forbid `#` interpolation in
231 // functions, as this leads to unanticipated capture of temporaries that are
232 // reused after capture.
233 if (source.startsWith("throw ")) {
234 return _findStatementTemplate(source);
235 } else {
236 return _findExpressionTemplate(source);
237 }
238 }
239
240 Template _findExpressionTemplate(String source) {
241 Template template = templateManager.lookupExpressionTemplate(source);
242 if (template == null) {
243 MiniJsParser parser = new MiniJsParser(source);
244 Expression expression = parser.expression();
245 template = templateManager.defineExpressionTemplate(source, expression);
246 }
247 return template;
248 }
249
250 Template _findStatementTemplate(String source) {
251 Template template = templateManager.lookupStatementTemplate(source);
252 if (template == null) {
253 MiniJsParser parser = new MiniJsParser(source);
254 Statement statement = parser.statement();
255 template = templateManager.defineStatementTemplate(source, statement);
256 }
257 return template;
258 }
259
260 /**
261 * Creates an Expression template without caching the result.
262 */
263 Template uncachedExpressionTemplate(String source) {
264 MiniJsParser parser = new MiniJsParser(source);
265 Expression expression = parser.expression();
266 return new Template(
267 source, expression, isExpression: true, forceCopy: false);
268 }
269
270 /**
271 * Creates a Statement template without caching the result.
272 */
273 Template uncachedStatementTemplate(String source) {
274 MiniJsParser parser = new MiniJsParser(source);
275 Statement statement = parser.statement();
276 return new Template(
277 source, statement, isExpression: false, forceCopy: false);
278 }
279
280 /**
281 * Create an Expression template which has [ast] as the result. This is used
282 * to wrap a generated AST in a zero-argument Template so it can be passed to
283 * context that expects a template.
284 */
285 Template expressionTemplateYielding(Node ast) {
286 return new Template.withExpressionResult(ast);
287 }
288
289 Template statementTemplateYielding(Node ast) {
290 return new Template.withStatementResult(ast);
291 }
292
293 /// Creates a literal js string from [value].
294 LiteralString escapedString(String value) {
295 // Start by escaping the backslashes.
296 String escaped = value.replaceAll('\\', '\\\\');
297 // Do not escape unicode characters and ' because they are allowed in the
298 // string literal anyway.
299 escaped = escaped.replaceAllMapped(new RegExp('\n|"|\b|\t|\v'), (match) {
300 switch (match.group(0)) {
301 case "\n" : return r"\n";
302 case "\"" : return r'\"';
303 case "\b" : return r"\b";
304 case "\t" : return r"\t";
305 case "\f" : return r"\f";
306 case "\v" : return r"\v";
307 }
308 });
309 LiteralString result = string(escaped);
310 // We don't escape ' under the assumption that the string is wrapped
311 // into ". Verify that assumption.
312 assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0));
313 return result;
314 }
315
316 /// Creates a literal js string from [value].
317 ///
318 /// Note that this function only puts quotes around [value]. It does not do
319 /// any escaping, so use only when you can guarantee that [value] does not
320 /// contain newlines or backslashes. For escaping the string use
321 /// [escapedString].
322 LiteralString string(String value) => new LiteralString('"$value"');
323
324 LiteralNumber number(num value) => new LiteralNumber('$value');
325
326 ArrayInitializer numArray(Iterable<int> list) =>
327 new ArrayInitializer.from(list.map(number));
328
329 ArrayInitializer stringArray(Iterable<String> list) =>
330 new ArrayInitializer.from(list.map(string));
331
332 Comment comment(String text) => new Comment(text);
333 }
334
335 LiteralString string(String value) => js.string(value);
336 LiteralNumber number(num value) => js.number(value);
337 ArrayInitializer numArray(Iterable<int> list) => js.numArray(list);
338 ArrayInitializer stringArray(Iterable<String> list) => js.stringArray(list);
339
340 class MiniJsParserError {
341 MiniJsParserError(this.parser, this.message) { }
342
343 final MiniJsParser parser;
344 final String message;
345
346 String toString() {
347 int pos = parser.lastPosition;
348
349 // Discard lines following the line containing lastPosition.
350 String src = parser.src;
351 int newlinePos = src.indexOf('\n', pos);
352 if (newlinePos >= pos) src = src.substring(0, newlinePos);
353
354 // Extract the prefix of the error line before lastPosition.
355 String line = src;
356 int lastLineStart = line.lastIndexOf('\n');
357 if (lastLineStart >= 0) line = line.substring(lastLineStart + 1);
358 String prefix = line.substring(0, pos - (src.length - line.length));
359
360 // Replace non-tabs with spaces, giving a print indent that matches the text
361 // for tabbing.
362 String spaces = prefix.replaceAll(new RegExp(r'[^\t]'), ' ');
363 return 'Error in MiniJsParser:\n${src}\n$spaces^\n$spaces$message\n';
364 }
365 }
366
367 /// Mini JavaScript parser for tiny snippets of code that we want to make into
368 /// AST nodes. Handles:
369 /// * identifiers.
370 /// * dot access.
371 /// * method calls.
372 /// * [] access.
373 /// * array, string, regexp, boolean, null and numeric literals.
374 /// * most operators.
375 /// * brackets.
376 /// * var declarations.
377 /// * operator precedence.
378 /// * anonymous funtions and named function expressions and declarations.
379 /// Notable things it can't do yet include:
380 /// * some statements are still missing (do-while, while, switch).
381 ///
382 /// It's a fairly standard recursive descent parser.
383 ///
384 /// Literal strings are passed through to the final JS source code unchanged,
385 /// including the choice of surrounding quotes, so if you parse
386 /// r'var x = "foo\n\"bar\""' you will end up with
387 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not
388 /// allowed in string and regexp literals because the machinery for checking
389 /// their correctness is rather involved.
390 class MiniJsParser {
391 MiniJsParser(this.src)
392 : lastCategory = NONE,
393 lastToken = null,
394 lastPosition = 0,
395 position = 0 {
396 getToken();
397 }
398
399 int lastCategory = NONE;
400 String lastToken = null;
401 int lastPosition = 0;
402 int position = 0;
403 bool skippedNewline = false; // skipped newline in last getToken?
404 final String src;
405 final List<InterpolatedNode> interpolatedValues = <InterpolatedNode>[];
406
407 static const NONE = -1;
408 static const ALPHA = 0;
409 static const NUMERIC = 1;
410 static const STRING = 2;
411 static const SYMBOL = 3;
412 static const ASSIGNMENT = 4;
413 static const DOT = 5;
414 static const LPAREN = 6;
415 static const RPAREN = 7;
416 static const LBRACE = 8;
417 static const RBRACE = 9;
418 static const LSQUARE = 10;
419 static const RSQUARE = 11;
420 static const COMMA = 12;
421 static const QUERY = 13;
422 static const COLON = 14;
423 static const SEMICOLON = 15;
424 static const HASH = 16;
425 static const WHITESPACE = 17;
426 static const OTHER = 18;
427
428 // Make sure that ]] is two symbols.
429 bool singleCharCategory(int category) => category >= DOT;
430
431 static String categoryToString(int cat) {
432 switch (cat) {
433 case NONE: return "NONE";
434 case ALPHA: return "ALPHA";
435 case NUMERIC: return "NUMERIC";
436 case SYMBOL: return "SYMBOL";
437 case ASSIGNMENT: return "ASSIGNMENT";
438 case DOT: return "DOT";
439 case LPAREN: return "LPAREN";
440 case RPAREN: return "RPAREN";
441 case LBRACE: return "LBRACE";
442 case RBRACE: return "RBRACE";
443 case LSQUARE: return "LSQUARE";
444 case RSQUARE: return "RSQUARE";
445 case STRING: return "STRING";
446 case COMMA: return "COMMA";
447 case QUERY: return "QUERY";
448 case COLON: return "COLON";
449 case SEMICOLON: return "SEMICOLON";
450 case HASH: return "HASH";
451 case WHITESPACE: return "WHITESPACE";
452 case OTHER: return "OTHER";
453 }
454 return "Unknown: $cat";
455 }
456
457 static const CATEGORIES = const <int>[
458 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
459 OTHER, WHITESPACE, WHITESPACE, OTHER, OTHER, WHITESPACE, // 8-13
460 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 14-21
461 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 22-29
462 OTHER, OTHER, WHITESPACE, // 30-32
463 SYMBOL, OTHER, HASH, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&´
464 LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./
465 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234
466 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789
467 COLON, SEMICOLON, SYMBOL, SYMBOL, SYMBOL, QUERY, OTHER, // :;<=>?@
468 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ABCDEFGH
469 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // IJKLMNOP
470 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // QRSTUVWX
471 ALPHA, ALPHA, LSQUARE, OTHER, RSQUARE, SYMBOL, ALPHA, OTHER, // YZ[\]^_'
472 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // abcdefgh
473 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ijklmnop
474 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // qrstuvwx
475 ALPHA, ALPHA, LBRACE, SYMBOL, RBRACE, SYMBOL]; // yz{|}~
476
477 // This must be a >= the highest precedence number handled by parseBinary.
478 static var HIGHEST_PARSE_BINARY_PRECEDENCE = 16;
479 static bool isAssignment(String symbol) => BINARY_PRECEDENCE[symbol] == 17;
480
481 // From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operator s/Operator_Precedence
482 static final BINARY_PRECEDENCE = {
483 '+=': 17, '-=': 17, '*=': 17, '/=': 17, '%=': 17, '^=': 17, '|=': 17,
484 '&=': 17, '<<=': 17, '>>=': 17, '>>>=': 17, '=': 17,
485 '||': 14,
486 '&&': 13,
487 '|': 12,
488 '^': 11,
489 '&': 10,
490 '!=': 9, '==': 9, '!==': 9, '===': 9,
491 '<': 8, '<=': 8, '>=': 8, '>': 8, 'in': 8, 'instanceof': 8,
492 '<<': 7, '>>': 7, '>>>': 7,
493 '+': 6, '-': 6,
494 '*': 5, '/': 5, '%': 5
495 };
496 static final UNARY_OPERATORS =
497 ['++', '--', '+', '-', '~', '!', 'typeof', 'void', 'delete'].toSet();
498
499 static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
500 ['typeof', 'void', 'delete', 'in', 'instanceof'].toSet();
501
502 static int category(int code) {
503 if (code >= CATEGORIES.length) return OTHER;
504 return CATEGORIES[code];
505 }
506
507 String getDelimited(int startPosition) {
508 position = startPosition;
509 int delimiter = src.codeUnitAt(startPosition);
510 int currentCode;
511 do {
512 position++;
513 if (position >= src.length) error("Unterminated literal");
514 currentCode = src.codeUnitAt(position);
515 if (currentCode == charCodes.$LF) error("Unterminated literal");
516 if (currentCode == charCodes.$BACKSLASH) {
517 if (++position >= src.length) error("Unterminated literal");
518 int escaped = src.codeUnitAt(position);
519 if (escaped == charCodes.$x || escaped == charCodes.$X ||
520 escaped == charCodes.$u || escaped == charCodes.$U ||
521 category(escaped) == NUMERIC) {
522 error('Numeric and hex escapes are not allowed in literals');
523 }
524 }
525 } while (currentCode != delimiter);
526 position++;
527 return src.substring(lastPosition, position);
528 }
529
530 void getToken() {
531 skippedNewline = false;
532 for (;;) {
533 if (position >= src.length) break;
534 int code = src.codeUnitAt(position);
535 // Skip '//' and '/*' style comments.
536 if (code == charCodes.$SLASH &&
537 position + 1 < src.length) {
538 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) {
539 int nextPosition = src.indexOf('\n', position);
540 if (nextPosition == -1) nextPosition = src.length;
541 position = nextPosition;
542 continue;
543 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) {
544 int nextPosition = src.indexOf('*/', position + 2);
545 if (nextPosition == -1) error('Unterminated comment');
546 position = nextPosition + 2;
547 continue;
548 }
549 }
550 if (category(code) != WHITESPACE) break;
551 if (code == charCodes.$LF) skippedNewline = true;
552 ++position;
553 }
554
555 if (position == src.length) {
556 lastCategory = NONE;
557 lastToken = null;
558 lastPosition = position;
559 return;
560 }
561 int code = src.codeUnitAt(position);
562 lastPosition = position;
563 if (code == charCodes.$SQ || code == charCodes.$DQ) {
564 // String literal.
565 lastCategory = STRING;
566 lastToken = getDelimited(position);
567 } else if (code == charCodes.$0 &&
568 position + 2 < src.length &&
569 src.codeUnitAt(position + 1) == charCodes.$x) {
570 // Hex literal.
571 for (position += 2; position < src.length; position++) {
572 int cat = category(src.codeUnitAt(position));
573 if (cat != NUMERIC && cat != ALPHA) break;
574 }
575 lastCategory = NUMERIC;
576 lastToken = src.substring(lastPosition, position);
577 int.parse(lastToken, onError: (_) {
578 error("Unparseable number");
579 });
580 } else if (code == charCodes.$SLASH) {
581 // Tokens that start with / are special due to regexp literals.
582 lastCategory = SYMBOL;
583 position++;
584 if (position < src.length && src.codeUnitAt(position) == charCodes.$EQ) {
585 position++;
586 }
587 lastToken = src.substring(lastPosition, position);
588 } else {
589 // All other tokens handled here.
590 int cat = category(src.codeUnitAt(position));
591 int newCat;
592 do {
593 position++;
594 if (position == src.length) break;
595 int code = src.codeUnitAt(position);
596 // Special code to disallow ! and / in non-first position in token, so
597 // that !! parses as two tokens and != parses as one, while =/ parses
598 // as a an equals token followed by a regexp literal start.
599 newCat = (code == charCodes.$BANG || code == charCodes.$SLASH)
600 ? NONE
601 : category(code);
602 } while (!singleCharCategory(cat) &&
603 (cat == newCat ||
604 (cat == ALPHA && newCat == NUMERIC) || // eg. level42.
605 (cat == NUMERIC && newCat == DOT))); // eg. 3.1415
606 lastCategory = cat;
607 lastToken = src.substring(lastPosition, position);
608 if (cat == NUMERIC) {
609 double.parse(lastToken, (_) {
610 error("Unparseable number");
611 });
612 } else if (cat == SYMBOL) {
613 int binaryPrecendence = BINARY_PRECEDENCE[lastToken];
614 if (binaryPrecendence == null && !UNARY_OPERATORS.contains(lastToken)) {
615 error("Unknown operator");
616 }
617 if (isAssignment(lastToken)) lastCategory = ASSIGNMENT;
618 } else if (cat == ALPHA) {
619 if (OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(lastToken)) {
620 lastCategory = SYMBOL;
621 }
622 }
623 }
624 }
625
626 void expectCategory(int cat) {
627 if (cat != lastCategory) error("Expected ${categoryToString(cat)}");
628 getToken();
629 }
630
631 bool acceptCategory(int cat) {
632 if (cat == lastCategory) {
633 getToken();
634 return true;
635 }
636 return false;
637 }
638
639 void expectSemicolon() {
640 if (acceptSemicolon()) return;
641 error('Expected SEMICOLON');
642 }
643
644 bool acceptSemicolon() {
645 // Accept semicolon or automatically inserted semicolon before close brace.
646 // Miniparser forbids other kinds of semicolon insertion.
647 if (RBRACE == lastCategory) return true;
648 if (NONE == lastCategory) return true; // end of input
649 if (skippedNewline) {
650 error('No automatic semicolon insertion at preceding newline');
651 }
652 return acceptCategory(SEMICOLON);
653 }
654
655 bool acceptString(String string) {
656 if (lastToken == string) {
657 getToken();
658 return true;
659 }
660 return false;
661 }
662
663 void error(message) {
664 throw new MiniJsParserError(this, message);
665 }
666
667 Expression parsePrimary() {
668 String last = lastToken;
669 if (acceptCategory(ALPHA)) {
670 if (last == "true") {
671 return new LiteralBool(true);
672 } else if (last == "false") {
673 return new LiteralBool(false);
674 } else if (last == "null") {
675 return new LiteralNull();
676 } else if (last == "function") {
677 return parseFunctionExpression();
678 } else {
679 return new VariableUse(last);
680 }
681 } else if (acceptCategory(LPAREN)) {
682 Expression expression = parseExpression();
683 expectCategory(RPAREN);
684 return expression;
685 } else if (acceptCategory(STRING)) {
686 return new LiteralString(last);
687 } else if (acceptCategory(NUMERIC)) {
688 return new LiteralNumber(last);
689 } else if (acceptCategory(LBRACE)) {
690 return parseObjectInitializer();
691 } else if (acceptCategory(LSQUARE)) {
692 var values = <ArrayElement>[];
693 if (!acceptCategory(RSQUARE)) {
694 do {
695 values.add(new ArrayElement(values.length, parseAssignment()));
696 } while (acceptCategory(COMMA));
697 expectCategory(RSQUARE);
698 }
699 return new ArrayInitializer(values.length, values);
700 } else if (last != null && last.startsWith("/")) {
701 String regexp = getDelimited(lastPosition);
702 getToken();
703 String flags = lastToken;
704 if (!acceptCategory(ALPHA)) flags = "";
705 Expression expression = new RegExpLiteral(regexp + flags);
706 return expression;
707 } else if (acceptCategory(HASH)) {
708 InterpolatedExpression expression =
709 new InterpolatedExpression(interpolatedValues.length);
710 interpolatedValues.add(expression);
711 return expression;
712 } else {
713 error("Expected primary expression");
714 return null;
715 }
716 }
717
718 Expression parseFunctionExpression() {
719 String last = lastToken;
720 if (acceptCategory(ALPHA)) {
721 String functionName = last;
722 return new NamedFunction(new VariableDeclaration(functionName),
723 parseFun());
724 }
725 return parseFun();
726 }
727
728 Expression parseFun() {
729 List<Parameter> params = <Parameter>[];
730
731 expectCategory(LPAREN);
732 if (!acceptCategory(RPAREN)) {
733 for (;;) {
734 if (acceptCategory(HASH)) {
735 InterpolatedParameter parameter =
736 new InterpolatedParameter(interpolatedValues.length);
737 interpolatedValues.add(parameter);
738 params.add(parameter);
739 } else {
740 String argumentName = lastToken;
741 expectCategory(ALPHA);
742 params.add(new Parameter(argumentName));
743 }
744 if (acceptCategory(COMMA)) continue;
745 expectCategory(RPAREN);
746 break;
747 }
748 }
749
750 expectCategory(LBRACE);
751 Block block = parseBlock();
752 return new Fun(params, block);
753 }
754
755 Expression parseObjectInitializer() {
756 List<Property> properties = <Property>[];
757 for (;;) {
758 if (acceptCategory(RBRACE)) break;
759 // Limited subset: keys are identifiers, no 'get' or 'set' properties.
760 Literal propertyName;
761 String identifier = lastToken;
762 if (acceptCategory(ALPHA)) {
763 propertyName = new LiteralString('"$identifier"');
764 } else if (acceptCategory(STRING)) {
765 propertyName = new LiteralString(identifier);
766 } else if (acceptCategory(SYMBOL)) { // e.g. void
767 propertyName = new LiteralString('"$identifier"');
768 } else if (acceptCategory(HASH)) {
769 InterpolatedLiteral interpolatedLiteral =
770 new InterpolatedLiteral(interpolatedValues.length);
771 interpolatedValues.add(interpolatedLiteral);
772 propertyName = interpolatedLiteral;
773 } else {
774 error('Expected property name');
775 }
776 expectCategory(COLON);
777 Expression value = parseAssignment();
778 properties.add(new Property(propertyName, value));
779 if (acceptCategory(RBRACE)) break;
780 expectCategory(COMMA);
781 }
782 return new ObjectInitializer(properties);
783 }
784
785 Expression parseMember() {
786 Expression receiver = parsePrimary();
787 while (true) {
788 if (acceptCategory(DOT)) {
789 receiver = getDotRhs(receiver);
790 } else if (acceptCategory(LSQUARE)) {
791 Expression inBraces = parseExpression();
792 expectCategory(RSQUARE);
793 receiver = new PropertyAccess(receiver, inBraces);
794 } else {
795 break;
796 }
797 }
798 return receiver;
799 }
800
801 Expression parseCall() {
802 bool constructor = acceptString("new");
803 Expression receiver = parseMember();
804 while (true) {
805 if (acceptCategory(LPAREN)) {
806 final arguments = <Expression>[];
807 if (!acceptCategory(RPAREN)) {
808 while (true) {
809 Expression argument = parseAssignment();
810 arguments.add(argument);
811 if (acceptCategory(RPAREN)) break;
812 expectCategory(COMMA);
813 }
814 }
815 receiver = constructor ?
816 new New(receiver, arguments) :
817 new Call(receiver, arguments);
818 constructor = false;
819 } else if (!constructor && acceptCategory(LSQUARE)) {
820 Expression inBraces = parseExpression();
821 expectCategory(RSQUARE);
822 receiver = new PropertyAccess(receiver, inBraces);
823 } else if (!constructor && acceptCategory(DOT)) {
824 receiver = getDotRhs(receiver);
825 } else {
826 // JS allows new without (), but we don't.
827 if (constructor) error("Parentheses are required for new");
828 break;
829 }
830 }
831 return receiver;
832 }
833
834 Expression getDotRhs(Expression receiver) {
835 if (acceptCategory(HASH)) {
836 InterpolatedSelector property =
837 new InterpolatedSelector(interpolatedValues.length);
838 interpolatedValues.add(property);
839 return new PropertyAccess(receiver, property);
840 }
841 String identifier = lastToken;
842 // In ES5 keywords like delete and continue are allowed as property
843 // names, and the IndexedDB API uses that, so we need to allow it here.
844 if (acceptCategory(SYMBOL)) {
845 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) {
846 error("Expected alphanumeric identifier");
847 }
848 } else {
849 expectCategory(ALPHA);
850 }
851 return new PropertyAccess.field(receiver, identifier);
852 }
853
854 Expression parsePostfix() {
855 Expression expression = parseCall();
856 String operator = lastToken;
857 // JavaScript grammar is:
858 // LeftHandSideExpression [no LineTerminator here] ++
859 if (lastCategory == SYMBOL &&
860 !skippedNewline &&
861 (acceptString("++") || acceptString("--"))) {
862 return new Postfix(operator, expression);
863 }
864 // If we don't accept '++' or '--' due to skippedNewline a newline, no other
865 // part of the parser will accept the token and we will get an error at the
866 // whole expression level.
867 return expression;
868 }
869
870 Expression parseUnaryHigh() {
871 String operator = lastToken;
872 if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
873 (acceptString("++") || acceptString("--"))) {
874 return new Prefix(operator, parsePostfix());
875 }
876 return parsePostfix();
877 }
878
879 Expression parseUnaryLow() {
880 String operator = lastToken;
881 if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
882 operator != "++" && operator != "--") {
883 expectCategory(SYMBOL);
884 return new Prefix(operator, parseUnaryLow());
885 }
886 return parseUnaryHigh();
887 }
888
889 Expression parseBinary(int maxPrecedence) {
890 Expression lhs = parseUnaryLow();
891 int minPrecedence;
892 String lastSymbol;
893 Expression rhs; // This is null first time around.
894 while (true) {
895 String symbol = lastToken;
896 if (lastCategory != SYMBOL ||
897 !BINARY_PRECEDENCE.containsKey(symbol) ||
898 BINARY_PRECEDENCE[symbol] > maxPrecedence) {
899 break;
900 }
901 expectCategory(SYMBOL);
902 if (rhs == null || BINARY_PRECEDENCE[symbol] >= minPrecedence) {
903 if (rhs != null) lhs = new Binary(lastSymbol, lhs, rhs);
904 minPrecedence = BINARY_PRECEDENCE[symbol];
905 rhs = parseUnaryLow();
906 lastSymbol = symbol;
907 } else {
908 Expression higher = parseBinary(BINARY_PRECEDENCE[symbol]);
909 rhs = new Binary(symbol, rhs, higher);
910 }
911 }
912 if (rhs == null) return lhs;
913 return new Binary(lastSymbol, lhs, rhs);
914 }
915
916 Expression parseConditional() {
917 Expression lhs = parseBinary(HIGHEST_PARSE_BINARY_PRECEDENCE);
918 if (!acceptCategory(QUERY)) return lhs;
919 Expression ifTrue = parseAssignment();
920 expectCategory(COLON);
921 Expression ifFalse = parseAssignment();
922 return new Conditional(lhs, ifTrue, ifFalse);
923 }
924
925
926 Expression parseAssignment() {
927 Expression lhs = parseConditional();
928 String assignmentOperator = lastToken;
929 if (acceptCategory(ASSIGNMENT)) {
930 Expression rhs = parseAssignment();
931 if (assignmentOperator == "=") {
932 return new Assignment(lhs, rhs);
933 } else {
934 // Handle +=, -=, etc.
935 String operator =
936 assignmentOperator.substring(0, assignmentOperator.length - 1);
937 return new Assignment.compound(lhs, operator, rhs);
938 }
939 }
940 return lhs;
941 }
942
943 Expression parseExpression() {
944 Expression expression = parseAssignment();
945 while (acceptCategory(COMMA)) {
946 Expression right = parseAssignment();
947 expression = new Binary(',', expression, right);
948 }
949 return expression;
950 }
951
952 VariableDeclarationList parseVariableDeclarationList() {
953 String firstVariable = lastToken;
954 expectCategory(ALPHA);
955 return finishVariableDeclarationList(firstVariable);
956 }
957
958 VariableDeclarationList finishVariableDeclarationList(String firstVariable) {
959 var initialization = [];
960
961 void declare(String variable) {
962 Expression initializer = null;
963 if (acceptString("=")) {
964 initializer = parseAssignment();
965 }
966 var declaration = new VariableDeclaration(variable);
967 initialization.add(new VariableInitialization(declaration, initializer));
968 }
969
970 declare(firstVariable);
971 while (acceptCategory(COMMA)) {
972 String variable = lastToken;
973 expectCategory(ALPHA);
974 declare(variable);
975 }
976 return new VariableDeclarationList(initialization);
977 }
978
979 Expression parseVarDeclarationOrExpression() {
980 if (acceptString("var")) {
981 return parseVariableDeclarationList();
982 } else {
983 return parseExpression();
984 }
985 }
986
987 Expression expression() {
988 Expression expression = parseVarDeclarationOrExpression();
989 if (lastCategory != NONE || position != src.length) {
990 error("Unparsed junk: ${categoryToString(lastCategory)}");
991 }
992 return expression;
993 }
994
995 Statement statement() {
996 Statement statement = parseStatement();
997 if (lastCategory != NONE || position != src.length) {
998 error("Unparsed junk: ${categoryToString(lastCategory)}");
999 }
1000 // TODO(sra): interpolated capture here?
1001 return statement;
1002 }
1003
1004 Block parseBlock() {
1005 List<Statement> statements = <Statement>[];
1006
1007 while (!acceptCategory(RBRACE)) {
1008 Statement statement = parseStatement();
1009 statements.add(statement);
1010 }
1011 return new Block(statements);
1012 }
1013
1014 Statement parseStatement() {
1015 if (acceptCategory(LBRACE)) return parseBlock();
1016
1017 if (acceptCategory(SEMICOLON)) return new EmptyStatement();
1018
1019 if (lastCategory == ALPHA) {
1020 if (acceptString('return')) return parseReturn();
1021
1022 if (acceptString('throw')) return parseThrow();
1023
1024 if (acceptString('break')) {
1025 return parseBreakOrContinue((label) => new Break(label));
1026 }
1027
1028 if (acceptString('continue')) {
1029 return parseBreakOrContinue((label) => new Continue(label));
1030 }
1031
1032 if (acceptString('if')) return parseIfThenElse();
1033
1034 if (acceptString('for')) return parseFor();
1035
1036 if (acceptString('function')) return parseFunctionDeclaration();
1037
1038 if (acceptString('try')) return parseTry();
1039
1040 if (acceptString('var')) {
1041 Expression declarations = parseVariableDeclarationList();
1042 expectSemicolon();
1043 return new ExpressionStatement(declarations);
1044 }
1045
1046 if (lastToken == 'case' ||
1047 lastToken == 'do' ||
1048 lastToken == 'while' ||
1049 lastToken == 'switch' ||
1050 lastToken == 'with') {
1051 error('Not implemented in mini parser');
1052 }
1053 }
1054
1055
1056 // TODO: label: statement
1057
1058 bool checkForInterpolatedStatement = lastCategory == HASH;
1059
1060 Expression expression = parseExpression();
1061 expectSemicolon();
1062
1063 if (checkForInterpolatedStatement) {
1064 // 'Promote' the interpolated expression `#;` to an interpolated
1065 // statement.
1066 if (expression is InterpolatedExpression) {
1067 assert(identical(interpolatedValues.last, expression));
1068 InterpolatedStatement statement =
1069 new InterpolatedStatement(expression.name);
1070 interpolatedValues[interpolatedValues.length - 1] = statement;
1071 return statement;
1072 }
1073 }
1074
1075 return new ExpressionStatement(expression);
1076 }
1077
1078 Statement parseReturn() {
1079 if (acceptSemicolon()) return new Return();
1080 Expression expression = parseExpression();
1081 expectSemicolon();
1082 return new Return(expression);
1083 }
1084
1085 Statement parseThrow() {
1086 if (skippedNewline) error('throw expression must be on same line');
1087 Expression expression = parseExpression();
1088 expectSemicolon();
1089 return new Throw(expression);
1090 }
1091
1092 Statement parseBreakOrContinue(constructor) {
1093 var identifier = lastToken;
1094 if (!skippedNewline && acceptCategory(ALPHA)) {
1095 expectSemicolon();
1096 return constructor(identifier);
1097 }
1098 expectSemicolon();
1099 return constructor(null);
1100 }
1101
1102 Statement parseIfThenElse() {
1103 expectCategory(LPAREN);
1104 Expression condition = parseExpression();
1105 expectCategory(RPAREN);
1106 Statement thenStatement = parseStatement();
1107 if (acceptString('else')) {
1108 // Resolves dangling else by binding 'else' to closest 'if'.
1109 Statement elseStatement = parseStatement();
1110 return new If(condition, thenStatement, elseStatement);
1111 } else {
1112 return new If.noElse(condition, thenStatement);
1113 }
1114 }
1115
1116 Statement parseFor() {
1117 // For-init-condition-increment style loops are fully supported.
1118 //
1119 // Only one for-in variant is currently implemented:
1120 //
1121 // for (var variable in Expression) Statement
1122 //
1123 Statement finishFor(Expression init) {
1124 Expression condition = null;
1125 if (!acceptCategory(SEMICOLON)) {
1126 condition = parseExpression();
1127 expectCategory(SEMICOLON);
1128 }
1129 Expression update = null;
1130 if (!acceptCategory(RPAREN)) {
1131 update = parseExpression();
1132 expectCategory(RPAREN);
1133 }
1134 Statement body = parseStatement();
1135 return new For(init, condition, update, body);
1136 }
1137
1138 expectCategory(LPAREN);
1139 if (acceptCategory(SEMICOLON)) {
1140 return finishFor(null);
1141 }
1142
1143 if (acceptString('var')) {
1144 String identifier = lastToken;
1145 expectCategory(ALPHA);
1146 if (acceptString('in')) {
1147 Expression objectExpression = parseExpression();
1148 expectCategory(RPAREN);
1149 Statement body = parseStatement();
1150 return new ForIn(
1151 new VariableDeclarationList([
1152 new VariableInitialization(
1153 new VariableDeclaration(identifier), null)]),
1154 objectExpression,
1155 body);
1156 }
1157 Expression declarations = finishVariableDeclarationList(identifier);
1158 expectCategory(SEMICOLON);
1159 return finishFor(declarations);
1160 }
1161
1162 Expression init = parseExpression();
1163 expectCategory(SEMICOLON);
1164 return finishFor(init);
1165 }
1166
1167 Statement parseFunctionDeclaration() {
1168 String name = lastToken;
1169 expectCategory(ALPHA);
1170 Expression fun = parseFun();
1171 return new FunctionDeclaration(new VariableDeclaration(name), fun);
1172 }
1173
1174 Statement parseTry() {
1175 expectCategory(LBRACE);
1176 Block body = parseBlock();
1177 String token = lastToken;
1178 Catch catchPart = null;
1179 if (acceptString('catch')) catchPart = parseCatch();
1180 Block finallyPart = null;
1181 if (acceptString('finally')) {
1182 expectCategory(LBRACE);
1183 finallyPart = parseBlock();
1184 } else {
1185 if (catchPart == null) error("expected 'finally'");
1186 }
1187 return new Try(body, catchPart, finallyPart);
1188 }
1189
1190 Catch parseCatch() {
1191 expectCategory(LPAREN);
1192 String identifier = lastToken;
1193 expectCategory(ALPHA);
1194 expectCategory(RPAREN);
1195 expectCategory(LBRACE);
1196 Block body = parseBlock();
1197 return new Catch(new VariableDeclaration(identifier), body);
1198 }
1199 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698