/*
* 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.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 org.elasticsearch.painless.WriterConstants;
import java.util.Objects;
import java.util.Set;
/**
* Represents a binary math expression.
*/
public final class EBinary extends AExpression {
final Operation operation;
private AExpression left;
private AExpression right;
private Type promote = null; // promoted type
private Type shiftDistance = null; // for shifts, the rhs is promoted independently
boolean cat = false;
private boolean originallyExplicit = false; // record whether there was originally an explicit cast
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
super(location);
this.operation = Objects.requireNonNull(operation);
this.left = Objects.requireNonNull(left);
this.right = Objects.requireNonNull(right);
}
@Override
void extractVariables(Set<String> variables) {
left.extractVariables(variables);
right.extractVariables(variables);
}
@Override
void analyze(Locals locals) {
originallyExplicit = explicit;
if (operation == Operation.MUL) {
analyzeMul(locals);
} else if (operation == Operation.DIV) {
analyzeDiv(locals);
} else if (operation == Operation.REM) {
analyzeRem(locals);
} else if (operation == Operation.ADD) {
analyzeAdd(locals);
} else if (operation == Operation.SUB) {
analyzeSub(locals);
} else if (operation == Operation.FIND) {
analyzeRegexOp(locals);
} else if (operation == Operation.MATCH) {
analyzeRegexOp(locals);
} else if (operation == Operation.LSH) {
analyzeLSH(locals);
} else if (operation == Operation.RSH) {
analyzeRSH(locals);
} else if (operation == Operation.USH) {
analyzeUSH(locals);
} else if (operation == Operation.BWAND) {
analyzeBWAnd(locals);
} else if (operation == Operation.XOR) {
analyzeXor(locals);
} else if (operation == Operation.BWOR) {
analyzeBWOr(locals);
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
private void analyzeMul(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply multiply [*] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant * (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant * (long)right.constant;
} else if (sort == Sort.FLOAT) {
constant = (float)left.constant * (float)right.constant;
} else if (sort == Sort.DOUBLE) {
constant = (double)left.constant * (double)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeDiv(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply divide [/] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
try {
if (sort == Sort.INT) {
constant = (int)left.constant / (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant / (long)right.constant;
} else if (sort == Sort.FLOAT) {
constant = (float)left.constant / (float)right.constant;
} else if (sort == Sort.DOUBLE) {
constant = (double)left.constant / (double)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
} catch (ArithmeticException exception) {
throw createError(exception);
}
}
}
private void analyzeRem(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply remainder [%] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
try {
if (sort == Sort.INT) {
constant = (int)left.constant % (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant % (long)right.constant;
} else if (sort == Sort.FLOAT) {
constant = (float)left.constant % (float)right.constant;
} else if (sort == Sort.DOUBLE) {
constant = (double)left.constant % (double)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
} catch (ArithmeticException exception) {
throw createError(exception);
}
}
}
private void analyzeAdd(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteAdd(left.actual, right.actual);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply add [+] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
Sort sort = promote.sort;
actual = promote;
if (sort == Sort.STRING) {
left.expected = left.actual;
if (left instanceof EBinary && ((EBinary)left).operation == Operation.ADD && left.actual.sort == Sort.STRING) {
((EBinary)left).cat = true;
}
right.expected = right.actual;
if (right instanceof EBinary && ((EBinary)right).operation == Operation.ADD && right.actual.sort == Sort.STRING) {
((EBinary)right).cat = true;
}
} else if (sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
if (sort == Sort.INT) {
constant = (int)left.constant + (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant + (long)right.constant;
} else if (sort == Sort.FLOAT) {
constant = (float)left.constant + (float)right.constant;
} else if (sort == Sort.DOUBLE) {
constant = (double)left.constant + (double)right.constant;
} else if (sort == Sort.STRING) {
constant = left.constant.toString() + right.constant.toString();
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeSub(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply subtract [-] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant - (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant - (long)right.constant;
} else if (sort == Sort.FLOAT) {
constant = (float)left.constant - (float)right.constant;
} else if (sort == Sort.DOUBLE) {
constant = (double)left.constant - (double)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeRegexOp(Locals variables) {
left.analyze(variables);
right.analyze(variables);
left.expected = Definition.STRING_TYPE;
right.expected = Definition.PATTERN_TYPE;
left = left.cast(variables);
right = right.cast(variables);
promote = Definition.BOOLEAN_TYPE;
actual = Definition.BOOLEAN_TYPE;
}
private void analyzeLSH(Locals variables) {
left.analyze(variables);
right.analyze(variables);
Type lhspromote = AnalyzerCaster.promoteNumeric(left.actual, false);
Type rhspromote = AnalyzerCaster.promoteNumeric(right.actual, false);
if (lhspromote == null || rhspromote == null) {
throw createError(new ClassCastException("Cannot apply left shift [<<] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote = lhspromote;
shiftDistance = rhspromote;
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = lhspromote;
if (rhspromote.sort == Sort.LONG) {
right.expected = Definition.INT_TYPE;
right.explicit = true;
} else {
right.expected = rhspromote;
}
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = lhspromote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant << (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant << (int)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeRSH(Locals variables) {
left.analyze(variables);
right.analyze(variables);
Type lhspromote = AnalyzerCaster.promoteNumeric(left.actual, false);
Type rhspromote = AnalyzerCaster.promoteNumeric(right.actual, false);
if (lhspromote == null || rhspromote == null) {
throw createError(new ClassCastException("Cannot apply right shift [>>] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote = lhspromote;
shiftDistance = rhspromote;
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = lhspromote;
if (rhspromote.sort == Sort.LONG) {
right.expected = Definition.INT_TYPE;
right.explicit = true;
} else {
right.expected = rhspromote;
}
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = lhspromote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant >> (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant >> (int)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeUSH(Locals variables) {
left.analyze(variables);
right.analyze(variables);
Type lhspromote = AnalyzerCaster.promoteNumeric(left.actual, false);
Type rhspromote = AnalyzerCaster.promoteNumeric(right.actual, false);
actual = promote = lhspromote;
shiftDistance = rhspromote;
if (lhspromote == null || rhspromote == null) {
throw createError(new ClassCastException("Cannot apply unsigned shift [>>>] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = lhspromote;
if (rhspromote.sort == Sort.LONG) {
right.expected = Definition.INT_TYPE;
right.explicit = true;
} else {
right.expected = rhspromote;
}
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = lhspromote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant >>> (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant >>> (int)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeBWAnd(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, false);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply and [&] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant & (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant & (long)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeXor(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteXor(left.actual, right.actual);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply xor [^] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
if (sort == Sort.BOOL) {
constant = (boolean)left.constant ^ (boolean)right.constant;
} else if (sort == Sort.INT) {
constant = (int)left.constant ^ (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant ^ (long)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
private void analyzeBWOr(Locals variables) {
left.analyze(variables);
right.analyze(variables);
promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, false);
if (promote == null) {
throw createError(new ClassCastException("Cannot apply or [|] to types " +
"[" + left.actual.name + "] and [" + right.actual.name + "]."));
}
actual = promote;
if (promote.sort == Sort.DEF) {
left.expected = left.actual;
right.expected = right.actual;
if (expected != null) {
actual = expected;
}
} else {
left.expected = promote;
right.expected = promote;
}
left = left.cast(variables);
right = right.cast(variables);
if (left.constant != null && right.constant != null) {
Sort sort = promote.sort;
if (sort == Sort.INT) {
constant = (int)left.constant | (int)right.constant;
} else if (sort == Sort.LONG) {
constant = (long)left.constant | (long)right.constant;
} else {
throw createError(new IllegalStateException("Illegal tree structure."));
}
}
}
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
if (promote.sort == Sort.STRING && operation == Operation.ADD) {
if (!cat) {
writer.writeNewStrings();
}
left.write(writer, globals);
if (!(left instanceof EBinary) || !((EBinary)left).cat) {
writer.writeAppendStrings(left.actual);
}
right.write(writer, globals);
if (!(right instanceof EBinary) || !((EBinary)right).cat) {
writer.writeAppendStrings(right.actual);
}
if (!cat) {
writer.writeToStrings();
}
} else if (operation == Operation.FIND || operation == Operation.MATCH) {
right.write(writer, globals);
left.write(writer, globals);
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
if (operation == Operation.FIND) {
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
} else if (operation == Operation.MATCH) {
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
} else {
throw new IllegalStateException("Illegal tree structure.");
}
} else {
left.write(writer, globals);
right.write(writer, globals);
if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) {
// def calls adopt the wanted return value. if there was a narrowing cast,
// we need to flag that so that its done at runtime.
int flags = 0;
if (originallyExplicit) {
flags |= DefBootstrap.OPERATOR_EXPLICIT_CAST;
}
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, flags);
} else {
writer.writeBinaryInstruction(location, actual, operation);
}
}
}
@Override
public String toString() {
return singleLineToString(left, operation.symbol, right);
}
}