| Index: sdk/lib/_internal/compiler/implementation/js/nodes.dart
|
| diff --git a/sdk/lib/_internal/compiler/implementation/js/nodes.dart b/sdk/lib/_internal/compiler/implementation/js/nodes.dart
|
| index dcdf975bb476170b597ad0c070c101bfb4522f04..b9b9b2291f052de97b66c6dd134861eacb0368f2 100644
|
| --- a/sdk/lib/_internal/compiler/implementation/js/nodes.dart
|
| +++ b/sdk/lib/_internal/compiler/implementation/js/nodes.dart
|
| @@ -734,8 +734,6 @@ class VariableUse extends VariableReference {
|
| accept(NodeVisitor visitor) => visitor.visitVariableUse(this);
|
|
|
| VariableUse asVariableUse() => this;
|
| -
|
| - VariableDeclarationList def([initializer]) => js.defineVar(name, initializer);
|
| }
|
|
|
| class VariableDeclaration extends VariableReference {
|
| @@ -953,7 +951,9 @@ class Comment extends Statement {
|
| class JsBuilder {
|
| const JsBuilder();
|
|
|
| - VariableUse operator [](String name) => new VariableUse(name);
|
| + Expression operator [](String source) {
|
| + return new MiniJsParser(source).expression();
|
| + }
|
|
|
| // TODO(ahe): Remove this method.
|
| Binary equals(Expression left, Expression right) {
|
| @@ -1073,3 +1073,389 @@ class JsBuilder {
|
| const JsBuilder js = const JsBuilder();
|
|
|
| LiteralString string(String value) => js.string(value);
|
| +
|
| +class MiniJsParserError {
|
| + MiniJsParserError(this.parser, this.message) { }
|
| +
|
| + MiniJsParser parser;
|
| + String message;
|
| +
|
| + String toString() {
|
| + var codes =
|
| + new List.fixedLength(parser.lastPosition, fill: charCodes.$SPACE);
|
| + var spaces = new String.fromCharCodes(codes);
|
| + return "Error in MiniJsParser:\n${parser.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.
|
| +/// * string, boolean, null and numeric literals (no hex).
|
| +/// * most operators.
|
| +/// * brackets.
|
| +/// * var declarations.
|
| +/// Notable things it can't do yet include:
|
| +/// * operator precedence.
|
| +/// * array and non-empty object literals.
|
| +/// * throw, return.
|
| +/// * statements, including any flow control (if, while, for, etc.)
|
| +/// * the 'in' keyword.
|
| +///
|
| +/// 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. String literals are
|
| +/// restricted to a small subset of the full set of allowed JS escapes in order
|
| +/// to get early errors for unintentional escape sequences without complicating
|
| +/// this parser unneccessarily.
|
| +class MiniJsParser {
|
| + MiniJsParser(this.src)
|
| + : lastCategory = NONE,
|
| + lastToken = null,
|
| + lastPosition = 0,
|
| + position = 0 {
|
| + getSymbol();
|
| + }
|
| +
|
| + int lastCategory;
|
| + String lastToken;
|
| + int lastPosition;
|
| + int position;
|
| + String src;
|
| +
|
| + static const NONE = -1;
|
| + static const ALPHA = 0;
|
| + static const NUMERIC = 1;
|
| + static const STRING = 2;
|
| + static const SYMBOL = 3;
|
| + static const RELATION = 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 OTHER = 13;
|
| +
|
| + // 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 RELATION: return "RELATION";
|
| + case DOT: return "DOT";
|
| + case LPAREN: return "LPAREN";
|
| + case RPAREN: return "RPAREN";
|
| + case LBRACE: return "LBRACE";
|
| + case RBRACE: return "RBRACE";
|
| + case RSQUARE: return "RSQUARE";
|
| + case STRING: return "STRING";
|
| + case COMMA: return "COMMA";
|
| + case OTHER: return "OTHER";
|
| + }
|
| + return "Unknown: $cat";
|
| + }
|
| +
|
| + static const CATEGORIES = const <int>[
|
| + OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
|
| + OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 8-15
|
| + OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 16-23
|
| + OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 24-31
|
| + OTHER, RELATION, OTHER, OTHER, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&ยด
|
| + LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./
|
| + NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234
|
| + NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789
|
| + OTHER, OTHER, RELATION, RELATION, RELATION, OTHER, 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{|}~
|
| +
|
| + static final BINARY_OPERATORS = [
|
| + '+', '-', '*', '/', '%', '^', '|', '&', '||', '&&',
|
| + '<<', '>>', '+=', '-=', '*=', '/=', '^=', '|=', '&=', '<<=', '>>=',
|
| + '=', '!=', '==', '!==', '===', '<', '<=', '>=', '>'].toSet();
|
| + static final UNARY_OPERATORS = ['++', '--', '+', '-', '~', '!'].toSet();
|
| +
|
| + // For sanity we only allow \\, \', \" and \n in string literals.
|
| + static final STRING_LITERAL_PATTERN =
|
| + new RegExp('^[\'"](?:[^\\\\]|\\\\[\\\\n\'"])*[\'"]\$');
|
| +
|
| + static int category(int code) {
|
| + if (code >= CATEGORIES.length) return OTHER;
|
| + return CATEGORIES[code];
|
| + }
|
| +
|
| + void getSymbol() {
|
| + while (position < src.length &&
|
| + src.codeUnitAt(position) == charCodes.$SPACE) {
|
| + 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) {
|
| + int currentCode;
|
| + do {
|
| + position++;
|
| + if (position >= src.length) {
|
| + throw new MiniJsParserError(this, "Unterminated string");
|
| + }
|
| + currentCode = src.codeUnitAt(position);
|
| + if (currentCode == charCodes.$BACKSLASH) {
|
| + if (++position >= src.length) {
|
| + throw new MiniJsParserError(this, "Unterminated string");
|
| + }
|
| + }
|
| + } while (currentCode != code);
|
| + lastCategory = STRING;
|
| + position++;
|
| + lastToken = src.substring(lastPosition, position);
|
| + if (!STRING_LITERAL_PATTERN.hasMatch(lastToken)) {
|
| + throw new MiniJsParserError(
|
| + this,
|
| + "Only escapes allowed in string literals are \\, \', \" and \n");
|
| + }
|
| + } else {
|
| + int cat = category(src.codeUnitAt(position));
|
| + int newCat;
|
| + do {
|
| + position++;
|
| + if (position == src.length) break;
|
| + newCat = category(src.codeUnitAt(position));
|
| + } while (!singleCharCategory(cat) &&
|
| + (cat == newCat ||
|
| + (cat == ALPHA && newCat == NUMERIC) || // eg. level42.
|
| + (cat == NUMERIC && newCat == DOT) || // eg. 3.1415
|
| + (cat == SYMBOL && newCat == RELATION))); // eg. +=.
|
| + lastCategory = cat;
|
| + lastToken = src.substring(lastPosition, position);
|
| + if (cat == NUMERIC) {
|
| + double.parse(lastToken, (_) {
|
| + throw new MiniJsParserError(this, "Unparseable number");
|
| + });
|
| + } else if (cat == SYMBOL || cat == RELATION) {
|
| + if (!BINARY_OPERATORS.contains(lastToken) &&
|
| + !UNARY_OPERATORS.contains(lastToken)) {
|
| + throw new MiniJsParserError(this, "Unknown operator");
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + void expectCategory(int cat) {
|
| + if (cat != lastCategory) {
|
| + throw new MiniJsParserError(this, "Expected ${categoryToString(cat)}");
|
| + }
|
| + getSymbol();
|
| + }
|
| +
|
| + bool acceptCategory(int cat) {
|
| + if (cat == lastCategory) {
|
| + getSymbol();
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool acceptString(String string) {
|
| + if (lastToken == string) {
|
| + getSymbol();
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + 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 {
|
| + 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)) {
|
| + expectCategory(RBRACE);
|
| + return new ObjectInitializer([]);
|
| + } else {
|
| + throw new MiniJsParserError(this, "Expected primary expression");
|
| + }
|
| + }
|
| +
|
| + Expression parseMember() {
|
| + Expression receiver = parsePrimary();
|
| + while (true) {
|
| + if (acceptCategory(DOT)) {
|
| + String identifier = lastToken;
|
| + expectCategory(ALPHA);
|
| + receiver = new PropertyAccess.field(receiver, identifier);
|
| + } else if (acceptCategory(LSQUARE)) {
|
| + Expression inBraces = parseExpression();
|
| + expectCategory(RSQUARE);
|
| + receiver = new PropertyAccess(receiver, inBraces);
|
| + } else {
|
| + return receiver;
|
| + }
|
| + }
|
| + }
|
| +
|
| + Expression parseCall() {
|
| + bool constructor = acceptString("new");
|
| + Expression receiver = parseMember();
|
| + if (acceptCategory(LPAREN)) {
|
| + final arguments = <Expression>[];
|
| + if (!acceptCategory(RPAREN)) {
|
| + while (true) {
|
| + Expression argument = parseExpression();
|
| + arguments.add(argument);
|
| + if (acceptCategory(RPAREN)) break;
|
| + expectCategory(COMMA);
|
| + }
|
| + }
|
| + return constructor ?
|
| + new New(receiver, arguments) :
|
| + new Call(receiver, arguments);
|
| + } else {
|
| + if (constructor) {
|
| + // JS allows new without (), but we don't.
|
| + throw new MiniJsParserError(this, "Parentheses are required for new");
|
| + }
|
| + return receiver;
|
| + }
|
| + }
|
| +
|
| + Expression parsePostfix() {
|
| + Expression expression = parseCall();
|
| + String operator = lastToken;
|
| + if (lastCategory == SYMBOL && (acceptString("++") || acceptString("--"))) {
|
| + return new Postfix(operator, expression);
|
| + }
|
| + return expression;
|
| + }
|
| +
|
| + Expression parseUnary() {
|
| + String operator = lastToken;
|
| + if (lastCategory == ALPHA) {
|
| + if (acceptString("typeof") || acceptString("void") ||
|
| + acceptString("delete")) {
|
| + return new Prefix(operator, parsePostfix());
|
| + }
|
| + } else if (lastCategory == SYMBOL) {
|
| + if (acceptString("~") || acceptString("-") || acceptString("++") ||
|
| + acceptString("--") || acceptString("+")) {
|
| + return new Prefix(operator, parsePostfix());
|
| + }
|
| + } else if (acceptString("!")) {
|
| + return new Prefix(operator, parsePostfix());
|
| + }
|
| + return parsePostfix();
|
| + }
|
| +
|
| + Expression parseBinary() {
|
| + // Since we don't handle precedence we don't allow two different symbols
|
| + // without parentheses.
|
| + Expression lhs = parseUnary();
|
| + String firstSymbol = lastToken;
|
| + while (true) {
|
| + String symbol = lastToken;
|
| + if (!acceptCategory(SYMBOL)) return lhs;
|
| + if (!BINARY_OPERATORS.contains(symbol)) {
|
| + throw new MiniJsParserError(this, "Unknown binary operator");
|
| + }
|
| + if (symbol != firstSymbol) {
|
| + throw new MiniJsParserError(
|
| + this, "Mixed $firstSymbol and $symbol operators without ()");
|
| + }
|
| + Expression rhs = parseUnary();
|
| + if (symbol.endsWith("=")) {
|
| + // +=, -=, *= etc.
|
| + lhs = new Assignment.compound(lhs,
|
| + symbol.substring(0, symbol.length - 1),
|
| + rhs);
|
| + } else {
|
| + lhs = new Binary(symbol, lhs, rhs);
|
| + }
|
| + }
|
| + }
|
| +
|
| + Expression parseRelation() {
|
| + Expression lhs = parseBinary();
|
| + String relation = lastToken;
|
| + if (!acceptCategory(RELATION)) return lhs;
|
| + Expression rhs = parseBinary();
|
| + if (relation == "=") {
|
| + return new Assignment(lhs, rhs);
|
| + } else if (relation == "<<=" || relation == ">>=") {
|
| + return new Assignment.compound(lhs,
|
| + relation.substring(0, relation.length - 1),
|
| + rhs);
|
| + } else {
|
| + // Regular binary operation.
|
| + return new Binary(relation, lhs, rhs);
|
| + }
|
| + }
|
| +
|
| + Expression parseExpression() => parseRelation();
|
| +
|
| + Expression parseVarDeclarationOrExpression() {
|
| + if (acceptString("var")) {
|
| + var initialization = [];
|
| + do {
|
| + String variable = lastToken;
|
| + expectCategory(ALPHA);
|
| + Expression initializer = null;
|
| + if (acceptString("=")) {
|
| + initializer = parseExpression();
|
| + }
|
| + var declaration = new VariableDeclaration(variable);
|
| + initialization.add(
|
| + new VariableInitialization(declaration, initializer));
|
| + } while (acceptCategory(COMMA));
|
| + return new VariableDeclarationList(initialization);
|
| + } else {
|
| + return parseExpression();
|
| + }
|
| + }
|
| +
|
| + Expression expression() {
|
| + Expression expression = parseVarDeclarationOrExpression();
|
| + if (lastCategory != NONE || position != src.length) {
|
| + throw new MiniJsParserError(
|
| + this, "Unparsed junk: ${categoryToString(lastCategory)}");
|
| + }
|
| + return expression;
|
| + }
|
| +}
|
|
|