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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/src/dart_sdk.dart ('k') | lib/src/js/characters.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/src/js/builder.dart
diff --git a/lib/src/js/builder.dart b/lib/src/js/builder.dart
deleted file mode 100644
index 5037a3f0ef142003125f4e83838acf2d5f2f8dba..0000000000000000000000000000000000000000
--- a/lib/src/js/builder.dart
+++ /dev/null
@@ -1,1651 +0,0 @@
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-// Utilities for building JS ASTs at runtime. Contains a builder class
-// and a parser that parses part of the language.
-
-part of js_ast;
-
-
-/**
- * Global template manager. We should aim to have a fixed number of
- * templates. This implies that we do not use js('xxx') to parse text that is
- * constructed from values that depend on names in the Dart program.
- *
- * TODO(sra): Find the remaining places where js('xxx') used to parse an
- * unbounded number of expression, or institute a cache policy.
- */
-TemplateManager templateManager = new TemplateManager();
-
-
-/**
-
-[js] is a singleton instace of JsBuilder. JsBuilder is a set of conveniences
-for constructing JavaScript ASTs.
-
-[string] and [number] are used to create leaf AST nodes:
-
- var s = js.string('hello'); // s = new LiteralString('"hello"')
- var n = js.number(123); // n = new LiteralNumber(123)
-
-In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript
-AST that would pretty-print as `b`.
-
-The [call] method constructs an Expression AST.
-
-No argument
-
- js('window.alert("hello")') --> window.alert("hello")
-
-The input text can contain placeholders `#` that are replaced with provided
-arguments. A single argument can be passed directly:
-
- js('window.alert(#)', s) --> window.alert("hello")
-
-Multiple arguments are passed as a list:
-
- js('# + #', [s, s]) --> "hello" + "hello"
-
-The [statement] method constructs a Statement AST, but is otherwise like the
-[call] method. This constructs a Return AST:
-
- var ret = js.statement('return #;', n); --> return 123;
-
-A placeholder in a Statement context must be followed by a semicolon ';'. You
-can think of a statement placeholder as being `#;` to explain why the output
-still has one semicolon:
-
- js.statement('if (happy) #;', ret)
- -->
- if (happy)
- return 123;
-
-If the placeholder is not followed by a semicolon, it is part of an expression.
-Here the paceholder is in the position of the function in a function call:
-
- var vFoo = new Identifier('foo');
- js.statement('if (happy) #("Happy!")', vFoo)
- -->
- if (happy)
- foo("Happy!");
-
-Generally, a placeholder in an expression position requires an Expression AST as
-an argument and a placeholder in a statement position requires a Statement AST.
-An expression will be converted to a Statement if needed by creating an
-ExpessionStatement. A String argument will be converted into a Identifier and
-requires that the string is a JavaScript identifier.
-
- js('# + 1', vFoo) --> foo + 1
- js('# + 1', 'foo') --> foo + 1
- js('# + 1', 'foo.bar') --> assertion failure
-
-Some placeholder positions are _splicing contexts_. A function argument list is
-a splicing expression context. A placeholder in a splicing expression context
-can take a single Expression (or String, converted to Identifier) or an
-Iterable of Expressions (and/or Strings).
-
- // non-splicing argument:
- js('#(#)', ['say', s]) --> say("hello")
- // splicing arguments:
- js('#(#)', ['say', []]) --> say()
- js('#(#)', ['say', [s]]) --> say("hello")
- js('#(#)', ['say', [s, n]]) --> say("hello", 123)
-
-A splicing context can be used to append 'lists' and add extra elements:
-
- js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1)
- js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1)
- js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1)
- js('foo(#, #, 1)', [ [], [] ]) --> foo(1)
-
-The generation of a compile-time optional argument expression can be chosen by
-providing an empty or singleton list.
-
-In addition to Expressions and Statements, there are Parameters, which occur
-only in the parameter list of a function expression or declaration.
-Placeholders in parameter positions behave like placeholders in Expression
-positions, except only Parameter AST nodes are permitted. String arguments for
-parameter placeholders are converted to Parameter AST nodes.
-
- var pFoo = new Parameter('foo')
- js('function(#) { return #; }', [pFoo, vFoo])
- -->
- function(foo) { return foo; }
-
-Expressions and Parameters are not compatible with each other's context:
-
- js('function(#) { return #; }', [vFoo, vFoo]) --> error
- js('function(#) { return #; }', [pFoo, pFoo]) --> error
-
-The parameter context is a splicing context. When combined with the
-context-sensitive conversion of Strings, this simplifies the construction of
-trampoline-like functions:
-
- var args = ['a', 'b'];
- js('function(#) { return f(this, #); }', [args, args])
- -->
- function(a, b) { return f(this, a, b); }
-
-A statement placeholder in a Block is also in a splicing context. In addition
-to splicing Iterables, statement placeholders in a Block will also splice a
-Block or an EmptyStatement. This flattens nested blocks and allows blocks to be
-appended.
-
- var b1 = js.statement('{ 1; 2; }');
- var sEmpty = new Emptystatement();
- js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty])
- -->
- { 1; 2; 1; 2; }
-
-A placeholder in the context of an if-statement condition also accepts a Dart
-bool argument, which selects the then-part or else-part of the if-statement:
-
- js.statement('if (#) return;', vFoo) --> if (foo) return;
- js.statement('if (#) return;', true) --> return;
- js.statement('if (#) return;', false) --> ; // empty statement
- var eTrue = new LiteralBool(true);
- js.statement('if (#) return;', eTrue) --> if (true) return;
-
-Combined with block splicing, if-statement condition context placeholders allows
-the creation of tenplates that select code depending on variables.
-
- js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true)
- --> { 1; 2; 5; }
-
- js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false)
- --> { 1; 3; 4; 5; }
-
-A placeholder following a period in a property access is in a property access
-context. This is just like an expression context, except String arguments are
-converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand
-for `a["b"]`:
-
- js('a[#]', vFoo) --> a[foo]
- js('a[#]', s) --> a.hello (i.e. a["hello"]).
- js('a[#]', 'x') --> a[x]
-
- js('a.#', vFoo) --> a[foo]
- js('a.#', s) --> a.hello (i.e. a["hello"])
- js('a.#', 'x') --> a.x (i.e. a["x"])
-
-(Question - should `.#` be restricted to permit only String arguments? The
-template should probably be writted with `[]` if non-strings are accepted.)
-
-
-Object initialiers allow placeholders in the key property name position:
-
- js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2}
-
-
-What is not implemented:
-
- - Array initializers and object initializers could support splicing. In the
- array case, we would need some way to know if an ArrayInitializer argument
- should be splice or is intended as a single value.
-
- - There are no placeholders in definition contexts:
-
- function #(){}
- var # = 1;
-
-*/
-const JsBuilder js = const JsBuilder();
-
-
-class JsBuilder {
- const JsBuilder();
-
- /**
- * Parses a bit of JavaScript, and returns an expression.
- *
- * See the MiniJsParser class.
- *
- * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or
- * a list of [Node]s, which will be interpolated into the source at the '#'
- * signs.
- */
- Expression call(String source, [var arguments]) {
- Template template = _findExpressionTemplate(source);
- if (arguments == null) return template.instantiate([]);
- // We allow a single argument to be given directly.
- if (arguments is! List && arguments is! Map) arguments = [arguments];
- return template.instantiate(arguments);
- }
-
- /**
- * Parses a JavaScript Statement, otherwise just like [call].
- */
- Statement statement(String source, [var arguments]) {
- Template template = _findStatementTemplate(source);
- if (arguments == null) return template.instantiate([]);
- // We allow a single argument to be given directly.
- if (arguments is! List && arguments is! Map) arguments = [arguments];
- return template.instantiate(arguments);
- }
-
- /**
- * Parses JavaScript written in the `JS` foreign instruction.
- *
- * The [source] must be a JavaScript expression or a JavaScript throw
- * statement.
- */
- Template parseForeignJS(String source) {
- // TODO(sra): Parse with extra validation to forbid `#` interpolation in
- // functions, as this leads to unanticipated capture of temporaries that are
- // reused after capture.
- if (source.startsWith("throw ")) {
- return _findStatementTemplate(source);
- } else {
- return _findExpressionTemplate(source);
- }
- }
-
- Template _findExpressionTemplate(String source) {
- Template template = templateManager.lookupExpressionTemplate(source);
- if (template == null) {
- MiniJsParser parser = new MiniJsParser(source);
- Expression expression = parser.expression();
- template = templateManager.defineExpressionTemplate(source, expression);
- }
- return template;
- }
-
- Template _findStatementTemplate(String source) {
- Template template = templateManager.lookupStatementTemplate(source);
- if (template == null) {
- MiniJsParser parser = new MiniJsParser(source);
- Statement statement = parser.statement();
- template = templateManager.defineStatementTemplate(source, statement);
- }
- return template;
- }
-
- /**
- * Creates an Expression template without caching the result.
- */
- Template uncachedExpressionTemplate(String source) {
- MiniJsParser parser = new MiniJsParser(source);
- Expression expression = parser.expression();
- return new Template(
- source, expression, isExpression: true, forceCopy: false);
- }
-
- /**
- * Creates a Statement template without caching the result.
- */
- Template uncachedStatementTemplate(String source) {
- MiniJsParser parser = new MiniJsParser(source);
- Statement statement = parser.statement();
- return new Template(
- source, statement, isExpression: false, forceCopy: false);
- }
-
- /**
- * Create an Expression template which has [ast] as the result. This is used
- * to wrap a generated AST in a zero-argument Template so it can be passed to
- * context that expects a template.
- */
- Template expressionTemplateYielding(Node ast) {
- return new Template.withExpressionResult(ast);
- }
-
- Template statementTemplateYielding(Node ast) {
- return new Template.withStatementResult(ast);
- }
-
- /// Creates a literal js string from [value].
- LiteralString escapedString(String value, [String quote = '"']) {
- // Start by escaping the backslashes.
- String escaped = value.replaceAll('\\', '\\\\');
-
-
- // Replace $ in template strings:
- // http://www.ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components
- var quoteReplace = quote == '`' ? r'`$' : quote;
-
- // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-string-literals
- // > All code points may appear literally in a string literal except for the
- // > closing quote code points, U+005C (REVERSE SOLIDUS),
- // > U+000D (CARRIAGE RETURN), U+2028 (LINE SEPARATOR),
- // > U+2029 (PARAGRAPH SEPARATOR), and U+000A (LINE FEED).
- var re = new RegExp('[\n\r$quoteReplace\b\f\t\v\u2028\u2029]');
- escaped = escaped.replaceAllMapped(re, (m) {
- switch (m.group(0)) {
- case "\n" : return r"\n";
- case "\r" : return r"\r";
- case "\u2028": return r"\u2028";
- case "\u2029": return r"\u2029";
- // Quotes and $ are only replaced if they conflict with the containing
- // quote, see regex above.
- case '"': return r'\"';
- case "'": return r"\'";
- case "`": return r"\`";
- case r"$": return r"\$";
- // TODO(jmesserly): these don't need to be escaped for correctness,
- // but they are conventionally escaped.
- case "\b": return r"\b";
- case "\t": return r"\t";
- case "\f": return r"\f";
- case "\v": return r"\v";
- }
- });
- LiteralString result = new LiteralString('$quote$escaped$quote');
- // We don't escape quotes of a different style under the assumption that the
- // string is wrapped into quotes. Verify that assumption.
- assert(result.value.codeUnitAt(0) == quote.codeUnitAt(0));
- return result;
- }
-
- /// Creates a literal js string from [value].
- ///
- /// Note that this function only puts quotes around [value]. It does not do
- /// any escaping, so use only when you can guarantee that [value] does not
- /// contain newlines or backslashes. For escaping the string use
- /// [escapedString].
- LiteralString string(String value, [String quote = '"']) =>
- new LiteralString('$quote$value$quote');
-
- LiteralNumber number(num value) => new LiteralNumber('$value');
-
- LiteralBool boolean(bool value) => new LiteralBool(value);
-
- ArrayInitializer numArray(Iterable<int> list) =>
- new ArrayInitializer(list.map(number).toList());
-
- ArrayInitializer stringArray(Iterable<String> list) =>
- new ArrayInitializer(list.map(string).toList());
-
- Comment comment(String text) => new Comment(text);
- CommentExpression commentExpression(String text, Expression expression) =>
- new CommentExpression(text, expression);
-
- Call propertyCall(Expression receiver,
- String fieldName,
- List<Expression> arguments) {
- return new Call(new PropertyAccess.field(receiver, fieldName), arguments);
- }
-}
-
-LiteralString string(String value) => js.string(value);
-LiteralNumber number(num value) => js.number(value);
-ArrayInitializer numArray(Iterable<int> list) => js.numArray(list);
-ArrayInitializer stringArray(Iterable<String> list) => js.stringArray(list);
-Call propertyCall(Expression receiver,
- String fieldName,
- List<Expression> arguments) {
- return js.propertyCall(receiver, fieldName, arguments);
-}
-
-class MiniJsParserError {
- MiniJsParserError(this.parser, this.message) { }
-
- final MiniJsParser parser;
- final String message;
-
- String toString() {
- int pos = parser.lastPosition;
-
- // Discard lines following the line containing lastPosition.
- String src = parser.src;
- int newlinePos = src.indexOf('\n', pos);
- if (newlinePos >= pos) src = src.substring(0, newlinePos);
-
- // Extract the prefix of the error line before lastPosition.
- String line = src;
- int lastLineStart = line.lastIndexOf('\n');
- if (lastLineStart >= 0) line = line.substring(lastLineStart + 1);
- String prefix = line.substring(0, pos - (src.length - line.length));
-
- // Replace non-tabs with spaces, giving a print indent that matches the text
- // for tabbing.
- String spaces = prefix.replaceAll(new RegExp(r'[^\t]'), ' ');
- return 'Error in MiniJsParser:\n${src}\n$spaces^\n$spaces$message\n';
- }
-}
-
-/// Mini JavaScript parser for tiny snippets of code that we want to make into
-/// AST nodes. Handles:
-/// * identifiers.
-/// * dot access.
-/// * method calls.
-/// * [] access.
-/// * array, string, regexp, boolean, null and numeric literals.
-/// * most operators.
-/// * brackets.
-/// * var declarations.
-/// * operator precedence.
-/// * anonymous funtions and named function expressions and declarations.
-/// Notable things it can't do yet include:
-/// * some statements are still missing (do-while, while, switch).
-///
-/// It's a fairly standard recursive descent parser.
-///
-/// Literal strings are passed through to the final JS source code unchanged,
-/// including the choice of surrounding quotes, so if you parse
-/// r'var x = "foo\n\"bar\""' you will end up with
-/// var x = "foo\n\"bar\"" in the final program. \x and \u escapes are not
-/// allowed in string and regexp literals because the machinery for checking
-/// their correctness is rather involved.
-class MiniJsParser {
- MiniJsParser(this.src)
- : lastCategory = NONE,
- lastToken = null,
- lastPosition = 0,
- position = 0 {
- getToken();
- }
-
- int lastCategory = NONE;
- String lastToken = null;
- int lastPosition = 0;
- int position = 0;
- bool skippedNewline = false; // skipped newline in last getToken?
- final String src;
-
- final List<InterpolatedNode> interpolatedValues = <InterpolatedNode>[];
- bool get hasNamedHoles =>
- interpolatedValues.isNotEmpty && interpolatedValues.first.isNamed;
- bool get hasPositionalHoles =>
- interpolatedValues.isNotEmpty && interpolatedValues.first.isPositional;
-
- static const NONE = -1;
- static const ALPHA = 0;
- static const NUMERIC = 1;
- static const STRING = 2;
- static const SYMBOL = 3;
- static const ASSIGNMENT = 4;
- static const DOT = 5;
- static const LPAREN = 6;
- static const RPAREN = 7;
- static const LBRACE = 8;
- static const RBRACE = 9;
- static const LSQUARE = 10;
- static const RSQUARE = 11;
- static const COMMA = 12;
- static const QUERY = 13;
- static const COLON = 14;
- static const SEMICOLON = 15;
- static const ARROW = 16;
- static const ELLIPSIS = 17;
- static const HASH = 18;
- static const WHITESPACE = 19;
- static const OTHER = 20;
-
- // Make sure that ]] is two symbols.
- // TODO(jmesserly): => and ... are not single char tokens, should we change
- // their numbers? It shouldn't matter because this is only called on values
- // from the [CATEGORIES] table.
- bool singleCharCategory(int category) => category > DOT;
-
- static String categoryToString(int cat) {
- switch (cat) {
- case NONE: return "NONE";
- case ALPHA: return "ALPHA";
- case NUMERIC: return "NUMERIC";
- case SYMBOL: return "SYMBOL";
- case ASSIGNMENT: return "ASSIGNMENT";
- case DOT: return "DOT";
- case LPAREN: return "LPAREN";
- case RPAREN: return "RPAREN";
- case LBRACE: return "LBRACE";
- case RBRACE: return "RBRACE";
- case LSQUARE: return "LSQUARE";
- case RSQUARE: return "RSQUARE";
- case STRING: return "STRING";
- case COMMA: return "COMMA";
- case QUERY: return "QUERY";
- case COLON: return "COLON";
- case SEMICOLON: return "SEMICOLON";
- case ARROW: return "ARROW";
- case ELLIPSIS: return "ELLIPSIS";
- case HASH: return "HASH";
- case WHITESPACE: return "WHITESPACE";
- case OTHER: return "OTHER";
- }
- return "Unknown: $cat";
- }
-
- static const CATEGORIES = const <int>[
- OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
- OTHER, WHITESPACE, WHITESPACE, OTHER, OTHER, WHITESPACE, // 8-13
- OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 14-21
- OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 22-29
- OTHER, OTHER, WHITESPACE, // 30-32
- SYMBOL, OTHER, HASH, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&´
- LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./
- NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234
- NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789
- COLON, SEMICOLON, SYMBOL, SYMBOL, SYMBOL, QUERY, OTHER, // :;<=>?@
- ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ABCDEFGH
- ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // IJKLMNOP
- ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // QRSTUVWX
- ALPHA, ALPHA, LSQUARE, OTHER, RSQUARE, SYMBOL, ALPHA, OTHER, // YZ[\]^_'
- ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // abcdefgh
- ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ijklmnop
- ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // qrstuvwx
- ALPHA, ALPHA, LBRACE, SYMBOL, RBRACE, SYMBOL]; // yz{|}~
-
- // This must be a >= the highest precedence number handled by parseBinary.
- static var HIGHEST_PARSE_BINARY_PRECEDENCE = 16;
- static bool isAssignment(String symbol) => BINARY_PRECEDENCE[symbol] == 17;
-
- // From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Operator_Precedence
- static final BINARY_PRECEDENCE = {
- '+=': 17, '-=': 17, '*=': 17, '/=': 17, '%=': 17, '^=': 17, '|=': 17,
- '&=': 17, '<<=': 17, '>>=': 17, '>>>=': 17, '=': 17,
- '||': 14,
- '&&': 13,
- '|': 12,
- '^': 11,
- '&': 10,
- '!=': 9, '==': 9, '!==': 9, '===': 9,
- '<': 8, '<=': 8, '>=': 8, '>': 8, 'in': 8, 'instanceof': 8,
- '<<': 7, '>>': 7, '>>>': 7,
- '+': 6, '-': 6,
- '*': 5, '/': 5, '%': 5
- };
- static final UNARY_OPERATORS =
- ['++', '--', '+', '-', '~', '!', 'typeof', 'void', 'delete', 'await']
- .toSet();
-
- static final ARROW_TOKEN = '=>';
- static final ELLIPSIS_TOKEN = '...';
-
- static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
- ['typeof', 'void', 'delete', 'in', 'instanceof', 'await'].toSet();
-
- static int category(int code) {
- if (code >= CATEGORIES.length) return OTHER;
- return CATEGORIES[code];
- }
-
- String getDelimited(int startPosition) {
- position = startPosition;
- int delimiter = src.codeUnitAt(startPosition);
- int currentCode;
- do {
- position++;
- if (position >= src.length) error("Unterminated literal");
- currentCode = src.codeUnitAt(position);
- if (currentCode == charCodes.$LF) error("Unterminated literal");
- if (currentCode == charCodes.$BACKSLASH) {
- if (++position >= src.length) error("Unterminated literal");
- int escaped = src.codeUnitAt(position);
- if (escaped == charCodes.$x || escaped == charCodes.$X ||
- escaped == charCodes.$u || escaped == charCodes.$U ||
- category(escaped) == NUMERIC) {
- error('Numeric and hex escapes are not allowed in literals');
- }
- }
- } while (currentCode != delimiter);
- position++;
- return src.substring(lastPosition, position);
- }
-
- void getToken() {
- skippedNewline = false;
- for (;;) {
- if (position >= src.length) break;
- int code = src.codeUnitAt(position);
- // Skip '//' and '/*' style comments.
- if (code == charCodes.$SLASH &&
- position + 1 < src.length) {
- if (src.codeUnitAt(position + 1) == charCodes.$SLASH) {
- int nextPosition = src.indexOf('\n', position);
- if (nextPosition == -1) nextPosition = src.length;
- position = nextPosition;
- continue;
- } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) {
- int nextPosition = src.indexOf('*/', position + 2);
- if (nextPosition == -1) error('Unterminated comment');
- position = nextPosition + 2;
- continue;
- }
- }
- if (category(code) != WHITESPACE) break;
- if (code == charCodes.$LF) skippedNewline = true;
- ++position;
- }
-
- if (position == src.length) {
- lastCategory = NONE;
- lastToken = null;
- lastPosition = position;
- return;
- }
- int code = src.codeUnitAt(position);
- lastPosition = position;
- if (code == charCodes.$SQ || code == charCodes.$DQ) {
- // String literal.
- lastCategory = STRING;
- lastToken = getDelimited(position);
- } else if (code == charCodes.$0 &&
- position + 2 < src.length &&
- src.codeUnitAt(position + 1) == charCodes.$x) {
- // Hex literal.
- for (position += 2; position < src.length; position++) {
- int cat = category(src.codeUnitAt(position));
- if (cat != NUMERIC && cat != ALPHA) break;
- }
- lastCategory = NUMERIC;
- lastToken = src.substring(lastPosition, position);
- int.parse(lastToken, onError: (_) {
- error("Unparseable number");
- });
- } else if (code == charCodes.$SLASH) {
- // Tokens that start with / are special due to regexp literals.
- lastCategory = SYMBOL;
- position++;
- if (position < src.length && src.codeUnitAt(position) == charCodes.$EQ) {
- position++;
- }
- lastToken = src.substring(lastPosition, position);
- } else {
- // All other tokens handled here.
- int cat = category(src.codeUnitAt(position));
- int newCat;
- do {
- position++;
- if (position == src.length) break;
- int code = src.codeUnitAt(position);
- // Special code to disallow ! and / in non-first position in token, so
- // that !! parses as two tokens and != parses as one, while =/ parses
- // as a an equals token followed by a regexp literal start.
- newCat = (code == charCodes.$BANG || code == charCodes.$SLASH)
- ? NONE
- : category(code);
- } while (!singleCharCategory(cat) &&
- (cat == newCat ||
- (cat == ALPHA && newCat == NUMERIC) || // eg. level42.
- (cat == NUMERIC && newCat == DOT))); // eg. 3.1415
- lastCategory = cat;
- lastToken = src.substring(lastPosition, position);
- if (cat == NUMERIC) {
- double.parse(lastToken, (_) {
- error("Unparseable number");
- });
- } else if (cat == DOT && lastToken.length > 1) {
- if (lastToken == ELLIPSIS_TOKEN) {
- lastCategory = ELLIPSIS;
- } else {
- error("Unknown operator");
- }
- } else if (cat == SYMBOL) {
- if (lastToken == ARROW_TOKEN) {
- lastCategory = ARROW;
- } else {
- int binaryPrecendence = BINARY_PRECEDENCE[lastToken];
- if (binaryPrecendence == null && !UNARY_OPERATORS.contains(lastToken)) {
- error("Unknown operator");
- }
- if (isAssignment(lastToken)) lastCategory = ASSIGNMENT;
- }
- } else if (cat == ALPHA) {
- if (OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(lastToken)) {
- lastCategory = SYMBOL;
- }
- }
- }
- }
-
- void expectCategory(int cat) {
- if (cat != lastCategory) error("Expected ${categoryToString(cat)}");
- getToken();
- }
-
- bool acceptCategory(int cat) {
- if (cat == lastCategory) {
- getToken();
- return true;
- }
- return false;
- }
-
- void expectSemicolon() {
- if (acceptSemicolon()) return;
- error('Expected SEMICOLON');
- }
-
- bool acceptSemicolon() {
- // Accept semicolon or automatically inserted semicolon before close brace.
- // Miniparser forbids other kinds of semicolon insertion.
- if (RBRACE == lastCategory) return true;
- if (NONE == lastCategory) return true; // end of input
- if (skippedNewline) {
- error('No automatic semicolon insertion at preceding newline');
- }
- return acceptCategory(SEMICOLON);
- }
-
- bool acceptString(String string) {
- if (lastToken == string) {
- getToken();
- return true;
- }
- return false;
- }
-
- void error(message) {
- throw new MiniJsParserError(this, message);
- }
-
- /// Returns either the name for the hole, or its integer position.
- parseHash() {
- String holeName = lastToken;
- if (acceptCategory(ALPHA)) {
- // Named hole. Example: 'function #funName() { ... }'
- if (hasPositionalHoles) {
- error('Holes must all be positional or named. $holeName');
- }
- return holeName;
- } else {
- if (hasNamedHoles) {
- error('Holes must all be positional or named. $holeName');
- }
- int position = interpolatedValues.length;
- return position;
- }
- }
-
- Expression parsePrimary() {
- String last = lastToken;
- if (acceptCategory(ALPHA)) {
- if (last == "true") {
- return new LiteralBool(true);
- } else if (last == "false") {
- return new LiteralBool(false);
- } else if (last == "null") {
- return new LiteralNull();
- } else if (last == "function") {
- return parseFunctionExpression();
- } else if (last == "this") {
- return new This();
- } else if (last == "super") {
- return new Super();
- } else if (last == "class") {
- return parseClass();
- } else {
- return new Identifier(last);
- }
- } else if (acceptCategory(LPAREN)) {
- return parseExpressionOrArrowFunction();
- } else if (acceptCategory(STRING)) {
- return new LiteralString(last);
- } else if (acceptCategory(NUMERIC)) {
- return new LiteralNumber(last);
- } else if (acceptCategory(LBRACE)) {
- return parseObjectInitializer();
- } else if (acceptCategory(LSQUARE)) {
- var values = <Expression>[];
-
- while (true) {
- if (acceptCategory(COMMA)) {
- values.add(new ArrayHole());
- continue;
- }
- if (acceptCategory(RSQUARE)) break;
- values.add(parseAssignment());
- if (acceptCategory(RSQUARE)) break;
- expectCategory(COMMA);
- }
- return new ArrayInitializer(values);
- } else if (last != null && last.startsWith("/")) {
- String regexp = getDelimited(lastPosition);
- getToken();
- String flags = lastToken;
- if (!acceptCategory(ALPHA)) flags = "";
- Expression expression = new RegExpLiteral(regexp + flags);
- return expression;
- } else if (acceptCategory(HASH)) {
- return parseInterpolatedExpression();
- } else {
- error("Expected primary expression");
- return null;
- }
- }
-
- InterpolatedExpression parseInterpolatedExpression() {
- var expression = new InterpolatedExpression(parseHash());
- interpolatedValues.add(expression);
- return expression;
- }
-
- InterpolatedIdentifier parseInterpolatedIdentifier() {
- var id = new InterpolatedIdentifier(parseHash());
- interpolatedValues.add(id);
- return id;
- }
-
- Identifier parseIdentifier() {
- if (acceptCategory(HASH)) {
- return parseInterpolatedIdentifier();
- } else {
- var id = new Identifier(lastToken);
- expectCategory(ALPHA);
- return id;
- }
- }
-
- /**
- * CoverParenthesizedExpressionAndArrowParameterList[Yield] :
- * ( Expression )
- * ( )
- * ( ... BindingIdentifier )
- * ( Expression , ... BindingIdentifier )
- */
- Expression parseExpressionOrArrowFunction() {
- if (acceptCategory(RPAREN)) {
- expectCategory(ARROW);
- return parseArrowFunctionBody(<Parameter>[]);
- }
- if (acceptCategory(ELLIPSIS)) {
- var params = <Parameter>[new RestParameter(parseParameter())];
- expectCategory(RPAREN);
- expectCategory(ARROW);
- return parseArrowFunctionBody(params);
- }
- Expression expression = parseAssignment();
- while (acceptCategory(COMMA)) {
- if (acceptCategory(ELLIPSIS)) {
- var params = <Parameter>[];
- _expressionToParameterList(expression, params);
- params.add(new RestParameter(parseParameter()));
- expectCategory(RPAREN);
- expectCategory(ARROW);
- return parseArrowFunctionBody(params);
- }
- Expression right = parseAssignment();
- expression = new Binary(',', expression, right);
- }
- expectCategory(RPAREN);
- if (acceptCategory(ARROW)) {
- var params = <Parameter>[];
- _expressionToParameterList(expression, params);
- return parseArrowFunctionBody(params);
- }
- return expression;
- }
-
- /**
- * Converts a parenthesized expression into a list of parameters, issuing an
- * error if the conversion fails.
- */
- void _expressionToParameterList(Expression node, List<Parameter> params) {
- if (node is Identifier) {
- params.add(node);
- } else if (node is Binary && node.op == ',') {
- // TODO(jmesserly): this will allow illegal parens, such as
- // `((a, b), (c, d))`. Fixing it on the left side needs an explicit
- // ParenthesizedExpression node, so we can distinguish
- // `((a, b), c)` from `(a, b, c)`.
- _expressionToParameterList(node.left, params);
- _expressionToParameterList(node.right, params);
- } else if (node is InterpolatedExpression) {
- params.add(new InterpolatedParameter(node.nameOrPosition));
- } else {
- error("Expected arrow function parameter list");
- }
- }
-
- Expression parseArrowFunctionBody(List<Parameter> params) {
- Node body;
- if (acceptCategory(LBRACE)) {
- body = parseBlock();
- } else {
- body = parseAssignment();
- }
- return new ArrowFun(params, body);
- }
-
- Expression parseFunctionExpression() {
- String last = lastToken;
- if (acceptCategory(ALPHA)) {
- String functionName = last;
- return new NamedFunction(new Identifier(functionName),
- parseFun());
- }
- return parseFun();
- }
-
- Expression parseFun() {
- List<Parameter> params = <Parameter>[];
-
- expectCategory(LPAREN);
- if (!acceptCategory(RPAREN)) {
- for (;;) {
- if (acceptCategory(ELLIPSIS)) {
- params.add(new RestParameter(parseParameter()));
- expectCategory(RPAREN);
- break;
- }
-
- params.add(parseParameter());
- if (!acceptCategory(COMMA)) {
- expectCategory(RPAREN);
- break;
- }
- }
- }
- AsyncModifier asyncModifier;
- if (acceptString('async')) {
- if (acceptString('*')) {
- asyncModifier = const AsyncModifier.asyncStar();
- } else {
- asyncModifier = const AsyncModifier.async();
- }
- } else if (acceptString('sync')) {
- if (!acceptString('*')) error("Only sync* is valid - sync is implied");
- asyncModifier = const AsyncModifier.syncStar();
- } else {
- asyncModifier = const AsyncModifier.sync();
- }
- expectCategory(LBRACE);
- Block block = parseBlock();
- return new Fun(params, block, asyncModifier: asyncModifier);
- }
-
- /** Parse parameter name or interpolated parameter. */
- Identifier parseParameter() {
- if (acceptCategory(HASH)) {
- var nameOrPosition = parseHash();
- var parameter = new InterpolatedParameter(nameOrPosition);
- interpolatedValues.add(parameter);
- return parameter;
- } else {
- // TODO(jmesserly): validate this is not a keyword
- String argumentName = lastToken;
- expectCategory(ALPHA);
- return new Identifier(argumentName);
- }
- }
-
- Expression parseObjectInitializer() {
- List<Property> properties = <Property>[];
- for (;;) {
- if (acceptCategory(RBRACE)) break;
- // Limited subset of ES6 object initializers.
- //
- // PropertyDefinition :
- // PropertyName : AssignmentExpression
- // MethodDefinition
- properties.add(parseMethodOrProperty());
-
- if (acceptCategory(RBRACE)) break;
- expectCategory(COMMA);
- }
- return new ObjectInitializer(properties);
- }
-
- Expression parseMember() {
- Expression receiver = parsePrimary();
- while (true) {
- if (acceptCategory(DOT)) {
- receiver = getDotRhs(receiver);
- } else if (acceptCategory(LSQUARE)) {
- Expression inBraces = parseExpression();
- expectCategory(RSQUARE);
- receiver = new PropertyAccess(receiver, inBraces);
- } else {
- break;
- }
- }
- return receiver;
- }
-
- Expression parseCall() {
- bool constructor = acceptString("new");
- Expression receiver = parseMember();
- while (true) {
- if (acceptCategory(LPAREN)) {
- final arguments = <Expression>[];
- if (!acceptCategory(RPAREN)) {
- while (true) {
- if (acceptCategory(ELLIPSIS)) {
- arguments.add(new Spread(parseAssignment()));
- expectCategory(RPAREN);
- break;
- }
- arguments.add(parseAssignment());
- if (acceptCategory(RPAREN)) break;
- expectCategory(COMMA);
- }
- }
- receiver = constructor ?
- new New(receiver, arguments) :
- new Call(receiver, arguments);
- constructor = false;
- } else if (!constructor && acceptCategory(LSQUARE)) {
- Expression inBraces = parseExpression();
- expectCategory(RSQUARE);
- receiver = new PropertyAccess(receiver, inBraces);
- } else if (!constructor && acceptCategory(DOT)) {
- receiver = getDotRhs(receiver);
- } else {
- // JS allows new without (), but we don't.
- if (constructor) error("Parentheses are required for new");
- break;
- }
- }
- return receiver;
- }
-
- Expression getDotRhs(Expression receiver) {
- if (acceptCategory(HASH)) {
- var nameOrPosition = parseHash();
- InterpolatedSelector property = new InterpolatedSelector(nameOrPosition);
- interpolatedValues.add(property);
- return new PropertyAccess(receiver, property);
- }
- String identifier = lastToken;
- // In ES5 keywords like delete and continue are allowed as property
- // names, and the IndexedDB API uses that, so we need to allow it here.
- if (acceptCategory(SYMBOL)) {
- if (!OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(identifier)) {
- error("Expected alphanumeric identifier");
- }
- } else {
- expectCategory(ALPHA);
- }
- return new PropertyAccess.field(receiver, identifier);
- }
-
- Expression parsePostfix() {
- Expression expression = parseCall();
- String operator = lastToken;
- // JavaScript grammar is:
- // LeftHandSideExpression [no LineTerminator here] ++
- if (lastCategory == SYMBOL &&
- !skippedNewline &&
- (acceptString("++") || acceptString("--"))) {
- return new Postfix(operator, expression);
- }
- // If we don't accept '++' or '--' due to skippedNewline a newline, no other
- // part of the parser will accept the token and we will get an error at the
- // whole expression level.
- return expression;
- }
-
- Expression parseUnaryHigh() {
- String operator = lastToken;
- if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
- (acceptString("++") || acceptString("--") || acceptString('await'))) {
- if (operator == "await") return new Await(parsePostfix());
- return new Prefix(operator, parsePostfix());
- }
- return parsePostfix();
- }
-
- Expression parseUnaryLow() {
- String operator = lastToken;
- if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
- operator != "++" && operator != "--") {
- expectCategory(SYMBOL);
- if (operator == "await") return new Await(parsePostfix());
- return new Prefix(operator, parseUnaryLow());
- }
- return parseUnaryHigh();
- }
-
- Expression parseBinary(int maxPrecedence) {
- Expression lhs = parseUnaryLow();
- int minPrecedence;
- String lastSymbol;
- Expression rhs; // This is null first time around.
- while (true) {
- String symbol = lastToken;
- if (lastCategory != SYMBOL ||
- !BINARY_PRECEDENCE.containsKey(symbol) ||
- BINARY_PRECEDENCE[symbol] > maxPrecedence) {
- break;
- }
- expectCategory(SYMBOL);
- if (rhs == null || BINARY_PRECEDENCE[symbol] >= minPrecedence) {
- if (rhs != null) lhs = new Binary(lastSymbol, lhs, rhs);
- minPrecedence = BINARY_PRECEDENCE[symbol];
- rhs = parseUnaryLow();
- lastSymbol = symbol;
- } else {
- Expression higher = parseBinary(BINARY_PRECEDENCE[symbol]);
- rhs = new Binary(symbol, rhs, higher);
- }
- }
- if (rhs == null) return lhs;
- return new Binary(lastSymbol, lhs, rhs);
- }
-
- Expression parseConditional() {
- Expression lhs = parseBinary(HIGHEST_PARSE_BINARY_PRECEDENCE);
- if (!acceptCategory(QUERY)) return lhs;
- Expression ifTrue = parseAssignment();
- expectCategory(COLON);
- Expression ifFalse = parseAssignment();
- return new Conditional(lhs, ifTrue, ifFalse);
- }
-
- Expression parseLeftHandSide() => parseConditional();
-
- Expression parseAssignment() {
- Expression lhs = parseLeftHandSide();
- String assignmentOperator = lastToken;
- if (acceptCategory(ASSIGNMENT)) {
- Expression rhs = parseAssignment();
- if (assignmentOperator == "=") {
- return new Assignment(lhs, rhs);
- } else {
- // Handle +=, -=, etc.
- String operator =
- assignmentOperator.substring(0, assignmentOperator.length - 1);
- return new Assignment.compound(lhs, operator, rhs);
- }
- }
- return lhs;
- }
-
- Expression parseExpression() {
- Expression expression = parseAssignment();
- while (acceptCategory(COMMA)) {
- Expression right = parseAssignment();
- expression = new Binary(',', expression, right);
- }
- return expression;
- }
-
- /** Parse a variable declaration list, with `var` or `let` [keyword] */
- VariableDeclarationList parseVariableDeclarationList(
- String keyword, [String firstIdentifier]) {
- var initialization = [];
-
- do {
- var declarator;
- if (firstIdentifier != null) {
- declarator = new Identifier(firstIdentifier);
- firstIdentifier = null;
- } else {
- declarator = parseVariableBinding();
- }
-
- var initializer = acceptString("=") ? parseAssignment() : null;
- initialization.add(new VariableInitialization(declarator, initializer));
- } while (acceptCategory(COMMA));
-
- return new VariableDeclarationList(keyword, initialization);
- }
-
- VariableBinding parseVariableBinding() {
- switch (lastCategory) {
- case ALPHA:
- case HASH:
- return parseIdentifier();
- case LBRACE:
- case LSQUARE:
- return parseBindingPattern();
- default:
- error('Unexpected token $lastToken: ${categoryToString(lastCategory)}');
- return null;
- }
- }
-
- /// Note: this doesn't deal with general-case destructuring yet, it just
- /// supports it in variable initialization.
- /// See ES6 spec:
- /// http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding-patterns
- /// http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-assignment
- /// TODO(ochafik): Support destructuring in LeftHandSideExpression.
- BindingPattern parseBindingPattern() {
- if (acceptCategory(LBRACE)) {
- return parseObjectBindingPattern();
- } else {
- expectCategory(LSQUARE);
- return parseArrayBindingPattern();
- }
- }
-
- ArrayBindingPattern parseArrayBindingPattern() {
- var variables = <DestructuredVariable>[];
- do {
- var name;
- var structure;
- var defaultValue;
-
- var declarator = parseVariableBinding();
- if (declarator is Identifier) name = declarator;
- else if (declarator is BindingPattern) structure = declarator;
- else error("Unexpected LHS: $declarator");
-
- if (acceptString("=")) {
- defaultValue = parseExpression();
- }
- variables.add(new DestructuredVariable(
- name: name, structure: structure, defaultValue: defaultValue));
- } while (acceptCategory(COMMA));
-
- expectCategory(RSQUARE);
- return new ArrayBindingPattern(variables);
- }
-
- ObjectBindingPattern parseObjectBindingPattern() {
- var variables = <DestructuredVariable>[];
- do {
- var name = parseIdentifier();
- var structure;
- var defaultValue;
-
- if (acceptCategory(COLON)) {
- structure = parseBindingPattern();
- } else if (acceptString("=")) {
- defaultValue = parseExpression();
- }
- variables.add(new DestructuredVariable(
- name: name, structure: structure, defaultValue: defaultValue));
- } while (acceptCategory(COMMA));
-
- expectCategory(RBRACE);
- return new ObjectBindingPattern(variables);
- }
-
- Expression parseVarDeclarationOrExpression() {
- var keyword = acceptVarLetOrConst();
- if (keyword != null) {
- return parseVariableDeclarationList(keyword);
- } else {
- return parseExpression();
- }
- }
-
- /** Accepts a `var` or `let` keyword. If neither is found, returns null. */
- String acceptVarLetOrConst() {
- if (acceptString('var')) return 'var';
- if (acceptString('let')) return 'let';
- if (acceptString('const')) return 'const';
- return null;
- }
-
- Expression expression() {
- Expression expression = parseVarDeclarationOrExpression();
- if (lastCategory != NONE || position != src.length) {
- error("Unparsed junk: ${categoryToString(lastCategory)}");
- }
- return expression;
- }
-
- Statement statement() {
- Statement statement = parseStatement();
- if (lastCategory != NONE || position != src.length) {
- error("Unparsed junk: ${categoryToString(lastCategory)}");
- }
- // TODO(sra): interpolated capture here?
- return statement;
- }
-
- Block parseBlock() {
- List<Statement> statements = <Statement>[];
-
- while (!acceptCategory(RBRACE)) {
- Statement statement = parseStatement();
- statements.add(statement);
- }
- return new Block(statements);
- }
-
- Statement parseStatement() {
- if (acceptCategory(LBRACE)) return parseBlock();
-
- if (acceptCategory(SEMICOLON)) return new EmptyStatement();
-
- if (lastCategory == ALPHA) {
- if (acceptString('return')) return parseReturn();
-
- if (acceptString('throw')) return parseThrow();
-
- if (acceptString('break')) {
- return parseBreakOrContinue((label) => new Break(label));
- }
-
- if (acceptString('continue')) {
- return parseBreakOrContinue((label) => new Continue(label));
- }
-
- if (acceptString('if')) return parseIfThenElse();
-
- if (acceptString('for')) return parseFor();
-
- if (acceptString('function')) return parseFunctionDeclaration();
-
- if (acceptString('class')) return new ClassDeclaration(parseClass());
-
- if (acceptString('try')) return parseTry();
-
- var keyword = acceptVarLetOrConst();
- if (keyword != null) {
- Expression declarations = parseVariableDeclarationList(keyword);
- expectSemicolon();
- return new ExpressionStatement(declarations);
- }
-
- if (acceptString('while')) return parseWhile();
-
- if (acceptString('do')) return parseDo();
-
- if (acceptString('switch')) return parseSwitch();
-
- if (lastToken == 'case') error("Case outside switch.");
-
- if (lastToken == 'default') error("Default outside switch.");
-
- if (lastToken == 'yield') return parseYield();
-
- if (lastToken == 'with') {
- error('Not implemented in mini parser');
- }
-
- }
-
- bool checkForInterpolatedStatement = lastCategory == HASH;
-
- Expression expression = parseExpression();
-
- if (expression is Identifier && acceptCategory(COLON)) {
- return new LabeledStatement(expression.name, parseStatement());
- }
-
- expectSemicolon();
-
- if (checkForInterpolatedStatement) {
- // 'Promote' the interpolated expression `#;` to an interpolated
- // statement.
- if (expression is InterpolatedExpression) {
- assert(identical(interpolatedValues.last, expression));
- InterpolatedStatement statement =
- new InterpolatedStatement(expression.nameOrPosition);
- interpolatedValues[interpolatedValues.length - 1] = statement;
- return statement;
- }
- }
-
- return new ExpressionStatement(expression);
- }
-
- Statement parseReturn() {
- if (acceptSemicolon()) return new Return();
- Expression expression = parseExpression();
- expectSemicolon();
- return new Return(expression);
- }
-
- Statement parseYield() {
- bool hasStar = acceptString('*');
- Expression expression = parseExpression();
- expectSemicolon();
- return new DartYield(expression, hasStar);
- }
-
- Statement parseThrow() {
- if (skippedNewline) error('throw expression must be on same line');
- Expression expression = parseExpression();
- expectSemicolon();
- return new Throw(expression);
- }
-
- Statement parseBreakOrContinue(constructor) {
- var identifier = lastToken;
- if (!skippedNewline && acceptCategory(ALPHA)) {
- expectSemicolon();
- return constructor(identifier);
- }
- expectSemicolon();
- return constructor(null);
- }
-
- Statement parseIfThenElse() {
- expectCategory(LPAREN);
- Expression condition = parseExpression();
- expectCategory(RPAREN);
- Statement thenStatement = parseStatement();
- if (acceptString('else')) {
- // Resolves dangling else by binding 'else' to closest 'if'.
- Statement elseStatement = parseStatement();
- return new If(condition, thenStatement, elseStatement);
- } else {
- return new If.noElse(condition, thenStatement);
- }
- }
-
- Statement parseFor() {
- // For-init-condition-increment style loops are fully supported.
- //
- // Only one for-in variant is currently implemented:
- //
- // for (var variable in Expression) Statement
- //
- // One variant of ES6 for-of is also implemented:
- //
- // for (let variable of Expression) Statement
- //
- Statement finishFor(Expression init) {
- Expression condition = null;
- if (!acceptCategory(SEMICOLON)) {
- condition = parseExpression();
- expectCategory(SEMICOLON);
- }
- Expression update = null;
- if (!acceptCategory(RPAREN)) {
- update = parseExpression();
- expectCategory(RPAREN);
- }
- Statement body = parseStatement();
- return new For(init, condition, update, body);
- }
-
- expectCategory(LPAREN);
- if (acceptCategory(SEMICOLON)) {
- return finishFor(null);
- }
-
- var keyword = acceptVarLetOrConst();
- if (keyword != null) {
- String identifier = lastToken;
- expectCategory(ALPHA);
-
- if (acceptString('in')) {
- Expression objectExpression = parseExpression();
- expectCategory(RPAREN);
- Statement body = parseStatement();
- return new ForIn(
- _createVariableDeclarationList(keyword, identifier),
- objectExpression,
- body);
- } else if (acceptString('of')) {
- Expression iterableExpression = parseAssignment();
- expectCategory(RPAREN);
- Statement body = parseStatement();
- return new ForOf(
- _createVariableDeclarationList(keyword, identifier),
- iterableExpression,
- body);
- }
- var declarations = parseVariableDeclarationList(keyword, identifier);
- expectCategory(SEMICOLON);
- return finishFor(declarations);
- }
-
- Expression init = parseExpression();
- expectCategory(SEMICOLON);
- return finishFor(init);
- }
-
- static VariableDeclarationList _createVariableDeclarationList(
- String keyword, String identifier) {
- return new VariableDeclarationList(keyword, [
- new VariableInitialization(
- new Identifier(identifier), null)]);
- }
-
- Statement parseFunctionDeclaration() {
- String name = lastToken;
- expectCategory(ALPHA);
- Expression fun = parseFun();
- return new FunctionDeclaration(new Identifier(name), fun);
- }
-
- Statement parseTry() {
- expectCategory(LBRACE);
- Block body = parseBlock();
- Catch catchPart = null;
- if (acceptString('catch')) catchPart = parseCatch();
- Block finallyPart = null;
- if (acceptString('finally')) {
- expectCategory(LBRACE);
- finallyPart = parseBlock();
- } else {
- if (catchPart == null) error("expected 'finally'");
- }
- return new Try(body, catchPart, finallyPart);
- }
-
- SwitchClause parseSwitchClause() {
- Expression expression = null;
- if (acceptString('case')) {
- expression = parseExpression();
- expectCategory(COLON);
- } else {
- if (!acceptString('default')) {
- error('expected case or default');
- }
- expectCategory(COLON);
- }
- List statements = new List<Statement>();
- while (lastCategory != RBRACE &&
- lastToken != 'case' &&
- lastToken != 'default') {
- statements.add(parseStatement());
- }
- return expression == null
- ? new Default(new Block(statements))
- : new Case(expression, new Block(statements));
- }
-
- Statement parseWhile() {
- expectCategory(LPAREN);
- Expression condition = parseExpression();
- expectCategory(RPAREN);
- Statement body = parseStatement();
- return new While(condition, body);
- }
-
- Statement parseDo() {
- Statement body = parseStatement();
- if (lastToken != "while") error("Missing while after do body.");
- getToken();
- expectCategory(LPAREN);
- Expression condition = parseExpression();
- expectCategory(RPAREN);
- expectSemicolon();
- return new Do(body, condition);
- }
-
- Statement parseSwitch() {
- expectCategory(LPAREN);
- Expression key = parseExpression();
- expectCategory(RPAREN);
- expectCategory(LBRACE);
- List<SwitchClause> clauses = new List<SwitchClause>();
- while(lastCategory != RBRACE) {
- clauses.add(parseSwitchClause());
- }
- expectCategory(RBRACE);
- return new Switch(key, clauses);
- }
-
- Catch parseCatch() {
- expectCategory(LPAREN);
- String identifier = lastToken;
- expectCategory(ALPHA);
- expectCategory(RPAREN);
- expectCategory(LBRACE);
- Block body = parseBlock();
- return new Catch(new Identifier(identifier), body);
- }
-
- ClassExpression parseClass() {
- Identifier name = parseIdentifier();
- Expression heritage = null;
- if (acceptString('extends')) {
- heritage = parseConditional();
- }
- expectCategory(LBRACE);
- var methods = new List<Method>();
- while (lastCategory != RBRACE) {
- methods.add(parseMethodOrProperty(onlyMethods: true));
- }
- expectCategory(RBRACE);
- return new ClassExpression(name, heritage, methods);
- }
-
- /**
- * Parses a [Method] or a [Property].
- *
- * Most of the complexity is from supporting interpolation. Several forms
- * are supported:
- *
- * - getter/setter names: `get #() { ... }`
- * - method names: `#() { ... }`
- * - property names: `#: ...`
- * - entire methods: `#`
- */
- Property parseMethodOrProperty({bool onlyMethods: false}) {
- bool isStatic = acceptString('static');
-
- bool isGetter = lastToken == 'get';
- bool isSetter = lastToken == 'set';
- Expression name = null;
- if (isGetter || isSetter) {
- var token = lastToken;
- getToken();
- if (lastCategory == COLON) {
- // That wasn't a accessor but the 'get' or 'set' property: retropedal.
- isGetter = isSetter = false;
- name = new LiteralString('"$token"');
- }
- }
- if (acceptCategory(HASH)) {
- if (lastCategory != LPAREN && (onlyMethods || lastCategory != COLON)) {
- // Interpolated method
- var member = new InterpolatedMethod(parseHash());
- interpolatedValues.add(member);
- return member;
- }
- name = parseInterpolatedExpression();
- } else {
- name ??= parsePropertyName();
- }
-
- if (!onlyMethods && acceptCategory(COLON)) {
- Expression value = parseAssignment();
- return new Property(name, value);
- } else {
- var fun = parseFun();
- return new Method(name, fun,
- isGetter: isGetter, isSetter: isSetter, isStatic: isStatic);
- }
- }
-
- Expression parsePropertyName() {
- String identifier = lastToken;
- if (acceptCategory(STRING)) {
- return new LiteralString(identifier);
- } else if (acceptCategory(ALPHA) || acceptCategory(SYMBOL)) {
- // ALPHA or a SYMBOL, e.g. void
- return new LiteralString('"$identifier"');
- } else if (acceptCategory(LSQUARE)) {
- var expr = parseAssignment();
- expectCategory(RSQUARE);
- return expr;
- } else if (acceptCategory(HASH)) {
- return parseInterpolatedExpression();
- } else {
- error('Expected property name');
- return null;
- }
- }
-}
« no previous file with comments | « lib/src/dart_sdk.dart ('k') | lib/src/js/characters.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698