Skip to content

Commit

Permalink
Add support for parsing and transpilation of the '**' and '**=' opera…
Browse files Browse the repository at this point in the history
…tors.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127999433
  • Loading branch information
brad4d authored and blickly committed Jul 21, 2016
1 parent 2cdc020 commit ca48609
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 6 deletions.
30 changes: 25 additions & 5 deletions src/com/google/javascript/jscomp/CodeGenerator.java
Expand Up @@ -139,9 +139,9 @@ protected void add(Node n, Context context) {
add("(");
}

if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(first, p, context);
if (NodeUtil.isAssignmentOp(n) || type == Token.EXPONENT) {
// Assignment operators and '**' are the only right-associative binary operators
addExpr(first, p + 1, context);
cc.addOp(opstr, true);
addExpr(last, p, rhsContext);
} else {
Expand Down Expand Up @@ -560,6 +560,8 @@ protected void add(Node n, Context context) {
case MEMBER_VARIABLE_DEF:
// nothing to do.
break;
default:
break;
}

// The name is on the GET or SET node.
Expand Down Expand Up @@ -1531,8 +1533,7 @@ private static boolean isBlockDeclOrDo(Node n) {
}

private void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) && n.isIn())){
if (opRequiresParentheses(n, minPrecedence, context)) {
add("(");
add(n, Context.OTHER);
add(")");
Expand All @@ -1541,6 +1542,25 @@ private void addExpr(Node n, int minPrecedence, Context context) {
}
}

private boolean opRequiresParentheses(Node n, int minPrecedence, Context context) {
if (context == Context.IN_FOR_INIT_CLAUSE && n.isIn()) {
// make sure this operator 'in' isn't confused with the for-loop 'in'
return true;
} else if (NodeUtil.isUnaryOperator(n) && isFirstOperandOfExponentiationExpression(n)) {
// Unary operators are higher precedence than '**', but
// ExponentiationExpression cannot expand to
// UnaryExpression ** ExponentiationExpression
return true;
} else {
return NodeUtil.precedence(n.getType()) < minPrecedence;
}
}

private boolean isFirstOperandOfExponentiationExpression(Node n) {
Node parent = n.getParent();
return parent != null && parent.getType() == Token.EXPONENT && parent.getFirstChild() == n;
}

void addList(Node firstInList) {
addList(firstInList, true, Context.OTHER, ",");
}
Expand Down
27 changes: 27 additions & 0 deletions src/com/google/javascript/jscomp/Es6ToEs3Converter.java
Expand Up @@ -188,6 +188,14 @@ public void visit(NodeTraversal t, Node n, Node parent) {
Es6TemplateLiterals.visitTemplateLiteral(t, n);
}
break;
case EXPONENT:
visitExponentiationExpression(n, parent);
break;
case ASSIGN_EXPONENT:
visitExponentiationAssignmentExpression(n, parent);
break;
default:
break;
}
}

Expand All @@ -213,6 +221,25 @@ private void initSymbolBefore(Node n) {
compiler.reportCodeChange();
}

private void visitExponentiationExpression(Node n, Node parent) {
Node left = n.removeFirstChild();
Node right = n.removeFirstChild();
Node mathDotPowCall =
IR.call(NodeUtil.newQName(compiler, "Math.pow"), left, right)
.useSourceInfoIfMissingFromForTree(n);
parent.replaceChild(n, mathDotPowCall);
compiler.reportCodeChange();
}

private void visitExponentiationAssignmentExpression(Node n, Node parent) {
Node left = n.removeFirstChild();
Node right = n.removeFirstChild();
Node mathDotPowCall = IR.call(NodeUtil.newQName(compiler, "Math.pow"), left.cloneTree(), right);
Node assign = IR.assign(left, mathDotPowCall).useSourceInfoIfMissingFromForTree(n);
parent.replaceChild(n, assign);
compiler.reportCodeChange();
}

