406 lines
14 KiB
Java
406 lines
14 KiB
Java
package btools.expressions;
|
|
|
|
import java.util.StringTokenizer;
|
|
|
|
final class BExpression {
|
|
private static final int OR_EXP = 10;
|
|
private static final int AND_EXP = 11;
|
|
private static final int NOT_EXP = 12;
|
|
|
|
private static final int ADD_EXP = 20;
|
|
private static final int MULTIPLY_EXP = 21;
|
|
private static final int DIVIDE_EXP = 22;
|
|
private static final int MAX_EXP = 23;
|
|
private static final int EQUAL_EXP = 24;
|
|
private static final int GREATER_EXP = 25;
|
|
private static final int MIN_EXP = 26;
|
|
|
|
private static final int SUB_EXP = 27;
|
|
private static final int LESSER_EXP = 28;
|
|
private static final int XOR_EXP = 29;
|
|
|
|
private static final int SWITCH_EXP = 30;
|
|
private static final int ASSIGN_EXP = 31;
|
|
private static final int LOOKUP_EXP = 32;
|
|
private static final int NUMBER_EXP = 33;
|
|
private static final int VARIABLE_EXP = 34;
|
|
private static final int FOREIGN_VARIABLE_EXP = 35;
|
|
private static final int VARIABLE_GET_EXP = 36;
|
|
|
|
private int typ;
|
|
private BExpression op1;
|
|
private BExpression op2;
|
|
private BExpression op3;
|
|
private float numberValue;
|
|
private int variableIdx;
|
|
private int lookupNameIdx = -1;
|
|
private int[] lookupValueIdxArray;
|
|
private boolean doNotChange;
|
|
|
|
// Parse the expression and all subexpression
|
|
public static BExpression parse(BExpressionContext ctx, int level) throws Exception {
|
|
return parse(ctx, level, null);
|
|
}
|
|
|
|
private static BExpression parse(BExpressionContext ctx, int level, String optionalToken) throws Exception {
|
|
BExpression e = parseRaw(ctx, level, optionalToken);
|
|
if (e == null) {
|
|
return null;
|
|
}
|
|
|
|
if (ASSIGN_EXP == e.typ) {
|
|
// manage assined an injected values
|
|
BExpression assignedBefore = ctx.lastAssignedExpression.get(e.variableIdx);
|
|
if (assignedBefore != null && assignedBefore.doNotChange) {
|
|
e.op1 = assignedBefore; // was injected as key-value
|
|
e.op1.doNotChange = false; // protect just once, can be changed in second assignement
|
|
}
|
|
ctx.lastAssignedExpression.set(e.variableIdx, e.op1);
|
|
}
|
|
else if (!ctx.skipConstantExpressionOptimizations) {
|
|
// try to simplify the expression
|
|
if (VARIABLE_EXP == e.typ) {
|
|
BExpression ae = ctx.lastAssignedExpression.get(e.variableIdx);
|
|
if (ae != null && ae.typ == NUMBER_EXP) {
|
|
e = ae;
|
|
}
|
|
} else {
|
|
BExpression eCollapsed = e.tryCollapse();
|
|
if (e != eCollapsed) {
|
|
e = eCollapsed; // allow breakpoint..
|
|
}
|
|
BExpression eEvaluated = e.tryEvaluateConstant();
|
|
if (e != eEvaluated) {
|
|
e = eEvaluated; // allow breakpoint..
|
|
}
|
|
}
|
|
}
|
|
if (level == 0) {
|
|
// mark the used lookups after the
|
|
// expression is collapsed to not mark
|
|
// lookups as used that appear in the profile
|
|
// but are de-activated by constant expressions
|
|
int nodeCount = e.markLookupIdxUsed(ctx);
|
|
ctx.expressionNodeCount += nodeCount;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
private int markLookupIdxUsed(BExpressionContext ctx) {
|
|
int nodeCount = 1;
|
|
if (lookupNameIdx >= 0) {
|
|
ctx.markLookupIdxUsed(lookupNameIdx);
|
|
}
|
|
if (op1 != null) {
|
|
nodeCount += op1.markLookupIdxUsed(ctx);
|
|
}
|
|
if (op2 != null) {
|
|
nodeCount += op2.markLookupIdxUsed(ctx);
|
|
}
|
|
if (op3 != null) {
|
|
nodeCount += op3.markLookupIdxUsed(ctx);
|
|
}
|
|
return nodeCount;
|
|
}
|
|
|
|
private static BExpression parseRaw(BExpressionContext ctx, int level, String optionalToken) throws Exception {
|
|
|
|
boolean brackets = false;
|
|
String operator = ctx.parseToken();
|
|
if (optionalToken != null && optionalToken.equals(operator)) {
|
|
operator = ctx.parseToken();
|
|
}
|
|
if ("(".equals(operator)) {
|
|
brackets = true;
|
|
operator = ctx.parseToken();
|
|
}
|
|
|
|
if (operator == null) {
|
|
if (level == 0) return null;
|
|
else throw new IllegalArgumentException("unexpected end of file");
|
|
}
|
|
|
|
if (level == 0) {
|
|
if (!"assign".equals(operator)) {
|
|
throw new IllegalArgumentException("operator " + operator + " is invalid on toplevel (only 'assign' allowed)");
|
|
}
|
|
}
|
|
|
|
BExpression exp = new BExpression();
|
|
int nops = 3;
|
|
|
|
boolean ifThenElse = false;
|
|
|
|
if ("switch".equals(operator)) {
|
|
exp.typ = SWITCH_EXP;
|
|
} else if ("if".equals(operator)) {
|
|
exp.typ = SWITCH_EXP;
|
|
ifThenElse = true;
|
|
} else {
|
|
nops = 2; // check binary expressions
|
|
|
|
if ("or".equals(operator)) {
|
|
exp.typ = OR_EXP;
|
|
} else if ("and".equals(operator)) {
|
|
exp.typ = AND_EXP;
|
|
} else if ("multiply".equals(operator)) {
|
|
exp.typ = MULTIPLY_EXP;
|
|
} else if ("divide".equals(operator)) {
|
|
exp.typ = DIVIDE_EXP;
|
|
} else if ("add".equals(operator)) {
|
|
exp.typ = ADD_EXP;
|
|
} else if ("max".equals(operator)) {
|
|
exp.typ = MAX_EXP;
|
|
} else if ("min".equals(operator)) {
|
|
exp.typ = MIN_EXP;
|
|
} else if ("equal".equals(operator)) {
|
|
exp.typ = EQUAL_EXP;
|
|
} else if ("greater".equals(operator)) {
|
|
exp.typ = GREATER_EXP;
|
|
} else if ("sub".equals(operator)) {
|
|
exp.typ = SUB_EXP;
|
|
} else if ("lesser".equals(operator)) {
|
|
exp.typ = LESSER_EXP;
|
|
} else if ("xor".equals(operator)) {
|
|
exp.typ = XOR_EXP;
|
|
} else {
|
|
nops = 1; // check unary expressions
|
|
if ("assign".equals(operator)) {
|
|
if (level > 0) throw new IllegalArgumentException("assign operator within expression");
|
|
exp.typ = ASSIGN_EXP;
|
|
String variable = ctx.parseToken();
|
|
if (variable == null) throw new IllegalArgumentException("unexpected end of file");
|
|
if (variable.indexOf('=') >= 0)
|
|
throw new IllegalArgumentException("variable name cannot contain '=': " + variable);
|
|
if (variable.indexOf(':') >= 0)
|
|
throw new IllegalArgumentException("cannot assign context-prefixed variable: " + variable);
|
|
exp.variableIdx = ctx.getVariableIdx(variable, true);
|
|
if (exp.variableIdx < ctx.getMinWriteIdx())
|
|
throw new IllegalArgumentException("cannot assign to readonly variable " + variable);
|
|
} else if ("not".equals(operator)) {
|
|
exp.typ = NOT_EXP;
|
|
} else {
|
|
nops = 0; // check elemantary expressions
|
|
int idx = operator.indexOf('=');
|
|
if (idx >= 0) {
|
|
exp.typ = LOOKUP_EXP;
|
|
String name = operator.substring(0, idx);
|
|
String values = operator.substring(idx + 1);
|
|
|
|
exp.lookupNameIdx = ctx.getLookupNameIdx(name);
|
|
if (exp.lookupNameIdx < 0) {
|
|
throw new IllegalArgumentException("unknown lookup name: " + name);
|
|
}
|
|
StringTokenizer tk = new StringTokenizer(values, "|");
|
|
int nt = tk.countTokens();
|
|
int nt2 = nt == 0 ? 1 : nt;
|
|
exp.lookupValueIdxArray = new int[nt2];
|
|
for (int ti = 0; ti < nt2; ti++) {
|
|
String value = ti < nt ? tk.nextToken() : "";
|
|
exp.lookupValueIdxArray[ti] = ctx.getLookupValueIdx(exp.lookupNameIdx, value);
|
|
if (exp.lookupValueIdxArray[ti] < 0) {
|
|
throw new IllegalArgumentException("unknown lookup value: " + value);
|
|
}
|
|
}
|
|
} else if ((idx = operator.indexOf(':')) >= 0) {
|
|
/*
|
|
use of variable values
|
|
assign no_height
|
|
switch and not maxheight=
|
|
lesser v:maxheight my_height true
|
|
false
|
|
*/
|
|
if (operator.startsWith("v:")) {
|
|
String name = operator.substring(2);
|
|
exp.typ = VARIABLE_GET_EXP;
|
|
exp.lookupNameIdx = ctx.getLookupNameIdx(name);
|
|
} else {
|
|
String context = operator.substring(0, idx);
|
|
String varname = operator.substring(idx + 1);
|
|
exp.typ = FOREIGN_VARIABLE_EXP;
|
|
exp.variableIdx = ctx.getForeignVariableIdx(context, varname);
|
|
}
|
|
} else if ((idx = ctx.getVariableIdx(operator, false)) >= 0) {
|
|
exp.typ = VARIABLE_EXP;
|
|
exp.variableIdx = idx;
|
|
} else if ("true".equals(operator)) {
|
|
exp.numberValue = 1.f;
|
|
exp.typ = NUMBER_EXP;
|
|
} else if ("false".equals(operator)) {
|
|
exp.numberValue = 0.f;
|
|
exp.typ = NUMBER_EXP;
|
|
} else {
|
|
try {
|
|
exp.numberValue = Float.parseFloat(operator);
|
|
exp.typ = NUMBER_EXP;
|
|
} catch (NumberFormatException nfe) {
|
|
throw new IllegalArgumentException("unknown expression: " + operator);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// parse operands
|
|
if (nops > 0) {
|
|
exp.op1 = parse(ctx, level + 1, exp.typ == ASSIGN_EXP ? "=" : null);
|
|
}
|
|
if (nops > 1) {
|
|
if (ifThenElse) checkExpectedToken(ctx, "then");
|
|
exp.op2 = parse(ctx, level + 1, null);
|
|
}
|
|
if (nops > 2) {
|
|
if (ifThenElse) checkExpectedToken(ctx, "else");
|
|
exp.op3 = parse(ctx, level + 1, null);
|
|
}
|
|
if (brackets) {
|
|
checkExpectedToken(ctx, ")");
|
|
}
|
|
return exp;
|
|
}
|
|
|
|
private static void checkExpectedToken(BExpressionContext ctx, String expected) throws Exception {
|
|
String token = ctx.parseToken();
|
|
if (!expected.equals(token)) {
|
|
throw new IllegalArgumentException("unexpected token: " + token + ", expected: " + expected);
|
|
}
|
|
}
|
|
|
|
// Evaluate the expression
|
|
public float evaluate(BExpressionContext ctx) {
|
|
switch (typ) {
|
|
case OR_EXP:
|
|
return op1.evaluate(ctx) != 0.f ? 1.f : (op2.evaluate(ctx) != 0.f ? 1.f : 0.f);
|
|
case XOR_EXP:
|
|
return ((op1.evaluate(ctx) != 0.f) ^ (op2.evaluate(ctx) != 0.f) ? 1.f : 0.f);
|
|
case AND_EXP:
|
|
return op1.evaluate(ctx) != 0.f ? (op2.evaluate(ctx) != 0.f ? 1.f : 0.f) : 0.f;
|
|
case ADD_EXP:
|
|
return op1.evaluate(ctx) + op2.evaluate(ctx);
|
|
case SUB_EXP:
|
|
return op1.evaluate(ctx) - op2.evaluate(ctx);
|
|
case MULTIPLY_EXP:
|
|
return op1.evaluate(ctx) * op2.evaluate(ctx);
|
|
case DIVIDE_EXP:
|
|
return divide(op1.evaluate(ctx), op2.evaluate(ctx));
|
|
case MAX_EXP:
|
|
return max(op1.evaluate(ctx), op2.evaluate(ctx));
|
|
case MIN_EXP:
|
|
return min(op1.evaluate(ctx), op2.evaluate(ctx));
|
|
case EQUAL_EXP:
|
|
return op1.evaluate(ctx) == op2.evaluate(ctx) ? 1.f : 0.f;
|
|
case GREATER_EXP:
|
|
return op1.evaluate(ctx) > op2.evaluate(ctx) ? 1.f : 0.f;
|
|
case LESSER_EXP:
|
|
return op1.evaluate(ctx) < op2.evaluate(ctx) ? 1.f : 0.f;
|
|
case SWITCH_EXP:
|
|
return op1.evaluate(ctx) != 0.f ? op2.evaluate(ctx) : op3.evaluate(ctx);
|
|
case ASSIGN_EXP:
|
|
return ctx.assign(variableIdx, op1.evaluate(ctx));
|
|
case LOOKUP_EXP:
|
|
return ctx.getLookupMatch(lookupNameIdx, lookupValueIdxArray);
|
|
case NUMBER_EXP:
|
|
return numberValue;
|
|
case VARIABLE_EXP:
|
|
return ctx.getVariableValue(variableIdx);
|
|
case FOREIGN_VARIABLE_EXP:
|
|
return ctx.getForeignVariableValue(variableIdx);
|
|
case VARIABLE_GET_EXP:
|
|
return ctx.getLookupValue(lookupNameIdx);
|
|
case NOT_EXP:
|
|
return op1.evaluate(ctx) == 0.f ? 1.f : 0.f;
|
|
default:
|
|
throw new IllegalArgumentException("unknown op-code: " + typ);
|
|
}
|
|
}
|
|
|
|
// Try to collapse the expression
|
|
// if logically possible
|
|
private BExpression tryCollapse() {
|
|
switch (typ) {
|
|
case OR_EXP:
|
|
return NUMBER_EXP == op1.typ ?
|
|
(op1.numberValue != 0.f ? op1 : op2)
|
|
: (NUMBER_EXP == op2.typ ?
|
|
(op2.numberValue != 0.f ? op2 : op1)
|
|
: this);
|
|
case AND_EXP:
|
|
return NUMBER_EXP == op1.typ ?
|
|
(op1.numberValue == 0.f ? op1 : op2)
|
|
: (NUMBER_EXP == op2.typ ?
|
|
(op2.numberValue == 0.f ? op2 : op1)
|
|
: this);
|
|
case ADD_EXP:
|
|
return NUMBER_EXP == op1.typ ?
|
|
(op1.numberValue == 0.f ? op2 : this)
|
|
: (NUMBER_EXP == op2.typ ?
|
|
(op2.numberValue == 0.f ? op1 : this)
|
|
: this);
|
|
case SWITCH_EXP:
|
|
return NUMBER_EXP == op1.typ ?
|
|
(op1.numberValue == 0.f ? op3 : op2) : this;
|
|
default:
|
|
return this;
|
|
}
|
|
}
|
|
|
|
// Try to evaluate the expression
|
|
// if all operands are constant
|
|
private BExpression tryEvaluateConstant() {
|
|
if (op1 != null && NUMBER_EXP == op1.typ
|
|
&& (op2 == null || NUMBER_EXP == op2.typ)
|
|
&& (op3 == null || NUMBER_EXP == op3.typ)) {
|
|
BExpression exp = new BExpression();
|
|
exp.typ = NUMBER_EXP;
|
|
exp.numberValue = evaluate(null);
|
|
return exp;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private float max(float v1, float v2) {
|
|
return v1 > v2 ? v1 : v2;
|
|
}
|
|
|
|
private float min(float v1, float v2) {
|
|
return v1 < v2 ? v1 : v2;
|
|
}
|
|
|
|
private float divide(float v1, float v2) {
|
|
if (v2 == 0f) throw new IllegalArgumentException("div by zero");
|
|
return v1 / v2;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (typ == NUMBER_EXP) {
|
|
return "" + numberValue;
|
|
}
|
|
if (typ == VARIABLE_EXP) {
|
|
return "vidx=" + variableIdx;
|
|
}
|
|
StringBuilder sb = new StringBuilder("typ=" + typ + " ops=(");
|
|
addOp(sb, op1);
|
|
addOp(sb, op2);
|
|
addOp(sb, op3);
|
|
sb.append(')');
|
|
return sb.toString();
|
|
}
|
|
|
|
private void addOp(StringBuilder sb, BExpression e) {
|
|
if (e != null) {
|
|
sb.append('[').append(e.toString()).append(']');
|
|
}
|
|
}
|
|
|
|
static BExpression createAssignExpressionFromKeyValue(BExpressionContext ctx, String key, String value) {
|
|
BExpression e = new BExpression();
|
|
e.typ = ASSIGN_EXP;
|
|
e.variableIdx = ctx.getVariableIdx(key, true);
|
|
e.op1 = new BExpression();
|
|
e.op1.typ = NUMBER_EXP;
|
|
e.op1.numberValue = Float.parseFloat(value);
|
|
e.op1.doNotChange = true;
|
|
ctx.lastAssignedExpression.set(e.variableIdx, e.op1);
|
|
return e;
|
|
}
|
|
}
|