| 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;
|
| + }
|
| + }
|
| }
|
|
|