// TODO(tbreisacher): Do this for all well-known symbols.
private void visitGetprop(NodeTraversal t, Node n) {
if (!n.matchesQualifiedName("Symbol.iterator")) {
Expand Down
8 changes: 8 additions & 0 deletions src/com/google/javascript/jscomp/parsing/IRFactory.java
Expand Up @@ -1366,6 +1366,10 @@ Node processIfStatement(IfStatementTree statementNode) {
}

Node processBinaryExpression(BinaryOperatorTree exprNode) {
if (exprNode.operator.type == TokenType.STAR_STAR
|| exprNode.operator.type == TokenType.STAR_STAR_EQUAL) {
maybeWarnForFeature(exprNode, Feature.EXPONENT_OP);
}
if (hasPendingCommentBefore(exprNode.right)) {
return newNode(
transformBinaryTokenType(exprNode.operator.type),
Expand Down Expand Up @@ -3203,6 +3207,8 @@ static Token transformBinaryTokenType(TokenType token) {
return Token.DIV;
case PERCENT:
return Token.MOD;
case STAR_STAR:
return Token.EXPONENT;

case EQUAL_EQUAL_EQUAL:
return Token.SHEQ;
Expand Down Expand Up @@ -3236,6 +3242,8 @@ static Token transformBinaryTokenType(TokenType token) {
return Token.ASSIGN_SUB;
case STAR_EQUAL:
return Token.ASSIGN_MUL;
case STAR_STAR_EQUAL:
return Token.ASSIGN_EXPONENT;
case SLASH_EQUAL:
return Token.ASSIGN_DIV;
case PERCENT_EQUAL:
Expand Down
29 changes: 28 additions & 1 deletion src/com/google/javascript/jscomp/parsing/parser/Parser.java
Expand Up @@ -2806,6 +2806,9 @@ private ParseTree parseAssignment(Expression expressionIn) {
reportError("invalid assignment target");
}
Token operator = nextToken();
if (TokenType.STAR_STAR_EQUAL.equals(operator.type)) {
features = features.require(Feature.EXPONENT_OP);
}
ParseTree right = parseAssignment(expressionIn);
return new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
}
Expand Down Expand Up @@ -2981,6 +2984,7 @@ private boolean peekAssignmentOperator() {
switch (peekType()) {
case EQUAL:
case STAR_EQUAL:
case STAR_STAR_EQUAL:
case SLASH_EQUAL:
case PERCENT_EQUAL:
case PLUS_EQUAL:
Expand Down Expand Up @@ -3193,7 +3197,7 @@ private boolean peekAdditiveOperator() {
// 11.5 Multiplicative Expression
private ParseTree parseMultiplicativeExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseUnaryExpression();
ParseTree left = parseExponentiationExpression();
while (peekMultiplicativeOperator()) {
Token operator = nextToken();
ParseTree right = parseUnaryExpression();
Expand All @@ -3213,6 +3217,29 @@ private boolean peekMultiplicativeOperator() {
}
}

private ParseTree parseExponentiationExpression() {
SourcePosition start = getTreeStartLocation();
ParseTree left = parseUnaryExpression();
if (peek(TokenType.STAR_STAR)) {
// ExponentiationExpression does not allow a UnaryExpression before '**'.
// Parentheses are required to disambiguate:
// (-x)**y is valid
// -(x**y) is valid
// -x**y is a syntax error
if (left.type == ParseTreeType.UNARY_EXPRESSION) {
reportError(
"Unary operator '%s' requires parentheses before '**'",
left.asUnaryExpression().operator);
}
features = features.require(Feature.EXPONENT_OP);
Token operator = nextToken();
ParseTree right = parseExponentiationExpression();
return new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
} else {
return left;
}
}

// 11.4 Unary Operator
private ParseTree parseUnaryExpression() {
SourcePosition start = getTreeStartLocation();
Expand Down
1 change: 1 addition & 0 deletions src/com/google/javascript/rhino/IR.java
Expand Up @@ -699,6 +699,7 @@ private static boolean mayBeExpression(Node n) {
case ASSIGN_ADD:
case ASSIGN_SUB:
case ASSIGN_MUL:
case ASSIGN_EXPONENT:
case ASSIGN_DIV:
case ASSIGN_MOD:
case AWAIT:
Expand Down
21 changes: 21 additions & 0 deletions test/com/google/javascript/jscomp/CodePrinterTest.java
Expand Up @@ -30,6 +30,27 @@
public final class CodePrinterTest extends CodePrinterTestBase {
private static final Joiner LINE_JOINER = Joiner.on('\n');

public void testExponentiationOperator() {
languageMode = LanguageMode.ECMASCRIPT7;
assertPrintSame("x**y");
// Exponentiation is right associative
assertPrint("x**(y**z)", "x**y**z");
assertPrintSame("(x**y)**z");
// parens are kept because ExponentiationExpression cannot expand to
// UnaryExpression ** ExponentiationExpression
assertPrintSame("(-x)**y");
// parens are kept because unary operators are higher precedence than '**'
assertPrintSame("-(x**y)");
// parens are not needed for a unary operator on the right operand
assertPrint("x**(-y)", "x**-y");
// NOTE: "-x**y" is a syntax error tested in ParserTest
}

public void testExponentiationAssignmentOperator() {
languageMode = LanguageMode.ECMASCRIPT7;
assertPrintSame("x**=y");
}

public void testPrint() {
assertPrint("10 + a + b", "10+a+b");
assertPrint("10 + (30*50)", "10+30*50");
Expand Down
10 changes: 10 additions & 0 deletions test/com/google/javascript/jscomp/Es6ToEs3ConverterTest.java
Expand Up @@ -258,6 +258,16 @@ public void testAnonymousSuper() {
"f(testcode$classdecl$var0)"));
}

public void testExponentiationOperator() {
setLanguage(LanguageMode.ECMASCRIPT7, LanguageMode.ECMASCRIPT5);
test("2 ** 2;", "Math.pow(2,2)");
}

public void testExponentiationAssignmentOperator() {
setLanguage(LanguageMode.ECMASCRIPT7, LanguageMode.ECMASCRIPT5);
test("x **= 2;", "x=Math.pow(x,2)");
}

public void testNewTarget() {
testError("function Foo() { new.target; }", CANNOT_CONVERT_YET);
}
Expand Down
29 changes: 29 additions & 0 deletions test/com/google/javascript/jscomp/parsing/ParserTest.java
Expand Up @@ -80,6 +80,35 @@ protected void setUp() throws Exception {
expectedFeatures = FeatureSet.ES3;
}

public void testExponentOperator() {
mode = LanguageMode.ECMASCRIPT7;
expectFeatures(Feature.EXPONENT_OP);
parse("x**y");

// Parentheses are required for disambiguation when a unary expression is desired as
// the left operand.
parse("-(x**y)");
parse("(-x)**y");
parseError("-x**y", "Unary operator '-' requires parentheses before '**'");
// Parens are not required for unary operator on the right operand
parse("x**-y");

mode = LanguageMode.ECMASCRIPT6;
expectFeatures(Feature.EXPONENT_OP);
parseWarning(
"x**y", requiresLanguageModeMessage(LanguageMode.ECMASCRIPT7, Feature.EXPONENT_OP));
}

public void testExponentAssignmentOperator() {
mode = LanguageMode.ECMASCRIPT7;
expectFeatures(Feature.EXPONENT_OP);
parse("x**=y;");

mode = LanguageMode.ECMASCRIPT6;
parseWarning(
"x**=y;", requiresLanguageModeMessage(LanguageMode.ECMASCRIPT7, Feature.EXPONENT_OP));
}

public void testFunction() {
parse("var f = function(x,y,z) { return 0; }");
parse("function f(x,y,z) { return 0; }");
Expand Down

0 comments on commit ca48609

Please sign in to comment.