package glslplugin.lang.elements.expressions.operator;
import glslplugin.lang.elements.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static glslplugin.lang.elements.types.GLSLTypes.*;
/**
* Base class for all operators.
*
* Contains prototypes for all operator types and their specific implementations.
*
* @author Jan Polák
*/
public class GLSLOperator {
private final String textRepresentation;
public GLSLOperator(@NotNull String textRepresentation) {
this.textRepresentation = textRepresentation;
}
@NotNull
public final String getTextRepresentation(){
return textRepresentation;
}
//region Prototypes
/**
* Unary operator - takes one input and has one output
* <br>
* For example: "-5"
*/
public static abstract class GLSLUnaryOperator extends GLSLOperator {
public GLSLUnaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
public abstract boolean isValidInput(GLSLType type);
@NotNull
public abstract GLSLType getResultType(GLSLType input);
/**
* Can be meaningfully called only if isValidInput returned true.
* Then it still might not return valid value.
*
* @param prefix - whether this operator is in prefix position
* @return result of applying this operator to the input
*/
@Nullable
public abstract Object getResultValue(Object input, boolean prefix);
}
/**
* Binary operator - takes two inputs and produces one output
* <br>
* For example: "3 + 4"
*/
public static abstract class GLSLBinaryOperator extends GLSLOperator {
protected GLSLBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
public boolean isValidInput(GLSLType firstType, GLSLType secondType){
//If the types are invalid, input is valid (this is not the greatest problem right now)
if(!firstType.isValidType() || !secondType.isValidType())return true;
//When types are valid, input is valid if the result is valid
return getResultType(firstType, secondType).isValidType();
}
@NotNull
public abstract GLSLType getResultType(GLSLType firstInput, GLSLType secondInput);
/**
* Can be meaningfully called only if isValidInput returned true.
* Then it still might not return valid value.
*
* @return result of applying this operator to the input
*/
@Nullable
public abstract Object getResultValue(Object firstInput, Object secondInput);
}
/**
* Assignment operator - takes the value on the right and somehow assigns it to the value on the left
* Result type is always of the value on the left. Expression on the left must be L type.
* <br>
* For example: "i += 1"
*/
public static class GLSLAssignmentOperator extends GLSLBinaryOperator {
public GLSLAssignmentOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
public boolean isValidInput(GLSLType leftType, GLSLType rightType) {
//If the types are invalid, input is valid (this is not the greatest problem right now)
if (!leftType.isValidType() || !rightType.isValidType()) return true;
return leftType == rightType || rightType.isConvertibleTo(leftType);
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
return firstInput;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
return null;
}
}
//endregion
//region Implementations
/**
* Arithmetic binary operator like + - and / as defined by GLSL spec 4.30.6 5.9
*
* Note that * has its own subclass
*/
protected static class ArithmeticBinaryOperator extends GLSLBinaryOperator {
public static final GLSLBinaryOperator ADDITION = new ArithmeticBinaryOperator("+");
public static final GLSLBinaryOperator SUBTRACTION = new ArithmeticBinaryOperator("-");
public static final GLSLBinaryOperator DIVISION = new ArithmeticBinaryOperator("/");
private ArithmeticBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
if(!firstInput.isValidType() || !secondInput.isValidType())return UNKNOWN_TYPE;
//Both types are scalars
if(firstInput instanceof GLSLScalarType && secondInput instanceof GLSLScalarType){
return unifyTypes(firstInput, secondInput);
}
//One type is scalar and the other is vector or matrix
if(firstInput instanceof GLSLScalarType && (secondInput instanceof GLSLVectorType || secondInput instanceof GLSLMatrixType)){
return getResultOfVectorOrMatrixAndScalar(secondInput, (GLSLScalarType) firstInput);
}
if(secondInput instanceof GLSLScalarType && (firstInput instanceof GLSLVectorType || firstInput instanceof GLSLMatrixType)){
return getResultOfVectorOrMatrixAndScalar(firstInput, (GLSLScalarType) secondInput);
}
//Two operands are vectors of same size
if(firstInput instanceof GLSLVectorType && secondInput instanceof GLSLVectorType){
GLSLVectorType first = (GLSLVectorType) firstInput;
GLSLVectorType second = (GLSLVectorType) secondInput;
if(first.getNumComponents() == second.getNumComponents()){
GLSLType unified = unifyTypes(first.getBaseType(), second.getBaseType());
if(unified.isValidType()){
return GLSLVectorType.getType(unified, first.getNumComponents());
}else{
return UNKNOWN_TYPE;
}
}
}
//Something else
return getResultTypeOther(firstInput, secondInput);
}
private GLSLType getResultOfVectorOrMatrixAndScalar(GLSLType vectorMatrixType, GLSLScalarType scalarType){
GLSLType unified = unifyTypes(vectorMatrixType.getBaseType(), scalarType);
if(!unified.isValidType())return UNKNOWN_TYPE;
else{
if(vectorMatrixType instanceof GLSLMatrixType){
GLSLMatrixType matrixType = (GLSLMatrixType) vectorMatrixType;
return GLSLMatrixType.getType(unified, matrixType.getNumColumns(), matrixType.getNumRows());
}else{
GLSLVectorType vectorType = (GLSLVectorType) vectorMatrixType;
return GLSLVectorType.getType(unified, vectorType.getNumComponents());
}
}
}
protected GLSLType getResultTypeOther(GLSLType firstInput, GLSLType secondInput){
//Only for + - and /
//Two operands are matrices of exactly same size
if(firstInput instanceof GLSLMatrixType && secondInput instanceof GLSLMatrixType){
GLSLMatrixType first = (GLSLMatrixType) firstInput;
GLSLMatrixType second = (GLSLMatrixType) secondInput;
if(first.getNumColumns() == second.getNumColumns() && first.getNumRows() == second.getNumRows()){
GLSLType unified = unifyTypes(first.getBaseType(), second.getBaseType());
if(unified.isValidType()){
return GLSLMatrixType.getType(unified, first.getNumColumns(), second.getNumRows());
}else{
return UNKNOWN_TYPE;
}
}
}
return UNKNOWN_TYPE;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
if(firstInput instanceof Double && secondInput instanceof Double){
return getResultValueD((Double) firstInput, (Double) secondInput);
}else if(firstInput instanceof Long && secondInput instanceof Double){
return getResultValueD(((Long) firstInput).doubleValue(), (Double) secondInput);
}else if(firstInput instanceof Double && secondInput instanceof Long){
return getResultValueD((Double) firstInput, ((Long) secondInput).doubleValue());
}else if(firstInput instanceof Long && secondInput instanceof Long){
long first = (Long)firstInput;
long second = (Long)secondInput;
if(this == ADDITION){
return first + second;
}else if(this == SUBTRACTION){
return first - second;
}else if(this == DIVISION){
return first / second;
}else if(this == ArithmeticMultiplicationBinaryOperator.MULTIPLICATION){
//Take care of multiplication here as well,
// since it is a subclass and its operation on scalars is pretty standard
return first * second;
}else return null;
}
return null;
}
private Object getResultValueD(double firstInput, double secondInput){
if(this == ADDITION){
return firstInput + secondInput;
}else if(this == SUBTRACTION){
return firstInput - secondInput;
}else if(this == DIVISION){
return firstInput / secondInput;
}else if(this == ArithmeticMultiplicationBinaryOperator.MULTIPLICATION){
return firstInput * secondInput;
}else return null;
}
}
/**
* *
*/
protected static class ArithmeticMultiplicationBinaryOperator extends ArithmeticBinaryOperator {
public static final GLSLBinaryOperator MULTIPLICATION = new ArithmeticMultiplicationBinaryOperator("*");
private ArithmeticMultiplicationBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
private int getRows(GLSLType matrixOrVector, boolean columnVector){
if(matrixOrVector instanceof GLSLMatrixType){
return ((GLSLMatrixType)matrixOrVector).getNumRows();
}else{
return columnVector ? ((GLSLVectorType)matrixOrVector).getNumComponents() : 1;
}
}
private int getColumns(GLSLType matrixOrVector, boolean rowVector){
if(matrixOrVector instanceof GLSLMatrixType){
return ((GLSLMatrixType)matrixOrVector).getNumColumns();
}else{
return rowVector ? ((GLSLVectorType)matrixOrVector).getNumComponents() : 1;
}
}
@Override
protected GLSLType getResultTypeOther(GLSLType firstInput, GLSLType secondInput) {
//Only for *
//Mat * mat || vec * mat || mat * vec
if((firstInput instanceof GLSLMatrixType && secondInput instanceof GLSLMatrixType)
|| (firstInput instanceof GLSLVectorType && secondInput instanceof GLSLMatrixType)
|| (firstInput instanceof GLSLMatrixType && secondInput instanceof GLSLVectorType)){
GLSLType unified = unifyTypes(firstInput.getBaseType(), secondInput.getBaseType());
if(!unified.isValidType())return UNKNOWN_TYPE;
int columnsLeft = getColumns(firstInput, true);
int rowsRight = getRows(secondInput, true);
if(columnsLeft != rowsRight)return UNKNOWN_TYPE;
int rowsLeft = getRows(firstInput, false);
int columnsRight = getColumns(secondInput, false);
if(rowsLeft > 1 && columnsRight > 1){
//Result is matrix
return GLSLMatrixType.getType(unified, columnsRight, rowsLeft);
}else {
//Result is a vector
if(rowsLeft != 1){
return GLSLVectorType.getType(unified, rowsLeft);
}else{
return GLSLVectorType.getType(unified, columnsRight);
}
}
}
return UNKNOWN_TYPE;
}
}
/**
* %
*/
protected static class ArithmeticModuloBinaryOperator extends GLSLBinaryOperator {
public static final GLSLBinaryOperator MODULO = new ArithmeticModuloBinaryOperator("%");
private ArithmeticModuloBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
if(!firstInput.isValidType() || !secondInput.isValidType())return UNKNOWN_TYPE;
GLSLType unified = unifyTypes(firstInput.getBaseType(), secondInput.getBaseType());
//Operates only on integers
if(unified != INT && unified != UINT)return UNKNOWN_TYPE;
//Two vectors
if(firstInput instanceof GLSLVectorType && secondInput instanceof GLSLVectorType){
int firstVectorSize = ((GLSLVectorType)firstInput).getNumComponents();
if(firstVectorSize != ((GLSLVectorType)secondInput).getNumComponents()){
//Vectors must have the same size
return UNKNOWN_TYPE;
}else{
//Modulo applied component-wise
return GLSLVectorType.getType(unified, firstVectorSize);
}
}
//Vector and scalar
if(firstInput instanceof GLSLVectorType && secondInput instanceof GLSLScalarType){
return GLSLVectorType.getType(unified, ((GLSLVectorType)firstInput).getNumComponents());
}else if(secondInput instanceof GLSLVectorType && firstInput instanceof GLSLScalarType){
return GLSLVectorType.getType(unified, ((GLSLVectorType)secondInput).getNumComponents());
}
//Scalar and scalar
return unified;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
if(firstInput instanceof Long && secondInput instanceof Long){
return ((Long)firstInput) % ((Long)secondInput);
}
return null;
}
}
/**
* Operators such as unary +, -, --, ++
*/
protected static class ArithmeticUnaryOperator extends GLSLUnaryOperator {
public static final GLSLUnaryOperator PLUS = new ArithmeticUnaryOperator("+");
public static final GLSLUnaryOperator MINUS = new ArithmeticUnaryOperator("-");
public static final GLSLUnaryOperator INCREMENT = new ArithmeticUnaryOperator("++");
public static final GLSLUnaryOperator DECREMENT = new ArithmeticUnaryOperator("--");
private ArithmeticUnaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType type) {
if(!type.isValidType())return true;
//Those operators work on integer/float scalars, vectors and matrices
return type.getBaseType() instanceof GLSLScalarType && type.getBaseType() != BOOL;
}
@NotNull
@Override
public GLSLType getResultType(GLSLType input) {
if(isValidInput(input)){
return input;
}else{
return UNKNOWN_TYPE;
}
}
@Nullable
@Override
public Object getResultValue(Object input, boolean prefix) {
if(this == PLUS){
return input;
}else if(this == MINUS){
if(input instanceof Double){
return -((Double)input);
}else if(input instanceof Long){
return -((Long)input);
}
} else {
//Increment and decrement handling
if(!prefix)return input;//If it is in postfix, it is returned first, then changed, so nothing happens here
int change;
if(this == INCREMENT){
change = 1;
}else if(this == DECREMENT){
change = -1;
}else return null;//Weird.
if(input instanceof Double){
return ((Double)input) + change;
}else if(input instanceof Long){
return ((Long)input) + change;
}
}
//Something is wrong and can't deduce it
return null;
}
}
/**
* Operators like <=, >=, <, >
*/
protected static class RelationalBinaryOperator extends GLSLBinaryOperator {
public RelationalBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType firstType, GLSLType secondType) {
//If the types are invalid, input is valid (this is not the greatest problem right now)
if(!firstType.isValidType() || !secondType.isValidType())return true;
//Operates only on integer/float scalars
return firstType instanceof GLSLScalarType && secondType instanceof GLSLScalarType &&
firstType != BOOL && secondType != BOOL;
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
//Relational operators always result in bool
return BOOL;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
return null;
}
}
/**
* == and !=
*/
protected static class EqualityBinaryOperator extends GLSLBinaryOperator {
public EqualityBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType firstType, GLSLType secondType) {
return true;
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
return BOOL;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
return null;
}
}
/**
* That is &&, || and ^^
*/
protected static class LogicalBinaryOperator extends GLSLBinaryOperator {
public LogicalBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType firstType, GLSLType secondType) {
//If the types are invalid, input is valid (this is not the greatest problem right now)
if(!firstType.isValidType() || !secondType.isValidType())return true;
//operates only on two booleans
return firstType == BOOL && secondType == BOOL;
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
return BOOL;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
return null;
}
}
/**
* Logical not: !
*/
protected static class LogicalUnaryOperator extends GLSLUnaryOperator {
public static final GLSLUnaryOperator LOGIC_NEGATION = new LogicalUnaryOperator("!");
private LogicalUnaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType type) {
return !type.isValidType() || type == BOOL;
}
@NotNull
@Override
public GLSLType getResultType(GLSLType input) {
return BOOL;
}
@Nullable
@Override
public Object getResultValue(Object input, boolean prefix) {
if (this == LOGIC_NEGATION) {
if (input instanceof Boolean) {
return !((Boolean) input);
}
}
return null;
}
}
/**
* Bitwise invert operator: ~
*/
protected static class OnesComplementOperator extends GLSLUnaryOperator {
public static final GLSLUnaryOperator BINARY_NEGATION = new OnesComplementOperator("~");
private OnesComplementOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType type) {
if(!type.isValidType())return true;
if(type instanceof GLSLMatrixType)return false;
//Operates on integer scalars and vectors
GLSLType baseType = type.getBaseType();
return baseType == INT || baseType == UINT;
}
@NotNull
@Override
public GLSLType getResultType(GLSLType input) {
if(isValidInput(input))return input;
else return UNKNOWN_TYPE;
}
@Nullable
@Override
public Object getResultValue(Object input, boolean prefix) {
if (this == BINARY_NEGATION) {
if (input instanceof Long) {
return ~((Long) input);
}
}
return null;
}
}
/**
* Bitwise shift: << and >>
*/
protected static class ShiftBinaryOperator extends GLSLBinaryOperator {
public ShiftBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@Override
public boolean isValidInput(GLSLType firstType, GLSLType secondType) {
//If the types are invalid, input is valid (this is not the greatest problem right now)
if(!firstType.isValidType() || !secondType.isValidType())return true;
if(firstType instanceof GLSLScalarType && !(secondType instanceof GLSLScalarType)){
//If first is scalar, second must be scalar as well
return false;
}
if(firstType instanceof GLSLMatrixType || secondType instanceof GLSLMatrixType){
//Operates only on scalars and vectors
return false;
}
GLSLType firstBaseType = firstType.getBaseType();
GLSLType secondBaseType = secondType.getBaseType();
return GLSLScalarType.isIntegerScalar(firstBaseType) && GLSLScalarType.isIntegerScalar(secondBaseType);
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
if(isValidInput(firstInput, secondInput)){
return firstInput;
}else return UNKNOWN_TYPE;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
return null;
}
}
/**
* Bitwise operators: &, ^ and |
*/
protected static class BitwiseBinaryOperator extends GLSLBinaryOperator {
public BitwiseBinaryOperator(@NotNull String textRepresentation) {
super(textRepresentation);
}
@NotNull
@Override
public GLSLType getResultType(GLSLType firstInput, GLSLType secondInput) {
if(!firstInput.isValidType() || !secondInput.isValidType())return UNKNOWN_TYPE;
if(firstInput instanceof GLSLMatrixType || secondInput instanceof GLSLMatrixType){
//Operates only on scalars and vectors
return UNKNOWN_TYPE;
}
GLSLType firstBaseType = firstInput.getBaseType();
GLSLType secondBaseType = secondInput.getBaseType();
if(firstBaseType != secondBaseType){
//Fundamental types must match (even "(un)signed-ness")
return UNKNOWN_TYPE;
}
if(!GLSLScalarType.isIntegerScalar(firstBaseType) || !GLSLScalarType.isIntegerScalar(secondBaseType)){
//Operates only on integer scalars and vectors
return UNKNOWN_TYPE;
}
if(firstInput instanceof GLSLVectorType && secondInput instanceof GLSLVectorType){
if(((GLSLVectorType)firstInput).getNumComponents() != ((GLSLVectorType)secondInput).getNumComponents()){
//Vectors must have the same size
return UNKNOWN_TYPE;
}
//Both are vectors
return firstInput;
}
//First is vector
if(firstInput instanceof GLSLVectorType){
return firstInput;
}
//Second is vector
if(secondInput instanceof GLSLVectorType){
return secondInput;
}
//Both are scalars
return firstBaseType;
}
@Nullable
@Override
public Object getResultValue(Object firstInput, Object secondInput) {
return null;
}
}
protected static class BinaryOperatorAssignmentOperator extends GLSLAssignmentOperator {
private final GLSLBinaryOperator operator;
public BinaryOperatorAssignmentOperator(@NotNull String textRepresentation, GLSLBinaryOperator operator) {
super(textRepresentation);
this.operator = operator;
}
@Override
public boolean isValidInput(GLSLType leftType, GLSLType rightType) {
if(operator.isValidInput(leftType, rightType)){
return super.isValidInput(leftType, operator.getResultType(leftType, rightType));
}else return false;
}
}
//endregion
}