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

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

Issue 931953002: Move js_ast library to its own package under pkg (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/compiler/lib/src/js/characters.dart » ('j') | pkg/js_ast/pubspec.yaml » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 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 // 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) {
299 // Start by escaping the backslashes.
300 String escaped = value.replaceAll('\\', '\\\\');
301 // Do not escape unicode characters and ' because they are allowed in the
302 // string literal anyway.
303 escaped = escaped.replaceAllMapped(new RegExp('\n|"|\b|\t|\v'), (match) {
304 switch (match.group(0)) {
305 case "\n" : return r"\n";
306 case "\"" : return r'\"';
307 case "\b" : return r"\b";
308 case "\t" : return r"\t";
309 case "\f" : return r"\f";
310 case "\v" : return r"\v";
311 }
312 });
313 LiteralString result = string(escaped);
314 // We don't escape ' under the assumption that the string is wrapped
315 // into ". Verify that assumption.
316 assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0));
317 return result;
318 }
319
320 /// Creates a literal js string from [value].
321 ///
322 /// Note that this function only puts quotes around [value]. It does not do
323 /// any escaping, so use only when you can guarantee that [value] does not
324 /// contain newlines or backslashes. For escaping the string use
325 /// [escapedString].
326 LiteralString string(String value) => new LiteralString('"$value"');
327
328 LiteralNumber number(num value) => new LiteralNumber('$value');
329
330 LiteralBool boolean(bool value) => new LiteralBool(value);
331
332 ArrayInitializer numArray(Iterable<int> list) =>
333 new ArrayInitializer(list.map(number).toList());
334
335 ArrayInitializer stringArray(Iterable<String> list) =>
336 new ArrayInitializer(list.map(string).toList());
337
338 Comment comment(String text) => new Comment(text);
339
340 Call propertyCall(Expression receiver,
341 String fieldName,
342 List<Expression> arguments) {
343 return new Call(new PropertyAccess.field(receiver, fieldName), arguments);
344 }
345 }
346
347 LiteralString string(String value) => js.string(value);
348 LiteralNumber number(num value) => js.number(value);
349 ArrayInitializer numArray(Iterable<int> list) => js.numArray(list);
350 ArrayInitializer stringArray(Iterable<String> list) => js.stringArray(list);
351 Call propertyCall(Expression receiver,
352 String fieldName,
353 List<Expression> arguments) {
354 return js.propertyCall(receiver, fieldName, arguments);
355 }
356
357 class MiniJsParserError {
358 MiniJsParserError(this.parser, this.message) { }
359
360 final MiniJsParser parser;
361 final String message;
362
363 String toString() {
364 int pos = parser.lastPosition;
365
366 // Discard lines following the line containing lastPosition.
367 String src = parser.src;
368 int newlinePos = src.indexOf('\n', pos);
369 if (newlinePos >= pos) src = src.substring(0, newlinePos);
370
371 // Extract the prefix of the error line before lastPosition.
372 String line = src;
373 int lastLineStart = line.lastIndexOf('\n');
374 if (lastLineStart >= 0) line = line.substring(lastLineStart + 1);
375 String prefix = line.substring(0, pos - (src.length - line.length));
376
377 // Replace non-tabs with spaces, giving a print indent that matches the text
378 // for tabbing.
379 String spaces = prefix.replaceAll(new RegExp(r'[^\t]'), ' ');
380 return 'Error in MiniJsParser:\n${src}\n$spaces^\n$spaces$message\n';
381 }
382 }
383
384 /// Mini JavaScript parser for tiny snippets of code that we want to make into
385 /// AST nodes. Handles:
386 /// * identifiers.
387 /// * dot access.
388 /// * method calls.
389 /// * [] access.
390 /// * array, string, regexp, boolean, null and numeric literals.
391 /// * most operators.
392 /// * brackets.
393 /// * var declarations.
394 /// * operator precedence.
395 /// * anonymous funtions and named function expressions and declarations.
396 /// Notable things it can't do yet include:
397 /// * some statements are still missing (do-while, while, switch).
398 ///
399 /// It's a fairly standard recursive descent parser.
400 ///
401 /// Literal strings are passed through to the final JS source code unchanged,
402 /// including the choice of surrounding quotes, so if you parse
403 /// r'var x = "foo\n\"bar\""' you will end up with
404 /// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not
405 /// allowed in string and regexp literals because the machinery for checking
406 /// their correctness is rather involved.
407 class MiniJsParser {
408 MiniJsParser(this.src)
409 : lastCategory = NONE,
410 lastToken = null,
411 lastPosition = 0,
412 position = 0 {
413 getToken();
414 }
415
416 int lastCategory = NONE;
417 String lastToken = null;
418 int lastPosition = 0;
419 int position = 0;
420 bool skippedNewline = false; // skipped newline in last getToken?
421 final String src;
422
423 final List<InterpolatedNode> interpolatedValues = <InterpolatedNode>[];
424 bool get hasNamedHoles =>
425 interpolatedValues.isNotEmpty && interpolatedValues.first.isNamed;
426 bool get hasPositionalHoles =>
427 interpolatedValues.isNotEmpty && interpolatedValues.first.isPositional;
428
429 static const NONE = -1;
430 static const ALPHA = 0;
431 static const NUMERIC = 1;
432 static const STRING = 2;
433 static const SYMBOL = 3;
434 static const ASSIGNMENT = 4;
435 static const DOT = 5;
436 static const LPAREN = 6;
437 static const RPAREN = 7;
438 static const LBRACE = 8;
439 static const RBRACE = 9;
440 static const LSQUARE = 10;
441 static const RSQUARE = 11;
442 static const COMMA = 12;
443 static const QUERY = 13;
444 static const COLON = 14;
445 static const SEMICOLON = 15;
446 static const HASH = 16;
447 static const WHITESPACE = 17;
448 static const OTHER = 18;
449
450 // Make sure that ]] is two symbols.
451 bool singleCharCategory(int category) => category >= DOT;
452
453 static String categoryToString(int cat) {
454 switch (cat) {
455 case NONE: return "NONE";
456 case ALPHA: return "ALPHA";
457 case NUMERIC: return "NUMERIC";
458 case SYMBOL: return "SYMBOL";
459 case ASSIGNMENT: return "ASSIGNMENT";
460 case DOT: return "DOT";
461 case LPAREN: return "LPAREN";
462 case RPAREN: return "RPAREN";
463 case LBRACE: return "LBRACE";
464 case RBRACE: return "RBRACE";
465 case LSQUARE: return "LSQUARE";
466 case RSQUARE: return "RSQUARE";
467 case STRING: return "STRING";
468 case COMMA: return "COMMA";
469 case QUERY: return "QUERY";
470 case COLON: return "COLON";
471 case SEMICOLON: return "SEMICOLON";
472 case HASH: return "HASH";
473 case WHITESPACE: return "WHITESPACE";
474 case OTHER: return "OTHER";
475 }
476 return "Unknown: $cat";
477 }
478
479 static const CATEGORIES = const <int>[
480 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
481 OTHER, WHITESPACE, WHITESPACE, OTHER, OTHER, WHITESPACE, // 8-13
482 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 14-21
483 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 22-29
484 OTHER, OTHER, WHITESPACE, // 30-32
485 SYMBOL, OTHER, HASH, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&´
486 LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./
487 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234
488 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789
489 COLON, SEMICOLON, SYMBOL, SYMBOL, SYMBOL, QUERY, OTHER, // :;<=>?@
490 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ABCDEFGH
491 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // IJKLMNOP
492 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // QRSTUVWX
493 ALPHA, ALPHA, LSQUARE, OTHER, RSQUARE, SYMBOL, ALPHA, OTHER, // YZ[\]^_'
494 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // abcdefgh
495 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ijklmnop
496 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // qrstuvwx
497 ALPHA, ALPHA, LBRACE, SYMBOL, RBRACE, SYMBOL]; // yz{|}~
498
499 // This must be a >= the highest precedence number handled by parseBinary.
500 static var HIGHEST_PARSE_BINARY_PRECEDENCE = 16;
501 static bool isAssignment(String symbol) => BINARY_PRECEDENCE[symbol] == 17;
502
503 // From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operator s/Operator_Precedence
504 static final BINARY_PRECEDENCE = {
505 '+=': 17, '-=': 17, '*=': 17, '/=': 17, '%=': 17, '^=': 17, '|=': 17,
506 '&=': 17, '<<=': 17, '>>=': 17, '>>>=': 17, '=': 17,
507 '||': 14,
508 '&&': 13,
509 '|': 12,
510 '^': 11,
511 '&': 10,
512 '!=': 9, '==': 9, '!==': 9, '===': 9,
513 '<': 8, '<=': 8, '>=': 8, '>': 8, 'in': 8, 'instanceof': 8,
514 '<<': 7, '>>': 7, '>>>': 7,
515 '+': 6, '-': 6,
516 '*': 5, '/': 5, '%': 5
517 };
518 static final UNARY_OPERATORS =
519 ['++', '--', '+', '-', '~', '!', 'typeof', 'void', 'delete', 'await']
520 .toSet();
521
522 static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
523 ['typeof', 'void', 'delete', 'in', 'instanceof', 'await'].toSet();
524
525 static int category(int code) {
526 if (code >= CATEGORIES.length) return OTHER;
527 return CATEGORIES[code];
528 }
529
530 String getDelimited(int startPosition) {
531 position = startPosition;
532 int delimiter = src.codeUnitAt(startPosition);
533 int currentCode;
534 do {
535 position++;
536 if (position >= src.length) error("Unterminated literal");
537 currentCode = src.codeUnitAt(position);
538 if (currentCode == charCodes.$LF) error("Unterminated literal");
539 if (currentCode == charCodes.$BACKSLASH) {
540 if (++position >= src.length) error("Unterminated literal");
541 int escaped = src.codeUnitAt(position);
542 if (escaped == charCodes.$x || escaped == charCodes.$X ||
543 escaped == charCodes.$u || escaped == charCodes.$U ||
544 category(escaped) == NUMERIC) {
545 error('Numeric and hex escapes are not allowed in literals');
546 }
547 }
548 } while (currentCode != delimiter);
549 position++;
550 return src.substring(lastPosition, position);
551 }
552
553 void getToken() {
554 skippedNewline = false;
555 for (;;) {
556 if (position >= src.length) break;
557 int code = src.codeUnitAt(position);
558 // Skip '//' and '/*' style comments.
559 if (code == charCodes.$SLASH &&
560 position + 1 < src.length) {
561 if (src.codeUnitAt(position + 1) == charCodes.$SLASH) {
562 int nextPosition = src.indexOf('\n', position);
563 if (nextPosition == -1) nextPosition = src.length;
564 position = nextPosition;
565 continue;
566 } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) {
567 int nextPosition = src.indexOf('*/', position + 2);
568 if (nextPosition == -1) error('Unterminated comment');
569 position = nextPosition + 2;
570 continue;
571 }
572 }
573 if (category(code) != WHITESPACE) break;
574 if (code == charCodes.$LF) skippedNewline = true;
575 ++position;
576 }
577
578 if (position == src.length) {
579 lastCategory = NONE;
580 lastToken = null;
581 lastPosition = position;
582 return;
583 }
584 int code = src.codeUnitAt(position);
585 lastPosition = position;
586 if (code == charCodes.$SQ || code == charCodes.$DQ) {
587 // String literal.
588 lastCategory = STRING;
589 lastToken = getDelimited(position);
590 } else if (code == charCodes.$0 &&
591 position + 2 < src.length &&
592 src.codeUnitAt(position + 1) == charCodes.$x) {
593 // Hex literal.
594 for (position += 2; position < src.length; position++) {
595 int cat = category(src.codeUnitAt(position));
596 if (cat != NUMERIC && cat != ALPHA) break;
597 }
598 lastCategory = NUMERIC;
599 lastToken = src.substring(lastPosition, position);
600 int.parse(lastToken, onError: (_) {
601 error("Unparseable number");
602 });
603 } else if (code == charCodes.$SLASH) {
604 // Tokens that start with / are special due to regexp literals.
605 lastCategory = SYMBOL;
606 position++;
607 if (position < src.length && src.codeUnitAt(position) == charCodes.$EQ) {
608 position++;
609 }
610 lastToken = src.substring(lastPosition, position);
611 } else {
612 // All other tokens handled here.
613 int cat = category(src.codeUnitAt(position));
614 int newCat;
615 do {
616 position++;
617 if (position == src.length) break;
618 int code = src.codeUnitAt(position);
619 // Special code to disallow ! and / in non-first position in token, so
620 // that !! parses as two tokens and != parses as one, while =/ parses
621 // as a an equals token followed by a regexp literal start.
622 newCat = (code == charCodes.$BANG || code == charCodes.$SLASH)
623 ? NONE
624 : category(code);
625 } while (!singleCharCategory(cat) &&
626 (cat == newCat ||
627 (cat == ALPHA && newCat == NUMERIC) || // eg. level42.
628 (cat == NUMERIC && newCat == DOT))); // eg. 3.1415
629 lastCategory = cat;
630 lastToken = src.substring(lastPosition, position);
631 if (cat == NUMERIC) {
632 double.parse(lastToken, (_) {
633 error("Unparseable number");
634 });
635 } else if (cat == SYMBOL) {
636 int binaryPrecendence = BINARY_PRECEDENCE[lastToken];
637 if (binaryPrecendence == null && !UNARY_OPERATORS.contains(lastToken)) {
638 error("Unknown operator");
639 }
640 if (isAssignment(lastToken)) lastCategory = ASSIGNMENT;
641 } else if (cat == ALPHA) {
642 if (OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(lastToken)) {
643 lastCategory = SYMBOL;
644 }
645 }
646 }
647 }
648
649 void expectCategory(int cat) {
650 if (cat != lastCategory) error("Expected ${categoryToString(cat)}");
651 getToken();
652 }
653
654 bool acceptCategory(int cat) {
655 if (cat == lastCategory) {
656 getToken();
657 return true;
658 }
659 return false;
660 }
661
662 void expectSemicolon() {
663 if (acceptSemicolon()) return;
664 error('Expected SEMICOLON');
665 }
666
667 bool acceptSemicolon() {
668 // Accept semicolon or automatically inserted semicolon before close brace.
669 // Miniparser forbids other kinds of semicolon insertion.
670 if (RBRACE == lastCategory) return true;
671 if (NONE == lastCategory) return true; // end of input
672 if (skippedNewline) {
673 error('No automatic semicolon insertion at preceding newline');
674 }
675 return acceptCategory(SEMICOLON);
676 }
677
678 bool acceptString(String string) {
679 if (lastToken == string) {
680 getToken();
681 return true;
682 }
683 return false;
684 }
685
686 void error(message) {
687 throw new MiniJsParserError(this, message);
688 }
689
690 /// Returns either the name for the hole, or its integer position.
691 parseHash() {
692 String holeName = lastToken;
693 if (acceptCategory(ALPHA)) {
694 // Named hole. Example: 'function #funName() { ... }'
695 if (hasPositionalHoles) {
696 error('Holes must all be positional or named. $holeName');
697 }
698 return holeName;
699 } else {
700 if (hasNamedHoles) {
701 error('Holes must all be positional or named. $holeName');
702 }
703 int position = interpolatedValues.length;
704 return position;
705 }
706 }
707
708 Expression parsePrimary() {
709 String last = lastToken;
710 if (acceptCategory(ALPHA)) {
711 if (last == "true") {
712 return new LiteralBool(true);
713 } else if (last == "false") {
714 return new LiteralBool(false);
715 } else if (last == "null") {
716 return new LiteralNull();
717 } else if (last == "function") {
718 return parseFunctionExpression();
719 } else if (last == "this") {
720 return new This();
721 } else {
722 return new VariableUse(last);
723 }
724 } else if (acceptCategory(LPAREN)) {
725 Expression expression = parseExpression();
726 expectCategory(RPAREN);
727 return expression;
728 } else if (acceptCategory(STRING)) {
729 return new LiteralString(last);
730 } else if (acceptCategory(NUMERIC)) {
731 return new LiteralNumber(last);
732 } else if (acceptCategory(LBRACE)) {
733 return parseObjectInitializer();
734 } else if (acceptCategory(LSQUARE)) {
735 var values = <Expression>[];
736
737 while (true) {
738 if (acceptCategory(COMMA)) {
739 values.add(new ArrayHole());
740 continue;
741 }
742 if (acceptCategory(RSQUARE)) break;
743 values.add(parseAssignment());
744 if (acceptCategory(RSQUARE)) break;
745 expectCategory(COMMA);
746 }
747 return new ArrayInitializer(values);
748 } else if (last != null && last.startsWith("/")) {
749 String regexp = getDelimited(lastPosition);
750 getToken();
751 String flags = lastToken;
752 if (!acceptCategory(ALPHA)) flags = "";
753 Expression expression = new RegExpLiteral(regexp + flags);
754 return expression;
755 } else if (acceptCategory(HASH)) {
756 var nameOrPosition = parseHash();
757 InterpolatedExpression expression =
758 new InterpolatedExpression(nameOrPosition);
759 interpolatedValues.add(expression);
760 return expression;
761 } else {
762 error("Expected primary expression");
763 return null;
764 }
765 }
766
767 Expression parseFunctionExpression() {
768 String last = lastToken;
769 if (acceptCategory(ALPHA)) {
770 String functionName = last;
771 return new NamedFunction(new VariableDeclaration(functionName),
772 parseFun());
773 }
774 return parseFun();
775 }
776
777 Expression parseFun() {
778 List<Parameter> params = <Parameter>[];
779
780 expectCategory(LPAREN);
781 if (!acceptCategory(RPAREN)) {
782 for (;;) {
783 if (acceptCategory(HASH)) {
784 var nameOrPosition = parseHash();
785 InterpolatedParameter parameter =
786 new InterpolatedParameter(nameOrPosition);
787 interpolatedValues.add(parameter);
788 params.add(parameter);
789 } else {
790 String argumentName = lastToken;
791 expectCategory(ALPHA);
792 params.add(new Parameter(argumentName));
793 }
794 if (acceptCategory(COMMA)) continue;
795 expectCategory(RPAREN);
796 break;
797 }
798 }
799 AsyncModifier asyncModifier;
800 if (acceptString('async')) {
801 if (acceptString('*')) {
802 asyncModifier = const AsyncModifier.asyncStar();
803 } else {
804 asyncModifier = const AsyncModifier.async();
805 }
806 } else if (acceptString('sync')) {
807 if (!acceptString('*')) error("Only sync* is valid - sync is implied");
808 asyncModifier = const AsyncModifier.syncStar();
809 } else {
810 asyncModifier = const AsyncModifier.sync();
811 }
812 expectCategory(LBRACE);
813 Block block = parseBlock();
814 return new Fun(params, block, asyncModifier: asyncModifier);
815 }
816
817 Expression parseObjectInitializer() {
818 List<Property> properties = <Property>[];
819 for (;;) {
820 if (acceptCategory(RBRACE)) break;
821 // Limited subset: keys are identifiers, no 'get' or 'set' properties.
822 Literal propertyName;
823 String identifier = lastToken;
824 if (acceptCategory(ALPHA)) {
825 propertyName = new LiteralString('"$identifier"');
826 } else if (acceptCategory(STRING)) {
827 propertyName = new LiteralString(identifier);
828 } else if (acceptCategory(SYMBOL)) { // e.g. void
829 propertyName = new LiteralString('"$identifier"');
830 } else if (acceptCategory(HASH)) {
831 var nameOrPosition = parseHash();
832 InterpolatedLiteral interpolatedLiteral =
833 new InterpolatedLiteral(nameOrPosition);
834 interpolatedValues.add(interpolatedLiteral);
835 propertyName = interpolatedLiteral;
836 } else {
837 error('Expected property name');
838 }
839 expectCategory(COLON);
840 Expression value = parseAssignment();
841 properties.add(new Property(propertyName, value));
842 if (acceptCategory(RBRACE)) break;
843 expectCategory(COMMA);
844 }
845 return new ObjectInitializer(properties);
846 }
847
848 Expression parseMember() {
849 Expression receiver = parsePrimary();
850 while (true) {
851 if (acceptCategory(DOT)) {
852 receiver = getDotRhs(receiver);
853 } else if (acceptCategory(LSQUARE)) {
854 Expression inBraces = parseExpression();
855 expectCategory(RSQUARE);
856 receiver = new PropertyAccess(receiver, inBraces);
857 } else {
858 break;
859 }
860 }
861 return receiver;
862 }
863
864 Expression parseCall() {
865 bool constructor = acceptString("new");
866 Expression receiver = parseMember();
867 while (true) {
868 if (acceptCategory(LPAREN)) {
869 final arguments = <Expression>[];
870 if (!acceptCategory(RPAREN)) {
871 while (true) {
872 Expression argument = parseAssignment();
873 arguments.add(argument);
874 if (acceptCategory(RPAREN)) break;
875 expectCategory(COMMA);
876 }
877 }
878 receiver = constructor ?
879 new New(receiver, arguments) :
880 new Call(receiver, arguments);
881 constructor = false;
882 } else if (!constructor && acceptCategory(LSQUARE)) {
883 Expression inBraces = parseExpression();
884 expectCategory(RSQUARE);
885 receiver = new PropertyAccess(receiver, inBraces);
886 } else if (!constructor && acceptCategory(DOT)) {
887 receiver = getDotRhs(receiver);
888 } else {
889 // JS allows new without (), but we don't.
890 if (constructor) error("Parentheses are required for new");
891 break;
892 }
893 }
894 return receiver;
895 }
896
897 Expression getDotRhs(Expression receiver) {
898 if (acceptCategory(HASH)) {
899 var nameOrPosition = parseHash();
900 InterpolatedSelector property = new InterpolatedSelector(nameOrPosition);
901 interpolatedValues.add(property);
902 return new PropertyAccess(receiver, property);
903 }
904 String identifier = lastToken;
905 // In ES5 keywords like delete and continue are allowed as property
906 // names, and the IndexedDB API uses that, so we need to allow it here.
907 if (acceptCategory(SYMBOL)) {
908 if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) {
909 error("Expected alphanumeric identifier");
910 }
911 } else {
912 expectCategory(ALPHA);
913 }
914 return new PropertyAccess.field(receiver, identifier);
915 }
916
917 Expression parsePostfix() {
918 Expression expression = parseCall();
919 String operator = lastToken;
920 // JavaScript grammar is:
921 // LeftHandSideExpression [no LineTerminator here] ++
922 if (lastCategory == SYMBOL &&
923 !skippedNewline &&
924 (acceptString("++") || acceptString("--"))) {
925 return new Postfix(operator, expression);
926 }
927 // If we don't accept '++' or '--' due to skippedNewline a newline, no other
928 // part of the parser will accept the token and we will get an error at the
929 // whole expression level.
930 return expression;
931 }
932
933 Expression parseUnaryHigh() {
934 String operator = lastToken;
935 if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
936 (acceptString("++") || acceptString("--") || acceptString('await'))) {
937 if (operator == "await") return new Await(parsePostfix());
938 return new Prefix(operator, parsePostfix());
939 }
940 return parsePostfix();
941 }
942
943 Expression parseUnaryLow() {
944 String operator = lastToken;
945 if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
946 operator != "++" && operator != "--") {
947 expectCategory(SYMBOL);
948 if (operator == "await") return new Await(parsePostfix());
949 return new Prefix(operator, parseUnaryLow());
950 }
951 return parseUnaryHigh();
952 }
953
954 Expression parseBinary(int maxPrecedence) {
955 Expression lhs = parseUnaryLow();
956 int minPrecedence;
957 String lastSymbol;
958 Expression rhs; // This is null first time around.
959 while (true) {
960 String symbol = lastToken;
961 if (lastCategory != SYMBOL ||
962 !BINARY_PRECEDENCE.containsKey(symbol) ||
963 BINARY_PRECEDENCE[symbol] > maxPrecedence) {
964 break;
965 }
966 expectCategory(SYMBOL);
967 if (rhs == null || BINARY_PRECEDENCE[symbol] >= minPrecedence) {
968 if (rhs != null) lhs = new Binary(lastSymbol, lhs, rhs);
969 minPrecedence = BINARY_PRECEDENCE[symbol];
970 rhs = parseUnaryLow();
971 lastSymbol = symbol;
972 } else {
973 Expression higher = parseBinary(BINARY_PRECEDENCE[symbol]);
974 rhs = new Binary(symbol, rhs, higher);
975 }
976 }
977 if (rhs == null) return lhs;
978 return new Binary(lastSymbol, lhs, rhs);
979 }
980
981 Expression parseConditional() {
982 Expression lhs = parseBinary(HIGHEST_PARSE_BINARY_PRECEDENCE);
983 if (!acceptCategory(QUERY)) return lhs;
984 Expression ifTrue = parseAssignment();
985 expectCategory(COLON);
986 Expression ifFalse = parseAssignment();
987 return new Conditional(lhs, ifTrue, ifFalse);
988 }
989
990
991 Expression parseAssignment() {
992 Expression lhs = parseConditional();
993 String assignmentOperator = lastToken;
994 if (acceptCategory(ASSIGNMENT)) {
995 Expression rhs = parseAssignment();
996 if (assignmentOperator == "=") {
997 return new Assignment(lhs, rhs);
998 } else {
999 // Handle +=, -=, etc.
1000 String operator =
1001 assignmentOperator.substring(0, assignmentOperator.length - 1);
1002 return new Assignment.compound(lhs, operator, rhs);
1003 }
1004 }
1005 return lhs;
1006 }
1007
1008 Expression parseExpression() {
1009 Expression expression = parseAssignment();
1010 while (acceptCategory(COMMA)) {
1011 Expression right = parseAssignment();
1012 expression = new Binary(',', expression, right);
1013 }
1014 return expression;
1015 }
1016
1017 VariableDeclarationList parseVariableDeclarationList() {
1018 String firstVariable = lastToken;
1019 expectCategory(ALPHA);
1020 return finishVariableDeclarationList(firstVariable);
1021 }
1022
1023 VariableDeclarationList finishVariableDeclarationList(String firstVariable) {
1024 var initialization = [];
1025
1026 void declare(String variable) {
1027 Expression initializer = null;
1028 if (acceptString("=")) {
1029 initializer = parseAssignment();
1030 }
1031 var declaration = new VariableDeclaration(variable);
1032 initialization.add(new VariableInitialization(declaration, initializer));
1033 }
1034
1035 declare(firstVariable);
1036 while (acceptCategory(COMMA)) {
1037 String variable = lastToken;
1038 expectCategory(ALPHA);
1039 declare(variable);
1040 }
1041 return new VariableDeclarationList(initialization);
1042 }
1043
1044 Expression parseVarDeclarationOrExpression() {
1045 if (acceptString("var")) {
1046 return parseVariableDeclarationList();
1047 } else {
1048 return parseExpression();
1049 }
1050 }
1051
1052 Expression expression() {
1053 Expression expression = parseVarDeclarationOrExpression();
1054 if (lastCategory != NONE || position != src.length) {
1055 error("Unparsed junk: ${categoryToString(lastCategory)}");
1056 }
1057 return expression;
1058 }
1059
1060 Statement statement() {
1061 Statement statement = parseStatement();
1062 if (lastCategory != NONE || position != src.length) {
1063 error("Unparsed junk: ${categoryToString(lastCategory)}");
1064 }
1065 // TODO(sra): interpolated capture here?
1066 return statement;
1067 }
1068
1069 Block parseBlock() {
1070 List<Statement> statements = <Statement>[];
1071
1072 while (!acceptCategory(RBRACE)) {
1073 Statement statement = parseStatement();
1074 statements.add(statement);
1075 }
1076 return new Block(statements);
1077 }
1078
1079 Statement parseStatement() {
1080 if (acceptCategory(LBRACE)) return parseBlock();
1081
1082 if (acceptCategory(SEMICOLON)) return new EmptyStatement();
1083
1084 if (lastCategory == ALPHA) {
1085 if (acceptString('return')) return parseReturn();
1086
1087 if (acceptString('throw')) return parseThrow();
1088
1089 if (acceptString('break')) {
1090 return parseBreakOrContinue((label) => new Break(label));
1091 }
1092
1093 if (acceptString('continue')) {
1094 return parseBreakOrContinue((label) => new Continue(label));
1095 }
1096
1097 if (acceptString('if')) return parseIfThenElse();
1098
1099 if (acceptString('for')) return parseFor();
1100
1101 if (acceptString('function')) return parseFunctionDeclaration();
1102
1103 if (acceptString('try')) return parseTry();
1104
1105 if (acceptString('var')) {
1106 Expression declarations = parseVariableDeclarationList();
1107 expectSemicolon();
1108 return new ExpressionStatement(declarations);
1109 }
1110
1111 if (acceptString('while')) return parseWhile();
1112
1113 if (acceptString('do')) return parseDo();
1114
1115 if (acceptString('switch')) return parseSwitch();
1116
1117 if (lastToken == 'case') error("Case outside switch.");
1118
1119 if (lastToken == 'default') error("Default outside switch.");
1120
1121 if (lastToken == 'yield') return parseYield();
1122
1123 if (lastToken == 'with') {
1124 error('Not implemented in mini parser');
1125 }
1126
1127 }
1128
1129 bool checkForInterpolatedStatement = lastCategory == HASH;
1130
1131 Expression expression = parseExpression();
1132
1133 if (expression is VariableUse && acceptCategory(COLON)) {
1134 return new LabeledStatement(expression.name, parseStatement());
1135 }
1136
1137 expectSemicolon();
1138
1139 if (checkForInterpolatedStatement) {
1140 // 'Promote' the interpolated expression `#;` to an interpolated
1141 // statement.
1142 if (expression is InterpolatedExpression) {
1143 assert(identical(interpolatedValues.last, expression));
1144 InterpolatedStatement statement =
1145 new InterpolatedStatement(expression.nameOrPosition);
1146 interpolatedValues[interpolatedValues.length - 1] = statement;
1147 return statement;
1148 }
1149 }
1150
1151 return new ExpressionStatement(expression);
1152 }
1153
1154 Statement parseReturn() {
1155 if (acceptSemicolon()) return new Return();
1156 Expression expression = parseExpression();
1157 expectSemicolon();
1158 return new Return(expression);
1159 }
1160
1161 Statement parseYield() {
1162 bool hasStar = acceptString('*');
1163 Expression expression = parseExpression();
1164 expectSemicolon();
1165 return new DartYield(expression, hasStar);
1166 }
1167
1168 Statement parseThrow() {
1169 if (skippedNewline) error('throw expression must be on same line');
1170 Expression expression = parseExpression();
1171 expectSemicolon();
1172 return new Throw(expression);
1173 }
1174
1175 Statement parseBreakOrContinue(constructor) {
1176 var identifier = lastToken;
1177 if (!skippedNewline && acceptCategory(ALPHA)) {
1178 expectSemicolon();
1179 return constructor(identifier);
1180 }
1181 expectSemicolon();
1182 return constructor(null);
1183 }
1184
1185 Statement parseIfThenElse() {
1186 expectCategory(LPAREN);
1187 Expression condition = parseExpression();
1188 expectCategory(RPAREN);
1189 Statement thenStatement = parseStatement();
1190 if (acceptString('else')) {
1191 // Resolves dangling else by binding 'else' to closest 'if'.
1192 Statement elseStatement = parseStatement();
1193 return new If(condition, thenStatement, elseStatement);
1194 } else {
1195 return new If.noElse(condition, thenStatement);
1196 }
1197 }
1198
1199 Statement parseFor() {
1200 // For-init-condition-increment style loops are fully supported.
1201 //
1202 // Only one for-in variant is currently implemented:
1203 //
1204 // for (var variable in Expression) Statement
1205 //
1206 Statement finishFor(Expression init) {
1207 Expression condition = null;
1208 if (!acceptCategory(SEMICOLON)) {
1209 condition = parseExpression();
1210 expectCategory(SEMICOLON);
1211 }
1212 Expression update = null;
1213 if (!acceptCategory(RPAREN)) {
1214 update = parseExpression();
1215 expectCategory(RPAREN);
1216 }
1217 Statement body = parseStatement();
1218 return new For(init, condition, update, body);
1219 }
1220
1221 expectCategory(LPAREN);
1222 if (acceptCategory(SEMICOLON)) {
1223 return finishFor(null);
1224 }
1225
1226 if (acceptString('var')) {
1227 String identifier = lastToken;
1228 expectCategory(ALPHA);
1229 if (acceptString('in')) {
1230 Expression objectExpression = parseExpression();
1231 expectCategory(RPAREN);
1232 Statement body = parseStatement();
1233 return new ForIn(
1234 new VariableDeclarationList([
1235 new VariableInitialization(
1236 new VariableDeclaration(identifier), null)]),
1237 objectExpression,
1238 body);
1239 }
1240 Expression declarations = finishVariableDeclarationList(identifier);
1241 expectCategory(SEMICOLON);
1242 return finishFor(declarations);
1243 }
1244
1245 Expression init = parseExpression();
1246 expectCategory(SEMICOLON);
1247 return finishFor(init);
1248 }
1249
1250 Statement parseFunctionDeclaration() {
1251 String name = lastToken;
1252 expectCategory(ALPHA);
1253 Expression fun = parseFun();
1254 return new FunctionDeclaration(new VariableDeclaration(name), fun);
1255 }
1256
1257 Statement parseTry() {
1258 expectCategory(LBRACE);
1259 Block body = parseBlock();
1260 String token = lastToken;
1261 Catch catchPart = null;
1262 if (acceptString('catch')) catchPart = parseCatch();
1263 Block finallyPart = null;
1264 if (acceptString('finally')) {
1265 expectCategory(LBRACE);
1266 finallyPart = parseBlock();
1267 } else {
1268 if (catchPart == null) error("expected 'finally'");
1269 }
1270 return new Try(body, catchPart, finallyPart);
1271 }
1272
1273 SwitchClause parseSwitchClause() {
1274 Expression expression = null;
1275 if (acceptString('case')) {
1276 expression = parseExpression();
1277 expectCategory(COLON);
1278 } else {
1279 if (!acceptString('default')) {
1280 error('expected case or default');
1281 }
1282 expectCategory(COLON);
1283 }
1284 List statements = new List<Statement>();
1285 while (lastCategory != RBRACE &&
1286 lastToken != 'case' &&
1287 lastToken != 'default') {
1288 statements.add(parseStatement());
1289 }
1290 return expression == null
1291 ? new Default(new Block(statements))
1292 : new Case(expression, new Block(statements));
1293 }
1294
1295 Statement parseWhile() {
1296 expectCategory(LPAREN);
1297 Expression condition = parseExpression();
1298 expectCategory(RPAREN);
1299 Statement body = parseStatement();
1300 return new While(condition, body);
1301 }
1302
1303 Statement parseDo() {
1304 Statement body = parseStatement();
1305 if (lastToken != "while") error("Missing while after do body.");
1306 getToken();
1307 expectCategory(LPAREN);
1308 Expression condition = parseExpression();
1309 expectCategory(RPAREN);
1310 expectSemicolon();
1311 return new Do(body, condition);
1312 }
1313
1314 Statement parseSwitch() {
1315 expectCategory(LPAREN);
1316 Expression key = parseExpression();
1317 expectCategory(RPAREN);
1318 expectCategory(LBRACE);
1319 List<SwitchClause> clauses = new List<SwitchClause>();
1320 while(lastCategory != RBRACE) {
1321 clauses.add(parseSwitchClause());
1322 }
1323 expectCategory(RBRACE);
1324 return new Switch(key, clauses);
1325 }
1326
1327 Catch parseCatch() {
1328 expectCategory(LPAREN);
1329 String identifier = lastToken;
1330 expectCategory(ALPHA);
1331 expectCategory(RPAREN);
1332 expectCategory(LBRACE);
1333 Block body = parseBlock();
1334 return new Catch(new VariableDeclaration(identifier), body);
1335 }
1336 }
OLDNEW
« no previous file with comments | « no previous file | pkg/compiler/lib/src/js/characters.dart » ('j') | pkg/js_ast/pubspec.yaml » ('J')

Powered by Google App Engine
This is Rietveld 408576698