/*
* 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.Definition.Cast;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import java.util.Objects;
/**
* The superclass for all E* (expression) and P* (postfix) nodes.
*/
public abstract class AExpression extends ANode {
/**
* Prefix is the predecessor to this node in a variable chain.
* This is used to analyze and write variable chains in a
* more natural order since the parent node of a variable
* chain will want the data from the final postfix to be
* analyzed.
*/
AExpression prefix;
/**
* Set to false when an expression will not be read from such as
* a basic assignment. Note this variable is always set by the parent
* as input.
*/
boolean read = true;
/**
* Set to true when an expression can be considered a stand alone
* statement. Used to prevent extraneous bytecode. This is always
* set by the node as output.
*/
boolean statement = false;
/**
* Set to the expected type this node needs to be. Note this variable
* is always set by the parent as input and should never be read from.
*/
Type expected = null;
/**
* Set to the actual type this node is. Note this variable is always
* set by the node as output and should only be read from outside of the
* node itself. <b>Also, actual can always be read after a cast is
* called on this node to get the type of the node after the cast.</b>
*/
Type actual = null;
/**
* Set by {@link EExplicit} if a cast made on an expression node should be
* explicit.
*/
boolean explicit = false;
/**
* Set to true if a cast is allowed to boxed/unboxed. This is used
* for method arguments because casting may be required.
*/
boolean internal = false;
/**
* Set to the value of the constant this expression node represents if
* and only if the node represents a constant. If this is not null
* this node will be replaced by an {@link EConstant} during casting
* if it's not already one.
*/
Object constant = null;
/**
* Set to true by {@link ENull} to represent a null value.
*/
boolean isNull = false;
/**
* Standard constructor with location used for error tracking.
*/
AExpression(Location location) {
super(location);
prefix = null;
}
/**
* This constructor is used by variable/method chains when postfixes are specified.
*/
AExpression(Location location, AExpression prefix) {
super(location);
this.prefix = Objects.requireNonNull(prefix);
}
/**
* Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces
* nodes with the constant variable set to a non-null value with {@link EConstant}.
* @return The new child node for the parent node calling this method.
*/
AExpression cast(Locals locals) {
Cast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
if (cast == null) {
if (constant == null || this instanceof EConstant) {
// For the case where a cast is not required and a constant is not set
// or the node is already an EConstant no changes are required to the tree.
return this;
} else {
// For the case where a cast is not required but a
// constant is set, an EConstant replaces this node
// with the constant copied from this node. Note that
// for constants output data does not need to be copied
// from this node because the output data for the EConstant
// will already be the same.
EConstant econstant = new EConstant(location, constant);
econstant.analyze(locals);
if (!expected.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
return econstant;
}
} else {
if (constant == null) {
// For the case where a cast is required and a constant is not set.
// Modify the tree to add an ECast between this node and its parent.
// The output data from this node is copied to the ECast for
// further reads done by the parent.
ECast ecast = new ECast(location, this, cast);
ecast.statement = statement;
ecast.actual = expected;
ecast.isNull = isNull;
return ecast;
} else {
if (expected.sort.constant) {
// For the case where a cast is required, a constant is set,
// and the constant can be immediately cast to the expected type.
// An EConstant replaces this node with the constant cast appropriately
// from the constant value defined by this node. Note that
// for constants output data does not need to be copied
// from this node because the output data for the EConstant
// will already be the same.
constant = AnalyzerCaster.constCast(location, constant, cast);
EConstant econstant = new EConstant(location, constant);
econstant.analyze(locals);
if (!expected.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
return econstant;
} else if (this instanceof EConstant) {
// For the case where a cast is required, a constant is set,
// the constant cannot be immediately cast to the expected type,
// and this node is already an EConstant. Modify the tree to add
// an ECast between this node and its parent. Note that
// for constants output data does not need to be copied
// from this node because the output data for the EConstant
// will already be the same.
ECast ecast = new ECast(location, this, cast);
ecast.actual = expected;
return ecast;
} else {
// For the case where a cast is required, a constant is set,
// the constant cannot be immediately cast to the expected type,
// and this node is not an EConstant. Replace this node with
// an Econstant node copying the constant from this node.
// Modify the tree to add an ECast between the EConstant node
// and its parent. Note that for constants output data does not
// need to be copied from this node because the output data for
// the EConstant will already be the same.
EConstant econstant = new EConstant(location, constant);
econstant.analyze(locals);
if (!actual.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
ECast ecast = new ECast(location, econstant, cast);
ecast.actual = expected;
return ecast;
}
}
}
}
}