package com.github.czyzby.lml.parser.impl.tag.macro.util;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.utils.IntMap;
import com.github.czyzby.kiwi.util.common.Nullables;
import com.github.czyzby.kiwi.util.common.Strings;
import com.github.czyzby.lml.parser.LmlParser;
import com.github.czyzby.lml.util.LmlUtilities;
/** Allows to evaluate string equations at runtime. Supports String, float, int and boolean types, determined upon
* parsing. See {@link DefaultOperator} for supported operations.
*
* @author MJ */
// I think that this is currently by far the most dragon-ish class in LML, as the logic is pretty complex and I decided
// to keep most variables method-scoped for easy nested equations. Also, I didn't really want the internal classes to
// flood even further than it already is with LML attributes, tags and macros, since this functionality will probably be
// rarely extended. So, basically: here be dragons. Kind of.
public class Equation {
private static final IntMap<Operator> OPERATORS = new IntMap<Operator>();
private final LmlParser parser;
private final Actor actor;
static {
for (final Operator operator : DefaultOperator.values()) {
registerOperator(operator);
}
}
/** Constructs a new equation without LML syntax support. */
public Equation() {
this(null, null);
}
/** @param operator will be registered and handled during equation parsing. Be careful not to override default
* operators. */
public static void registerOperator(final Operator operator) {
OPERATORS.put(operator.getSign(), operator);
}
/** @param parser will be used to parse values.
* @param actor will be used to invoke parsed actions, if any found. */
public Equation(final LmlParser parser, final Actor actor) {
this.parser = parser;
this.actor = actor;
}
/** @param value plain text value.
* @return if parser exists, it will return pre-processed value. */
protected String parseValue(final String value) {
if (parser != null) {
return parser.parseString(value, actor);
}
return value;
}
/** @param exception was thrown during evaluation. If parser exists, it will be used to process this exception. */
protected void throwException(final RuntimeException exception) {
if (parser != null) {
parser.throwError("Unable to evaluate equation.", exception);
}
throw exception;
}
/** @param equation will be evaluated.
* @return result of the equation as string. */
public String getResult(final CharSequence equation) {
try {
return Nullables.toString(parseEquation(equation));
} catch (final RuntimeException exception) {
throwException(exception);
}
return null;
}
private Object parseEquation(final CharSequence equation) {
Element firstNode = new OperatorElement(DefaultOperator.NO_OP, null, null);
// Separating equation into values (boolean, int, float, String) and operators:
findEquationElements(equation, firstNode);
// Merging neighbor operators (for example, != will be parsed as one operator: NOT_EQUALS):
firstNode = mergeOperatorElements(firstNode);
// Executing operators:
firstNode = evaluateOperators(firstNode, findMaxPriority(firstNode));
return convertToResult(firstNode);
}
protected void findEquationElements(final CharSequence equation, final Element firstNode) {
final StringBuilder valueBuilder = new StringBuilder();
Element lastNode = firstNode;
for (int index = 0, length = equation.length(); index < length; index++) {
char character = equation.charAt(index);
Element node;
if (character == ')') {
// Assuming this was properly parsed by a nested equation.
continue;
} else if (character == '(') {
Strings.clearBuilder(valueBuilder);
int nested = 1;
// This is not extracted to another method, as A) we want to keep variables method-scoped, B) it needs
// both index and parsed value after the loop - wrapping it in an array/collection/pair is an overkill.
for (++index; index < length; index++) {
character = equation.charAt(index);
if (character == '(') {
nested++;
} else if (character == ')' && --nested == 0) {
break;
}
valueBuilder.append(character);
}
if (nested != 0) {
throw new IllegalStateException("Invalid amount of parenthesis in equation: " + equation);
}
node = new ValueElement(parseEquation(valueBuilder.toString()), lastNode, null);
} else if (isOperator(character)) {
node = new OperatorElement(getOperator(character), lastNode, null);
} else {
Strings.clearBuilder(valueBuilder);
// This is not extracted to another method, as A) we want to keep variables method-scoped, B) it needs
// both index and parsed value after the loop - wrapping it in an array/collection/pair is an overkill.
for (; index < length; index++) {
character = equation.charAt(index);
if (isOperator(character) || character == '(') {
index--; // This is not ours to parse.
break;
} else if (character == ')') {
// Assuming parenthesis was properly parsed by nested equation.
break;
}
valueBuilder.append(character);
}
if (Strings.isBlank(valueBuilder)) {
continue;
}
node = new ValueElement(buildValue(valueBuilder), lastNode, null);
}
lastNode.setNext(node);
lastNode = node;
}
}
private String buildValue(final StringBuilder valueBuilder) {
String value = valueBuilder.toString().trim().replace("\\n", "\n");
if (isInQuotation(value)) {
value = LmlUtilities.stripQuotation(value);
}
if (parser != null) {
return parser.parseString(value, actor);
}
return value;
}
protected boolean isInQuotation(final String value) {
return Strings.startsWith(value, '"') && Strings.endsWith(value, '"')
|| Strings.startsWith(value, '\'') && Strings.endsWith(value, '\'');
}
protected String printNodes(Element firstNode) {
final StringBuilder debug = new StringBuilder();
debug.append(firstNode);
while (firstNode.hasNext()) {
debug.append(", ");
firstNode = firstNode.next();
debug.append(firstNode);
}
return debug.toString();
}
protected Element mergeOperatorElements(Element firstNode) {
for (Element element = firstNode; element != null;) {
if (element.isOperator() && element.hasNext()) {
final Element next = element.next();
if (next != null && next.isOperator()) {
final Operator mergedOperator = element.getOperator().merge(next.getOperator());
if (mergedOperator != null) {
final Element mergedElement = new OperatorElement(mergedOperator, element.previous(),
next.next());
if (element == firstNode) {
firstNode = mergedElement;
}
element = mergedElement;
// Correct next/previous for old elements are set in abstract elements.
continue;
}
}
}
element = element.next();
}
return firstNode;
}
protected int findMaxPriority(final Element firstNode) {
int priority = -1;
for (Element element = firstNode; element != null;) {
if (element.isOperator()) {
priority = Math.max(priority, Math.max(element.getOperator().getDoubleArgumentPriority(),
element.getOperator().getSingleArgumentPriority()));
}
element = element.next();
}
return priority;
}
private Element evaluateOperators(Element firstNode, final int maxPriority) {
for (int currentPriority = maxPriority; currentPriority <= maxPriority
&& currentPriority >= 0; currentPriority--) {
for (Element element = firstNode; element != null;) {
if (!element.isOperator()) {
element = element.next();
continue;
}
Element resultElement = null;
if (element.getOperator().getDoubleArgumentPriority() == currentPriority) {
// Evaluating double argument expression.
validateEvaluatedElement(element);
if (isSingleArgument(element)) {
// Has only two arguments...
if (element.getOperator().getSingleArgumentPriority() < 0) {
// ...if it supports single arguments, its OK, but if it doesn't, we have to exit this.
throw new IllegalStateException(
element + " is expected to receive two logical arguments, but it is proceeded by: "
+ element.previous() + " and followed by " + element.next());
}
} else {
final Object result = evaluateDoubleArgumentOperator(element);
resultElement = new ValueElement(result, element.previous().previous(), element.next().next());
}
}
if (resultElement == null && element.getOperator().getSingleArgumentPriority() == currentPriority) {
// Evaluating a single argument expression.
validateEvaluatedElement(element);
if (element.getOperator().getDoubleArgumentPriority() >= 0 && !isSingleArgument(element)) {
// It is a single argument-handling operator, but it also parses double arguments and now its
// got two of them. For example:
// - operator would normally negate number, but it it has two arguments, it subtracts them
// instead.
// ! operator negates value and never enters this if.
element = element.next();
continue;
}
final Object result = evaluateSingleArgumentOperator(element);
resultElement = new ValueElement(result, element.previous(), element.next().next());
} else {
resultElement = resultElement == null ? element.next() : resultElement;
}
if (resultElement != null && !resultElement.hasPrevious()) {
firstNode = resultElement;
}
element = resultElement;
}
}
return firstNode;
}
protected boolean isSingleArgument(final Element element) {
return !element.hasPrevious() || element.hasPrevious() && element.previous().isOperator();
}
protected void validateEvaluatedElement(final Element element) {
if (!element.hasNext()) {
throw new IllegalStateException(
"Operator cannot end an expression - it needs a logical value to work on. Found " + element
+ " without an argument.");
}
if (element.next().isOperator()) {
throw new IllegalStateException("Invalid operator usage. " + element + " cannot be before " + element.next()
+ ". Could not merge operators.");
}
}
protected Object evaluateDoubleArgumentOperator(final Element element) {
final Operator operator = element.getOperator();
final Element leftArgument = element.previous();
final Element rightArgument = element.next();
if (leftArgument.isBoolean() && rightArgument.isBoolean()) {
return operator.process(leftArgument.getBoolean(), rightArgument.getBoolean());
} else if (leftArgument.isInt() && rightArgument.isInt()) {
return operator.process(leftArgument.getInt(), rightArgument.getInt());
} else if (leftArgument.isFloat() && rightArgument.isFloat()) {
return operator.process(leftArgument.getFloat(), rightArgument.getFloat());
}
return operator.process(leftArgument.getString(), rightArgument.getString());
}
protected Object evaluateSingleArgumentOperator(final Element element) {
final Operator operator = element.getOperator();
final Element argument = element.next();
if (argument.isBoolean()) {
return operator.process(argument.getBoolean());
} else if (argument.isInt()) {
return operator.process(argument.getInt());
} else if (argument.isFloat()) {
return operator.process(argument.getFloat());
}
return operator.process(argument.getString());
}
protected Object convertToResult(final Element firstNode) {
if (firstNode.isOperator()) {
throw new IllegalStateException(
"No logical values in the equation. Equation cannot be empty or contain only operators.");
}
if (firstNode.hasNext()) {
throw new IllegalStateException(
"Equation could not have been evaluated to a single value. Most likely not enough operators were given. Leftover nodes: "
+ printNodes(firstNode));
}
return firstNode.getString();
}
protected Operator getOperator(final char character) {
return OPERATORS.get(character);
}
protected boolean isOperator(final char character) {
return OPERATORS.containsKey(character);
}
/** Utility method that calls {@link #getResult(CharSequence)} and converts returned value to a boolean.
*
* @param equation will be evaluated.
* @return true if: returned value is boolean true, a positive number or non-null string. */
public boolean getBooleanResult(final CharSequence equation) {
final Element result = new ValueElement(getResult(equation), null, null);
if (result.isBoolean()) {
return result.getBoolean();
} else if (result.isInt()) {
return result.getInt() > 0;
} else if (result.isFloat()) {
return result.getFloat() > 0f;
}
return !isNullOrFalse(result.getString());
}
/** @param value LML value.
* @return true if value is mapped to null or boolean false. */
protected boolean isNullOrFalse(final String value) {
return value == null || Strings.isWhitespace(value) || Nullables.DEFAULT_NULL_STRING.equalsIgnoreCase(value)
|| Boolean.FALSE.toString().equalsIgnoreCase(value);
}
/** Common interface for parsed operators.
*
* @author MJ */
public static interface Operator {
/** @return sign of the operator as it should appear in the equation. */
char getSign();
/** @return non-negative operator priority value. By default, in range of [0,5]. -1 if not supported. 0 priority
* is reserved for finalizing operators (and, or), 1 is for comparing operators (lower than, greater
* than, equal, etc), 2 for low priority operations (modulo), 3 for moderate priority (add, subtract), 4
* for high priority (multiply, divide, pow), 5 is usually only for single arguments (negate, increment,
* decrement). */
int getDoubleArgumentPriority();
/** @return -1 if not supports single argument. Usually 5 otherwise. */
int getSingleArgumentPriority();
/** @param operator is right after this operator.
* @return merged operator or null if cannot merge. */
Operator merge(Operator operator);
/** @return negated operator. */
Operator negate();
/** @param leftValue value before the operator.
* @param rightValue value after the operator.
* @return result of the operation. */
Object process(String leftValue, String rightValue);
/** @param value proceeded by the operator.
* @return result of the single-argument operation. */
Object process(String value);
/** @param leftValue value before the operator.
* @param rightValue value after the operator.
* @return result of the operation. */
Object process(float leftValue, float rightValue);
/** @param value proceeded by the operator.
* @return result of the single-argument operation. */
Object process(float value);
/** @param leftValue value before the operator.
* @param rightValue value after the operator.
* @return result of the operation. */
Object process(int leftValue, int rightValue);
/** @param value proceeded by the operator.
* @return result of the single-argument operation. */
Object process(int value);
/** @param leftValue value before the operator.
* @param rightValue value after the operator.
* @return result of the operation. */
Object process(boolean leftValue, boolean rightValue);
/** @param value proceeded by the operator.
* @return result of the single-argument operation. */
Object process(boolean value);
}
/** Common base interface for elements of equation.
*
* @author MJ */
protected static interface Element {
/** @return previous element in the equation. Might be null. */
Element previous();
/** @return true if previous element is not null. */
boolean hasPrevious();
/** @param previous becomes previous element in the equation. */
void setPrevious(Element previous);
/** @return next element in the equation. Might be null. */
Element next();
/** @return true if next element is not null. */
boolean hasNext();
/** @param next becomes next element in the equation. */
void setNext(Element next);
/** @return true if element holds an operator. */
boolean isOperator();
/** @return wrapped operator, if the element is an operator. */
Operator getOperator();
/** @return true if element holds a boolean. */
boolean isBoolean();
/** @return true if element holds an int. */
boolean isInt();
/** @return true if element holds a float. */
boolean isFloat();
/** @return stored value as boolean. */
boolean getBoolean();
/** @return stored value as int. */
int getInt();
/** @return stored value as float. */
float getFloat();
/** @return stored value as String. */
String getString();
}
/** Abstract base for elements. Throws exceptions for any getters except next/previous element.
*
* @author MJ */
protected static abstract class AbstractElement implements Element {
private Element previous, next;
public AbstractElement(final Element previous, final Element next) {
this.previous = previous;
if (previous != null) {
previous.setNext(this);
}
this.next = next;
if (next != null) {
next.setPrevious(this);
}
}
@Override
public Element previous() {
return previous;
}
@Override
public void setPrevious(final Element previous) {
this.previous = previous;
}
@Override
public boolean hasPrevious() {
return previous != null;
}
@Override
public Element next() {
return next;
}
@Override
public void setNext(final Element next) {
this.next = next;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public boolean isOperator() {
return false;
}
@Override
public Operator getOperator() {
throw new IllegalStateException("Not an operator.");
}
@Override
public boolean isBoolean() {
return false;
}
@Override
public boolean isInt() {
return false;
}
@Override
public boolean isFloat() {
return false;
}
@Override
public boolean getBoolean() {
return false;
}
@Override
public int getInt() {
throw new IllegalStateException("Not a value.");
}
@Override
public float getFloat() {
throw new IllegalStateException("Not a value.");
}
@Override
public String getString() {
throw new IllegalStateException("Not a value.");
}
@Override
public String toString() {
return isOperator() ? getOperator().toString() : getString();
}
}
/** Holds operator elements.
*
* @author MJ */
protected static class OperatorElement extends AbstractElement {
private final Operator operator;
public OperatorElement(final Operator operator, final Element previous, final Element next) {
super(previous, next);
this.operator = operator;
}
@Override
public boolean isOperator() {
return true;
}
@Override
public Operator getOperator() {
return operator;
}
}
/** Holds actual equation values.
*
* @author MJ */
protected static class ValueElement extends AbstractElement {
private final String value;
public ValueElement(final Object value, final Element previous, final Element next) {
this(Nullables.toString(value, Strings.EMPTY_STRING), previous, next);
}
public ValueElement(final String value, final Element previous, final Element next) {
super(previous, next);
if (value == null || value.equalsIgnoreCase(Nullables.DEFAULT_NULL_STRING)) {
this.value = Strings.EMPTY_STRING;
} else {
this.value = value;
}
}
@Override
public boolean isBoolean() {
return Strings.isBoolean(value);
}
@Override
public boolean isInt() {
return Strings.isInt(value);
}
@Override
public boolean isFloat() {
return Strings.isFloat(value);
}
@Override
public boolean getBoolean() {
return Boolean.valueOf(value);
}
@Override
public float getFloat() {
return Float.valueOf(value);
}
@Override
public int getInt() {
return Integer.valueOf(value);
}
@Override
public String getString() {
return value;
}
}
/** Contains operators supported by default.
*
* @author MJ */
protected enum DefaultOperator implements Operator {
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>!value</td>
* </tr>
* <tr>
* <th>int</th>
* <td>-value</td>
* </tr>
* <tr>
* <th>float</th>
* <td>-value</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Return empty string.</td>
* </tr>
* <tr>
* <th>Operator</th>
* <td>Returns negated operator.</td>
* </tr>
* </table>
*/
NEGATE('!') {
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final boolean value) {
return !value;
}
@Override
public Object process(final int value) {
return -value;
}
@Override
public Object process(final float value) {
return -value;
}
@Override
public String process(final String value) {
return Strings.EMPTY_STRING;
}
@Override
public Operator merge(final Operator operator) {
return operator.negate();
}
@Override
public Operator negate() {
return DefaultOperator.NO_OP;
}
},
/** Utility operator that can process 1 value at a time (cannot process 2 arguments) or an operator. Returns
* unchanged passed value while processing and passed operator while merging. This is basically negated
* negation. */
NO_OP('\uFFEE') {
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final boolean value) {
return value;
}
@Override
public Object process(final int value) {
return value;
}
@Override
public Object process(final float value) {
return value;
}
@Override
public String process(final String value) {
return value;
}
@Override
public Operator merge(final Operator operator) {
return operator;
}
@Override
public Operator negate() {
return DefaultOperator.NEGATE;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>!value</td>
* </tr>
* <tr>
* <th>int</th>
* <td>~value</td>
* </tr>
* <tr>
* <th>float</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>Operator</th>
* <td>Returns negated operator.</td>
* </tr>
* </table>
*/
BIT_NEGATE('~') {
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final boolean value) {
return !value;
}
@Override
public Object process(final int value) {
return ~value;
}
@Override
public Operator merge(final Operator operator) {
return operator.negate();
}
@Override
public Operator negate() {
return DefaultOperator.NO_OP;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>first == second</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first == second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>{@link Float#compare(float, float)} == 0</td>
* </tr>
* <tr>
* <th>String</th>
* <td>{@link String#equalsIgnoreCase(String)}</td>
* </tr>
* </table>
*/
EQUALS('=') {
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue == rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue == rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return Float.compare(leftValue, rightValue) == 0;
}
@Override
public Object process(final String leftValue, final String rightValue) {
return leftValue.equalsIgnoreCase(rightValue);
}
@Override
public Operator merge(final Operator operator) {
if (operator == EQUALS) {
return EQUALS_STRICT;
} else if (operator == LOWER_THAN) {
return LOWER_OR_EQUALS;
} else if (operator == GREATER_THAN) {
return GREATER_OR_EQUALS;
}
return super.merge(operator);
}
@Override
public Operator negate() {
return NOT_EQUALS;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>first != second</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first != second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>{@link Float#compare(float, float)} != 0</td>
* </tr>
* <tr>
* <th>String</th>
* <td>!{@link String#equalsIgnoreCase(String)}</td>
* </tr>
* </table>
*/
NOT_EQUALS('\uFFEF') { // != is converted to this.
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue != rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue != rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return Float.compare(leftValue, rightValue) != 0;
}
@Override
public Object process(final String leftValue, final String rightValue) {
return !leftValue.equalsIgnoreCase(rightValue);
}
@Override
public Operator merge(final Operator operator) {
if (operator == EQUALS) {
return NOT_EQUALS_STRICT;
} else if (operator == LOWER_THAN) {
return GREATER_OR_EQUALS;
} else if (operator == GREATER_THAN) {
return LOWER_OR_EQUALS;
}
return super.merge(operator);
}
@Override
protected String complexName() {
return "!=";
}
@Override
public Operator negate() {
return EQUALS;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>first != second</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first != second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>{@link Float#compare(float, float)} != 0</td>
* </tr>
* <tr>
* <th>String</th>
* <td>!{@link Strings#equals(CharSequence, CharSequence)}</td>
* </tr>
* </table>
*/
NOT_EQUALS_STRICT('\uFFFA') { // !== is converted to this.
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue != rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue != rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return Float.compare(leftValue, rightValue) != 0;
}
@Override
public Object process(final String leftValue, final String rightValue) {
return !Strings.equals(leftValue, rightValue);
}
@Override
protected String complexName() {
return "!==";
}
@Override
public Operator negate() {
return EQUALS_STRICT;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>first == second</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first == second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>{@link Float#compare(float, float)} == 0</td>
* </tr>
* <tr>
* <th>String</th>
* <td>{@link Strings#equals(CharSequence, CharSequence)}</td>
* </tr>
* </table>
*/
EQUALS_STRICT('\uFFFB') { // == is converted to this value.
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue == rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue == rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return Float.compare(leftValue, rightValue) == 0;
}
@Override
public Object process(final String leftValue, final String rightValue) {
return Strings.equals(leftValue, rightValue);
}
@Override
protected String complexName() {
return "==";
}
@Override
public Operator negate() {
return NOT_EQUALS_STRICT;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first < second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first < second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Compares lengths with <.</td>
* </tr>
* </table>
*/
LOWER_THAN('<') {
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue < rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue < rightValue;
}
@Override
public Object process(final String leftValue, final String rightValue) {
if (Strings.isInt(rightValue)) {
return leftValue.length() < Integer.parseInt(rightValue);
} else if (Strings.isInt(leftValue)) {
return Integer.parseInt(leftValue) < rightValue.length();
}
return leftValue.length() < rightValue.length();
}
@Override
public Operator merge(final Operator operator) {
if (operator == EQUALS) {
return LOWER_OR_EQUALS;
}
return super.merge(operator);
}
@Override
public Operator negate() {
return GREATER_THAN;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first <= second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first <= second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Compares lengths with <=.</td>
* </tr>
* </table>
*/
LOWER_OR_EQUALS('\uFFFC') { // <= and =< are converted to this operator.
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue <= rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue <= rightValue;
}
@Override
public Object process(final String leftValue, final String rightValue) {
if (Strings.isInt(rightValue)) {
return leftValue.length() <= Integer.parseInt(rightValue);
} else if (Strings.isInt(leftValue)) {
return Integer.parseInt(leftValue) <= rightValue.length();
}
return leftValue.length() <= rightValue.length();
}
@Override
protected String complexName() {
return "<=";
}
@Override
public Operator negate() {
return GREATER_OR_EQUALS;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first > second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first > second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Compares lengths with >.</td>
* </tr>
* </table>
*/
GREATER_THAN('>') {
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue > rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue > rightValue;
}
@Override
public Object process(final String leftValue, final String rightValue) {
if (Strings.isInt(rightValue)) {
return leftValue.length() > Integer.parseInt(rightValue);
} else if (Strings.isInt(leftValue)) {
return Integer.parseInt(leftValue) > rightValue.length();
}
return leftValue.length() > rightValue.length();
}
@Override
public Operator merge(final Operator operator) {
if (operator == EQUALS) {
return GREATER_OR_EQUALS;
}
return super.merge(operator);
}
@Override
public Operator negate() {
return LOWER_THAN;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first >= second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first >= second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Compares lengths with >=.</td>
* </tr>
* </table>
*/
GREATER_OR_EQUALS('\uFFFD') { // >= and => are converted to this operator
@Override
public int getDoubleArgumentPriority() {
return 1;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue >= rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue >= rightValue;
}
@Override
public Object process(final String leftValue, final String rightValue) {
if (Strings.isInt(rightValue)) {
return leftValue.length() >= Integer.parseInt(rightValue);
} else if (Strings.isInt(leftValue)) {
return Integer.parseInt(leftValue) >= rightValue.length();
}
return leftValue.length() >= rightValue.length();
}
@Override
protected String complexName() {
return ">=";
}
@Override
public Operator negate() {
return LOWER_OR_EQUALS;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first * second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first * second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
MULTIPLY('*') {
@Override
public int getDoubleArgumentPriority() {
return 4;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue * rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue * rightValue;
}
@Override
public Operator negate() {
return DIVIDE;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first / second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first / second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
DIVIDE('/') {
@Override
public int getDoubleArgumentPriority() {
return 4;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue / rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue / rightValue;
}
@Override
public Operator negate() {
return MULTIPLY;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>XOR (^).</td>
* </tr>
* <tr>
* <th>int</th>
* <td>{@link Math#pow(double, double)} with result converted to int.</td>
* </tr>
* <tr>
* <th>float</th>
* <td>{@link Math#pow(double, double)} with result converted to float.</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
POW('^') {
@Override
public int getDoubleArgumentPriority() {
return 4;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue ^ rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return (int) Math.pow(leftValue, rightValue);
}
@Override
public Object process(final float leftValue, final float rightValue) {
return (float) Math.pow(leftValue, rightValue);
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first + second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first + second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>first + second</td>
* </tr>
* </table>
*/
ADD('+') {
@Override
public int getDoubleArgumentPriority() {
return 3;
}
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final int value) {
return value;
}
@Override
public Object process(final float value) {
return value;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue + rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue + rightValue;
}
@Override
public Object process(final String leftValue, final String rightValue) {
return leftValue + rightValue;
}
@Override
public Operator merge(final Operator operator) {
if (operator == ADD) {
return INCREMENT;
} else if (operator == SUBTRACT) {
return SUBTRACT;
}
return super.merge(operator);
}
@Override
public Operator negate() {
return SUBTRACT;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>first - second</td>
* </tr>
* <tr>
* <th>float</th>
* <td>first - second</td>
* </tr>
* <tr>
* <th>String</th>
* <td>{@link String#replace(CharSequence, CharSequence)}. Occurrences of second value in first value are
* replaced with empty strings.</td>
* </tr>
* </table>
*/
SUBTRACT('-') {
@Override
public int getDoubleArgumentPriority() {
return 3;
}
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final int value) {
return -value;
}
@Override
public Object process(final float value) {
return -value;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue - rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue - rightValue;
}
@Override
public Object process(final String leftValue, final String rightValue) {
return leftValue.replace(rightValue, Strings.EMPTY_STRING);
}
@Override
public Operator merge(final Operator operator) {
if (operator == SUBTRACT) {
return DECREMENT;
} else if (operator == ADD) {
return SUBTRACT;
}
return super.merge(operator);
}
@Override
public Operator negate() {
return ADD;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>Modulo (%).</td>
* </tr>
* <tr>
* <th>float</th>
* <td>Modulo (%)</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
MODULO('%') {
@Override
public int getDoubleArgumentPriority() {
return 2;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue % rightValue;
}
@Override
public Object process(final float leftValue, final float rightValue) {
return leftValue % rightValue;
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>"Or" (||).</td>
* </tr>
* <tr>
* <th>int</th>
* <td>Bit "or" (|).</td>
* </tr>
* <tr>
* <th>float</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
OR('|') {
@Override
public int getDoubleArgumentPriority() {
return 0;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue || rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue | rightValue;
}
@Override
public Operator merge(final Operator operator) {
if (operator == OR) {
return OR;
}
return super.merge(operator);
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>"And" (&&).</td>
* </tr>
* <tr>
* <th>int</th>
* <td>Bit "and" (&).</td>
* </tr>
* <tr>
* <th>float</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
AND('&') {
@Override
public int getDoubleArgumentPriority() {
return 0;
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
return leftValue && rightValue;
}
@Override
public Object process(final int leftValue, final int rightValue) {
return leftValue & rightValue;
}
@Override
public Operator merge(final Operator operator) {
if (operator == AND) {
return AND;
}
return super.merge(operator);
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>++value</td>
* </tr>
* <tr>
* <th>float</th>
* <td>++value</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
INCREMENT('\uFFFE') { // Converted from ++.
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final int value) {
return value + 1;
}
@Override
public Object process(float value) {
return ++value;
}
@Override
public Operator negate() {
return DECREMENT;
}
@Override
protected String complexName() {
return "++";
}
},
/** <table summary="">
* <tr>
* <th>Type</th>
* <th>Effect</th>
* </tr>
* <tr>
* <th>boolean</th>
* <td>Not supported.</td>
* </tr>
* <tr>
* <th>int</th>
* <td>--value</td>
* </tr>
* <tr>
* <th>float</th>
* <td>--value</td>
* </tr>
* <tr>
* <th>String</th>
* <td>Not supported.</td>
* </tr>
* </table>
*/
DECREMENT('\uFFFF') { // Converted from --.
@Override
public int getSingleArgumentPriority() {
return 5;
}
@Override
public Object process(final int value) {
return value - 1;
}
@Override
public Object process(float value) {
return --value;
}
@Override
public Operator negate() {
return INCREMENT;
}
@Override
protected String complexName() {
return "--";
}
};
private final char sign;
private DefaultOperator(final char sign) {
this.sign = sign;
}
@Override
public Operator merge(final Operator operator) {
return null;
}
@Override
public Object process(final String leftValue, final String rightValue) {
throw new IllegalStateException(
this + " operator cannot handle two string arguments: " + leftValue + ", " + rightValue);
}
@Override
public Object process(final String value) {
throw new IllegalStateException(this + " operator cannot handle string argument: " + value);
}
@Override
public Object process(final float leftValue, final float rightValue) {
throw new IllegalStateException(
this + " operator cannot handle two float arguments: " + leftValue + ", " + rightValue);
}
@Override
public Object process(final float value) {
throw new IllegalStateException(this + " operator cannot handle float argument: " + value);
}
@Override
public Object process(final int leftValue, final int rightValue) {
throw new IllegalStateException(
this + " operator cannot handle two int arguments: " + leftValue + ", " + rightValue);
}
@Override
public Object process(final int value) {
throw new IllegalStateException(this + " operator cannot handle int argument: " + value);
}
@Override
public Object process(final boolean leftValue, final boolean rightValue) {
throw new IllegalStateException(
this + " operator cannot handle two boolean arguments: " + leftValue + ", " + rightValue);
}
@Override
public Object process(final boolean value) {
throw new IllegalStateException(this + " operator cannot handle boolean argument: " + value);
}
@Override
public Operator negate() {
throw new IllegalStateException(this + " operator cannot be negated.");
}
@Override
public char getSign() {
return sign;
}
/** @return value of a multiple-sign operator. */
protected String complexName() {
return null;
}
@Override
public int getDoubleArgumentPriority() {
return -1;
}
@Override
public int getSingleArgumentPriority() {
return -1;
}
@Override
public String toString() {
return (complexName() == null ? String.valueOf(getSign()) : complexName()) + " (" + name() + ')';
}
}
}