From 6d2362686727cbf578bc0746c58129c85109424b Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 11 Oct 2015 17:30:28 +0300 Subject: [PATCH] Refactor precedence support --- .../{Priority.java => Precedence.java} | 20 +- .../java/org/teavm/javascript/Renderer.java | 425 ++++++++++++------ .../teavm/javascript/spi/InjectorContext.java | 5 + .../java/org/teavm/jso/impl/AstWriter.java | 54 +-- .../org/teavm/jso/impl/JSBodyAstEmitter.java | 94 +++- .../java/org/teavm/jso/impl/NameEmitter.java | 2 +- .../org/teavm/jso/ImplementationTest.java | 22 +- 7 files changed, 445 insertions(+), 177 deletions(-) rename core/src/main/java/org/teavm/javascript/{Priority.java => Precedence.java} (67%) rename core/src/main/java/org/teavm/javascript/Associativity.java => tests/src/test/java/org/teavm/jso/ImplementationTest.java (56%) diff --git a/core/src/main/java/org/teavm/javascript/Priority.java b/core/src/main/java/org/teavm/javascript/Precedence.java similarity index 67% rename from core/src/main/java/org/teavm/javascript/Priority.java rename to core/src/main/java/org/teavm/javascript/Precedence.java index e538e174a..7cedc626f 100644 --- a/core/src/main/java/org/teavm/javascript/Priority.java +++ b/core/src/main/java/org/teavm/javascript/Precedence.java @@ -19,7 +19,7 @@ package org.teavm.javascript; * * @author Alexey Andreev */ -public enum Priority { +public enum Precedence { COMMA, ASSIGNMENT, CONDITIONAL, @@ -36,5 +36,21 @@ public enum Priority { UNARY, FUNCTION_CALL, MEMBER_ACCESS, - GROUPING + GROUPING; + + private static Precedence[] cache = Precedence.values(); + + public Precedence next() { + int index = ordinal(); + return index + 1 < cache.length ? cache[index + 1] : cache[index]; + } + + public Precedence previous() { + int index = ordinal(); + return index > 0 ? cache[index - 1] : cache[index]; + } + + public static Precedence min() { + return Precedence.COMMA; + } } diff --git a/core/src/main/java/org/teavm/javascript/Renderer.java b/core/src/main/java/org/teavm/javascript/Renderer.java index 3511ebfb8..1c1805fea 100644 --- a/core/src/main/java/org/teavm/javascript/Renderer.java +++ b/core/src/main/java/org/teavm/javascript/Renderer.java @@ -18,7 +18,16 @@ package org.teavm.javascript; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import org.teavm.codegen.NamingException; import org.teavm.codegen.NamingOrderer; import org.teavm.codegen.NamingStrategy; @@ -28,12 +37,68 @@ import org.teavm.debugging.information.DebugInformationEmitter; import org.teavm.debugging.information.DeferredCallSite; import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.diagnostics.Diagnostics; -import org.teavm.javascript.ast.*; +import org.teavm.javascript.ast.AssignmentStatement; +import org.teavm.javascript.ast.AsyncMethodNode; +import org.teavm.javascript.ast.AsyncMethodPart; +import org.teavm.javascript.ast.BinaryExpr; +import org.teavm.javascript.ast.BinaryOperation; +import org.teavm.javascript.ast.BlockStatement; +import org.teavm.javascript.ast.BreakStatement; +import org.teavm.javascript.ast.ClassNode; +import org.teavm.javascript.ast.ConditionalExpr; +import org.teavm.javascript.ast.ConditionalStatement; +import org.teavm.javascript.ast.ConstantExpr; +import org.teavm.javascript.ast.ContinueStatement; +import org.teavm.javascript.ast.Expr; +import org.teavm.javascript.ast.ExprVisitor; +import org.teavm.javascript.ast.FieldNode; +import org.teavm.javascript.ast.GotoPartStatement; +import org.teavm.javascript.ast.InitClassStatement; +import org.teavm.javascript.ast.InstanceOfExpr; +import org.teavm.javascript.ast.InvocationExpr; +import org.teavm.javascript.ast.InvocationType; +import org.teavm.javascript.ast.MethodNode; +import org.teavm.javascript.ast.MethodNodeVisitor; +import org.teavm.javascript.ast.MonitorEnterStatement; +import org.teavm.javascript.ast.MonitorExitStatement; +import org.teavm.javascript.ast.NativeMethodNode; +import org.teavm.javascript.ast.NewArrayExpr; +import org.teavm.javascript.ast.NewExpr; +import org.teavm.javascript.ast.NewMultiArrayExpr; +import org.teavm.javascript.ast.NodeLocation; +import org.teavm.javascript.ast.NodeModifier; +import org.teavm.javascript.ast.QualificationExpr; +import org.teavm.javascript.ast.RegularMethodNode; +import org.teavm.javascript.ast.ReturnStatement; +import org.teavm.javascript.ast.SequentialStatement; +import org.teavm.javascript.ast.Statement; +import org.teavm.javascript.ast.StatementVisitor; +import org.teavm.javascript.ast.StaticClassExpr; +import org.teavm.javascript.ast.SubscriptExpr; +import org.teavm.javascript.ast.SwitchClause; +import org.teavm.javascript.ast.SwitchStatement; +import org.teavm.javascript.ast.ThrowStatement; +import org.teavm.javascript.ast.TryCatchStatement; +import org.teavm.javascript.ast.UnaryExpr; +import org.teavm.javascript.ast.UnwrapArrayExpr; +import org.teavm.javascript.ast.VariableExpr; +import org.teavm.javascript.ast.WhileStatement; import org.teavm.javascript.spi.GeneratorContext; import org.teavm.javascript.spi.InjectedBy; import org.teavm.javascript.spi.Injector; import org.teavm.javascript.spi.InjectorContext; -import org.teavm.model.*; +import org.teavm.model.AnnotationHolder; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReference; +import org.teavm.model.ListableClassHolderSource; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; /** * @@ -60,22 +125,13 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private Set asyncFamilyMethods; private Diagnostics diagnostics; private boolean async; - private Priority priority; - private Associativity associativity; - private boolean wasGrouped; - private Deque precedenceStack = new ArrayDeque<>(); + private Precedence precedence; private Map blockIdMap = new HashMap<>(); private List> debugNames = new ArrayList<>(); private List cachedVariableNames = new ArrayList<>(); private boolean end; private int currentPart; - private static class OperatorPrecedence { - Priority priority; - Associativity associativity; - boolean wasGrouped; - } - private static class InjectorHolder { public final Injector injector; @@ -870,22 +926,19 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (statement.isAsync()) { writer.append(tempVarName()); } else { - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.COMMA; statement.getLeftValue().acceptVisitor(this); } writer.ws().append("=").ws(); } - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.COMMA; statement.getRightValue().acceptVisitor(this); debugEmitter.emitCallSite(); writer.append(";").softNewLine(); if (statement.isAsync()) { emitSuspendChecker(); if (statement.getLeftValue() != null) { - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.COMMA; statement.getLeftValue().acceptVisitor(this); writer.ws().append("=").ws().append(tempVarName()).append(";").softNewLine(); } @@ -918,8 +971,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } prevCallSite = debugEmitter.emitCallSite(); writer.append("if").ws().append("("); - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.COMMA; statement.getCondition().acceptVisitor(this); if (statement.getCondition().getLocation() != null) { popLocation(); @@ -958,8 +1010,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } prevCallSite = debugEmitter.emitCallSite(); writer.append("switch").ws().append("("); - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.min(); statement.getValue().acceptVisitor(this); if (statement.getValue().getLocation() != null) { popLocation(); @@ -1008,8 +1059,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append("while").ws().append("("); if (statement.getCondition() != null) { prevCallSite = debugEmitter.emitCallSite(); - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.min(); statement.getCondition().acceptVisitor(this); debugEmitter.emitCallSite(); if (statement.getCondition().getLocation() != null) { @@ -1112,8 +1162,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (statement.getResult() != null) { writer.append(' '); prevCallSite = debugEmitter.emitCallSite(); - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.min(); statement.getResult().acceptVisitor(this); debugEmitter.emitCallSite(); } @@ -1135,8 +1184,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } writer.appendFunction("$rt_throw").append("("); prevCallSite = debugEmitter.emitCallSite(); - priority = Priority.COMMA; - associativity = Associativity.NONE; + precedence = Precedence.min(); statement.getException().acceptVisitor(this); writer.append(");").softNewLine(); debugEmitter.emitCallSite(); @@ -1224,17 +1272,61 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext return minifying ? "$T" : "$thread"; } - private void visitBinary(BinaryExpr expr, String op, Priority priority, Associativity associativity) { + private void visitBinary(BinaryExpr expr, String op) { try { if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(priority, associativity == Associativity.LEFT ? associativity : Associativity.NONE, true); + + Precedence outerPrecedence = precedence; + Precedence innerPrecedence = getPrecedence(expr.getOperation()); + if (innerPrecedence.ordinal() < outerPrecedence.ordinal()) { + writer.append('('); + } + + switch (expr.getOperation()) { + case ADD: + case SUBTRACT: + case MULTIPLY: + case DIVIDE: + case MODULO: + case AND: + case OR: + case BITWISE_AND: + case BITWISE_OR: + case BITWISE_XOR: + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + precedence = innerPrecedence; + break; + default: + precedence = innerPrecedence.next(); + } expr.getFirstOperand().acceptVisitor(this); + writer.ws().append(op).ws(); - this.associativity = associativity == Associativity.RIGHT ? associativity : Associativity.NONE; + + switch (expr.getOperation()) { + case ADD: + case MULTIPLY: + case AND: + case OR: + case BITWISE_AND: + case BITWISE_OR: + case BITWISE_XOR: + precedence = innerPrecedence; + break; + default: + precedence = innerPrecedence.next(); + break; + } expr.getSecondOperand().acceptVisitor(this); - exitPriority(); + + if (innerPrecedence.ordinal() < outerPrecedence.ordinal()) { + writer.append(')'); + } + if (expr.getLocation() != null) { popLocation(); } @@ -1243,31 +1335,42 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } } - private void enterPriority(Priority priority, Associativity associativity, boolean autoGroup) throws IOException { - OperatorPrecedence precedence = new OperatorPrecedence(); - precedence.wasGrouped = this.wasGrouped; - precedence.priority = this.priority; - precedence.associativity = this.associativity; - precedenceStack.push(precedence); - wasGrouped = false; - if (autoGroup && (priority.ordinal() < this.priority.ordinal() - || priority.ordinal() == this.priority.ordinal() - && (associativity != this.associativity || associativity == Associativity.NONE))) { - wasGrouped = true; - writer.append('('); + private static Precedence getPrecedence(BinaryOperation op) { + switch (op) { + case ADD: + case SUBTRACT: + return Precedence.ADDITION; + case MULTIPLY: + case DIVIDE: + case MODULO: + return Precedence.MULTIPLICATION; + case AND: + return Precedence.LOGICAL_AND; + case OR: + return Precedence.LOGICAL_OR; + case STRICT_EQUALS: + case STRICT_NOT_EQUALS: + case EQUALS: + case NOT_EQUALS: + return Precedence.EQUALITY; + case GREATER: + case GREATER_OR_EQUALS: + case LESS: + case LESS_OR_EQUALS: + return Precedence.COMPARISON; + case BITWISE_AND: + return Precedence.BITWISE_AND; + case BITWISE_OR: + return Precedence.BITWISE_OR; + case BITWISE_XOR: + return Precedence.BITWISE_XOR; + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: + return Precedence.BITWISE_SHIFT; + default: + return Precedence.GROUPING; } - this.priority = priority; - this.associativity = associativity; - } - - private void exitPriority() throws IOException { - if (wasGrouped) { - writer.append(')'); - } - OperatorPrecedence precedence = precedenceStack.pop(); - this.priority = precedence.priority; - this.associativity = precedence.associativity; - this.wasGrouped = precedence.wasGrouped; } private void visitBinaryFunction(BinaryExpr expr, String function) { @@ -1275,14 +1378,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.append(function); writer.append('('); + precedence = Precedence.min(); expr.getFirstOperand().acceptVisitor(this); writer.append(",").ws(); + precedence = Precedence.min(); expr.getSecondOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); if (expr.getLocation() != null) { popLocation(); } @@ -1295,58 +1398,58 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public void visit(BinaryExpr expr) { switch (expr.getOperation()) { case ADD: - visitBinary(expr, "+", Priority.ADDITION, Associativity.LEFT); + visitBinary(expr, "+"); break; case ADD_LONG: visitBinaryFunction(expr, "Long_add"); break; case SUBTRACT: - visitBinary(expr, "-", Priority.ADDITION, Associativity.LEFT); + visitBinary(expr, "-"); break; case SUBTRACT_LONG: visitBinaryFunction(expr, "Long_sub"); break; case MULTIPLY: - visitBinary(expr, "*", Priority.MULTIPLICATION, Associativity.LEFT); + visitBinary(expr, "*"); break; case MULTIPLY_LONG: visitBinaryFunction(expr, "Long_mul"); break; case DIVIDE: - visitBinary(expr, "/", Priority.MULTIPLICATION, Associativity.LEFT); + visitBinary(expr, "/"); break; case DIVIDE_LONG: visitBinaryFunction(expr, "Long_div"); break; case MODULO: - visitBinary(expr, "%", Priority.MULTIPLICATION, Associativity.LEFT); + visitBinary(expr, "%"); break; case MODULO_LONG: visitBinaryFunction(expr, "Long_rem"); break; case EQUALS: - visitBinary(expr, "==", Priority.EQUALITY, Associativity.LEFT); + visitBinary(expr, "=="); break; case NOT_EQUALS: - visitBinary(expr, "!=", Priority.EQUALITY, Associativity.LEFT); + visitBinary(expr, "!="); break; case GREATER: - visitBinary(expr, ">", Priority.COMPARISON, Associativity.LEFT); + visitBinary(expr, ">"); break; case GREATER_OR_EQUALS: - visitBinary(expr, ">=", Priority.COMPARISON, Associativity.LEFT); + visitBinary(expr, ">="); break; case LESS: - visitBinary(expr, "<", Priority.COMPARISON, Associativity.LEFT); + visitBinary(expr, "<"); break; case LESS_OR_EQUALS: - visitBinary(expr, "<=", Priority.COMPARISON, Associativity.LEFT); + visitBinary(expr, "<="); break; case STRICT_EQUALS: - visitBinary(expr, "===", Priority.COMPARISON, Associativity.LEFT); + visitBinary(expr, "==="); break; case STRICT_NOT_EQUALS: - visitBinary(expr, "!==", Priority.COMPARISON, Associativity.LEFT); + visitBinary(expr, "!=="); break; case COMPARE: visitBinaryFunction(expr, naming.getNameForFunction("$rt_compare")); @@ -1355,43 +1458,43 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext visitBinaryFunction(expr, "Long_compare"); break; case OR: - visitBinary(expr, "||", Priority.LOGICAL_OR, Associativity.LEFT); + visitBinary(expr, "||"); break; case AND: - visitBinary(expr, "&&", Priority.LOGICAL_AND, Associativity.LEFT); + visitBinary(expr, "&&"); break; case BITWISE_OR: - visitBinary(expr, "|", Priority.BITWISE_OR, Associativity.LEFT); + visitBinary(expr, "|"); break; case BITWISE_OR_LONG: visitBinaryFunction(expr, "Long_or"); break; case BITWISE_AND: - visitBinary(expr, "&", Priority.BITWISE_AND, Associativity.LEFT); + visitBinary(expr, "&"); break; case BITWISE_AND_LONG: visitBinaryFunction(expr, "Long_and"); break; case BITWISE_XOR: - visitBinary(expr, "^", Priority.BITWISE_XOR, Associativity.LEFT); + visitBinary(expr, "^"); break; case BITWISE_XOR_LONG: visitBinaryFunction(expr, "Long_xor"); break; case LEFT_SHIFT: - visitBinary(expr, "<<", Priority.BITWISE_SHIFT, Associativity.LEFT); + visitBinary(expr, "<<"); break; case LEFT_SHIFT_LONG: visitBinaryFunction(expr, "Long_shl"); break; case RIGHT_SHIFT: - visitBinary(expr, ">>", Priority.BITWISE_SHIFT, Associativity.LEFT); + visitBinary(expr, ">>"); break; case RIGHT_SHIFT_LONG: visitBinaryFunction(expr, "Long_shr"); break; case UNSIGNED_RIGHT_SHIFT: - visitBinary(expr, ">>>", Priority.BITWISE_SHIFT, Associativity.LEFT); + visitBinary(expr, ">>>"); break; case UNSIGNED_RIGHT_SHIFT_LONG: visitBinaryFunction(expr, "Long_shru"); @@ -1405,92 +1508,109 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } + Precedence outerPrecedence = precedence; switch (expr.getOperation()) { - case NOT: - enterPriority(Priority.UNARY, Associativity.RIGHT, true); + case NOT: { + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append('('); + } writer.append("!"); + precedence = Precedence.UNARY; expr.getOperand().acceptVisitor(this); - exitPriority(); + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append(')'); + } break; + } case NEGATE: - enterPriority(Priority.MULTIPLICATION, Associativity.RIGHT, true); + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append('('); + } writer.append("-"); + precedence = Precedence.UNARY; expr.getOperand().acceptVisitor(this); - exitPriority(); + if (outerPrecedence.ordinal() > Precedence.UNARY.ordinal()) { + writer.append(')'); + } break; case LENGTH: - enterPriority(Priority.MEMBER_ACCESS, Associativity.LEFT, true); + precedence = Precedence.MEMBER_ACCESS; expr.getOperand().acceptVisitor(this); - exitPriority(); writer.append(".length"); break; case INT_TO_LONG: - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.append("Long_fromInt("); + precedence = Precedence.min(); expr.getOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); break; case NUM_TO_LONG: - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.append("Long_fromNumber("); + precedence = Precedence.min(); expr.getOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); break; case LONG_TO_NUM: - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.append("Long_toNumber("); + precedence = Precedence.min(); expr.getOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); break; case LONG_TO_INT: - enterPriority(Priority.MEMBER_ACCESS, Associativity.LEFT, false); + precedence = Precedence.MEMBER_ACCESS; expr.getOperand().acceptVisitor(this); - exitPriority(); writer.append(".lo"); break; case NEGATE_LONG: - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.append("Long_neg("); + precedence = Precedence.min(); expr.getOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); break; case NOT_LONG: - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.append("Long_not("); + precedence = Precedence.min(); expr.getOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); break; case INT_TO_BYTE: - enterPriority(Priority.BITWISE_SHIFT, Associativity.LEFT, true); - writer.append("("); + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append('('); + } + precedence = Precedence.BITWISE_SHIFT; expr.getOperand().acceptVisitor(this); - writer.ws().append("<<").ws().append("24)").ws().append(">>").ws().append("24"); - exitPriority(); + writer.ws().append("<<").ws().append("24").ws().append(">>").ws().append("24"); + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append(')'); + } break; case INT_TO_SHORT: - enterPriority(Priority.BITWISE_SHIFT, Associativity.LEFT, true); - writer.append("("); + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append('('); + } + precedence = Precedence.BITWISE_SHIFT; expr.getOperand().acceptVisitor(this); - writer.ws().append("<<").ws().append("16)").ws().append(">>").ws().append("16"); - exitPriority(); + writer.ws().append("<<").ws().append("16").ws().append(">>").ws().append("16"); + if (outerPrecedence.ordinal() > Precedence.BITWISE_SHIFT.ordinal()) { + writer.append(')'); + } break; case INT_TO_CHAR: - enterPriority(Priority.BITWISE_AND, Associativity.LEFT, true); + if (outerPrecedence.ordinal() > Precedence.BITWISE_AND.ordinal()) { + writer.append('('); + } + precedence = Precedence.BITWISE_AND; expr.getOperand().acceptVisitor(this); writer.ws().append("&").ws().append("65535"); - exitPriority(); + if (outerPrecedence.ordinal() > Precedence.BITWISE_AND.ordinal()) { + writer.append('('); + } break; case NULL_CHECK: - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.appendFunction("$rt_nullCheck").append("("); + precedence = Precedence.min(); expr.getOperand().acceptVisitor(this); writer.append(')'); - exitPriority(); break; } if (expr.getLocation() != null) { @@ -1507,13 +1627,25 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(priority, Associativity.RIGHT, async); + + Precedence outerPrecedence = precedence; + if (outerPrecedence.ordinal() > Precedence.CONDITIONAL.ordinal()) { + writer.append('('); + } + + precedence = Precedence.CONDITIONAL.next(); expr.getCondition().acceptVisitor(this); writer.ws().append("?").ws(); + precedence = Precedence.CONDITIONAL.next(); expr.getConsequent().acceptVisitor(this); writer.ws().append(":").ws(); + precedence = Precedence.CONDITIONAL; expr.getAlternative().acceptVisitor(this); - exitPriority(); + + if (outerPrecedence.ordinal() > Precedence.CONDITIONAL.ordinal()) { + writer.append('('); + } + if (expr.getLocation() != null) { popLocation(); } @@ -1529,12 +1661,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext pushLocation(expr.getLocation()); } String str = constantToString(expr.getValue()); - if (str.startsWith("-")) { - enterPriority(Priority.MULTIPLICATION, Associativity.NONE, true); + if (str.startsWith("-") && precedence.ordinal() > Precedence.MULTIPLICATION.ordinal()) { + writer.append('('); } writer.append(str); - if (str.startsWith("-")) { - exitPriority(); + if (str.startsWith("-") && precedence.ordinal() > Precedence.MULTIPLICATION.ordinal()) { + writer.append(')'); } if (expr.getLocation() != null) { popLocation(); @@ -1691,14 +1823,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(Priority.MEMBER_ACCESS, Associativity.LEFT, true); + precedence = Precedence.MEMBER_ACCESS; expr.getArray().acceptVisitor(this); writer.append('['); - enterPriority(Priority.COMMA, Associativity.NONE, false); + precedence = Precedence.min(); expr.getIndex().acceptVisitor(this); - exitPriority(); writer.append(']'); - exitPriority(); if (expr.getLocation() != null) { popLocation(); } @@ -1713,13 +1843,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(Priority.MEMBER_ACCESS, Associativity.LEFT, true); + precedence = Precedence.MEMBER_ACCESS; expr.getArray().acceptVisitor(this); writer.append(".data"); if (expr.getLocation() != null) { popLocation(); } - exitPriority(); } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1736,6 +1865,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); } else { if (expr.getType() == InvocationType.DYNAMIC) { + precedence = Precedence.MEMBER_ACCESS; expr.getArguments().get(0).acceptVisitor(this); } MethodReference method = expr.getMethod(); @@ -1746,7 +1876,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext lastCallSite = callSite; } boolean virtual = false; - enterPriority(Priority.COMMA, Associativity.NONE, false); switch (expr.getType()) { case STATIC: writer.append(naming.getFullNameFor(method)).append("("); @@ -1755,15 +1884,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 0) { writer.append(",").ws(); } + precedence = Precedence.min(); expr.getArguments().get(i).acceptVisitor(this); } break; case SPECIAL: writer.append(naming.getFullNameFor(method)).append("("); prevCallSite = debugEmitter.emitCallSite(); + precedence = Precedence.min(); expr.getArguments().get(0).acceptVisitor(this); for (int i = 1; i < expr.getArguments().size(); ++i) { writer.append(",").ws(); + precedence = Precedence.min(); expr.getArguments().get(i).acceptVisitor(this); } break; @@ -1774,6 +1906,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 1) { writer.append(",").ws(); } + precedence = Precedence.min(); expr.getArguments().get(i).acceptVisitor(this); } virtual = true; @@ -1785,12 +1918,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 0) { writer.append(",").ws(); } + precedence = Precedence.min(); expr.getArguments().get(i).acceptVisitor(this); } break; } writer.append(')'); - exitPriority(); if (lastCallSite != null) { if (virtual) { lastCallSite.setVirtualMethod(expr.getMethod()); @@ -1817,13 +1950,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(Priority.MEMBER_ACCESS, Associativity.LEFT, true); + precedence = Precedence.MEMBER_ACCESS; expr.getQualified().acceptVisitor(this); writer.append('.').appendField(expr.getField()); if (expr.getLocation() != null) { popLocation(); } - exitPriority(); } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1835,9 +1967,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (expr.getLocation() != null) { pushLocation(expr.getLocation()); } - enterPriority(Priority.FUNCTION_CALL, Associativity.RIGHT, true); + precedence = Precedence.FUNCTION_CALL; writer.append("new ").append(naming.getNameFor(expr.getConstructedClass())); - exitPriority(); if (expr.getLocation() != null) { popLocation(); } @@ -1853,46 +1984,53 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext pushLocation(expr.getLocation()); } ValueType type = expr.getType(); - enterPriority(Priority.COMMA, Associativity.NONE, false); if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive) type).getKind()) { case BOOLEAN: writer.append("$rt_createBooleanArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case BYTE: writer.append("$rt_createByteArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case SHORT: writer.append("$rt_createShortArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case INTEGER: writer.append("$rt_createIntArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case LONG: writer.append("$rt_createLongArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case FLOAT: writer.append("$rt_createFloatArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case DOUBLE: writer.append("$rt_createDoubleArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; case CHARACTER: writer.append("$rt_createCharArray("); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); break; @@ -1900,10 +2038,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } else { writer.appendFunction("$rt_createArray").append("(").append(typeToClsString(naming, expr.getType())) .append(",").ws(); + precedence = Precedence.min(); expr.getLength().acceptVisitor(this); writer.append(")"); } - exitPriority(); if (expr.getLocation() != null) { popLocation(); } @@ -1922,7 +2060,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext for (int i = 0; i < expr.getDimensions().size(); ++i) { type = ((ValueType.Array) type).getItemType(); } - enterPriority(Priority.COMMA, Associativity.NONE, false); if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive) type).getKind()) { case BOOLEAN: @@ -1963,10 +2100,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws(); } first = false; + precedence = Precedence.min(); dimension.acceptVisitor(this); } writer.append("])"); - exitPriority(); if (expr.getLocation() != null) { popLocation(); } @@ -1985,21 +2122,19 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext String clsName = ((ValueType.Object) expr.getType()).getClassName(); ClassHolder cls = classSource.get(clsName); if (cls != null && !cls.getModifiers().contains(ElementModifier.INTERFACE)) { - enterPriority(Priority.COMPARISON, Associativity.LEFT, true); + precedence = Precedence.COMPARISON.next(); expr.getExpr().acceptVisitor(this); writer.append(" instanceof ").appendClass(clsName); - exitPriority(); if (expr.getLocation() != null) { popLocation(); } return; } } - enterPriority(Priority.COMMA, Associativity.NONE, false); writer.appendFunction("$rt_isInstance").append("("); + precedence = Precedence.min(); expr.getExpr().acceptVisitor(this); writer.append(",").ws().append(typeToClsString(naming, expr.getType())).append(")"); - exitPriority(); if (expr.getLocation() != null) { popLocation(); } @@ -2095,6 +2230,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext MethodReference monitorEnterRef = new MethodReference( Object.class, "monitorEnter", Object.class, void.class); writer.appendMethodBody(monitorEnterRef).append("("); + precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); emitSuspendChecker(); @@ -2102,6 +2238,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext MethodReference monitorEnterRef = new MethodReference( Object.class, "monitorEnterSync", Object.class, void.class); writer.appendMethodBody(monitorEnterRef).append('('); + precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); } @@ -2124,12 +2261,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext MethodReference monitorExitRef = new MethodReference( Object.class, "monitorExit", Object.class, void.class); writer.appendMethodBody(monitorExitRef).append("("); + precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); } else { MethodReference monitorEnterRef = new MethodReference( Object.class, "monitorExitSync", Object.class, void.class); writer.appendMethodBody(monitorEnterRef).append('('); + precedence = Precedence.min(); statement.getObjectRef().acceptVisitor(this); writer.append(");").softNewLine(); } @@ -2175,6 +2314,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private class InjectorContextImpl implements InjectorContext { private List arguments; + private Precedence precedence = Renderer.this.precedence; public InjectorContextImpl(List arguments) { this.arguments = arguments; @@ -2207,6 +2347,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void writeExpr(Expr expr) throws IOException { + writeExpr(expr, Precedence.GROUPING); + } + + @Override + public void writeExpr(Expr expr, Precedence precedence) throws IOException { + Renderer.this.precedence = precedence; expr.acceptVisitor(Renderer.this); } @@ -2224,6 +2370,11 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public Properties getProperties() { return new Properties(properties); } + + @Override + public Precedence getPrecedence() { + return precedence; + } } @Override diff --git a/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java b/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java index 4bc112be5..15af2fcaf 100644 --- a/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java +++ b/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.Properties; import org.teavm.codegen.SourceWriter; import org.teavm.common.ServiceRepository; +import org.teavm.javascript.Precedence; import org.teavm.javascript.ast.Expr; import org.teavm.model.ValueType; @@ -42,4 +43,8 @@ public interface InjectorContext extends ServiceRepository { void writeType(ValueType type) throws IOException; void writeExpr(Expr expr) throws IOException; + + void writeExpr(Expr expr, Precedence precedence) throws IOException; + + Precedence getPrecedence(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/AstWriter.java b/jso/impl/src/main/java/org/teavm/jso/impl/AstWriter.java index 4c1a792d1..e03c54f84 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/AstWriter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/AstWriter.java @@ -78,23 +78,23 @@ import org.teavm.model.MethodReference; * @author Alexey Andreev */ public class AstWriter { - private static final int PRECEDENCE_MEMBER = 2; - private static final int PRECEDENCE_FUNCTION = 3; - private static final int PRECEDENCE_POSTFIX = 4; - private static final int PRECEDENCE_PREFIX = 5; - private static final int PRECEDENCE_MUL = 6; - private static final int PRECEDENCE_ADD = 7; - private static final int PRECEDENCE_SHIFT = 8; - private static final int PRECEDENCE_RELATION = 9; - private static final int PRECEDENCE_EQUALITY = 10; - private static final int PRECEDENCE_BITWISE_AND = 11; - private static final int PRECEDENCE_BITWISE_XOR = 12; - private static final int PRECEDENCE_BITWISE_OR = 13; - private static final int PRECEDENCE_AND = 14; - private static final int PRECEDENCE_OR = 15; - private static final int PRECEDENCE_COND = 16; - private static final int PRECEDENCE_ASSIGN = 17; - private static final int PRECEDENCE_COMMA = 18; + public static final int PRECEDENCE_MEMBER = 2; + public static final int PRECEDENCE_FUNCTION = 3; + public static final int PRECEDENCE_POSTFIX = 4; + public static final int PRECEDENCE_PREFIX = 5; + public static final int PRECEDENCE_MUL = 6; + public static final int PRECEDENCE_ADD = 7; + public static final int PRECEDENCE_SHIFT = 8; + public static final int PRECEDENCE_RELATION = 9; + public static final int PRECEDENCE_EQUALITY = 10; + public static final int PRECEDENCE_BITWISE_AND = 11; + public static final int PRECEDENCE_BITWISE_XOR = 12; + public static final int PRECEDENCE_BITWISE_OR = 13; + public static final int PRECEDENCE_AND = 14; + public static final int PRECEDENCE_OR = 15; + public static final int PRECEDENCE_COND = 16; + public static final int PRECEDENCE_ASSIGN = 17; + public static final int PRECEDENCE_COMMA = 18; private SourceWriter writer; private Map nameMap = new HashMap<>(); private Set aliases = new HashSet<>(); @@ -108,13 +108,13 @@ public class AstWriter { return; } if (aliases.add(name)) { - nameMap.put(name, () -> writer.append(name)); + nameMap.put(name, p -> writer.append(name)); return; } for (int i = 0;; ++i) { String alias = name + "_" + i; if (aliases.add(alias)) { - nameMap.put(name, () -> writer.append(alias)); + nameMap.put(name, p -> writer.append(alias)); return; } } @@ -124,12 +124,12 @@ public class AstWriter { if (!aliases.add(alias)) { throw new IllegalArgumentException("Alias " + alias + " is already occupied"); } - nameMap.put(name, () -> writer.append(alias)); + nameMap.put(name, p -> writer.append(alias)); } public void reserveName(String name) { aliases.add(name); - nameMap.put(name, () -> writer.append(name)); + nameMap.put(name, p -> writer.append(name)); } public void declareNameEmitter(String name, NameEmitter emitter) { @@ -157,7 +157,7 @@ public class AstWriter { print(node, PRECEDENCE_COMMA); } - private void print(AstNode node, int precedence) throws IOException { + public void print(AstNode node, int precedence) throws IOException { switch (node.getType()) { case Token.SCRIPT: print((AstRoot) node); @@ -192,7 +192,7 @@ public class AstWriter { break; case Token.THIS: if (nameMap.containsKey("this")) { - nameMap.get("this").emit(); + nameMap.get("this").emit(precedence); } else { writer.append("this"); } @@ -201,7 +201,7 @@ public class AstWriter { writer.append("null"); break; case Token.NAME: - print((Name) node); + print((Name) node, precedence); break; case Token.REGEXP: print((RegExpLiteral) node); @@ -633,12 +633,12 @@ public class AstWriter { writer.append(node.getQuoteCharacter()); } - private void print(Name node) throws IOException { + private void print(Name node, int precedence) throws IOException { NameEmitter alias = nameMap.get(node.getIdentifier()); if (alias == null) { - alias = () -> writer.append(node.getIdentifier()); + alias = prec -> writer.append(node.getIdentifier()); } - alias.emit(); + alias.emit(precedence); } private void print(RegExpLiteral node) throws IOException { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java index 29c46d1e8..ac3cbe045 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java @@ -18,6 +18,7 @@ package org.teavm.jso.impl; import java.io.IOException; import org.mozilla.javascript.ast.AstNode; import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.Precedence; import org.teavm.javascript.spi.GeneratorContext; import org.teavm.javascript.spi.InjectorContext; import org.teavm.model.MethodReference; @@ -43,14 +44,97 @@ class JSBodyAstEmitter implements JSBodyEmitter { int paramIndex = 0; if (!isStatic) { int index = paramIndex++; - astWriter.declareNameEmitter("this", () -> context.writeExpr(context.getArgument(index))); + astWriter.declareNameEmitter("this", prec -> context.writeExpr(context.getArgument(index))); } for (int i = 0; i < parameterNames.length; ++i) { int index = paramIndex++; - astWriter.declareNameEmitter(parameterNames[i], () -> context.writeExpr(context.getArgument(index))); + astWriter.declareNameEmitter(parameterNames[i], + prec -> context.writeExpr(context.getArgument(index), convert(prec))); } astWriter.hoist(ast); - astWriter.print(ast); + astWriter.print(ast, convert(context.getPrecedence())); + } + + private static int convert(Precedence precedence) { + switch (precedence) { + case ADDITION: + return AstWriter.PRECEDENCE_ADD; + case ASSIGNMENT: + return AstWriter.PRECEDENCE_ASSIGN; + case BITWISE_AND: + return AstWriter.PRECEDENCE_BITWISE_AND; + case BITWISE_OR: + return AstWriter.PRECEDENCE_BITWISE_OR; + case BITWISE_XOR: + return AstWriter.PRECEDENCE_BITWISE_XOR; + case BITWISE_SHIFT: + return AstWriter.PRECEDENCE_SHIFT; + case COMMA: + return AstWriter.PRECEDENCE_COMMA; + case COMPARISON: + return AstWriter.PRECEDENCE_RELATION; + case CONDITIONAL: + return AstWriter.PRECEDENCE_COND; + case EQUALITY: + return AstWriter.PRECEDENCE_EQUALITY; + case FUNCTION_CALL: + return AstWriter.PRECEDENCE_FUNCTION; + case GROUPING: + return 1; + case LOGICAL_AND: + return AstWriter.PRECEDENCE_AND; + case LOGICAL_OR: + return AstWriter.PRECEDENCE_OR; + case MEMBER_ACCESS: + return AstWriter.PRECEDENCE_MEMBER; + case MULTIPLICATION: + return AstWriter.PRECEDENCE_MUL; + case UNARY: + return AstWriter.PRECEDENCE_PREFIX; + default: + return AstWriter.PRECEDENCE_COMMA; + } + } + + private static Precedence convert(int precedence) { + switch (precedence) { + case AstWriter.PRECEDENCE_ADD: + return Precedence.ADDITION; + case AstWriter.PRECEDENCE_ASSIGN: + return Precedence.ASSIGNMENT; + case AstWriter.PRECEDENCE_BITWISE_AND: + return Precedence.BITWISE_AND; + case AstWriter.PRECEDENCE_BITWISE_OR: + return Precedence.BITWISE_OR; + case AstWriter.PRECEDENCE_BITWISE_XOR: + return Precedence.BITWISE_XOR; + case AstWriter.PRECEDENCE_SHIFT: + return Precedence.BITWISE_SHIFT; + case AstWriter.PRECEDENCE_COMMA: + return Precedence.COMMA; + case AstWriter.PRECEDENCE_RELATION: + return Precedence.COMPARISON; + case AstWriter.PRECEDENCE_COND: + return Precedence.CONDITIONAL; + case AstWriter.PRECEDENCE_EQUALITY: + return Precedence.EQUALITY; + case AstWriter.PRECEDENCE_FUNCTION: + return Precedence.FUNCTION_CALL; + case 1: + return Precedence.GROUPING; + case AstWriter.PRECEDENCE_AND: + return Precedence.LOGICAL_AND; + case AstWriter.PRECEDENCE_OR: + return Precedence.LOGICAL_OR; + case AstWriter.PRECEDENCE_MEMBER: + return Precedence.MEMBER_ACCESS; + case AstWriter.PRECEDENCE_MUL: + return Precedence.MULTIPLICATION; + case AstWriter.PRECEDENCE_PREFIX: + return Precedence.UNARY; + default: + return Precedence.min(); + } } @Override @@ -59,11 +143,11 @@ class JSBodyAstEmitter implements JSBodyEmitter { int paramIndex = 1; if (!isStatic) { int index = paramIndex++; - astWriter.declareNameEmitter("this", () -> writer.append(context.getParameterName(index))); + astWriter.declareNameEmitter("this", prec -> writer.append(context.getParameterName(index))); } for (int i = 0; i < parameterNames.length; ++i) { int index = paramIndex++; - astWriter.declareNameEmitter(parameterNames[i], () -> writer.append(context.getParameterName(index))); + astWriter.declareNameEmitter(parameterNames[i], prec -> writer.append(context.getParameterName(index))); } astWriter.hoist(ast); astWriter.print(ast); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/NameEmitter.java b/jso/impl/src/main/java/org/teavm/jso/impl/NameEmitter.java index 45f759b0e..5c4237f8b 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/NameEmitter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/NameEmitter.java @@ -22,5 +22,5 @@ import java.io.IOException; * @author Alexey Andreev */ interface NameEmitter { - void emit() throws IOException; + void emit(int precedence) throws IOException; } diff --git a/core/src/main/java/org/teavm/javascript/Associativity.java b/tests/src/test/java/org/teavm/jso/ImplementationTest.java similarity index 56% rename from core/src/main/java/org/teavm/javascript/Associativity.java rename to tests/src/test/java/org/teavm/jso/ImplementationTest.java index cf28fef26..d8d357641 100644 --- a/core/src/main/java/org/teavm/javascript/Associativity.java +++ b/tests/src/test/java/org/teavm/jso/ImplementationTest.java @@ -13,14 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.javascript; +package org.teavm.jso; + +import static org.junit.Assert.*; +import org.junit.Test; +import org.teavm.jso.JSBody; /** * * @author Alexey Andreev */ -public enum Associativity { - LEFT, - RIGHT, - NONE +public class ImplementationTest { + @Test + public void respectsPrecedence() { + assertEquals(12, mul(add(2, 2), 3)); + assertEquals(8, add(2, mul(2, 3))); + } + + @JSBody(params = { "a", "b" }, script = "return a + b;") + static final native int add(int a, int b); + + @JSBody(params = { "a", "b" }, script = "return a * b;") + static final native int mul(int a, int b); }