/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Represents an assignment with the lhs and rhs as child nodes.
*/
public final class EAssignment extends AExpression {
private AExpression lhs;
private AExpression rhs;
private final boolean pre;
private final boolean post;
private Operation operation;
private boolean cat = false;
private Type promote = null;
private Type shiftDistance; // for shifts, the RHS is promoted independently
private Cast there = null;
private Cast back = null;
public EAssignment(Location location, AExpression lhs, AExpression rhs, boolean pre, boolean post, Operation operation) {
super(location);
this.lhs = Objects.requireNonNull(lhs);
this.rhs = rhs;
this.pre = pre;
this.post = post;
this.operation = operation;
}
@Override
void extractVariables(Set<String> variables) {
lhs.extractVariables(variables);
rhs.extractVariables(variables);
}
@Override
void analyze(Locals locals) {
analyzeLHS(locals);
analyzeIncrDecr();
if (operation != null) {
analyzeCompound(locals);
} else if (rhs != null) {
analyzeSimple(locals);
} else {
throw new IllegalStateException("Illegal tree structure.");
}
}
private void analyzeLHS(Locals locals) {
if (lhs instanceof AStoreable) {
AStoreable lhs = (AStoreable)this.lhs;
lhs.read = read;
lhs.write = true;
lhs.analyze(locals);
} else {
throw new IllegalArgumentException("Left-hand side cannot be assigned a value.");
}
}
private void analyzeIncrDecr() {
if (pre && post) {
throw createError(new IllegalStateException("Illegal tree structure."));
} else if (pre || post) {
if (rhs != null) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
Sort sort = lhs.actual.sort;
if (operation == Operation.INCR) {
if (sort == Sort.DOUBLE) {
rhs = new EConstant(location, 1D);
} else if (sort == Sort.FLOAT) {
rhs = new EConstant(location, 1F);
} else if (sort == Sort.LONG) {
rhs = new EConstant(location, 1L);
} else {
rhs = new EConstant(location, 1);
}
operation = Operation.ADD;
} else if (operation == Operation.DECR) {
if (sort == Sort.DOUBLE) {
rhs = new EConstant(location, 1D);
} else if (sort == Sort.FLOAT) {
rhs = new EConstant(location, 1F);
} else if (sort == Sort.LONG) {
rhs = new EConstant(location, 1L);
} else {
rhs = new EConstant(location, 1);
}
operation = Operation.SUB;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeCompound(Locals locals) {
rhs.analyze(locals);
boolean shift = false;
if (operation == Operation.MUL) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.DIV) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.REM) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.ADD) {
promote = AnalyzerCaster.promoteAdd(lhs.actual, rhs.actual);
} else if (operation == Operation.SUB) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
} else if (operation == Operation.LSH) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
shift = true;
} else if (operation == Operation.RSH) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
shift = true;
} else if (operation == Operation.USH) {
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
shift = true;
} else if (operation == Operation.BWAND) {
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
} else if (operation == Operation.XOR) {
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
} else if (operation == Operation.BWOR) {
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
if (promote == null || (shift && shiftDistance == null)) {
throw createError(new ClassCastException("Cannot apply compound assignment " +
"[" + operation.symbol + "=] to types [" + lhs.actual + "] and [" + rhs.actual + "]."));
}
cat = operation == Operation.ADD && promote.sort == Sort.STRING;
if (cat) {
if (rhs instanceof EBinary && ((EBinary)rhs).operation == Operation.ADD && rhs.actual.sort == Sort.STRING) {
((EBinary)rhs).cat = true;
}
rhs.expected = rhs.actual;
} else if (shift) {
if (promote.sort == Sort.DEF) {
// shifts are promoted independently, but for the def type, we need object.
rhs.expected = promote;
} else if (shiftDistance.sort == Sort.LONG) {
rhs.expected = Definition.INT_TYPE;
rhs.explicit = true;
} else {
rhs.expected = shiftDistance;
}
} else {
rhs.expected = promote;
}
rhs = rhs.cast(locals);
there = AnalyzerCaster.getLegalCast(location, lhs.actual, promote, false, false);
back = AnalyzerCaster.getLegalCast(location, promote, lhs.actual, true, false);
this.statement = true;
this.actual = read ? lhs.actual : Definition.VOID_TYPE;
}
private void analyzeSimple(Locals locals) {
AStoreable lhs = (AStoreable)this.lhs;
// If the lhs node is a def optimized node we update the actual type to remove the need for a cast.
if (lhs.isDefOptimized()) {
rhs.analyze(locals);
rhs.expected = rhs.actual;
lhs.updateActual(rhs.actual);
// Otherwise, we must adapt the rhs type to the lhs type with a cast.
} else {
rhs.expected = lhs.actual;
rhs.analyze(locals);
}
rhs = rhs.cast(locals);
this.statement = true;
this.actual = read ? lhs.actual : Definition.VOID_TYPE;
}
/**
* Handles writing byte code for variable/method chains for all given possibilities
* including String concatenation, compound assignment, regular assignment, and simple
* reads. Includes proper duplication for chained assignments and assignments that are
* also read from.
*/
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
// For the case where the assignment represents a String concatenation
// we must, depending on the Java version, write a StringBuilder or
// track types going onto the stack. This must be done before the
// lhs is read because we need the StringBuilder to be placed on the
// stack ahead of any potential concatenation arguments.
int catElementStackSize = 0;
if (cat) {
catElementStackSize = writer.writeNewStrings();
}
// Cast the lhs to a storeable to perform the necessary operations to store the rhs.
AStoreable lhs = (AStoreable)this.lhs;
lhs.setup(writer, globals); // call the setup method on the lhs to prepare for a load/store operation
if (cat) {
// Handle the case where we are doing a compound assignment
// representing a String concatenation.
writer.writeDup(lhs.accessElementCount(), catElementStackSize); // dup the top element and insert it
// before concat helper on stack
lhs.load(writer, globals); // read the current lhs's value
writer.writeAppendStrings(lhs.actual); // append the lhs's value using the StringBuilder
rhs.write(writer, globals); // write the bytecode for the rhs
if (!(rhs instanceof EBinary) || !((EBinary)rhs).cat) { // check to see if the rhs has already done a concatenation
writer.writeAppendStrings(rhs.actual); // append the rhs's value since it's hasn't already
}
writer.writeToStrings(); // put the value for string concat onto the stack
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
if (lhs.read) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // if this lhs is also read
// from dup the value onto the stack
}
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
} else if (operation != null) {
// Handle the case where we are doing a compound assignment that
// does not represent a String concatenation.
writer.writeDup(lhs.accessElementCount(), 0); // if necessary, dup the previous lhs's value
// to be both loaded from and stored to
lhs.load(writer, globals); // load the current lhs's value
if (lhs.read && post) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also
// read from and is a post increment
}
writer.writeCast(there); // if necessary cast the current lhs's value
// to the promotion type between the lhs and rhs types
rhs.write(writer, globals); // write the bytecode for the rhs
// XXX: fix these types, but first we need def compound assignment tests.
// its tricky here as there are possibly explicit casts, too.
// write the operation instruction for compound assignment
if (promote.sort == Sort.DEF) {
writer.writeDynamicBinaryInstruction(location, promote,
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else {
writer.writeBinaryInstruction(location, promote, operation);
}
writer.writeCast(back); // if necessary cast the promotion type value back to the lhs's type
if (lhs.read && !post) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also
// read from and is not a post increment
}
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
} else {
// Handle the case for a simple write.
rhs.write(writer, globals); // write the bytecode for the rhs rhs
if (lhs.read) {
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also read from
}
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
}
}
@Override
public String toString() {
List<Object> subs = new ArrayList<>();
subs.add(lhs);
if (rhs != null) {
// Make sure "=" is in the symbol so this is easy to read at a glance
subs.add(operation == null ? "=" : operation.symbol + "=");
subs.add(rhs);
return singleLineToString(subs);
}
subs.add(operation.symbol);
if (pre) {
subs.add("pre");
}
if (post) {
subs.add("post");
}
return singleLineToString(subs);
}
}