blob: eaefc56c01cf1b9bb368ae7c5aebbeb9352ff3c3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.classgen.asm;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ElvisOperatorExpression;
import org.codehaus.groovy.ast.expr.EmptyExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NotExpression;
import org.codehaus.groovy.ast.expr.PostfixExpression;
import org.codehaus.groovy.ast.expr.PrefixExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.TernaryExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.tools.WideningCategories;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.BytecodeExpression;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL;
import static org.codehaus.groovy.syntax.Types.BITWISE_OR;
import static org.codehaus.groovy.syntax.Types.BITWISE_OR_EQUAL;
import static org.codehaus.groovy.syntax.Types.BITWISE_XOR;
import static org.codehaus.groovy.syntax.Types.BITWISE_XOR_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN;
import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_IDENTICAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN;
import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IDENTICAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IN;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_INSTANCEOF;
import static org.codehaus.groovy.syntax.Types.COMPARE_TO;
import static org.codehaus.groovy.syntax.Types.DIVIDE;
import static org.codehaus.groovy.syntax.Types.DIVIDE_EQUAL;
import static org.codehaus.groovy.syntax.Types.ELVIS_EQUAL;
import static org.codehaus.groovy.syntax.Types.EQUAL;
import static org.codehaus.groovy.syntax.Types.FIND_REGEX;
import static org.codehaus.groovy.syntax.Types.INTDIV;
import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL;
import static org.codehaus.groovy.syntax.Types.KEYWORD_IN;
import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF;
import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT;
import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT_EQUAL;
import static org.codehaus.groovy.syntax.Types.LEFT_SQUARE_BRACKET;
import static org.codehaus.groovy.syntax.Types.LOGICAL_AND;
import static org.codehaus.groovy.syntax.Types.LOGICAL_OR;
import static org.codehaus.groovy.syntax.Types.MATCH_REGEX;
import static org.codehaus.groovy.syntax.Types.MINUS;
import static org.codehaus.groovy.syntax.Types.MINUS_EQUAL;
import static org.codehaus.groovy.syntax.Types.MOD;
import static org.codehaus.groovy.syntax.Types.MOD_EQUAL;
import static org.codehaus.groovy.syntax.Types.MULTIPLY;
import static org.codehaus.groovy.syntax.Types.MULTIPLY_EQUAL;
import static org.codehaus.groovy.syntax.Types.PLUS;
import static org.codehaus.groovy.syntax.Types.PLUS_EQUAL;
import static org.codehaus.groovy.syntax.Types.POWER;
import static org.codehaus.groovy.syntax.Types.POWER_EQUAL;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_EQUAL;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.INSTANCEOF;
public class BinaryExpressionHelper {
//compare
private static final MethodCaller compareIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareIdentical");
private static final MethodCaller compareNotIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotIdentical");
private static final MethodCaller compareEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareEqual");
private static final MethodCaller compareNotEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotEqual");
private static final MethodCaller compareToMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareTo");
private static final MethodCaller compareLessThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThan");
private static final MethodCaller compareLessThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThanEqual");
private static final MethodCaller compareGreaterThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThan");
private static final MethodCaller compareGreaterThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThanEqual");
//regexpr
private static final MethodCaller findRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "findRegex");
private static final MethodCaller matchRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "matchRegex");
// isCase
private static final MethodCaller isCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isCase");
// isNotCase
private static final MethodCaller isNotCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isNotCase");
private final WriterController controller;
private final UnaryExpressionHelper unaryExpressionHelper;
public BinaryExpressionHelper(WriterController wc) {
this.controller = wc;
this.unaryExpressionHelper = new UnaryExpressionHelper(this.controller);
}
public WriterController getController(){
return controller;
}
public void eval(BinaryExpression expression) {
switch (expression.getOperation().getType()) {
case EQUAL: // = assignment
evaluateEqual(expression, false);
break;
case COMPARE_EQUAL: // ==
evaluateCompareExpression(compareEqualMethod, expression);
break;
case COMPARE_NOT_EQUAL:
evaluateCompareExpression(compareNotEqualMethod, expression);
break;
case COMPARE_TO:
evaluateCompareTo(expression);
break;
case COMPARE_GREATER_THAN:
evaluateCompareExpression(compareGreaterThanMethod, expression);
break;
case COMPARE_GREATER_THAN_EQUAL:
evaluateCompareExpression(compareGreaterThanEqualMethod, expression);
break;
case COMPARE_LESS_THAN:
evaluateCompareExpression(compareLessThanMethod, expression);
break;
case COMPARE_LESS_THAN_EQUAL:
evaluateCompareExpression(compareLessThanEqualMethod, expression);
break;
case LOGICAL_AND:
evaluateLogicalAndExpression(expression);
break;
case LOGICAL_OR:
evaluateLogicalOrExpression(expression);
break;
case BITWISE_AND:
evaluateBinaryExpression("and", expression);
break;
case BITWISE_AND_EQUAL:
evaluateBinaryExpressionWithAssignment("and", expression);
break;
case BITWISE_OR:
evaluateBinaryExpression("or", expression);
break;
case BITWISE_OR_EQUAL:
evaluateBinaryExpressionWithAssignment("or", expression);
break;
case BITWISE_XOR:
evaluateBinaryExpression("xor", expression);
break;
case BITWISE_XOR_EQUAL:
evaluateBinaryExpressionWithAssignment("xor", expression);
break;
case PLUS:
evaluateBinaryExpression("plus", expression);
break;
case PLUS_EQUAL:
evaluateBinaryExpressionWithAssignment("plus", expression);
break;
case MINUS:
evaluateBinaryExpression("minus", expression);
break;
case MINUS_EQUAL:
evaluateBinaryExpressionWithAssignment("minus", expression);
break;
case MULTIPLY:
evaluateBinaryExpression("multiply", expression);
break;
case MULTIPLY_EQUAL:
evaluateBinaryExpressionWithAssignment("multiply", expression);
break;
case DIVIDE:
evaluateBinaryExpression("div", expression);
break;
case DIVIDE_EQUAL:
//SPG don't use divide since BigInteger implements directly
//and we want to dispatch through DefaultGroovyMethods to get a BigDecimal result
evaluateBinaryExpressionWithAssignment("div", expression);
break;
case INTDIV:
evaluateBinaryExpression("intdiv", expression);
break;
case INTDIV_EQUAL:
evaluateBinaryExpressionWithAssignment("intdiv", expression);
break;
case MOD:
evaluateBinaryExpression("mod", expression);
break;
case MOD_EQUAL:
evaluateBinaryExpressionWithAssignment("mod", expression);
break;
case POWER:
evaluateBinaryExpression("power", expression);
break;
case POWER_EQUAL:
evaluateBinaryExpressionWithAssignment("power", expression);
break;
case ELVIS_EQUAL:
evaluateElvisEqual(expression);
break;
case LEFT_SHIFT:
evaluateBinaryExpression("leftShift", expression);
break;
case LEFT_SHIFT_EQUAL:
evaluateBinaryExpressionWithAssignment("leftShift", expression);
break;
case RIGHT_SHIFT:
evaluateBinaryExpression("rightShift", expression);
break;
case RIGHT_SHIFT_EQUAL:
evaluateBinaryExpressionWithAssignment("rightShift", expression);
break;
case RIGHT_SHIFT_UNSIGNED:
evaluateBinaryExpression("rightShiftUnsigned", expression);
break;
case RIGHT_SHIFT_UNSIGNED_EQUAL:
evaluateBinaryExpressionWithAssignment("rightShiftUnsigned", expression);
break;
case KEYWORD_INSTANCEOF:
evaluateInstanceof(expression);
break;
case COMPARE_NOT_INSTANCEOF:
evaluateNotInstanceof(expression);
break;
case FIND_REGEX:
evaluateCompareExpression(findRegexMethod, expression);
break;
case MATCH_REGEX:
evaluateCompareExpression(matchRegexMethod, expression);
break;
case LEFT_SQUARE_BRACKET:
if (controller.getCompileStack().isLHS()) {
evaluateEqual(expression, false);
} else {
evaluateBinaryExpression("getAt", expression);
}
break;
case KEYWORD_IN:
evaluateCompareExpression(isCaseMethod, expression);
break;
case COMPARE_NOT_IN:
evaluateCompareExpression(isNotCaseMethod, expression);
break;
case COMPARE_IDENTICAL:
evaluateCompareExpression(compareIdenticalMethod, expression);
break;
case COMPARE_NOT_IDENTICAL:
evaluateCompareExpression(compareNotIdenticalMethod, expression);
break;
default:
throw new GroovyBugError("Operation: " + expression.getOperation() + " not supported");
}
}
protected void assignToArray(Expression parent, Expression receiver, Expression index, Expression rhsValueLoader, boolean safe) {
// let's replace this assignment to a subscript operator with a
// method call
// e.g. x[5] = 10
// -> (x, [], 5), =, 10
// -> methodCall(x, "putAt", [5, 10])
ArgumentListExpression ae = new ArgumentListExpression(index,rhsValueLoader);
controller.getInvocationWriter().makeCall(
parent, receiver, new ConstantExpression("putAt"),
ae, InvocationWriter.invokeMethod, safe, false, false);
controller.getOperandStack().pop();
// return value of assignment
rhsValueLoader.visit(controller.getAcg());
}
private static boolean isNull(Expression exp) {
if (exp instanceof ConstantExpression){
return ((ConstantExpression) exp).getValue()==null;
} else {
return false;
}
}
public void evaluateElvisEqual(BinaryExpression expression) {
Token operation = expression.getOperation();
BinaryExpression elvisAssignmentExpression =
new BinaryExpression(
expression.getLeftExpression(),
Token.newSymbol(Types.EQUAL, operation.getStartLine(), operation.getStartColumn()),
new ElvisOperatorExpression(expression.getLeftExpression(), expression.getRightExpression())
);
this.evaluateEqual(elvisAssignmentExpression, false);
}
public void evaluateEqual(BinaryExpression expression, boolean defineVariable) {
AsmClassGenerator acg = controller.getAcg();
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
Expression rightExpression = expression.getRightExpression();
Expression leftExpression = expression.getLeftExpression();
ClassNode lhsType = controller.getTypeChooser().resolveType(leftExpression, controller.getClassNode());
if ( defineVariable &&
rightExpression instanceof EmptyExpression &&
!(leftExpression instanceof TupleExpression) )
{
VariableExpression ve = (VariableExpression) leftExpression;
BytecodeVariable var = compileStack.defineVariable(ve, controller.getTypeChooser().resolveType(ve, controller.getClassNode()), false);
operandStack.loadOrStoreVariable(var, false);
return;
}
// let's evaluate the RHS and store the result
ClassNode rhsType;
if (rightExpression instanceof ListExpression && lhsType.isArray()) {
ListExpression list = (ListExpression) rightExpression;
ArrayExpression array = new ArrayExpression(lhsType.getComponentType(), list.getExpressions());
array.setSourcePosition(list);
array.visit(acg);
} else if (rightExpression instanceof EmptyExpression) {
rhsType = leftExpression.getType();
loadInitValue(rhsType);
} else {
rightExpression.visit(acg);
}
rhsType = operandStack.getTopOperand();
boolean directAssignment = defineVariable && !(leftExpression instanceof TupleExpression);
int rhsValueId;
if (directAssignment) {
VariableExpression var = (VariableExpression) leftExpression;
if (var.isClosureSharedVariable() && ClassHelper.isPrimitiveType(rhsType)) {
// GROOVY-5570: if a closure shared variable is a primitive type, it must be boxed
rhsType = ClassHelper.getWrapper(rhsType);
operandStack.box();
}
// ensure we try to unbox null to cause a runtime NPE in case we assign
// null to a primitive typed variable, even if it is used only in boxed
// form as it is closure shared
if (var.isClosureSharedVariable() && ClassHelper.isPrimitiveType(var.getOriginType()) && isNull(rightExpression)) {
operandStack.doGroovyCast(var.getOriginType());
// these two are never reached in bytecode and only there
// to avoid verifyerrors and compiler infrastructure hazzle
operandStack.box();
operandStack.doGroovyCast(lhsType);
}
// normal type transformation
if (!ClassHelper.isPrimitiveType(lhsType) && isNull(rightExpression)) {
operandStack.replace(lhsType);
} else {
operandStack.doGroovyCast(lhsType);
}
rhsType = lhsType;
rhsValueId = compileStack.defineVariable(var, lhsType, true).getIndex();
} else {
rhsValueId = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
}
//TODO: if rhs is VariableSlotLoader already, then skip crating a new one
BytecodeExpression rhsValueLoader = new VariableSlotLoader(rhsType,rhsValueId,operandStack);
// assignment for subscript
if (leftExpression instanceof BinaryExpression) {
BinaryExpression leftBinExpr = (BinaryExpression) leftExpression;
if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
assignToArray(expression, leftBinExpr.getLeftExpression(), leftBinExpr.getRightExpression(), rhsValueLoader, leftBinExpr.isSafe());
}
compileStack.removeVar(rhsValueId);
return;
}
compileStack.pushLHS(true);
// multiple declaration
if (leftExpression instanceof TupleExpression) {
TupleExpression tuple = (TupleExpression) leftExpression;
int i = 0;
for (Expression e : tuple.getExpressions()) {
VariableExpression var = (VariableExpression) e;
MethodCallExpression call = new MethodCallExpression(
rhsValueLoader, "getAt",
new ArgumentListExpression(new ConstantExpression(i)));
call.visit(acg);
i++;
if (defineVariable) {
operandStack.doGroovyCast(var);
compileStack.defineVariable(var, true);
operandStack.remove(1);
} else {
acg.visitVariableExpression(var);
}
}
}
// single declaration
else if (defineVariable) {
rhsValueLoader.visit(acg);
operandStack.remove(1);
compileStack.popLHS();
return;
}
// normal assignment
else {
int mark = operandStack.getStackLength();
// to leave a copy of the rightExpression value on the stack after the assignment.
rhsValueLoader.visit(acg);
TypeChooser typeChooser = controller.getTypeChooser();
ClassNode targetType = typeChooser.resolveType(leftExpression, controller.getClassNode());
operandStack.doGroovyCast(targetType);
leftExpression.visit(acg);
operandStack.remove(operandStack.getStackLength()-mark);
}
compileStack.popLHS();
// return value of assignment
rhsValueLoader.visit(acg);
compileStack.removeVar(rhsValueId);
}
private void loadInitValue(ClassNode type) {
MethodVisitor mv = controller.getMethodVisitor();
if (ClassHelper.isPrimitiveType(type)) {
mv.visitLdcInsn(0);
} else {
mv.visitInsn(ACONST_NULL);
}
controller.getOperandStack().push(type);
}
protected void evaluateCompareExpression(MethodCaller compareMethod, BinaryExpression expression) {
Expression leftExp = expression.getLeftExpression();
TypeChooser typeChooser = controller.getTypeChooser();
ClassNode cn = controller.getClassNode();
ClassNode leftType = typeChooser.resolveType(leftExp,cn);
Expression rightExp = expression.getRightExpression();
ClassNode rightType = typeChooser.resolveType(rightExp,cn);
boolean done = false;
if ( ClassHelper.isPrimitiveType(leftType) &&
ClassHelper.isPrimitiveType(rightType))
{
BinaryExpressionMultiTypeDispatcher helper = new BinaryExpressionMultiTypeDispatcher(getController());
done = helper.doPrimitiveCompare(leftType, rightType, expression);
}
if (!done) {
AsmClassGenerator acg = controller.getAcg();
OperandStack operandStack = controller.getOperandStack();
leftExp.visit(acg);
operandStack.box();
rightExp.visit(acg);
operandStack.box();
compareMethod.call(controller.getMethodVisitor());
ClassNode resType = ClassHelper.boolean_TYPE;
if (compareMethod==findRegexMethod) {
resType = ClassHelper.OBJECT_TYPE;
}
operandStack.replace(resType,2);
}
}
private void evaluateCompareTo(BinaryExpression expression) {
Expression leftExpression = expression.getLeftExpression();
AsmClassGenerator acg = controller.getAcg();
OperandStack operandStack = controller.getOperandStack();
leftExpression.visit(acg);
operandStack.box();
// if the right hand side is a boolean expression, we need to autobox
Expression rightExpression = expression.getRightExpression();
rightExpression.visit(acg);
operandStack.box();
compareToMethod.call(controller.getMethodVisitor());
operandStack.replace(ClassHelper.Integer_TYPE,2);
}
private void evaluateLogicalAndExpression(BinaryExpression expression) {
MethodVisitor mv = controller.getMethodVisitor();
AsmClassGenerator acg = controller.getAcg();
OperandStack operandStack = controller.getOperandStack();
expression.getLeftExpression().visit(acg);
operandStack.doGroovyCast(ClassHelper.boolean_TYPE);
Label falseCase = operandStack.jump(IFEQ);
expression.getRightExpression().visit(acg);
operandStack.doGroovyCast(ClassHelper.boolean_TYPE);
operandStack.jump(IFEQ,falseCase);
ConstantExpression.PRIM_TRUE.visit(acg);
Label trueCase = new Label();
mv.visitJumpInsn(GOTO, trueCase);
mv.visitLabel(falseCase);
ConstantExpression.PRIM_FALSE.visit(acg);
mv.visitLabel(trueCase);
operandStack.remove(1); // have to remove 1 because of the GOTO
}
private void evaluateLogicalOrExpression(BinaryExpression expression) {
MethodVisitor mv = controller.getMethodVisitor();
AsmClassGenerator acg = controller.getAcg();
OperandStack operandStack = controller.getOperandStack();
Label end = new Label();
expression.getLeftExpression().visit(acg);
operandStack.doGroovyCast(ClassHelper.boolean_TYPE);
Label trueCase = operandStack.jump(IFNE);
expression.getRightExpression().visit(acg);
operandStack.doGroovyCast(ClassHelper.boolean_TYPE);
Label falseCase = operandStack.jump(IFEQ);
mv.visitLabel(trueCase);
ConstantExpression.PRIM_TRUE.visit(acg);
operandStack.jump(GOTO, end);
mv.visitLabel(falseCase);
ConstantExpression.PRIM_FALSE.visit(acg);
mv.visitLabel(end);
}
protected void evaluateBinaryExpression(String message, BinaryExpression binExp) {
CompileStack compileStack = controller.getCompileStack();
Expression receiver = binExp.getLeftExpression();
Expression arguments = binExp.getRightExpression();
// ensure VariableArguments are read, not stored
compileStack.pushLHS(false);
controller.getInvocationWriter().makeSingleArgumentCall(receiver, message, arguments, binExp.isSafe());
compileStack.popLHS();
}
protected void evaluateArrayAssignmentWithOperator(String method, BinaryExpression expression, BinaryExpression leftBinExpr) {
CompileStack compileStack = getController().getCompileStack();
AsmClassGenerator acg = getController().getAcg();
OperandStack os = getController().getOperandStack();
// e.g. x[a] += b
// to avoid loading x and a twice we transform the expression to use
// ExpressionAsVariableSlot
// -> subscript=a, receiver=x, receiver[subscript]+b, =, receiver[subscript]
// -> subscript=a, receiver=x, receiver#getAt(subscript)#plus(b), =, receiver#putAt(subscript)
// -> subscript=a, receiver=x, receiver#putAt(subscript, receiver#getAt(subscript)#plus(b))
// the result of x[a] += b is x[a]+b, thus:
// -> subscript=a, receiver=x, receiver#putAt(subscript, ret=receiver#getAt(subscript)#plus(b)), ret
ExpressionAsVariableSlot subscript = new ExpressionAsVariableSlot(controller, leftBinExpr.getRightExpression(), "subscript");
ExpressionAsVariableSlot receiver = new ExpressionAsVariableSlot(controller, leftBinExpr.getLeftExpression(), "receiver");
MethodCallExpression getAt = new MethodCallExpression(receiver, "getAt", new ArgumentListExpression(subscript));
MethodCallExpression operation = new MethodCallExpression(getAt, method, expression.getRightExpression());
ExpressionAsVariableSlot ret = new ExpressionAsVariableSlot(controller, operation, "ret");
MethodCallExpression putAt = new MethodCallExpression(receiver, "putAt", new ArgumentListExpression(subscript, ret));
putAt.visit(acg);
os.pop();
os.load(ret.getType(), ret.getIndex());
compileStack.removeVar(ret.getIndex());
compileStack.removeVar(subscript.getIndex());
compileStack.removeVar(receiver.getIndex());
}
protected void evaluateBinaryExpressionWithAssignment(String method, BinaryExpression expression) {
Expression leftExpression = expression.getLeftExpression();
AsmClassGenerator acg = controller.getAcg();
OperandStack operandStack = controller.getOperandStack();
if (leftExpression instanceof BinaryExpression) {
BinaryExpression leftBinExpr = (BinaryExpression) leftExpression;
if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
evaluateArrayAssignmentWithOperator(method, expression, leftBinExpr);
return;
}
}
evaluateBinaryExpression(method, expression);
// br to leave a copy of rvalue on the stack. see also isPopRequired()
operandStack.dup();
controller.getCompileStack().pushLHS(true);
leftExpression.visit(acg);
controller.getCompileStack().popLHS();
}
private void evaluateInstanceof(BinaryExpression expression) {
OperandStack operandStack = controller.getOperandStack();
expression.getLeftExpression().visit(controller.getAcg());
operandStack.box();
Expression rightExp = expression.getRightExpression();
ClassNode classType;
if (rightExp instanceof ClassExpression) {
ClassExpression classExp = (ClassExpression) rightExp;
classType = classExp.getType();
} else {
throw new RuntimeException(
"Right hand side of the instanceof keyword must be a class name, not: " + rightExp);
}
String classInternalName = BytecodeHelper.getClassInternalName(classType);
controller.getMethodVisitor().visitTypeInsn(INSTANCEOF, classInternalName);
operandStack.replace(ClassHelper.boolean_TYPE);
}
private void evaluateNotInstanceof(BinaryExpression expression) {
unaryExpressionHelper.writeNotExpression(
new NotExpression(
new BinaryExpression(
expression.getLeftExpression(),
Token.newSymbol(KEYWORD_INSTANCEOF, -1, -1),
expression.getRightExpression()
)
)
);
}
public MethodCaller getIsCaseMethod() {
return isCaseMethod;
}
private void evaluatePostfixMethod(int op, String method, Expression expression, Expression orig) {
CompileStack compileStack = controller.getCompileStack();
final OperandStack operandStack = controller.getOperandStack();
// load Expressions
VariableSlotLoader usesSubscript = loadWithSubscript(expression);
// save copy for later
operandStack.dup();
ClassNode expressionType = operandStack.getTopOperand();
int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, expressionType, true);
// execute Method
execMethodAndStoreForSubscriptOperator(op,method,expression,usesSubscript,orig);
// remove the result of the method call
operandStack.pop();
//reload saved value
operandStack.load(expressionType, tempIdx);
compileStack.removeVar(tempIdx);
if (usesSubscript!=null) compileStack.removeVar(usesSubscript.getIndex());
}
public void evaluatePostfixMethod(PostfixExpression expression) {
int op = expression.getOperation().getType();
switch (op) {
case Types.PLUS_PLUS:
evaluatePostfixMethod(op, "next", expression.getExpression(), expression);
break;
case Types.MINUS_MINUS:
evaluatePostfixMethod(op, "previous", expression.getExpression(), expression);
break;
}
}
public void evaluatePrefixMethod(PrefixExpression expression) {
int type = expression.getOperation().getType();
switch (type) {
case Types.PLUS_PLUS:
evaluatePrefixMethod(type, "next", expression.getExpression(), expression);
break;
case Types.MINUS_MINUS:
evaluatePrefixMethod(type, "previous", expression.getExpression(), expression);
break;
}
}
private void evaluatePrefixMethod(int op, String method, Expression expression, Expression orig) {
// load Expressions
VariableSlotLoader usesSubscript = loadWithSubscript(expression);
// execute Method
execMethodAndStoreForSubscriptOperator(op,method,expression,usesSubscript,orig);
// new value is already on stack, so nothing to do here
if (usesSubscript!=null) controller.getCompileStack().removeVar(usesSubscript.getIndex());
}
private VariableSlotLoader loadWithSubscript(Expression expression) {
final OperandStack operandStack = controller.getOperandStack();
// if we have a BinaryExpression, let us check if it is with
// subscription
if (expression instanceof BinaryExpression) {
BinaryExpression be = (BinaryExpression) expression;
if (be.getOperation().getType()== Types.LEFT_SQUARE_BRACKET) {
// right expression is the subscript expression
// we store the result of the subscription on the stack
Expression subscript = be.getRightExpression();
subscript.visit(controller.getAcg());
ClassNode subscriptType = operandStack.getTopOperand();
int id = controller.getCompileStack().defineTemporaryVariable("$subscript", subscriptType, true);
VariableSlotLoader subscriptExpression = new VariableSlotLoader(subscriptType, id, operandStack);
// do modified visit
BinaryExpression newBe = new BinaryExpression(be.getLeftExpression(), be.getOperation(), subscriptExpression);
newBe.copyNodeMetaData(be);
newBe.setSourcePosition(be);
newBe.visit(controller.getAcg());
return subscriptExpression;
}
}
// normal loading of expression
expression.visit(controller.getAcg());
return null;
}
private void execMethodAndStoreForSubscriptOperator(int op, String method, Expression expression, VariableSlotLoader usesSubscript, Expression orig) {
final OperandStack operandStack = controller.getOperandStack();
writePostOrPrefixMethod(op,method,expression,orig);
// we need special code for arrays to store the result (like for a[1]++)
if (usesSubscript!=null) {
CompileStack compileStack = controller.getCompileStack();
BinaryExpression be = (BinaryExpression) expression;
ClassNode methodResultType = operandStack.getTopOperand();
final int resultIdx = compileStack.defineTemporaryVariable("postfix_" + method, methodResultType, true);
BytecodeExpression methodResultLoader = new VariableSlotLoader(methodResultType, resultIdx, operandStack);
// execute the assignment, this will leave the right side
// (here the method call result) on the stack
assignToArray(be, be.getLeftExpression(), usesSubscript, methodResultLoader, be.isSafe());
compileStack.removeVar(resultIdx);
}
// here we handle a.b++ and a++
else if (expression instanceof VariableExpression ||
expression instanceof FieldExpression ||
expression instanceof PropertyExpression)
{
operandStack.dup();
controller.getCompileStack().pushLHS(true);
expression.visit(controller.getAcg());
controller.getCompileStack().popLHS();
}
// other cases don't need storing, so nothing to be done for them
}
protected void writePostOrPrefixMethod(int op, String method, Expression expression, Expression orig) {
final OperandStack operandStack = controller.getOperandStack();
// at this point the receiver will be already on the stack.
// in a[1]++ the method will be "++" aka "next" and the receiver a[1]
ClassNode BEType = controller.getTypeChooser().resolveType(expression, controller.getClassNode());
Expression callSiteReceiverSwap = new BytecodeExpression(BEType) {
@Override
public void visit(MethodVisitor mv) {
// CallSite is normally not showing up on the
// operandStack, so we place a dummy here with same
// slot length.
operandStack.push(ClassHelper.OBJECT_TYPE);
// change (receiver,callsite) to (callsite,receiver)
operandStack.swap();
setType(operandStack.getTopOperand());
// no need to keep any of those on the operand stack
// after this expression is processed, the operand stack
// will contain callSiteReceiverSwap.getType()
operandStack.remove(2);
}
};
// execute method
// this will load the callsite and the receiver normally in the wrong
// order since the receiver is already present, but before the callsite
// Therefore we use callSiteReceiverSwap to correct the order.
// After this call the JVM operand stack will contain the result of
// the method call... usually simply Object in operandStack
controller.getCallSiteWriter().makeCallSite(
callSiteReceiverSwap,
method,
MethodCallExpression.NO_ARGUMENTS,
false, false, false, false);
// now rhs is completely done and we need only to store. In a[1]++ this
// would be a.getAt(1).next() for the rhs, "lhs" code is a.putAt(1, rhs)
}
private void evaluateElvisOperatorExpression(ElvisOperatorExpression expression) {
MethodVisitor mv = controller.getMethodVisitor();
CompileStack compileStack = controller.getCompileStack();
OperandStack operandStack = controller.getOperandStack();
TypeChooser typeChooser = controller.getTypeChooser();
Expression boolPart = expression.getBooleanExpression().getExpression();
Expression falsePart = expression.getFalseExpression();
ClassNode truePartType = typeChooser.resolveType(boolPart, controller.getClassNode());
ClassNode falsePartType = typeChooser.resolveType(falsePart, controller.getClassNode());
ClassNode common = WideningCategories.lowestUpperBound(truePartType, falsePartType);
// x?:y is equal to x?x:y, which evals to
// var t=x; boolean(t)?t:y
// first we load x, dup it, convert the dupped to boolean, then
// jump depending on the value. For true we are done, for false we
// have to load y, thus we first remove x and then load y.
// But since x and y may have different stack lengths, this cannot work
// Thus we have to have to do the following:
// Be X the type of x, Y the type of y and S the common supertype of
// X and Y, then we have to see x?:y as
// var t=x;boolean(t)?S(t):S(y)
// so we load x, dup it, store the value in a local variable (t), then
// do boolean conversion. In the true part load t and cast it to S,
// in the false part load y and cast y to S
// load x, dup it, store one in $t and cast the remaining one to boolean
int mark = operandStack.getStackLength();
boolPart.visit(controller.getAcg());
operandStack.dup();
if (ClassHelper.isPrimitiveType(truePartType) && !ClassHelper.isPrimitiveType(operandStack.getTopOperand())) {
truePartType = ClassHelper.getWrapper(truePartType);
}
int retValueId = compileStack.defineTemporaryVariable("$t", truePartType, true);
operandStack.castToBool(mark,true);
Label l0 = operandStack.jump(IFEQ);
// true part: load $t and cast to S
operandStack.load(truePartType, retValueId);
operandStack.doGroovyCast(common);
Label l1 = new Label();
mv.visitJumpInsn(GOTO, l1);
// false part: load false expression and cast to S
mv.visitLabel(l0);
falsePart.visit(controller.getAcg());
operandStack.doGroovyCast(common);
// finish and cleanup
mv.visitLabel(l1);
compileStack.removeVar(retValueId);
controller.getOperandStack().replace(common, 2);
}
private void evaluateNormalTernary(TernaryExpression expression) {
MethodVisitor mv = controller.getMethodVisitor();
OperandStack operandStack = controller.getOperandStack();
TypeChooser typeChooser = controller.getTypeChooser();
Expression boolPart = expression.getBooleanExpression();
Expression truePart = expression.getTrueExpression();
Expression falsePart = expression.getFalseExpression();
ClassNode truePartType = typeChooser.resolveType(truePart, controller.getClassNode());
ClassNode falsePartType = typeChooser.resolveType(falsePart, controller.getClassNode());
ClassNode common = WideningCategories.lowestUpperBound(truePartType, falsePartType);
// we compile b?x:y as
// boolean(b)?S(x):S(y), S = common super type of x,y
// so we load b, do boolean conversion.
// In the true part load x and cast it to S,
// in the false part load y and cast y to S
// load b and convert to boolean
int mark = operandStack.getStackLength();
boolPart.visit(controller.getAcg());
operandStack.castToBool(mark,true);
Label l0 = operandStack.jump(IFEQ);
// true part: load x and cast to S
truePart.visit(controller.getAcg());
operandStack.doGroovyCast(common);
Label l1 = new Label();
mv.visitJumpInsn(GOTO, l1);
// false part: load y and cast to S
mv.visitLabel(l0);
falsePart.visit(controller.getAcg());
operandStack.doGroovyCast(common);
// finish and cleanup
mv.visitLabel(l1);
controller.getOperandStack().replace(common, 2);
}
public void evaluateTernary(TernaryExpression expression) {
if (expression instanceof ElvisOperatorExpression) {
evaluateElvisOperatorExpression((ElvisOperatorExpression) expression);
} else {
evaluateNormalTernary(expression);
}
}
}