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

Unified Diff: lib/src/js/builder.dart

Issue 949383003: use js_ast instead of strings to generate JS (Closed) Base URL: git@github.com:dart-lang/dart-dev-compiler.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/src/js/builder.dart
diff --git a/lib/src/js/builder.dart b/lib/src/js/builder.dart
index 72b4507081ebca30903142b36121319d01c75640..6914b4ac96a187385df146dace080648b0bd2c28 100644
--- a/lib/src/js/builder.dart
+++ b/lib/src/js/builder.dart
@@ -295,25 +295,28 @@ class JsBuilder {
}
/// Creates a literal js string from [value].
- LiteralString escapedString(String value) {
+ LiteralString escapedString(String value, [String quote = '"']) {
// 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)) {
+ escaped = escaped.replaceAllMapped(new RegExp('\n|$quote|\b|\t|\v'), (m) {
+ switch (m.group(0)) {
case "\n" : return r"\n";
- case "\"" : return r'\"';
+ // Quotes are only replaced if they conflict with the containing quote
+ case '"': return r'\"';
+ case "'": return r"\'";
+ 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));
+ 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;
}
@@ -323,7 +326,8 @@ class JsBuilder {
/// 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"');
+ LiteralString string(String value, [String quote = '"']) =>
+ new LiteralString('$quote$value$quote');
LiteralNumber number(num value) => new LiteralNumber('$value');
@@ -336,6 +340,8 @@ class JsBuilder {
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,
@@ -443,9 +449,10 @@ class MiniJsParser {
static const QUERY = 13;
static const COLON = 14;
static const SEMICOLON = 15;
- static const HASH = 16;
- static const WHITESPACE = 17;
- static const OTHER = 18;
+ static const ARROW = 16;
+ static const HASH = 17;
+ static const WHITESPACE = 18;
+ static const OTHER = 19;
// Make sure that ]] is two symbols.
bool singleCharCategory(int category) => category >= DOT;
@@ -469,6 +476,7 @@ class MiniJsParser {
case QUERY: return "QUERY";
case COLON: return "COLON";
case SEMICOLON: return "SEMICOLON";
+ case ARROW: return "ARROW";
case HASH: return "HASH";
case WHITESPACE: return "WHITESPACE";
case OTHER: return "OTHER";
@@ -519,6 +527,8 @@ class MiniJsParser {
['++', '--', '+', '-', '~', '!', 'typeof', 'void', 'delete', 'await']
.toSet();
+ static final ARROW_TOKEN = '=>';
+
static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
['typeof', 'void', 'delete', 'in', 'instanceof', 'await'].toSet();
@@ -633,11 +643,15 @@ class MiniJsParser {
error("Unparseable number");
});
} else if (cat == SYMBOL) {
- int binaryPrecendence = BINARY_PRECEDENCE[lastToken];
- if (binaryPrecendence == null && !UNARY_OPERATORS.contains(lastToken)) {
- error("Unknown operator");
+ 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;
}
- if (isAssignment(lastToken)) lastCategory = ASSIGNMENT;
} else if (cat == ALPHA) {
if (OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS.contains(lastToken)) {
lastCategory = SYMBOL;
@@ -718,13 +732,15 @@ class MiniJsParser {
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 VariableUse(last);
}
} else if (acceptCategory(LPAREN)) {
- Expression expression = parseExpression();
- expectCategory(RPAREN);
- return expression;
+ return parseExpressionOrArrowFunction();
} else if (acceptCategory(STRING)) {
return new LiteralString(last);
} else if (acceptCategory(NUMERIC)) {
@@ -764,6 +780,61 @@ class MiniJsParser {
}
}
+ /**
+ * CoverParenthesizedExpressionAndArrowParameterList[Yield] :
+ * ( Expression )
+ * ( )
+ * ( ... BindingIdentifier )
+ * ( Expression , ... BindingIdentifier )
+ */
+ Expression parseExpressionOrArrowFunction() {
+ if (acceptCategory(RPAREN)) {
+ expectCategory(ARROW);
+ return parseArrowFunctionBody(<Parameter>[]);
+ }
+ Expression expression = parseExpression();
+ 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 VariableUse) {
+ // TODO(jmesserly): support default/rest parameters
+ params.add(new Parameter(node.name));
+ } 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)) {
@@ -818,29 +889,31 @@ class MiniJsParser {
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)) {
- var nameOrPosition = parseHash();
- InterpolatedLiteral interpolatedLiteral =
- new InterpolatedLiteral(nameOrPosition);
- interpolatedValues.add(interpolatedLiteral);
- propertyName = interpolatedLiteral;
+ // Limited subset of ES6 object initializers.
+ //
+ // PropertyDefinition :
+ // PropertyName : AssignmentExpression
+ // MethodDefinition
+
+ if (acceptCategory(HASH)) {
+ properties.add(parseInterpolatedMember());
} else {
- error('Expected property name');
+ bool isGetter = acceptString('get');
+ bool isSetter = isGetter ? false : acceptString('set');
+ Expression name = parsePropertyName();
+
+ if (lastCategory == LPAREN) {
+ Fun fun = parseFun();
+ properties.add(
+ new Method(name, fun, isGetter: isGetter, isSetter: isSetter));
+ } else {
+ expectCategory(COLON);
+ Expression value = parseAssignment();
+ properties.add(new Property(name, value));
+ }
+ if (acceptCategory(RBRACE)) break;
+ expectCategory(COMMA);
}
- expectCategory(COLON);
- Expression value = parseAssignment();
- properties.add(new Property(propertyName, value));
- if (acceptCategory(RBRACE)) break;
- expectCategory(COMMA);
}
return new ObjectInitializer(properties);
}
@@ -987,9 +1060,10 @@ class MiniJsParser {
return new Conditional(lhs, ifTrue, ifFalse);
}
+ Expression parseLeftHandSide() => parseConditional();
Expression parseAssignment() {
- Expression lhs = parseConditional();
+ Expression lhs = parseLeftHandSide();
String assignmentOperator = lastToken;
if (acceptCategory(ASSIGNMENT)) {
Expression rhs = parseAssignment();
@@ -1014,13 +1088,26 @@ class MiniJsParser {
return expression;
}
- VariableDeclarationList parseVariableDeclarationList() {
+ /** Parse a variable declaration list, with `var` or `let` [keyword] */
+ VariableDeclarationList parseVariableDeclarationList(String keyword) {
+ // Supports one form for interpolated variable declaration:
+ // let # = ...
+ if (acceptCategory(HASH)) {
+ var name = new InterpolatedVariableDeclaration(parseHash());
+ interpolatedValues.add(name);
+
+ Expression initializer = acceptString("=") ? parseAssignment() : null;
+ return new VariableDeclarationList(keyword,
+ [new VariableInitialization(name, initializer)]);
+ }
+
String firstVariable = lastToken;
expectCategory(ALPHA);
- return finishVariableDeclarationList(firstVariable);
+ return finishVariableDeclarationList(keyword, firstVariable);
}
- VariableDeclarationList finishVariableDeclarationList(String firstVariable) {
+ VariableDeclarationList finishVariableDeclarationList(
+ String keyword, String firstVariable) {
var initialization = [];
void declare(String variable) {
@@ -1038,17 +1125,25 @@ class MiniJsParser {
expectCategory(ALPHA);
declare(variable);
}
- return new VariableDeclarationList(initialization);
+ return new VariableDeclarationList(keyword, initialization);
}
Expression parseVarDeclarationOrExpression() {
- if (acceptString("var")) {
- return parseVariableDeclarationList();
+ var keyword = acceptVarOrLet();
+ if (keyword != null) {
+ return parseVariableDeclarationList(keyword);
} else {
return parseExpression();
}
}
+ /** Accepts a `var` or `let` keyword. If neither is found, returns null. */
+ String acceptVarOrLet() {
+ if (acceptString('var')) return 'var';
+ if (acceptString('let')) return 'let';
+ return null;
+ }
+
Expression expression() {
Expression expression = parseVarDeclarationOrExpression();
if (lastCategory != NONE || position != src.length) {
@@ -1100,10 +1195,13 @@ class MiniJsParser {
if (acceptString('function')) return parseFunctionDeclaration();
+ if (acceptString('class')) return new ClassDeclaration(parseClass());
+
if (acceptString('try')) return parseTry();
- if (acceptString('var')) {
- Expression declarations = parseVariableDeclarationList();
+ var keyword = acceptVarOrLet();
+ if (keyword != null) {
+ Expression declarations = parseVariableDeclarationList(keyword);
expectSemicolon();
return new ExpressionStatement(declarations);
}
@@ -1203,6 +1301,10 @@ class MiniJsParser {
//
// 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)) {
@@ -1223,21 +1325,29 @@ class MiniJsParser {
return finishFor(null);
}
- if (acceptString('var')) {
+ var keyword = acceptVarOrLet();
+ if (keyword != null) {
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)]),
+ _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);
}
- Expression declarations = finishVariableDeclarationList(identifier);
+ var declarations = finishVariableDeclarationList(keyword, identifier);
expectCategory(SEMICOLON);
return finishFor(declarations);
}
@@ -1247,6 +1357,13 @@ class MiniJsParser {
return finishFor(init);
}
+ static VariableDeclarationList _createVariableDeclarationList(
+ String keyword, String identifier) {
+ return new VariableDeclarationList(keyword, [
+ new VariableInitialization(
+ new VariableDeclaration(identifier), null)]);
+ }
+
Statement parseFunctionDeclaration() {
String name = lastToken;
expectCategory(ALPHA);
@@ -1257,7 +1374,6 @@ class MiniJsParser {
Statement parseTry() {
expectCategory(LBRACE);
Block body = parseBlock();
- String token = lastToken;
Catch catchPart = null;
if (acceptString('catch')) catchPart = parseCatch();
Block finallyPart = null;
@@ -1333,4 +1449,69 @@ class MiniJsParser {
Block body = parseBlock();
return new Catch(new VariableDeclaration(identifier), body);
}
+
+ ClassExpression parseClass() {
+ VariableDeclaration name;
+ if (acceptCategory(HASH)) {
+ var interpolatedName = new InterpolatedVariableDeclaration(parseHash());
+ interpolatedValues.add(interpolatedName);
+ name = interpolatedName;
+ } else {
+ name = new VariableDeclaration(lastToken);
+ expectCategory(ALPHA);
+ }
+ Expression heritage = null;
+ if (acceptString('extends')) {
+ heritage = parseLeftHandSide();
+ }
+ expectCategory(LBRACE);
+ var methods = new List<Method>();
+ while (lastCategory != RBRACE) {
+ methods.add(parseMethod());
+ }
+ expectCategory(RBRACE);
+ return new ClassExpression(name, heritage, methods);
+ }
+
+ Method parseMethod() {
+ if (acceptCategory(HASH)) return parseInterpolatedMember();
+
+ bool isStatic = acceptString('static');
+ bool isGetter = acceptString('get');
+ bool isSetter = isGetter ? false : acceptString('set');
+ var name = parsePropertyName();
+ var fun = parseFun();
+ return new Method(name, fun,
+ isGetter: isGetter, isSetter: isSetter, isStatic: isStatic);
+ }
+
+ InterpolatedMethod parseInterpolatedMember() {
+ var member = new InterpolatedMethod(parseHash());
+ interpolatedValues.add(member);
+ return member;
+ }
+
+ Expression parsePropertyName() {
+ String identifier = lastToken;
+ if (acceptCategory(ALPHA)) {
+ return new PropertyName(identifier);
+ } else if (acceptCategory(STRING)) {
+ return new LiteralString(identifier);
+ } else if (acceptCategory(SYMBOL)) {
+ // e.g. void
+ return new LiteralString('"$identifier"');
+ } else if (acceptCategory(LSQUARE)) {
+ var expr = parseAssignment();
+ expectCategory(RSQUARE);
+ return expr;
+ } else if (acceptCategory(HASH)) {
+ var nameOrPosition = parseHash();
+ var interpolatedLiteral = new InterpolatedLiteral(nameOrPosition);
+ interpolatedValues.add(interpolatedLiteral);
+ return interpolatedLiteral;
+ } else {
+ error('Expected property name');
+ return null;
+ }
+ }
}

Powered by Google App Engine
This is Rietveld 408576698