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

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

Issue 12276002: Add a small JS parser to ease the building of ASTs (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Remove def() method, analyzer doesn't like it. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | sdk/lib/_internal/compiler/implementation/js_backend/constant_emitter.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
+ }
+}
« no previous file with comments | « no previous file | sdk/lib/_internal/compiler/implementation/js_backend/constant_emitter.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698