/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2014 Ausenco Engineering Canada Inc.
* Copyright (C) 2016 JaamSim Software Inc.
*
* Licensed 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 com.jaamsim.input;
import java.util.ArrayList;
import com.jaamsim.basicsim.ObjectType;
import com.jaamsim.units.AngleUnit;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.Unit;
public class ExpParser {
public interface UnOpFunc {
public void checkTypeAndUnits(ParseContext context, ExpResult val, String source, int pos) throws ExpError;
public ExpResult apply(ParseContext context, ExpResult val) throws ExpError;
public ExpValResult validate(ParseContext context, ExpValResult val, String source, int pos);
}
public interface BinOpFunc {
public void checkTypeAndUnits(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError;
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError;
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos);
}
public interface CallableFunc {
public void checkUnits(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError;
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError;
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos);
}
public static class UnitData {
double scaleFactor;
Class<? extends Unit> unitType;
}
public interface OutputResolver {
public ExpResult resolve(EvalContext ec, ExpResult ent) throws ExpError;
public ExpValResult validate(ExpValResult entValRes);
}
public interface Assigner {
public void assign(ExpResult ent, ExpResult index, ExpResult val) throws ExpError;
}
public interface ParseContext {
public UnitData getUnitByName(String name);
public Class<? extends Unit> multUnitTypes(Class<? extends Unit> a, Class<? extends Unit> b);
public Class<? extends Unit> divUnitTypes(Class<? extends Unit> num, Class<? extends Unit> denom);
public ExpResult getValFromName(String name, String source, int pos) throws ExpError;
public OutputResolver getOutputResolver(String name) throws ExpError;
public OutputResolver getConstOutputResolver(ExpResult constEnt, String name) throws ExpError;
public Assigner getAssigner(String attribName) throws ExpError;
public Assigner getConstAssigner(ExpResult constEnt, String attribName) throws ExpError;
}
public interface EvalContext {
//public ExpResult getVariableValue(String[] names, ExpResult[] indices) throws ExpError;
//public boolean eagerEval();
}
// public interface ValContext {
//
// }
private interface ExpressionWalker {
public void visit(ExpNode exp) throws ExpError;
public ExpNode updateRef(ExpNode exp) throws ExpError;
}
////////////////////////////////////////////////////////////////////
// Expression types
public static class Expression {
public final String source;
public ExpValResult validationResult;
protected final ArrayList<Thread> executingThreads = new ArrayList<>();
private ExpNode rootNode;
public Expression(String source) {
this.source = source;
}
public ExpResult evaluate(EvalContext ec) throws ExpError {
synchronized(executingThreads) {
if (executingThreads.contains(Thread.currentThread())) {
throw new ExpError(null, 0, "Expression recursion detected for expression: %s", source);
}
executingThreads.add(Thread.currentThread());
}
ExpResult res = null;
try {
res = rootNode.evaluate(ec);
} finally {
synchronized(executingThreads) {
executingThreads.remove(Thread.currentThread());
}
}
return res;
}
void setRootNode(ExpNode node) {
rootNode = node;
}
@Override
public String toString() {
return source;
}
}
public static class Assignment extends Expression {
public ExpNode entExp;
public ExpNode attribIndex;
public ExpNode valueExp;
public Assigner assigner;
public Assignment(String source) {
super(source);
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
synchronized(executingThreads) {
if (executingThreads.contains(Thread.currentThread())) {
throw new ExpError(null, 0, "Expression recursion detected for expression: %s", source);
}
executingThreads.add(Thread.currentThread());
}
try {
ExpResult ent = entExp.evaluate(ec);
ExpResult value = valueExp.evaluate(ec);
ExpResult index = null;
if (attribIndex != null) {
index = attribIndex.evaluate(ec);
}
assigner.assign(ent, index, value);
return value;
} finally {
synchronized(executingThreads) {
executingThreads.remove(Thread.currentThread());
}
}
}
}
private abstract static class ExpNode {
public final ParseContext context;
public final Expression exp;
public final int tokenPos;
public abstract ExpResult evaluate(EvalContext ec) throws ExpError;
public abstract ExpValResult validate();
public ExpNode(ParseContext context, Expression exp, int pos) {
this.context = context;
this.tokenPos = pos;
this.exp = exp;
}
abstract void walk(ExpressionWalker w) throws ExpError;
// Get a version of this node that skips runtime checks if safe to do so,
// otherwise return null
public ExpNode getNoCheckVer() {
return null;
}
}
private static class Constant extends ExpNode {
public ExpResult val;
public Constant(ParseContext context, ExpResult val, Expression exp, int pos) {
super(context, exp, pos);
this.val = val;
}
@Override
public ExpResult evaluate(EvalContext ec) {
return val;
}
@Override
public ExpValResult validate() {
return ExpValResult.makeValidRes(val.type, val.unitType);
}
@Override
void walk(ExpressionWalker w) throws ExpError {
w.visit(this);
}
}
public static class ResolveOutput extends ExpNode {
public ExpNode entNode;
public String outputName;
private final OutputResolver resolver;
public ResolveOutput(ParseContext context, String outputName, ExpNode entNode, Expression exp, int pos) throws ExpError {
super(context, exp, pos);
this.entNode = entNode;
this.outputName = outputName;
try {
if (entNode instanceof Constant) {
this.resolver = context.getConstOutputResolver(entNode.evaluate(null), outputName);
} else {
this.resolver = context.getOutputResolver(outputName);
}
} catch (ExpError ex) {
throw (fixError(ex, exp.source, pos));
}
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
try {
ExpResult ent = entNode.evaluate(ec);
return resolver.resolve(ec, ent);
} catch (ExpError ex) {
throw fixError(ex, exp.source, tokenPos);
}
}
@Override
public ExpValResult validate() {
ExpValResult entValRes = entNode.validate();
if ( entValRes.state == ExpValResult.State.ERROR ||
entValRes.state == ExpValResult.State.UNDECIDABLE) {
return entValRes;
}
ExpValResult res = resolver.validate(entValRes);
fixValidationErrors(res, exp.source, tokenPos);
return res;
}
@Override
void walk(ExpressionWalker w) throws ExpError {
entNode.walk(w);
entNode = w.updateRef(entNode);
w.visit(this);
}
}
private static class IndexCollection extends ExpNode {
public ExpNode collection;
public ExpNode index;
public IndexCollection(ParseContext context, ExpNode collection, ExpNode index, Expression exp, int pos) throws ExpError {
super(context, exp, pos);
this.collection = collection;
this.index = index;
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
try {
ExpResult colRes = collection.evaluate(ec);
ExpResult indRes = index.evaluate(ec);
if (colRes.type != ExpResType.COLLECTION) {
throw new ExpError(exp.source, tokenPos, "Expression does not evaluate to a collection type.");
}
return colRes.colVal.index(indRes);
} catch (ExpError ex) {
throw fixError(ex, exp.source, tokenPos);
}
}
@Override
public ExpValResult validate() {
ExpValResult colValRes = collection.validate();
ExpValResult indValRes = index.validate();
if (colValRes.state == ExpValResult.State.ERROR) {
fixValidationErrors(colValRes, exp.source, tokenPos);
return colValRes;
}
if (indValRes.state == ExpValResult.State.ERROR) {
fixValidationErrors(indValRes, exp.source, tokenPos);
return indValRes;
}
if (colValRes.state == ExpValResult.State.UNDECIDABLE) {
return colValRes;
}
if (indValRes.state == ExpValResult.State.UNDECIDABLE) {
return indValRes;
}
if (colValRes.type != ExpResType.COLLECTION) {
return ExpValResult.makeErrorRes(new ExpError(exp.source, tokenPos, "Expression does not evaluate to a collection type."));
}
// TODO: validate collection types
return ExpValResult.makeUndecidableRes();
}
@Override
void walk(ExpressionWalker w) throws ExpError {
collection.walk(w);
collection = w.updateRef(collection);
index.walk(w);
index = w.updateRef(index);
w.visit(this);
}
}
private static class UnaryOp extends ExpNode {
public ExpNode subExp;
protected final UnOpFunc func;
public String name;
public boolean canSkipRuntimeChecks = false;
UnaryOp(String name, ParseContext context, ExpNode subExp, UnOpFunc func, Expression exp, int pos) {
super(context, exp, pos);
this.subExp = subExp;
this.func = func;
this.name = name;
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult subExpVal = subExp.evaluate(ec);
func.checkTypeAndUnits(context, subExpVal, exp.source, tokenPos);
return func.apply(context, subExpVal);
}
@Override
public ExpValResult validate() {
ExpValResult res = func.validate(context, subExp.validate(), exp.source, tokenPos);
if (res.state == ExpValResult.State.VALID)
canSkipRuntimeChecks = true;
return res;
}
@Override
void walk(ExpressionWalker w) throws ExpError {
subExp.walk(w);
subExp = w.updateRef(subExp);
w.visit(this);
}
@Override
public ExpNode getNoCheckVer() {
if (canSkipRuntimeChecks)
return new UnaryOpNoChecks(this);
else
return null;
}
@Override
public String toString() {
return "UnaryOp: " + name;
}
}
private static class UnaryOpNoChecks extends UnaryOp {
UnaryOpNoChecks(UnaryOp uo) {
super(uo.name, uo.context, uo.subExp, uo.func, uo.exp, uo.tokenPos);
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult subExpVal = subExp.evaluate(ec);
return func.apply(context, subExpVal);
}
}
private static class BinaryOp extends ExpNode {
public ExpNode lSubExp;
public ExpNode rSubExp;
public boolean canSkipRuntimeChecks = false;
public String name;
protected final BinOpFunc func;
BinaryOp(String name, ParseContext context, ExpNode lSubExp, ExpNode rSubExp, BinOpFunc func, Expression exp, int pos) {
super(context, exp, pos);
this.lSubExp = lSubExp;
this.rSubExp = rSubExp;
this.func = func;
this.name = name;
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult lRes = lSubExp.evaluate(ec);
ExpResult rRes = rSubExp.evaluate(ec);
func.checkTypeAndUnits(context, lRes, rRes, exp.source, tokenPos);
return func.apply(context, lRes, rRes, exp.source, tokenPos);
}
@Override
public ExpValResult validate() {
ExpValResult lRes = lSubExp.validate();
ExpValResult rRes = rSubExp.validate();
ExpValResult res = func.validate(context, lRes, rRes, exp.source, tokenPos);
if (res.state == ExpValResult.State.VALID)
canSkipRuntimeChecks = true;
return res;
}
@Override
void walk(ExpressionWalker w) throws ExpError {
lSubExp.walk(w);
rSubExp.walk(w);
lSubExp = w.updateRef(lSubExp);
rSubExp = w.updateRef(rSubExp);
w.visit(this);
}
@Override
public ExpNode getNoCheckVer() {
if (canSkipRuntimeChecks)
return new BinaryOpNoChecks(this);
else
return null;
}
@Override
public String toString() {
return "BinaryOp: " + name;
}
}
private static class BinaryOpNoChecks extends BinaryOp {
BinaryOpNoChecks(BinaryOp bo) {
super(bo.name, bo.context, bo.lSubExp, bo.rSubExp, bo.func, bo.exp, bo.tokenPos);
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult lRes = lSubExp.evaluate(ec);
ExpResult rRes = rSubExp.evaluate(ec);
return func.apply(context, lRes, rRes, exp.source, tokenPos);
}
}
public static class Conditional extends ExpNode {
private ExpNode condExp;
private ExpNode trueExp;
private ExpNode falseExp;
public Conditional(ParseContext context, ExpNode c, ExpNode t, ExpNode f, Expression exp, int pos) {
super(context, exp, pos);
condExp = c;
trueExp = t;
falseExp =f;
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult condRes = condExp.evaluate(ec); //constCondRes != null ? constCondRes : condExp.evaluate(ec);
if (condRes.value == 0)
return falseExp.evaluate(ec);
else
return trueExp.evaluate(ec);
}
@Override
public ExpValResult validate() {
ExpValResult condRes = condExp.validate();
ExpValResult trueRes = trueExp.validate();
ExpValResult falseRes = falseExp.validate();
if ( condRes.state == ExpValResult.State.ERROR ||
trueRes.state == ExpValResult.State.ERROR ||
falseRes.state == ExpValResult.State.ERROR) {
// Error state, merge all returned errors
ArrayList<ExpError> errors = new ArrayList<>();
if (condRes.errors != null)
errors.addAll(condRes.errors);
if (trueRes.errors != null)
errors.addAll(trueRes.errors);
if (falseRes.errors != null)
errors.addAll(falseRes.errors);
return ExpValResult.makeErrorRes(errors);
}
else if ( condRes.state == ExpValResult.State.UNDECIDABLE ||
trueRes.state == ExpValResult.State.UNDECIDABLE ||
falseRes.state == ExpValResult.State.UNDECIDABLE) {
return ExpValResult.makeUndecidableRes();
}
// All valid case
// Check that both sides of the branch return the same type
if (trueRes.type != falseRes.type) {
ExpError typeError = new ExpError(exp.source, tokenPos,
"Type mismatch in conditional. True branch is %s, false branch is %s",
trueRes.type.toString(), falseRes.type.toString());
return ExpValResult.makeErrorRes(typeError);
}
// Check that both sides of the branch return the same unit types, for numerical types
if (trueRes.type == ExpResType.NUMBER && trueRes.unitType != falseRes.unitType) {
ExpError unitError = new ExpError(exp.source, tokenPos,
"Unit mismatch in conditional. True branch is %s, false branch is %s",
trueRes.unitType.getSimpleName(), falseRes.unitType.getSimpleName());
return ExpValResult.makeErrorRes(unitError);
}
return ExpValResult.makeValidRes(trueRes.type, trueRes.unitType);
}
@Override
void walk(ExpressionWalker w) throws ExpError {
condExp.walk(w);
trueExp.walk(w);
falseExp.walk(w);
condExp = w.updateRef(condExp);
trueExp = w.updateRef(trueExp);
falseExp = w.updateRef(falseExp);
w.visit(this);
}
@Override
public String toString() {
return "Conditional";
}
}
public static class FuncCall extends ExpNode {
protected final ArrayList<ExpNode> args;
protected final CallableFunc function;
private boolean canSkipRuntimeChecks = false;
private final String name;
public FuncCall(String name, ParseContext context, CallableFunc function, ArrayList<ExpNode> args, Expression exp, int pos) {
super(context, exp, pos);
this.function = function;
this.args = args;
this.name = name;
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult[] argVals = new ExpResult[args.size()];
for (int i = 0; i < args.size(); ++i) {
argVals[i] = args.get(i).evaluate(ec);
}
function.checkUnits(context, argVals, exp.source, tokenPos);
return function.call(context, argVals, exp.source, tokenPos);
}
@Override
public ExpValResult validate() {
ExpValResult[] argVals = new ExpValResult[args.size()];
for (int i = 0; i < args.size(); ++i) {
argVals[i] = args.get(i).validate();
}
ExpValResult res = function.validate(context, argVals, exp.source, tokenPos);
if (res.state == ExpValResult.State.VALID)
canSkipRuntimeChecks = true;
return res;
}
@Override
void walk(ExpressionWalker w) throws ExpError {
for (int i = 0; i < args.size(); ++i) {
args.get(i).walk(w);
}
for (int i = 0; i < args.size(); ++i) {
args.set(i, w.updateRef(args.get(i)));
}
w.visit(this);
}
@Override
public ExpNode getNoCheckVer() {
if (canSkipRuntimeChecks)
return new FuncCallNoChecks(this);
else
return null;
}
@Override
public String toString() {
return "Function: " + name;
}
}
private static class FuncCallNoChecks extends FuncCall {
FuncCallNoChecks(FuncCall fc) {
super(fc.name, fc.context, fc.function, fc.args, fc.exp, fc.tokenPos);
}
@Override
public ExpResult evaluate(EvalContext ec) throws ExpError {
ExpResult[] argVals = new ExpResult[args.size()];
for (int i = 0; i < args.size(); ++i) {
argVals[i] = args.get(i).evaluate(ec);
}
return function.call(context, argVals, exp.source, tokenPos);
}
}
// Some errors can be throw without a known source or position, update such errors with the given info
private static ExpError fixError(ExpError ex, String source, int pos) {
ExpError exFixed = ex;
if (ex.source == null) {
exFixed = new ExpError(source, pos, ex.getMessage());
}
return exFixed;
}
private static void fixValidationErrors(ExpValResult res, String source, int pos) {
if (res.state == ExpValResult.State.ERROR ) {
for (int i = 0; i < res.errors.size(); ++i) {
res.errors.set(i, fixError(res.errors.get(i), source, pos));
}
}
}
///////////////////////////////////////////////////////////
// Entries for user definable operators and functions
private static class UnaryOpEntry {
public String symbol;
public UnOpFunc function;
public double bindingPower;
}
private static class BinaryOpEntry {
public String symbol;
public BinOpFunc function;
public double bindingPower;
public boolean rAssoc;
}
private static class FunctionEntry {
public String name;
public CallableFunc function;
public int numMinArgs;
public int numMaxArgs;
}
private static ArrayList<UnaryOpEntry> unaryOps = new ArrayList<>();
private static ArrayList<BinaryOpEntry> binaryOps = new ArrayList<>();
private static ArrayList<FunctionEntry> functions = new ArrayList<>();
private static void addUnaryOp(String symbol, double bindPower, UnOpFunc func) {
UnaryOpEntry oe = new UnaryOpEntry();
oe.symbol = symbol;
oe.function = func;
oe.bindingPower = bindPower;
unaryOps.add(oe);
}
private static void addBinaryOp(String symbol, double bindPower, boolean rAssoc, BinOpFunc func) {
BinaryOpEntry oe = new BinaryOpEntry();
oe.symbol = symbol;
oe.function = func;
oe.bindingPower = bindPower;
oe.rAssoc = rAssoc;
binaryOps.add(oe);
}
private static void addFunction(String name, int numMinArgs, int numMaxArgs, CallableFunc func) {
FunctionEntry fe = new FunctionEntry();
fe.name = name;
fe.function = func;
fe.numMinArgs = numMinArgs;
fe.numMaxArgs = numMaxArgs;
functions.add(fe);
}
private static UnaryOpEntry getUnaryOp(String symbol) {
for (UnaryOpEntry oe: unaryOps) {
if (oe.symbol.equals(symbol))
return oe;
}
return null;
}
private static BinaryOpEntry getBinaryOp(String symbol) {
for (BinaryOpEntry oe: binaryOps) {
if (oe.symbol.equals(symbol))
return oe;
}
return null;
}
private static FunctionEntry getFunctionEntry(String funcName) {
for (FunctionEntry fe : functions) {
if (fe.name.equals(funcName)){
return fe;
}
}
return null;
}
///////////////////////////////////////////////////
// Operator Utility Functions
// See if there are any existing errors, or this branch is undecidable
private static ExpValResult mergeBinaryErrors(ExpValResult lval, ExpValResult rval) {
if ( lval.state == ExpValResult.State.ERROR ||
rval.state == ExpValResult.State.ERROR) {
// Propagate the error, no further checking
ArrayList<ExpError> errors = new ArrayList<>();
if (lval.errors != null)
errors.addAll(lval.errors);
if (rval.errors != null)
errors.addAll(rval.errors);
return ExpValResult.makeErrorRes(errors);
}
if ( lval.state == ExpValResult.State.UNDECIDABLE ||
rval.state == ExpValResult.State.UNDECIDABLE) {
return ExpValResult.makeUndecidableRes();
}
return null;
}
private static ExpValResult mergeMultipleErrors(ExpValResult[] args) {
for (ExpValResult val : args) {
if (val.state == ExpValResult.State.ERROR) {
// We have an error, merge all error results and return
ArrayList<ExpError> errors = new ArrayList<>();
for (ExpValResult errVal : args) {
if (errVal.errors != null)
errors.addAll(errVal.errors);
}
return ExpValResult.makeErrorRes(errors);
}
}
for (ExpValResult val : args) {
if (val.state == ExpValResult.State.UNDECIDABLE) {
// At least one value in undecidable, propagate it
return ExpValResult.makeUndecidableRes();
}
}
return null;
}
private static void checkBothNumbers(ExpResult lval, ExpResult rval, String source, int pos)
throws ExpError {
if (lval.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Left operand must be a number");
}
if (rval.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Right operand must be a number");
}
}
private static ExpValResult validateBothNumbers(ExpValResult lval, ExpValResult rval, String source, int pos) {
if (lval.type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, "Left operand must be a number");
return ExpValResult.makeErrorRes(error);
}
if (rval.type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, "Right operand must be a number");
return ExpValResult.makeErrorRes(error);
}
return null;
}
private static ExpValResult validateComparison(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
// Require both values to be the same unit type and return a dimensionless unit
// Propagate errors
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null) {
return mergedErrors;
}
ExpValResult numRes = validateBothNumbers(lval, rval, source, pos);
if (numRes != null) {
return numRes;
}
// Both sub values should be valid here
if (lval.unitType != rval.unitType) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
private static void checkTypedComparison(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
// Check that the types are the same
if (lval.type != rval.type) {
throw new ExpError(source, pos, "Can not compare different types. LHS: %s, RHS: %s",
ExpValResult.typeString(lval.type),
ExpValResult.typeString(rval.type));
}
if (lval.type == ExpResType.NUMBER) {
// Also check that the unit types are the same
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, "Can not compare different unit types. LHS: %s, RHS: %s",
lval.unitType.getSimpleName(),
rval.unitType.getSimpleName());
}
}
}
private static boolean evaluteTypedEquality(ExpResult lval, ExpResult rval) {
boolean equal;
switch(lval.type) {
case ENTITY:
equal = lval.entVal == rval.entVal;
break;
case STRING:
equal = lval.stringVal.equals(rval.stringVal);
break;
case NUMBER:
equal = lval.value == rval.value;
break;
default:
assert(false);
equal = false;
}
return equal;
}
private static ExpValResult validateTypedComparison(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
// Propagate errors
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null) {
return mergedErrors;
}
// Otherwise, check that the types are the same
if (lval.type != rval.type) {
ExpError err = new ExpError(source, pos, "Can not compare different types. LHS: %s, RHS: %s",
ExpValResult.typeString(lval.type),
ExpValResult.typeString(rval.type));
return ExpValResult.makeErrorRes(err);
}
if (lval.type == ExpResType.NUMBER) {
// Also check that the unit types are the same
if (lval.unitType != rval.unitType) {
ExpError err = new ExpError(source, pos, "Can not compare different unit types. LHS: %s, RHS: %s",
lval.unitType.getSimpleName(),
rval.unitType.getSimpleName());
return ExpValResult.makeErrorRes(err);
}
}
return ExpValResult.makeValidRes(lval.type, DimensionlessUnit.class);
}
// Validate with all args using the same units, and a new result returning 'newType' unit type
private static ExpValResult validateSameUnits(ParseContext context, ExpValResult[] args, String source, int pos, Class<? extends Unit> newType) {
ExpValResult mergedErrors = mergeMultipleErrors(args);
if (mergedErrors != null)
return mergedErrors;
for (int i = 1; i < args.length; ++ i) {
if (args[i].type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, String.format("Argument %d must be a number", i+1));
return ExpValResult.makeErrorRes(error);
}
if (args[0].unitType != args[i].unitType) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(args[0].unitType, args[i].unitType));
return ExpValResult.makeErrorRes(error);
}
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, newType);
}
// Make sure the single argument is a collection
private static ExpValResult validateCollection(ParseContext context, ExpValResult[] args, String source, int pos) {
if ( args[0].state == ExpValResult.State.ERROR ||
args[0].state == ExpValResult.State.UNDECIDABLE) {
return args[0];
}
// Check that the argument is a collection
if (args[0].type != ExpResType.COLLECTION) {
ExpError error = new ExpError(source, pos, "Expected Collection type argument");
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeUndecidableRes();
}
// Check that a single argument is not an error and is a dimensionless unit
private static ExpValResult validateSingleArgDimensionless(ParseContext context, ExpValResult arg, String source, int pos) {
if ( arg.state == ExpValResult.State.ERROR ||
arg.state == ExpValResult.State.UNDECIDABLE)
return arg;
if (arg.type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, "Argument must be a number");
return ExpValResult.makeErrorRes(error);
}
if (arg.unitType != DimensionlessUnit.class) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(arg.unitType, DimensionlessUnit.class));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
// Check that a single argument is not an error and is a dimensionless unit or angle unit
private static ExpValResult validateTrigFunction(ParseContext context, ExpValResult arg, String source, int pos) {
if ( arg.state == ExpValResult.State.ERROR ||
arg.state == ExpValResult.State.UNDECIDABLE)
return arg;
if (arg.type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, "Argument must be a number");
return ExpValResult.makeErrorRes(error);
}
if (arg.unitType != DimensionlessUnit.class && arg.unitType != AngleUnit.class) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(arg.unitType, DimensionlessUnit.class));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
////////////////////////////////////////////////////////
// Statically initialize the operators and functions
static {
///////////////////////////////////////////////////
// Unary Operators
addUnaryOp("-", 50, new UnOpFunc() {
@Override
public ExpResult apply(ParseContext context, ExpResult val){
return ExpResult.makeNumResult(-val.value, val.unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult val, String source, int pos) {
if (val.state == ExpValResult.State.VALID && val.type != ExpResType.NUMBER) {
ExpError err = new ExpError(source, pos, "Unary negation only applies to numbers");
return ExpValResult.makeErrorRes(err);
}
return val;
}
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult val, String source, int pos)
throws ExpError {
if (val.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Unary negation only applies to numbers");
}
}
});
addUnaryOp("+", 50, new UnOpFunc() {
@Override
public ExpResult apply(ParseContext context, ExpResult val){
return ExpResult.makeNumResult(val.value, val.unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult val, String source, int pos) {
if (val.state == ExpValResult.State.VALID && val.type != ExpResType.NUMBER) {
ExpError err = new ExpError(source, pos, "Unary positive only applies to numbers");
return ExpValResult.makeErrorRes(err);
}
return val;
}
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult val, String source, int pos)
throws ExpError {
if (val.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Unary positive only applies to numbers");
}
}
});
addUnaryOp("!", 50, new UnOpFunc() {
@Override
public ExpResult apply(ParseContext context, ExpResult val){
return ExpResult.makeNumResult(val.value == 0 ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult val, String source, int pos) {
// If the sub expression result was valid, make it dimensionless, otherwise return the sub expression result
if (val.state == ExpValResult.State.VALID) {
if (val.type == ExpResType.NUMBER)
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
// The expression is valid, but not a number
ExpError error = new ExpError(source, pos, "Argument must be a number");
return ExpValResult.makeErrorRes(error);
} else {
return val;
}
}
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult val, String source, int pos)
throws ExpError {
if (val.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Unary not only applies to numbers");
}
}
});
///////////////////////////////////////////////////
// Binary operators
addBinaryOp("+", 20, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
boolean bothNumbers = (lval.type==ExpResType.NUMBER) && (rval.type==ExpResType.NUMBER);
boolean bothStrings = (lval.type==ExpResType.STRING) && (rval.type==ExpResType.STRING);
if (!bothNumbers && !bothStrings) {
throw new ExpError(source, pos, "Operator '+' requires two numbers or two strings");
}
if (bothNumbers && lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
if (lval.type == ExpResType.STRING && rval.type == ExpResType.STRING) {
return ExpResult.makeStringResult(lval.stringVal.concat(rval.stringVal));
}
return ExpResult.makeNumResult(lval.value + rval.value, lval.unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null)
return mergedErrors;
boolean bothNumbers = (lval.type==ExpResType.NUMBER) && (rval.type==ExpResType.NUMBER);
boolean bothStrings = (lval.type==ExpResType.STRING) && (rval.type==ExpResType.STRING);
if (!bothNumbers && !bothStrings) {
ExpError err = new ExpError(source, pos, "Operator '+' requires two numbers or two strings");
return ExpValResult.makeErrorRes(err);
}
if (bothStrings) {
return ExpValResult.makeValidRes(ExpResType.STRING, DimensionlessUnit.class);
}
// Both numbers
if (lval.unitType != rval.unitType) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, lval.unitType);
}
});
addBinaryOp("-", 20, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source,int pos) throws ExpError {
return ExpResult.makeNumResult(lval.value - rval.value, lval.unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null)
return mergedErrors;
ExpValResult numRes = validateBothNumbers(lval, rval, source, pos);
if (numRes != null) {
return numRes;
}
if (lval.unitType != rval.unitType) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, lval.unitType);
}
});
addBinaryOp("*", 30, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
Class<? extends Unit> newType = context.multUnitTypes(lval.unitType, rval.unitType);
if (newType == null) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
Class<? extends Unit> newType = context.multUnitTypes(lval.unitType, rval.unitType);
return ExpResult.makeNumResult(lval.value * rval.value, newType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null)
return mergedErrors;
ExpValResult numRes = validateBothNumbers(lval, rval, source, pos);
if (numRes != null) {
return numRes;
}
Class<? extends Unit> newType = context.multUnitTypes(lval.unitType, rval.unitType);
if (newType == null) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, newType);
}
});
addBinaryOp("/", 30, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
Class<? extends Unit> newType = context.divUnitTypes(lval.unitType, rval.unitType);
if (newType == null) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
Class<? extends Unit> newType = context.divUnitTypes(lval.unitType, rval.unitType);
return ExpResult.makeNumResult(lval.value / rval.value, newType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null)
return mergedErrors;
ExpValResult numRes = validateBothNumbers(lval, rval, source, pos);
if (numRes != null) {
return numRes;
}
Class<? extends Unit> newType = context.divUnitTypes(lval.unitType, rval.unitType);
if (newType == null) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, newType);
}
});
addBinaryOp("^", 40, true, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != DimensionlessUnit.class ||
rval.unitType != DimensionlessUnit.class) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.pow(lval.value, rval.value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null)
return mergedErrors;
ExpValResult numRes = validateBothNumbers(lval, rval, source, pos);
if (numRes != null) {
return numRes;
}
if ( lval.unitType != DimensionlessUnit.class ||
rval.unitType != DimensionlessUnit.class) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
});
addBinaryOp("%", 30, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(lval.value % rval.value, lval.unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
ExpValResult mergedErrors = mergeBinaryErrors(lval, rval);
if (mergedErrors != null)
return mergedErrors;
ExpValResult numRes = validateBothNumbers(lval, rval, source, pos);
if (numRes != null) {
return numRes;
}
if (lval.unitType != rval.unitType) {
ExpError error = new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, lval.unitType);
}
});
addBinaryOp("==", 10, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkTypedComparison(context, lval, rval, source, pos);
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
boolean equal = evaluteTypedEquality(lval, rval);
return ExpResult.makeNumResult(equal ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateTypedComparison(context, lval, rval, source, pos);
}
});
addBinaryOp("!=", 10, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkTypedComparison(context, lval, rval, source, pos);
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
boolean equal = evaluteTypedEquality(lval, rval);
return ExpResult.makeNumResult(!equal ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateTypedComparison(context, lval, rval, source, pos);
}
});
addBinaryOp("&&", 8, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos){
return ExpResult.makeNumResult((lval.value!=0) && (rval.value!=0) ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateComparison(context, lval, rval, source, pos);
}
});
addBinaryOp("||", 6, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos){
return ExpResult.makeNumResult((lval.value!=0) || (rval.value!=0) ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateComparison(context, lval, rval, source, pos);
}
});
addBinaryOp("<", 12, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(lval.value < rval.value ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateComparison(context, lval, rval, source, pos);
}
});
addBinaryOp("<=", 12, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(lval.value <= rval.value ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateComparison(context, lval, rval, source, pos);
}
});
addBinaryOp(">", 12, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(lval.value > rval.value ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateComparison(context, lval, rval, source, pos);
}
});
addBinaryOp(">=", 12, false, new BinOpFunc() {
@Override
public void checkTypeAndUnits(ParseContext context, ExpResult lval,
ExpResult rval, String source, int pos) throws ExpError {
checkBothNumbers(lval, rval, source, pos);
if (lval.unitType != rval.unitType) {
throw new ExpError(source, pos, getUnitMismatchString(lval.unitType, rval.unitType));
}
}
@Override
public ExpResult apply(ParseContext context, ExpResult lval, ExpResult rval, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(lval.value >= rval.value ? 1 : 0, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult lval, ExpValResult rval, String source, int pos) {
return validateComparison(context, lval, rval, source, pos);
}
});
////////////////////////////////////////////////////
// Functions
addFunction("max", 2, -1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
for (int i = 1; i < args.length; ++ i) {
if (args[0].unitType != args[i].unitType)
throw new ExpError(source, pos, getUnitMismatchString(args[0].unitType, args[i].unitType));
}
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
ExpResult res = args[0];
for (int i = 1; i < args.length; ++ i) {
if (args[i].value > res.value)
res = args[i];
}
return res;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSameUnits(context, args, source, pos, args[0].unitType);
}
});
addFunction("min", 2, -1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
for (int i = 1; i < args.length; ++ i) {
if (args[0].unitType != args[i].unitType)
throw new ExpError(source, pos, getUnitMismatchString(args[0].unitType, args[i].unitType));
}
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
ExpResult res = args[0];
for (int i = 1; i < args.length; ++ i) {
if (args[i].value < res.value)
res = args[i];
}
return res;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSameUnits(context, args, source, pos, args[0].unitType);
}
});
addFunction("maxCol", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
if (args[0].type != ExpResType.COLLECTION) {
throw new ExpError(source, pos, "Expected Collection type argument");
}
ExpResult.Collection col = args[0].colVal;
ExpResult.Iterator it = col.getIter();
if (!it.hasNext()) {
throw new ExpError(source, pos, "Can not get max of empty collection");
}
ExpResult ret = col.index(it.nextKey());
Class<? extends Unit> ut = ret.unitType;
if (ret.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take max of non-numeric type in collection");
}
while (it.hasNext()) {
ExpResult comp = col.index(it.nextKey());
if (comp.unitType != ut) {
throw new ExpError(source, pos, "Unmatched Unit types in collection: %s, %s",
ut.getSimpleName(), comp.unitType.getSimpleName());
}
if (comp.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take max of non-numeric type in collection");
}
if (comp.value > ret.value) {
ret = comp;
}
}
return ret;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateCollection(context, args, source, pos);
}
});
addFunction("minCol", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
if (args[0].type != ExpResType.COLLECTION) {
throw new ExpError(source, pos, "Expected Collection type argument");
}
ExpResult.Collection col = args[0].colVal;
ExpResult.Iterator it = col.getIter();
if (!it.hasNext()) {
throw new ExpError(source, pos, "Can not get min of empty collection");
}
ExpResult ret = col.index(it.nextKey());
Class<? extends Unit> ut = ret.unitType;
if (ret.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take min of non-numeric type in collection");
}
while (it.hasNext()) {
ExpResult comp = col.index(it.nextKey());
if (comp.unitType != ut) {
throw new ExpError(source, pos, "Unmatched Unit types in collection: %s, %s",
ut.getSimpleName(), comp.unitType.getSimpleName());
}
if (comp.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take min of non-numeric type in collection");
}
if (comp.value < ret.value) {
ret = comp;
}
}
return ret;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateCollection(context, args, source, pos);
}
});
addFunction("abs", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
// N/A
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) {
return ExpResult.makeNumResult(Math.abs(args[0].value), args[0].unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return args[0];
}
});
addFunction("ceil", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
// N/A
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) {
return ExpResult.makeNumResult(Math.ceil(args[0].value), args[0].unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return args[0];
}
});
addFunction("floor", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
// N/A
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) {
return ExpResult.makeNumResult(Math.floor(args[0].value), args[0].unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return args[0];
}
});
addFunction("signum", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
// N/A
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) {
return ExpResult.makeNumResult(Math.signum(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
if (args[0].state == ExpValResult.State.VALID) {
if (args[0].type == ExpResType.NUMBER) {
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
ExpError err = new ExpError(source, pos, "First parameter must be a number");
return ExpValResult.makeErrorRes(err);
} else {
return args[0];
}
}
});
addFunction("sqrt", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.sqrt(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("cbrt", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.cbrt(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("indexOfMin", 2, -1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
for (int i = 1; i < args.length; ++ i) {
if (args[0].unitType != args[i].unitType)
throw new ExpError(source, pos, getUnitMismatchString(args[0].unitType, args[i].unitType));
}
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
ExpResult res = args[0];
int index = 0;
for (int i = 1; i < args.length; ++ i) {
if (args[i].value < res.value) {
res = args[i];
index = i;
}
}
return ExpResult.makeNumResult(index + 1, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSameUnits(context, args, source, pos, DimensionlessUnit.class);
}
});
addFunction("indexOfMax", 2, -1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
for (int i = 1; i < args.length; ++ i) {
if (args[0].unitType != args[i].unitType)
throw new ExpError(source, pos, getUnitMismatchString(args[0].unitType, args[i].unitType));
}
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
ExpResult res = args[0];
int index = 0;
for (int i = 1; i < args.length; ++ i) {
if (args[i].value > res.value) {
res = args[i];
index = i;
}
}
return ExpResult.makeNumResult(index + 1, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSameUnits(context, args, source, pos, DimensionlessUnit.class);
}
});
addFunction("indexOfMaxCol", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
if (args[0].type != ExpResType.COLLECTION) {
throw new ExpError(source, pos, "Expected Collection type argument");
}
ExpResult.Collection col = args[0].colVal;
ExpResult.Iterator it = col.getIter();
if (!it.hasNext()) {
throw new ExpError(source, pos, "Can not get max of empty collection");
}
ExpResult retKey = it.nextKey();
ExpResult ret = col.index(retKey);
Class<? extends Unit> ut = ret.unitType;
if (ret.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take max of non-numeric type in collection");
}
while (it.hasNext()) {
ExpResult compKey = it.nextKey();
ExpResult comp = col.index(compKey);
if (comp.unitType != ut) {
throw new ExpError(source, pos, "Unmatched Unit types in collection: %s, %s",
ut.getSimpleName(), comp.unitType.getSimpleName());
}
if (comp.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take max of non-numeric type in collection");
}
if (comp.value > ret.value) {
ret = comp;
retKey = compKey;
}
}
return retKey;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateCollection(context, args, source, pos);
}
});
addFunction("indexOfMinCol", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
if (args[0].type != ExpResType.COLLECTION) {
throw new ExpError(source, pos, "Expected Collection type argument");
}
ExpResult.Collection col = args[0].colVal;
ExpResult.Iterator it = col.getIter();
if (!it.hasNext()) {
throw new ExpError(source, pos, "Can not get min of empty collection");
}
ExpResult retKey = it.nextKey();
ExpResult ret = col.index(retKey);
Class<? extends Unit> ut = ret.unitType;
if (ret.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take min of non-numeric type in collection");
}
while (it.hasNext()) {
ExpResult compKey = it.nextKey();
ExpResult comp = col.index(compKey);
if (comp.unitType != ut) {
throw new ExpError(source, pos, "Unmatched Unit types in collection: %s, %s",
ut.getSimpleName(), comp.unitType.getSimpleName());
}
if (comp.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not take min of non-numeric type in collection");
}
if (comp.value < ret.value) {
ret = comp;
retKey = compKey;
}
}
return retKey;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateCollection(context, args, source, pos);
}
});
addFunction("indexOfNearest", 2, 2, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
if (args[0].type != ExpResType.COLLECTION) {
throw new ExpError(source, pos, "Expected Collection type argument as first argument.");
}
if (args[1].type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Expected numerical argument as second argument.");
}
ExpResult.Collection col = args[0].colVal;
ExpResult nearPoint = args[1];
ExpResult.Iterator it = col.getIter();
if (!it.hasNext()) {
throw new ExpError(source, pos, "Can not get nearest value of empty collection.");
}
double nearestDist = Double.MAX_VALUE;
ExpResult retKey = null;
while (it.hasNext()) {
ExpResult compKey = it.nextKey();
ExpResult comp = col.index(compKey);
if (comp.unitType != nearPoint.unitType) {
throw new ExpError(source, pos, "Unmatched Unit types when finding nearest: %s, %s",
nearPoint.unitType.getSimpleName(), comp.unitType.getSimpleName());
}
if (comp.type != ExpResType.NUMBER) {
throw new ExpError(source, pos, "Can not find nearest value of non-numeric type in collection.");
}
double dist = Math.abs(comp.value - nearPoint.value);
if (dist < nearestDist) {
nearestDist = dist;
retKey = compKey;
}
}
return retKey;
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
if (args[1].state == ExpValResult.State.ERROR || args[1].state == ExpValResult.State.UNDECIDABLE) {
return args[1];
}
if (args[1].type != ExpResType.NUMBER) {
return ExpValResult.makeErrorRes(new ExpError(source, pos, "Second argument to 'indexOfNearest' must be a number."));
}
return validateCollection(context, args, source, pos);
}
});
addFunction("size", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
if (args[0].type != ExpResType.COLLECTION) {
throw new ExpError(source, pos, "Expected Collection type argument");
}
ExpResult.Collection col = args[0].colVal;
return ExpResult.makeNumResult(col.getSize(), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateCollection(context, args, source, pos);
}
});
addFunction("choose", 2, -1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
for (int i = 2; i < args.length; ++ i) {
if (args[1].unitType != args[i].unitType)
throw new ExpError(source, pos, getUnitMismatchString(args[1].unitType, args[i].unitType));
}
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
int k = (int) args[0].value;
if (k < 1 || k >= args.length)
throw new ExpError(source, pos,
String.format("Invalid index: %s. Index must be between 1 and %s.", k, args.length-1));
return ExpResult.makeNumResult(args[k].value, args[k].unitType);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
ExpValResult mergedErrors = mergeMultipleErrors(args);
if (mergedErrors != null)
return mergedErrors;
if (args[0].type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, "Parameter must be a number");
return ExpValResult.makeErrorRes(error);
}
if (args[0].unitType != DimensionlessUnit.class) {
ExpError error = new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
return ExpValResult.makeErrorRes(error);
}
for (int i = 2; i < args.length; ++ i) {
if (args[i].type != ExpResType.NUMBER) {
ExpError error = new ExpError(source, pos, String.format("Parameter #%d must be a number", i+1));
return ExpValResult.makeErrorRes(error);
}
// TODO: allow choose to return non-number types
if (args[1].unitType != args[i].unitType) {
ExpError error = new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
return ExpValResult.makeErrorRes(error);
}
}
return ExpValResult.makeValidRes(args[1].type, args[1].unitType);
}
});
///////////////////////////////////////////////////
// Mathematical Constants
addFunction("E", 0, 0, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
// N/A
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.E, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
});
addFunction("PI", 0, 0, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
// N/A
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.PI, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
});
///////////////////////////////////////////////////
// Trigonometric Functions
addFunction("sin", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class && args[0].unitType != AngleUnit.class)
throw new ExpError(source, pos, getInvalidTrigUnitString(args[0].unitType));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.sin(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateTrigFunction(context, args[0], source, pos);
}
});
addFunction("cos", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class && args[0].unitType != AngleUnit.class)
throw new ExpError(source, pos, getInvalidTrigUnitString(args[0].unitType));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.cos(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateTrigFunction(context, args[0], source, pos);
}
});
addFunction("tan", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class && args[0].unitType != AngleUnit.class)
throw new ExpError(source, pos, getInvalidTrigUnitString(args[0].unitType));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.tan(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateTrigFunction(context, args[0], source, pos);
}
});
///////////////////////////////////////////////////
// Inverse Trigonometric Functions
addFunction("asin", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.asin(args[0].value), AngleUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("acos", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.acos(args[0].value), AngleUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("atan", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.atan(args[0].value), AngleUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("atan2", 2, 2, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
if (args[1].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[1].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.atan2(args[0].value, args[1].value), AngleUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
ExpValResult mergedErrors = mergeMultipleErrors(args);
if (mergedErrors != null)
return mergedErrors;
if (args[0].type != ExpResType.NUMBER || args[1].type != ExpResType.NUMBER ) {
ExpError error = new ExpError(source, pos, "Both parameters must be numbers");
return ExpValResult.makeErrorRes(error);
}
if (args[0].unitType != DimensionlessUnit.class) {
ExpError error = new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
return ExpValResult.makeErrorRes(error);
}
if (args[1].unitType != DimensionlessUnit.class) {
ExpError error = new ExpError(source, pos, getInvalidUnitString(args[1].unitType, DimensionlessUnit.class));
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
});
///////////////////////////////////////////////////
// Exponential Functions
addFunction("exp", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.exp(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("ln", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.log(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("log", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].unitType != DimensionlessUnit.class)
throw new ExpError(source, pos, getInvalidUnitString(args[0].unitType, DimensionlessUnit.class));
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(Math.log10(args[0].value), DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
return validateSingleArgDimensionless(context, args[0], source, pos);
}
});
addFunction("notNull", 1, 1, new CallableFunc() {
@Override
public void checkUnits(ParseContext context, ExpResult[] args,
String source, int pos) throws ExpError {
if (args[0].type != ExpResType.ENTITY) {
throw new ExpError(source, pos, "notNull requires entity as argument");
}
}
@Override
public ExpResult call(ParseContext context, ExpResult[] args, String source, int pos) throws ExpError {
return ExpResult.makeNumResult(args[0].entVal == null ? 0 : 1, DimensionlessUnit.class);
}
@Override
public ExpValResult validate(ParseContext context, ExpValResult[] args, String source, int pos) {
if ( args[0].state == ExpValResult.State.ERROR ||
args[0].state == ExpValResult.State.UNDECIDABLE)
return args[0];
if (args[0].type != ExpResType.ENTITY) {
ExpError error = new ExpError(source, pos, "Argument must be an entity");
return ExpValResult.makeErrorRes(error);
}
return ExpValResult.makeValidRes(ExpResType.NUMBER, DimensionlessUnit.class);
}
});
}
private static String unitToString(Class<? extends Unit> unit) {
ObjectType type = ObjectType.getObjectTypeForClass(unit);
if (type == null)
return "Unknown Unit";
return type.getName();
}
private static String getUnitMismatchString(Class<? extends Unit> u0, Class<? extends Unit> u1) {
String s0 = unitToString(u0);
String s1 = unitToString(u1);
return String.format("Unit mismatch: '%s' and '%s' are not compatible", s0, s1);
}
private static String getInvalidTrigUnitString(Class<? extends Unit> u0) {
String s0 = unitToString(u0);
return String.format("Invalid unit: %s. The input to a trigonometric function must be dimensionless or an angle.", s0);
}
private static String getInvalidUnitString(Class<? extends Unit> u0, Class<? extends Unit> u1) {
String s0 = unitToString(u0);
String s1 = unitToString(u1);
if (u1 == DimensionlessUnit.class)
return String.format("Invalid unit: %s. A dimensionless number is required.", s0);
return String.format("Invalid unit: %s. Units of %s are required.", s0, s1);
}
/**
* A utility class to make dealing with a list of tokens easier
*
*/
private static class TokenList {
private final ArrayList<ExpTokenizer.Token> tokens;
private int pos;
public TokenList(ArrayList<ExpTokenizer.Token> tokens) {
this.tokens = tokens;
this.pos = 0;
}
public void expect(int type, String val, String source) throws ExpError {
if (pos == tokens.size()) {
throw new ExpError(source, source.length(), String.format("Expected \"%s\", past the end of input", val));
}
ExpTokenizer.Token nextTok = tokens.get(pos);
if (nextTok.type != type || !nextTok.value.equals(val)) {
throw new ExpError(source, nextTok.pos, String.format("Expected \"%s\", got \"%s\"", val, nextTok.value));
}
pos++;
}
public ExpTokenizer.Token next() {
if (pos >= tokens.size()) {
return null;
}
return tokens.get(pos++);
}
public ExpTokenizer.Token peek() {
if (pos >= tokens.size()) {
return null;
}
return tokens.get(pos);
}
}
private static class ConstOptimizer implements ExpressionWalker {
@Override
public void visit(ExpNode exp) throws ExpError {
// N/A
}
/**
* Give a node a chance to swap itself out with a different subtree.
*/
@Override
public ExpNode updateRef(ExpNode origNode) throws ExpError {
// Note: Below we are passing 'null' as an EvalContext, this is not typically
// acceptable, but is 'safe enough' when we know the expression is a constant
if (origNode instanceof UnaryOp) {
UnaryOp uo = (UnaryOp)origNode;
if (uo.subExp instanceof Constant) {
// This is an unary operation on a constant, we can replace it with a constant
ExpResult val = uo.evaluate(null);
return new Constant(uo.context, val, origNode.exp, uo.tokenPos);
}
}
if (origNode instanceof BinaryOp) {
BinaryOp bo = (BinaryOp)origNode;
if ((bo.lSubExp instanceof Constant) && (bo.rSubExp instanceof Constant)) {
// both sub expressions are constants, so replace the binop with a constant
ExpResult val = bo.evaluate(null);
return new Constant(bo.context, val, origNode.exp, bo.tokenPos);
}
}
if (origNode instanceof FuncCall) {
FuncCall fc = (FuncCall)origNode;
boolean constArgs = true;
for (int i = 0; i < fc.args.size(); ++i) {
if (!(fc.args.get(i) instanceof Constant)) {
constArgs = false;
}
}
if (constArgs) {
ExpResult val = fc.evaluate(null);
return new Constant(fc.context, val, origNode.exp, fc.tokenPos);
}
}
return origNode;
}
}
private static ConstOptimizer CONST_OP = new ConstOptimizer();
private static class RuntimeCheckOptimizer implements ExpressionWalker {
@Override
public void visit(ExpNode exp) throws ExpError {
// N/A
}
@Override
public ExpNode updateRef(ExpNode exp) throws ExpError {
ExpNode noCheckVer = exp.getNoCheckVer();
if (noCheckVer != null)
return noCheckVer;
else
return exp;
}
}
private static RuntimeCheckOptimizer RTC_OP = new RuntimeCheckOptimizer();
private static ExpNode optimizeAndValidateExpression(String input, ExpNode expNode, Expression exp) throws ExpError {
expNode.walk(CONST_OP);
expNode = CONST_OP.updateRef(expNode); // Finally, give the entire expression a chance to optimize itself into a constant
// Run the validation
ExpValResult valRes = expNode.validate();
if (valRes.state == ExpValResult.State.ERROR) {
if (valRes.errors.size() == 0) {
throw new ExpError(input, 0, "An unknown expression error occurred. This is probably a bug. Please inform the developers.");
}
// We received at least one error while validating.
throw valRes.errors.get(0);
}
// Now that validation is complete, we can run the optimizer that removes runtime checks on validated nodes
expNode.walk(RTC_OP);
expNode = RTC_OP.updateRef(expNode); // Give the top level node a chance to optimize
exp.validationResult = valRes;
return expNode;
}
/**
* The main entry point to the expression parsing system, will either return a valid
* expression that can be evaluated, or throw an error.
*/
public static Expression parseExpression(ParseContext context, String input) throws ExpError {
ArrayList<ExpTokenizer.Token> ts;
ts = ExpTokenizer.tokenize(input);
TokenList tokens = new TokenList(ts);
Expression ret = new Expression(input);
ExpNode expNode = parseExp(context, tokens, 0, ret);
// Make sure we've parsed all the tokens
ExpTokenizer.Token peeked = tokens.peek();
if (peeked != null) {
throw new ExpError(input, peeked.pos, "Unexpected additional values");
}
expNode = optimizeAndValidateExpression(input, expNode, ret);
ret.setRootNode(expNode);
return ret;
}
private static ExpNode parseExp(ParseContext context, TokenList tokens, double bindPower, Expression exp) throws ExpError {
ExpNode lhs = parseOpeningExp(context, tokens, bindPower, exp);
// Parse as many indices as are present
while (true) {
ExpTokenizer.Token peeked = tokens.peek();
if (peeked == null || !peeked.value.equals("(")) {
break;
}
tokens.next(); // Consume '('
ExpNode index = parseExp(context, tokens, 0, exp);
tokens.expect(ExpTokenizer.SYM_TYPE, ")", exp.source);
lhs = new IndexCollection(context, lhs, index, exp, peeked.pos);
}
// Now peek for a binary op to modify this expression
while (true) {
ExpTokenizer.Token peeked = tokens.peek();
if (peeked == null || peeked.type != ExpTokenizer.SYM_TYPE) {
break;
}
BinaryOpEntry binOp = getBinaryOp(peeked.value);
if (binOp != null && binOp.bindingPower > bindPower) {
// The next token is a binary op and powerful enough to bind us
lhs = handleBinOp(context, tokens, lhs, binOp, exp, peeked.pos);
continue;
}
// Specific check for binding the conditional (?:) operator
if (peeked.value.equals("?") && bindPower == 0) {
lhs = handleConditional(context, tokens, lhs, exp, peeked.pos);
continue;
}
break;
}
// We have bound as many operators as we can, return it
return lhs;
}
private static ExpNode handleBinOp(ParseContext context, TokenList tokens, ExpNode lhs, BinaryOpEntry binOp, Expression exp, int pos) throws ExpError {
tokens.next(); // Consume the operator
// For right associative operators, we weaken the binding power a bit at application time (but not testing time)
double assocMod = binOp.rAssoc ? -0.5 : 0;
ExpNode rhs = parseExp(context, tokens, binOp.bindingPower + assocMod, exp);
//currentPower = oe.bindingPower;
return new BinaryOp(binOp.symbol, context, lhs, rhs, binOp.function, exp, pos);
}
private static ExpNode handleConditional(ParseContext context, TokenList tokens, ExpNode lhs, Expression exp, int pos) throws ExpError {
tokens.next(); // Consume the '?'
ExpNode trueExp = parseExp(context, tokens, 0, exp);
tokens.expect(ExpTokenizer.SYM_TYPE, ":", exp.source);
ExpNode falseExp = parseExp(context, tokens , 0, exp);
return new Conditional(context, lhs, trueExp, falseExp, exp, pos);
}
public static Assignment parseAssignment(ParseContext context, String input) throws ExpError {
ArrayList<ExpTokenizer.Token> ts;
ts = ExpTokenizer.tokenize(input);
TokenList tokens = new TokenList(ts);
Assignment ret = new Assignment(input);
ExpNode lhsNode = parseExp(context, tokens, 0, ret);
tokens.expect(ExpTokenizer.SYM_TYPE, "=", input);
ExpNode rhsNode = parseExp(context, tokens, 0, ret);
// Make sure we've parsed all the tokens
ExpTokenizer.Token peeked = tokens.peek();
if (peeked != null) {
throw new ExpError(input, peeked.pos, "Unexpected additional values");
}
rhsNode = optimizeAndValidateExpression(input, rhsNode, ret);
ret.valueExp = rhsNode;
// Parsing is done, now we need to unwind the lhs expression to get the necessary components
ExpNode indexExp = null;
// Check for an optional index at the end
if (lhsNode instanceof IndexCollection) {
// the lhs ended with an index, split that off
IndexCollection ind = (IndexCollection)lhsNode;
indexExp = ind.index;
lhsNode = ind.collection;
indexExp = optimizeAndValidateExpression(input, indexExp, ret);
}
ret.attribIndex = indexExp;
// Now make sure the last node of the lhs ends with a output resolve
if (!(lhsNode instanceof ResolveOutput)) {
throw new ExpError(input, lhsNode.tokenPos, "Assignment left side must end with an output");
}
ResolveOutput lhsResolve = (ResolveOutput)lhsNode;
ExpNode entNode = lhsResolve.entNode;
entNode = optimizeAndValidateExpression(input, entNode, ret);
ret.entExp = entNode;
if (ret.entExp instanceof Constant) {
ExpResult ent = ret.entExp.evaluate(null);
ret.assigner = context.getConstAssigner(ent, lhsResolve.outputName);
} else {
ret.assigner = context.getAssigner(lhsResolve.outputName);
}
return ret;
}
// The first half of expression parsing, parse a simple expression based on the next token
private static ExpNode parseOpeningExp(ParseContext context, TokenList tokens, double bindPower, Expression exp) throws ExpError{
ExpTokenizer.Token nextTok = tokens.next(); // consume the first token
if (nextTok == null) {
throw new ExpError(exp.source, exp.source.length(), "Unexpected end of string");
}
if (nextTok.type == ExpTokenizer.NUM_TYPE) {
return parseConstant(context, nextTok.value, tokens, exp, nextTok.pos);
}
if (nextTok.type == ExpTokenizer.DSQ_TYPE) {
// Return a literal string constant
return new Constant(context, ExpResult.makeStringResult(nextTok.value), exp, nextTok.pos);
}
if (nextTok.type == ExpTokenizer.VAR_TYPE &&
!nextTok.value.equals("this")) {
return parseFuncCall(context, nextTok.value, tokens, exp, nextTok.pos);
}
if (nextTok.type == ExpTokenizer.SQ_TYPE ||
nextTok.value.equals("this")) {
return parseVariable(context, nextTok, tokens, exp, nextTok.pos);
}
// The next token must be a symbol
if (nextTok.value.equals("{")) {
boolean foundComma = true;
ArrayList<ExpNode> exps = new ArrayList<>();
while(true) {
// Parse an array
ExpTokenizer.Token peeked = tokens.peek();
if (peeked != null && peeked.value.equals("}")) {
tokens.next(); // consume
break;
}
if (!foundComma) {
throw new ExpError(exp.source, peeked.pos, "Expected ',' or '}' in literal array.");
}
foundComma = false;
exps.add(parseExp(context, tokens, 0, exp));
peeked = tokens.peek();
if (peeked != null && peeked.value.equals(",")) {
tokens.next();
foundComma = true;
}
}
ArrayList<ExpResult> res = new ArrayList<>();
for (ExpNode e : exps) {
res.add(e.evaluate(null));
}
return new Constant(context, ExpCollections.makeExpressionCollection(res), exp, nextTok.pos);
}
// handle parenthesis
if (nextTok.value.equals("(")) {
ExpNode expNode = parseExp(context, tokens, 0, exp);
tokens.expect(ExpTokenizer.SYM_TYPE, ")", exp.source); // Expect the closing paren
return expNode;
}
UnaryOpEntry oe = getUnaryOp(nextTok.value);
if (oe != null) {
ExpNode expNode = parseExp(context, tokens, oe.bindingPower, exp);
return new UnaryOp(oe.symbol, context, expNode, oe.function, exp, nextTok.pos);
}
// We're all out of tricks here, this is an unknown expression
throw new ExpError(exp.source, nextTok.pos, "Can not parse expression");
}
private static ExpNode parseConstant(ParseContext context, String constant, TokenList tokens, Expression exp, int pos) throws ExpError {
double mult = 1;
Class<? extends Unit> ut = DimensionlessUnit.class;
ExpTokenizer.Token peeked = tokens.peek();
if (peeked != null && peeked.type == ExpTokenizer.SQ_TYPE) {
// This constant is followed by a square quoted token, it must be the unit
tokens.next(); // Consume unit token
UnitData unit = context.getUnitByName(peeked.value);
if (unit == null) {
throw new ExpError(exp.source, peeked.pos, "Unknown unit: %s", peeked.value);
}
mult = unit.scaleFactor;
ut = unit.unitType;
}
return new Constant(context, ExpResult.makeNumResult(Double.parseDouble(constant)*mult, ut), exp, pos);
}
private static ExpNode parseFuncCall(ParseContext context, String funcName, TokenList tokens, Expression exp, int pos) throws ExpError {
tokens.expect(ExpTokenizer.SYM_TYPE, "(", exp.source);
ArrayList<ExpNode> arguments = new ArrayList<>();
ExpTokenizer.Token peeked = tokens.peek();
if (peeked == null) {
throw new ExpError(exp.source, exp.source.length(), "Unexpected end of input in argument list");
}
boolean isEmpty = false;
if (peeked.value.equals(")")) {
// Special case with empty argument list
isEmpty = true;
tokens.next(); // Consume closing parens
}
while (!isEmpty) {
ExpNode nextArg = parseExp(context, tokens, 0, exp);
arguments.add(nextArg);
ExpTokenizer.Token nextTok = tokens.next();
if (nextTok == null) {
throw new ExpError(exp.source, exp.source.length(), "Unexpected end of input in argument list.");
}
if (nextTok.value.equals(")")) {
break;
}
if (nextTok.value.equals(",")) {
continue;
}
// Unexpected token
throw new ExpError(exp.source, nextTok.pos, "Unexpected token in arguement list");
}
FunctionEntry fe = getFunctionEntry(funcName);
if (fe == null) {
throw new ExpError(exp.source, pos, "Uknown function: \"%s\"", funcName);
}
if (fe.numMinArgs >= 0 && arguments.size() < fe.numMinArgs){
throw new ExpError(exp.source, pos, "Function \"%s\" expects at least %d arguments. %d provided.",
funcName, fe.numMinArgs, arguments.size());
}
if (fe.numMaxArgs >= 0 && arguments.size() > fe.numMaxArgs){
throw new ExpError(exp.source, pos, "Function \"%s\" expects at most %d arguments. %d provided.",
funcName, fe.numMaxArgs, arguments.size());
}
return new FuncCall(fe.name, context, fe.function, arguments, exp, pos);
}
private static ExpNode parseVariable(ParseContext context, ExpTokenizer.Token firstName, TokenList tokens, Expression exp, int pos) throws ExpError {
ExpNode curExp = new Constant(context, context.getValFromName(firstName.value, exp.source, pos), exp, pos);
while (true) {
ExpTokenizer.Token peeked = tokens.peek();
if (peeked == null || peeked.type != ExpTokenizer.SYM_TYPE || !peeked.value.equals(".")) {
break;
}
// Next token is a '.' so parse a ResolveOutput node
tokens.next(); // consume
ExpTokenizer.Token outputName = tokens.next();
if (outputName == null || outputName.type != ExpTokenizer.VAR_TYPE) {
throw new ExpError(exp.source, peeked.pos, "Expected Identifier after '.'");
}
curExp = new ResolveOutput(context, outputName.value, curExp, exp, peeked.pos);
peeked = tokens.peek();
if (peeked != null && peeked.value.equals("(")) {
// Optional index
tokens.next(); // consume
ExpNode indexExp = parseExp(context, tokens, 0, exp);
tokens.expect(ExpTokenizer.SYM_TYPE, ")", exp.source);
curExp = new IndexCollection(context, curExp, indexExp, exp, peeked.pos);
}
}
return curExp;
}
}