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

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

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/compiler/implementation/js/builder.dart
diff --git a/sdk/lib/_internal/compiler/implementation/js/builder.dart b/sdk/lib/_internal/compiler/implementation/js/builder.dart
deleted file mode 100644
index 838dbc82dbae9b2bb2f3addc4eae4cba91eede68..0000000000000000000000000000000000000000
--- a/sdk/lib/_internal/compiler/implementation/js/builder.dart
+++ /dev/null
@@ -1,1199 +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;
-
-
-/**
- * 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 VariableUse('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 VariableUse 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 VariableUse) 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([]);
- return template.instantiate(arguments is List ? arguments : [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([]);
- return template.instantiate(arguments is List ? arguments : [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) {
- // Start by escaping the backslashes.
- String escaped = value.replaceAll('\\', '\\\\');
- // Do not escape unicode characters and ' because they are allowed in the
- // string literal anyway.
- escaped = escaped.replaceAllMapped(new RegExp('\n|"|\b|\t|\v'), (match) {
- switch (match.group(0)) {
- case "\n" : return r"\n";
- case "\"" : return r'\"';
- case "\b" : return r"\b";
- case "\t" : return r"\t";
- case "\f" : return r"\f";
- case "\v" : return r"\v";
- }
- });
- LiteralString result = string(escaped);
- // We don't escape ' under the assumption that the string is wrapped
- // into ". Verify that assumption.
- assert(result.value.codeUnitAt(0) == '"'.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) => new LiteralString('"$value"');
-
- LiteralNumber number(num value) => new LiteralNumber('$value');
-
- ArrayInitializer numArray(Iterable<int> list) =>
- new ArrayInitializer.from(list.map(number));
-
- ArrayInitializer stringArray(Iterable<String> list) =>
- new ArrayInitializer.from(list.map(string));
-
- Comment comment(String text) => new Comment(text);
-}
-
-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);
-
-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>[];
-
- 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 HASH = 16;
- static const WHITESPACE = 17;
- static const OTHER = 18;
-
- // Make sure that ]] is two symbols.
- 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 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'].toSet();
-
- static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
- ['typeof', 'void', 'delete', 'in', 'instanceof'].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 == SYMBOL) {
- 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);
- }
-
- 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 {
- return new VariableUse(last);
- }
- } else if (acceptCategory(LPAREN)) {
- Expression expression = parseExpression();
- expectCategory(RPAREN);
- return expression;
- } 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 = <ArrayElement>[];
- if (!acceptCategory(RSQUARE)) {
- do {
- values.add(new ArrayElement(values.length, parseAssignment()));
- } while (acceptCategory(COMMA));
- expectCategory(RSQUARE);
- }
- return new ArrayInitializer(values.length, 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)) {
- InterpolatedExpression expression =
- new InterpolatedExpression(interpolatedValues.length);
- interpolatedValues.add(expression);
- return expression;
- } else {
- error("Expected primary expression");
- return null;
- }
- }
-
- Expression parseFunctionExpression() {
- String last = lastToken;
- if (acceptCategory(ALPHA)) {
- String functionName = last;
- return new NamedFunction(new VariableDeclaration(functionName),
- parseFun());
- }
- return parseFun();
- }
-
- Expression parseFun() {
- List<Parameter> params = <Parameter>[];
-
- expectCategory(LPAREN);
- if (!acceptCategory(RPAREN)) {
- for (;;) {
- if (acceptCategory(HASH)) {
- InterpolatedParameter parameter =
- new InterpolatedParameter(interpolatedValues.length);
- interpolatedValues.add(parameter);
- params.add(parameter);
- } else {
- String argumentName = lastToken;
- expectCategory(ALPHA);
- params.add(new Parameter(argumentName));
- }
- if (acceptCategory(COMMA)) continue;
- expectCategory(RPAREN);
- break;
- }
- }
-
- expectCategory(LBRACE);
- Block block = parseBlock();
- return new Fun(params, block);
- }
-
- Expression parseObjectInitializer() {
- List<Property> properties = <Property>[];
- for (;;) {
- if (acceptCategory(RBRACE)) break;
- // Limited subset: keys are identifiers, no 'get' or 'set' properties.
- Literal propertyName;
- String identifier = lastToken;
- if (acceptCategory(ALPHA)) {
- propertyName = new LiteralString('"$identifier"');
- } else if (acceptCategory(STRING)) {
- propertyName = new LiteralString(identifier);
- } else if (acceptCategory(SYMBOL)) { // e.g. void
- propertyName = new LiteralString('"$identifier"');
- } else if (acceptCategory(HASH)) {
- InterpolatedLiteral interpolatedLiteral =
- new InterpolatedLiteral(interpolatedValues.length);
- interpolatedValues.add(interpolatedLiteral);
- propertyName = interpolatedLiteral;
- } else {
- error('Expected property name');
- }
- expectCategory(COLON);
- Expression value = parseAssignment();
- properties.add(new Property(propertyName, value));
- 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) {
- Expression argument = parseAssignment();
- arguments.add(argument);
- 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)) {
- InterpolatedSelector property =
- new InterpolatedSelector(interpolatedValues.length);
- 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("--"))) {
- return new Prefix(operator, parsePostfix());
- }
- return parsePostfix();
- }
-
- Expression parseUnaryLow() {
- String operator = lastToken;
- if (lastCategory == SYMBOL && UNARY_OPERATORS.contains(operator) &&
- operator != "++" && operator != "--") {
- expectCategory(SYMBOL);
- 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 parseAssignment() {
- Expression lhs = parseConditional();
- 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;
- }
-
- VariableDeclarationList parseVariableDeclarationList() {
- String firstVariable = lastToken;
- expectCategory(ALPHA);
- return finishVariableDeclarationList(firstVariable);
- }
-
- VariableDeclarationList finishVariableDeclarationList(String firstVariable) {
- var initialization = [];
-
- void declare(String variable) {
- Expression initializer = null;
- if (acceptString("=")) {
- initializer = parseAssignment();
- }
- var declaration = new VariableDeclaration(variable);
- initialization.add(new VariableInitialization(declaration, initializer));
- }
-
- declare(firstVariable);
- while (acceptCategory(COMMA)) {
- String variable = lastToken;
- expectCategory(ALPHA);
- declare(variable);
- }
- return new VariableDeclarationList(initialization);
- }
-
- Expression parseVarDeclarationOrExpression() {
- if (acceptString("var")) {
- return parseVariableDeclarationList();
- } else {
- return parseExpression();
- }
- }
-
- 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('try')) return parseTry();
-
- if (acceptString('var')) {
- Expression declarations = parseVariableDeclarationList();
- expectSemicolon();
- return new ExpressionStatement(declarations);
- }
-
- if (lastToken == 'case' ||
- lastToken == 'do' ||
- lastToken == 'while' ||
- lastToken == 'switch' ||
- lastToken == 'with') {
- error('Not implemented in mini parser');
- }
- }
-
-
- // TODO: label: statement
-
- bool checkForInterpolatedStatement = lastCategory == HASH;
-
- Expression expression = parseExpression();
- 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.name);
- 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 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
- //
- 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);
- }
-
- if (acceptString('var')) {
- String identifier = lastToken;
- expectCategory(ALPHA);
- if (acceptString('in')) {
- Expression objectExpression = parseExpression();
- expectCategory(RPAREN);
- Statement body = parseStatement();
- return new ForIn(
- new VariableDeclarationList([
- new VariableInitialization(
- new VariableDeclaration(identifier), null)]),
- objectExpression,
- body);
- }
- Expression declarations = finishVariableDeclarationList(identifier);
- expectCategory(SEMICOLON);
- return finishFor(declarations);
- }
-
- Expression init = parseExpression();
- expectCategory(SEMICOLON);
- return finishFor(init);
- }
-
- Statement parseFunctionDeclaration() {
- String name = lastToken;
- expectCategory(ALPHA);
- Expression fun = parseFun();
- return new FunctionDeclaration(new VariableDeclaration(name), fun);
- }
-
- Statement parseTry() {
- expectCategory(LBRACE);
- Block body = parseBlock();
- String token = lastToken;
- 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);
- }
-
- Catch parseCatch() {
- expectCategory(LPAREN);
- String identifier = lastToken;
- expectCategory(ALPHA);
- expectCategory(RPAREN);
- expectCategory(LBRACE);
- Block body = parseBlock();
- return new Catch(new VariableDeclaration(identifier), body);
- }
-}

Powered by Google App Engine
This is Rietveld 408576698